Merge pull request #1 from sth2018/master

更新
This commit is contained in:
WorkTimer 2019-02-27 16:10:48 +08:00 committed by GitHub
commit 4007dbbb28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 492 additions and 3378 deletions

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -19,6 +19,7 @@
import ssl import ssl
import sys import sys
from anki.hooks import addHook from anki.hooks import addHook
from anki.utils import isMac from anki.utils import isMac
@ -28,6 +29,7 @@ if isMac:
############## other config here ################## ############## other config here ##################
shortcut = ('Ctrl+Alt' if isMac else 'Ctrl') + '+Q' shortcut = ('Ctrl+Alt' if isMac else 'Ctrl') + '+Q'
################################################### ###################################################
@ -42,7 +44,6 @@ def start_here():
fastwq.browser_menu() fastwq.browser_menu()
fastwq.context_menu() fastwq.context_menu()
fastwq.customize_addcards() fastwq.customize_addcards()
# if fastwq.config.auto_update:
# fastwq.check_updates(True)
addHook("profileLoaded", start_here) addHook("profileLoaded", start_here)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,24 +18,23 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from operator import itemgetter from operator import itemgetter
from anki.hooks import addHook, remHook, wrap
from aqt import mw from aqt import mw
from aqt.qt import *
from anki.hooks import addHook, wrap, remHook
from aqt.addcards import AddCards from aqt.addcards import AddCards
from aqt.utils import showInfo, shortcut, downArrow from aqt.qt import *
from .gui import show_options, show_about_dialog #, check_updates from aqt.utils import downArrow, shortcut, showInfo
from .query import query_from_browser, query_from_editor_fields
from .context import config, APP_ICON, Config from .context import APP_ICON, Config, config
from .gui import show_about_dialog, show_options # , check_updates
from .lang import _ from .lang import _
from .query import query_from_browser, query_from_editor_fields
from .service import service_pool from .service import service_pool
from .utils import get_icon from .utils import get_icon
__all__ = [ __all__ = [
'browser_menu', 'customize_addcards', 'browser_menu', 'customize_addcards', 'config_menu', 'context_menu'
'config_menu', 'context_menu' ] # 'check_updates',
] # 'check_updates',
have_setup = False have_setup = False
my_shortcut = '' my_shortcut = ''
@ -52,18 +51,23 @@ def set_options_def(mid, i):
data = dict() data = dict()
data[mid] = conf data[mid] = conf
config.update(data) config.update(data)
# end set_options_def # end set_options_def
def browser_menu(): def browser_menu():
""" """
add add-on's menu to browser window add add-on's menu to browser window
""" """
def on_setup_menus(browser): def on_setup_menus(browser):
""" """
on browser setupMenus was called on browser setupMenus was called
""" """
# main menu # main menu
menu = browser.form.menubar.addMenu("FastWQ") menu = browser.form.menubar.addMenu("FastWQ")
# menu gen # menu gen
def init_fastwq_menu(): def init_fastwq_menu():
try: try:
@ -78,6 +82,7 @@ def browser_menu():
menu.addAction(action) menu.addAction(action)
# Options # Options
action = QAction(_('OPTIONS'), browser) action = QAction(_('OPTIONS'), browser)
def _show_options(): def _show_options():
model_id = -1 model_id = -1
for note_id in browser.selectedNotes(): for note_id in browser.selectedNotes():
@ -85,24 +90,29 @@ def browser_menu():
model_id = note.model()['id'] model_id = note.model()['id']
break break
show_options(browser, model_id) show_options(browser, model_id)
action.triggered.connect(_show_options) action.triggered.connect(_show_options)
menu.addAction(action) menu.addAction(action)
# Default configs # Default configs
menu.addSeparator() menu.addSeparator()
b = False b = False
for m in sorted(browser.mw.col.models.all(), key=itemgetter("name")): for m in sorted(
browser.mw.col.models.all(), key=itemgetter("name")):
conf = config.get_maps(m['id']) conf = config.get_maps(m['id'])
conf = {'list': [conf], 'def': 0} if isinstance(conf, list) else conf conf = {
'list': [conf],
'def': 0
} if isinstance(conf, list) else conf
maps_list = conf['list'] maps_list = conf['list']
if len(maps_list) > 1: if len(maps_list) > 1:
submenu = menu.addMenu(m['name']) submenu = menu.addMenu(m['name'])
for i, maps in enumerate(maps_list): for i, maps in enumerate(maps_list):
submenu.addAction( submenu.addAction(
_OK_ICON if i==conf['def'] else _NULL_ICON, _OK_ICON if i == conf['def'] else _NULL_ICON,
_('CONFIG_INDEX') % (i+1) if isinstance(maps, list) else maps['name'], _('CONFIG_INDEX') % (i + 1) if isinstance(
lambda mid=m['id'], i=i: set_options_def(mid, i) maps, list) else maps['name'],
) lambda mid=m['id'], i=i: set_options_def(mid, i))
b = True b = True
if b: if b:
menu.addSeparator() menu.addSeparator()
@ -120,6 +130,7 @@ def browser_menu():
# end init_fastwq_menu # end init_fastwq_menu
init_fastwq_menu() init_fastwq_menu()
addHook('config.update', init_fastwq_menu) addHook('config.update', init_fastwq_menu)
addHook('browser.setupMenus', on_setup_menus) addHook('browser.setupMenus', on_setup_menus)
@ -127,6 +138,7 @@ def customize_addcards():
""" """
add button to addcards window add button to addcards window
""" """
def add_query_button(self): def add_query_button(self):
''' '''
add a button in add card window add a button in add card window
@ -136,44 +148,47 @@ def customize_addcards():
# button # button
fastwqBtn = QPushButton(_("QUERY") + u" " + downArrow()) fastwqBtn = QPushButton(_("QUERY") + u" " + downArrow())
fastwqBtn.setShortcut(QKeySequence(my_shortcut)) fastwqBtn.setShortcut(QKeySequence(my_shortcut))
fastwqBtn.setToolTip( fastwqBtn.setToolTip(_(u"Shortcut: %s") % shortcut(my_shortcut))
_(u"Shortcut: %s") % shortcut(my_shortcut)
)
bb.addButton(fastwqBtn, ar) bb.addButton(fastwqBtn, ar)
# signal # signal
def onQuery(e): def onQuery(e):
if isinstance(e, QMouseEvent): if isinstance(e, QMouseEvent):
if e.buttons() & Qt.LeftButton: if e.buttons() & Qt.LeftButton:
menu = QMenu(self) menu = QMenu(self)
menu.addAction(_("ALL_FIELDS"), lambda: query_from_editor_fields(self.editor), QKeySequence(my_shortcut)) menu.addAction(
_("ALL_FIELDS"),
lambda: query_from_editor_fields(self.editor),
QKeySequence(my_shortcut))
# default options # default options
mid = self.editor.note.model()['id'] mid = self.editor.note.model()['id']
conf = config.get_maps(mid) conf = config.get_maps(mid)
conf = {'list': [conf], 'def': 0} if isinstance(conf, list) else conf conf = {
'list': [conf],
'def': 0
} if isinstance(conf, list) else conf
maps_list = conf['list'] maps_list = conf['list']
if len(maps_list) > 1: if len(maps_list) > 1:
menu.addSeparator() menu.addSeparator()
for i, maps in enumerate(maps_list): for i, maps in enumerate(maps_list):
menu.addAction( menu.addAction(
_OK_ICON if i==conf['def'] else _NULL_ICON, _OK_ICON if i == conf['def'] else _NULL_ICON,
_('CONFIG_INDEX') % (i+1) if isinstance(maps, list) else maps['name'], _('CONFIG_INDEX') % (i + 1) if isinstance(
lambda mid=mid, i=i: set_options_def(mid, i) maps, list) else maps['name'],
) lambda mid=mid, i=i: set_options_def(mid, i))
menu.addSeparator() menu.addSeparator()
# end default options # end default options
menu.addAction(_("OPTIONS"), lambda: show_options(self, self.editor.note.model()['id'])) menu.addAction(_("OPTIONS"), lambda: show_options(self, self.editor.note.model()['id']))
menu.exec_(fastwqBtn.mapToGlobal(QPoint(0, fastwqBtn.height()))) menu.exec_(
fastwqBtn.mapToGlobal(QPoint(0, fastwqBtn.height())))
else: else:
query_from_editor_fields(self.editor) query_from_editor_fields(self.editor)
fastwqBtn.mousePressEvent = onQuery fastwqBtn.mousePressEvent = onQuery
fastwqBtn.clicked.connect(onQuery) fastwqBtn.clicked.connect(onQuery)
AddCards.setupButtons = wrap( AddCards.setupButtons = wrap(AddCards.setupButtons, add_query_button,
AddCards.setupButtons, "after")
add_query_button,
"after"
)
def config_menu(): def config_menu():
@ -187,6 +202,7 @@ def config_menu():
def context_menu(): def context_menu():
'''mouse right click menu''' '''mouse right click menu'''
def on_setup_menus(web_view, menu): def on_setup_menus(web_view, menu):
""" """
add context menu to webview add context menu to webview
@ -217,32 +233,30 @@ def context_menu():
name = s.title + ' :-> ' + s.fields[dict_fld_ord] name = s.title + ' :-> ' + s.fields[dict_fld_ord]
if name not in names: if name not in names:
names.append(name) names.append(name)
curr_flds.append({ curr_flds.append({'name': name, 'def': i})
'name': name,
'def': i
})
service_pool.put(s) service_pool.put(s)
submenu = menu.addMenu(_('QUERY')) submenu = menu.addMenu(_('QUERY'))
submenu.addAction(_('ALL_FIELDS'), lambda: query_from_editor_fields(web_view.editor), QKeySequence(my_shortcut)) submenu.addAction(
_('ALL_FIELDS'), lambda: query_from_editor_fields(web_view.editor),
QKeySequence(my_shortcut))
if len(curr_flds) > 0: if len(curr_flds) > 0:
# quer hook method # quer hook method
def query_from_editor_hook(i): def query_from_editor_hook(i):
conf = config.get_maps(current_model_id) conf = config.get_maps(current_model_id)
maps_old_def = 0 if isinstance(conf, list) else conf.get('def', 0) maps_old_def = 0 if isinstance(conf, list) else conf.get(
'def', 0)
set_options_def(current_model_id, i) set_options_def(current_model_id, i)
query_from_editor_fields( query_from_editor_fields(
web_view.editor, web_view.editor, fields=[web_view.editor.currentField])
fields=[web_view.editor.currentField]
)
set_options_def(current_model_id, maps_old_def) set_options_def(current_model_id, maps_old_def)
# sub menu # sub menu
#flds_menu = submenu.addMenu(_('CURRENT_FIELDS')) # flds_menu = submenu.addMenu(_('CURRENT_FIELDS'))
submenu.addSeparator() submenu.addSeparator()
for c in curr_flds: for c in curr_flds:
submenu.addAction(c['name'], submenu.addAction(
lambda i=c['def']: query_from_editor_hook(i) c['name'], lambda i=c['def']: query_from_editor_hook(i))
)
submenu.addSeparator() submenu.addSeparator()
submenu.addAction(_("OPTIONS"), lambda: show_options(web_view, web_view.editor.note.model()['id'])) submenu.addAction(_("OPTIONS"), lambda: show_options(web_view, web_view.editor.note.model()['id']))

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from .lang import _ from .lang import _
__all__ = ['VERSION', 'Endpoint', 'Template'] __all__ = ['VERSION', 'Endpoint', 'Template']
VERSION = 'v1.3.5' VERSION = 'v2.0.0b'
class Endpoint: class Endpoint:
repository = u'https://github.com/sth2018/FastWordQuery' repository = u'https://github.com/sth2018/FastWordQuery'
@ -36,10 +35,13 @@ class Endpoint:
class Template: class Template:
tmpl_about = u'''<b>{t0}</b><br/>{version}<br/><b>{t1}</b><br/> tmpl_about = u'''<b>{t0}</b><br/>{version}<br/><b>{t1}</b><br/>
<a href="{url}">{url}</a><br/><b>{t2}</b><br/> <a href="{url}">{url}</a><br/><b>{t2}</b><br/>
<a href="{feedback0}">{feedback0}</a><br/> <a href="{feedback0}">{feedback0}</a><br/>
<a href="mailto:{feedback1}">{feedback1}</a>'''.format( <a href="mailto:{feedback1}">{feedback1}</a>'''.format(
t0=_('VERSION'), version=VERSION, t0=_('VERSION'),
t1=_('REPOSITORY'), url=Endpoint.repository, version=VERSION,
t2=_('FEEDBACK'), feedback0=Endpoint.feedback_issue, feedback1=Endpoint.feedback_mail) t1=_('REPOSITORY'),
miss_css = u'MDX dictonary <b>{dict}</b> misses css file <b>{css}</b>. <br/>Please choose the file.' url=Endpoint.repository,
t2=_('FEEDBACK'),
feedback0=Endpoint.feedback_issue,
feedback1=Endpoint.feedback_mail)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -19,25 +19,24 @@
import json import json
import os import os
from aqt import mw
from anki.hooks import runHook from anki.hooks import runHook
from aqt import mw
from .constants import VERSION from .constants import VERSION
from .utils import get_icon from .utils import get_icon
__all__ = ['APP_ICON', 'config'] __all__ = ['APP_ICON', 'config']
APP_ICON = get_icon('wqicon.png') # Addon Icon
APP_ICON = get_icon('wqicon.png') #Addon Icon
class Config(object): class Config(object):
""" """
Addon Config Addon Config
""" """
_CONFIG_FILENAME = 'fastwqcfg.json' #Config File Path _CONFIG_FILENAME = 'fastwqcfg.json' # Config File Path
def __init__(self, window): def __init__(self, window):
self.path = u'_' + self._CONFIG_FILENAME self.path = u'_' + self._CONFIG_FILENAME
@ -55,10 +54,12 @@ class Config(object):
Update && Save Update && Save
""" """
data['version'] = VERSION data['version'] = VERSION
data['%s_last' % self.pmname] = data.get('last_model', self.last_model_id) data['%s_last' % self.pmname] = data.get('last_model',
self.last_model_id)
self.data.update(data) self.data.update(data)
with open(self.path, 'w', encoding='utf-8') as f: with open(self.path, 'w', encoding='utf-8') as f:
json.dump(self.data, f, indent=4, sort_keys=True, ensure_ascii=False) json.dump(
self.data, f, indent=4, sort_keys=True, ensure_ascii=False)
f.close() f.close()
runHook('config.update') runHook('config.update')
@ -69,13 +70,15 @@ class Config(object):
if self.data: if self.data:
return self.data return self.data
try: try:
path = self.path if os.path.exists(self.path) else u'.' + self._CONFIG_FILENAME path = self.path if os.path.exists(
self.path) else u'.' + self._CONFIG_FILENAME
with open(path, 'r', encoding="utf-8") as f: with open(path, 'r', encoding="utf-8") as f:
self.data = json.load(f) self.data = json.load(f)
f.close() f.close()
if not os.path.exists(self.path): if not os.path.exists(self.path):
self.update(self.data) self.update(self.data)
except: except Exception as e:
print('can not find config file', e)
self.data = dict() self.data = dict()
def get_maps(self, model_id): def get_maps(self, model_id):
@ -131,15 +134,13 @@ class Config(object):
'''ignore accents of field in querying''' '''ignore accents of field in querying'''
return self.data.get('ignore_accents', False) return self.data.get('ignore_accents', False)
@property
def auto_update(self):
'''auto check new version'''
return self.data.get('auto_update', True)
@property @property
def cloze_str(self): def cloze_str(self):
'''cloze formater string''' '''cloze formater string'''
return self.data.get('cloze_str', '{{c1::%s}}') tmpstr = self.data.get('cloze_str', '{{c1::%s}}')
if len(tmpstr.split('%s')) != 2:
tmpstr = '{{c1::%s}}'
return tmpstr
config = Config(mw) config = Config(mw)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from .common import * from .common import *
from .progress import * from .progress import *

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,10 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys import sys
from anki.utils import isMac from anki.utils import isMac
from aqt.qt import * from aqt.qt import *
from ..context import APP_ICON
from ..context import APP_ICON
__all__ = ['Dialog', 'WIDGET_SIZE'] __all__ = ['Dialog', 'WIDGET_SIZE']
@ -37,20 +38,15 @@ class Dialog(QDialog):
return value of the _ui() method, and sets a default title. return value of the _ui() method, and sets a default title.
''' '''
self._title = title self._title = title if "FastWQ" in title else "FastWQ - " + title
self._parent = parent self._parent = parent
super(Dialog, self).__init__(parent) super(Dialog, self).__init__(parent)
self.setModal(True) self.setModal(True)
self.setWindowFlags( self.setWindowFlags(
self.windowFlags() & self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
~Qt.WindowContextHelpButtonHint
)
self.setWindowIcon(APP_ICON) self.setWindowIcon(APP_ICON)
self.setWindowTitle( self.setWindowTitle(self._title)
title if "FastWQ" in title
else "FastWQ - " + title
)
# 2 & 3 & mac compatible # 2 & 3 & mac compatible
if isMac and sys.hexversion >= 0x03000000: if isMac and sys.hexversion >= 0x03000000:
QApplication.setStyle('Fusion') QApplication.setStyle('Fusion')

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,38 +18,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import types import types
from aqt import mw from aqt import mw
from aqt.qt import * from aqt.qt import *
from aqt.utils import showInfo
from .options import OptionsDialog from ..constants import Template
from .foldermanager import FoldersManageDialog
from .dictmanager import DictManageDialog
from ..libs import ankihub
from ..context import config from ..context import config
from ..lang import _ from ..lang import _
from ..constants import Endpoint, Template
from ..service import service_manager, service_pool from ..service import service_manager, service_pool
from .dictmanager import DictManageDialog
from .foldermanager import FoldersManageDialog
from .options import OptionsDialog
__all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog']
__all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog'] #'check_updates', def show_fm_dialog(browser=None):
# def check_updates(background=False, parent=None):
# '''check add-on last version'''
# try:
# parent = mw if parent is None else parent
# state = ankihub.update([Endpoint.check_version], Endpoint.version, background, parent)
# if not background:
# if state == 0:
# showInfo(_('LATEST_VERSION'))
# elif state == -1:
# showInfo(_('CHECK_FAILURE'))
# except:
# if not background:
# showInfo(_('CHECK_FAILURE'))
def show_fm_dialog(browser = None):
'''open dictionary folder manager window''' '''open dictionary folder manager window'''
parent = mw if browser is None else browser parent = mw if browser is None else browser
fm_dialog = FoldersManageDialog(parent, u'Dictionary Folder Manager') fm_dialog = FoldersManageDialog(parent, u'Dictionary Folder Manager')
@ -64,7 +48,7 @@ def show_fm_dialog(browser = None):
show_options(browser) show_options(browser)
def show_dm_dialog(browser = None): def show_dm_dialog(browser=None):
parent = mw if browser is None else browser parent = mw if browser is None else browser
dm_dialog = DictManageDialog(parent, u'Dictionary Manager') dm_dialog = DictManageDialog(parent, u'Dictionary Manager')
dm_dialog.activateWindow() dm_dialog.activateWindow()
@ -78,7 +62,7 @@ def show_dm_dialog(browser = None):
show_options(browser) show_options(browser)
def show_options(browser = None, model_id = -1, callback = None, *args, **kwargs): def show_options(browser=None, model_id=-1, callback=None, *args, **kwargs):
'''open options window''' '''open options window'''
parent = mw if browser is None else browser parent = mw if browser is None else browser
config.read() config.read()

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -19,19 +19,20 @@
import os import os
import sys import sys
from aqt.qt import *
from aqt.forms.editaddon import Ui_Dialog from aqt.forms.editaddon import Ui_Dialog
from .base import Dialog, WIDGET_SIZE from aqt.qt import *
from ..service import service_manager, service_pool
from ..context import config from ..context import config
from ..lang import _, _sl from ..lang import _, _sl
from ..service import service_manager, service_pool
from ..utils import get_icon from ..utils import get_icon
from .base import WIDGET_SIZE, Dialog
# 2x3 compatible # 2x3 compatible
if sys.hexversion >= 0x03000000: if sys.hexversion >= 0x03000000:
unicode = str unicode = str
__all__ = ['DictManageDialog'] __all__ = ['DictManageDialog']
@ -45,7 +46,7 @@ class DictManageDialog(Dialog):
self.main_layout = QVBoxLayout() self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout) self.setLayout(self.main_layout)
self._options = list() self._options = list()
btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal , self) btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self)
btnbox.accepted.connect(self.accept) btnbox.accepted.connect(self.accept)
# add dicts mapping # add dicts mapping
self.dicts_layout = QGridLayout() self.dicts_layout = QGridLayout()
@ -80,10 +81,14 @@ class DictManageDialog(Dialog):
services = service_manager.local_custom_services + service_manager.web_services services = service_manager.local_custom_services + service_manager.web_services
for clazz in services: for clazz in services:
dicts.append({ dicts.append({
'title': clazz.__title__, 'title':
'unique': clazz.__unique__, clazz.__title__,
'path': clazz.__path__, 'unique':
'enabled': confs.get(clazz.__unique__, dict()).get('enabled', True) clazz.__unique__,
'path':
clazz.__path__,
'enabled':
confs.get(clazz.__unique__, dict()).get('enabled', True)
}) })
# add dict # add dict
for i, d in enumerate(dicts): for i, d in enumerate(dicts):
@ -102,7 +107,7 @@ class DictManageDialog(Dialog):
) )
# button # button
check_btn = QCheckBox(title) check_btn = QCheckBox(title)
check_btn.setMinimumSize(WIDGET_SIZE.map_dict_width*1.5, 0) check_btn.setMinimumSize(WIDGET_SIZE.map_dict_width * 1.5, 0)
check_btn.setEnabled(True) check_btn.setEnabled(True)
check_btn.setChecked(enabled) check_btn.setChecked(enabled)
edit_btn = QToolButton(self) edit_btn = QToolButton(self)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,10 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from aqt.qt import * from aqt.qt import *
from .base import Dialog, WIDGET_SIZE
from ..context import config from ..context import config
from ..lang import _, _sl from ..lang import _, _sl
from .base import WIDGET_SIZE, Dialog
__all__ = ['FoldersManageDialog'] __all__ = ['FoldersManageDialog']
@ -33,7 +33,7 @@ class FoldersManageDialog(Dialog):
def __init__(self, parent, title=u'Dictionary Folder Manager'): def __init__(self, parent, title=u'Dictionary Folder Manager'):
super(FoldersManageDialog, self).__init__(parent, title) super(FoldersManageDialog, self).__init__(parent, title)
#self._dict_paths = [] # self._dict_paths = []
self.build() self.build()
def build(self): def build(self):
@ -67,8 +67,7 @@ class FoldersManageDialog(Dialog):
self, self,
caption=u"Select Folder", caption=u"Select Folder",
directory=config.last_folder, directory=config.last_folder,
options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
)
if dir_: if dir_:
self.folders_lst.addItem(dir_) self.folders_lst.addItem(dir_)
config.update({'last_folder': dir_}) config.update({'last_folder': dir_})
@ -80,8 +79,10 @@ class FoldersManageDialog(Dialog):
@property @property
def dirs(self): def dirs(self):
'''dictionary folders list''' '''dictionary folders list'''
return [self.folders_lst.item(i).text() return [
for i in range(self.folders_lst.count())] self.folders_lst.item(i).text()
for i in range(self.folders_lst.count())
]
def accept(self): def accept(self):
'''ok button clicked''' '''ok button clicked'''

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,22 +18,23 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys import sys
import anki import anki
import aqt import aqt
import aqt.models import aqt.models
import sip import sip
from anki.utils import isMac
from aqt import mw from aqt import mw
from aqt.qt import * from aqt.qt import *
from aqt.studydeck import StudyDeck from aqt.studydeck import StudyDeck
from anki.utils import isMac
from .base import Dialog, WIDGET_SIZE from ..constants import Endpoint
from .setting import SettingDialog
from ..context import config from ..context import config
from ..lang import _, _sl from ..lang import _, _sl
from ..service import service_manager, service_pool from ..service import service_manager, service_pool
from ..utils import get_model_byId, get_icon from ..utils import get_icon, get_model_byId
from ..constants import Endpoint from .base import WIDGET_SIZE, Dialog
from .setting import SettingDialog
__all__ = ['OptionsDialog'] __all__ = ['OptionsDialog']
@ -44,16 +45,13 @@ class OptionsDialog(Dialog):
setting query dictionary and fileds setting query dictionary and fileds
''' '''
__slot__ = [ __slot__ = ['begore_build', 'after_build']
'begore_build',
'after_build'
]
_signal = pyqtSignal(str) _signal = pyqtSignal(str)
_NULL_ICON = get_icon('null.png') _NULL_ICON = get_icon('null.png')
_OK_ICON = get_icon('ok.png') _OK_ICON = get_icon('ok.png')
def __init__(self, parent, title=u'Options', model_id = -1): def __init__(self, parent, title=u'Options', model_id=-1):
super(OptionsDialog, self).__init__(parent, title) super(OptionsDialog, self).__init__(parent, title)
self._signal.connect(self._before_build) self._signal.connect(self._before_build)
self._signal.connect(self._after_build) self._signal.connect(self._after_build)
@ -61,15 +59,17 @@ class OptionsDialog(Dialog):
self.main_layout = QVBoxLayout() self.main_layout = QVBoxLayout()
self.loading_label = QLabel(_('INITLIZING_DICT')) self.loading_label = QLabel(_('INITLIZING_DICT'))
self.main_layout.addWidget(self.loading_label, 0, Qt.AlignCenter) self.main_layout.addWidget(self.loading_label, 0, Qt.AlignCenter)
#self.loading_layout.addLayout(models_layout) # self.loading_layout.addLayout(models_layout)
self.setLayout(self.main_layout) self.setLayout(self.main_layout)
#initlize properties # initlize properties
self.model_id = model_id if model_id != -1 else config.last_model_id self.model_id = model_id if model_id != -1 else config.last_model_id
self.current_model = None self.current_model = None
self.tabs = [] self.tabs = []
self.dict_services = None self.dict_services = None
# size and signal # size and signal
self.resize(WIDGET_SIZE.dialog_width, 4 * WIDGET_SIZE.map_max_height + WIDGET_SIZE.dialog_height_margin) self.resize(
WIDGET_SIZE.dialog_width,
4 * WIDGET_SIZE.map_max_height + WIDGET_SIZE.dialog_height_margin)
self._signal.emit('before_build') self._signal.emit('before_build')
def _before_build(self, s): def _before_build(self, s):
@ -78,8 +78,8 @@ class OptionsDialog(Dialog):
# dict service list # dict service list
dicts = config.dicts dicts = config.dicts
self.dict_services = { self.dict_services = {
'local': [], #本地词典 'local': [], # 本地词典
'web': [] #网络词典 'web': [] # 网络词典
} }
for clazz in service_manager.local_services: for clazz in service_manager.local_services:
if dicts.get(clazz.__unique__, dict()).get('enabled', True): if dicts.get(clazz.__unique__, dict()).get('enabled', True):
@ -121,13 +121,11 @@ class OptionsDialog(Dialog):
# tabs # tabs
self.tab_widget = QTabWidget() self.tab_widget = QTabWidget()
self.tab_widget.setTabBar(CTabBar()) self.tab_widget.setTabBar(CTabBar())
self.tab_widget.setStyleSheet( self.tab_widget.setStyleSheet("""
"""
QTabWidget::pane { /* The tab widget frame */ QTabWidget::pane { /* The tab widget frame */
border: 1px solid #c3c3c3; border: 1px solid #c3c3c3;
} }
""" """)
)
tab_corner = QWidget() tab_corner = QWidget()
tab_corner_layout = QHBoxLayout() tab_corner_layout = QHBoxLayout()
tab_corner_layout.setSpacing(1) tab_corner_layout.setSpacing(1)
@ -160,8 +158,7 @@ class OptionsDialog(Dialog):
# chk_update_btn = QPushButton(_('UPDATE')) # chk_update_btn = QPushButton(_('UPDATE'))
# chk_update_btn.clicked.connect(self.check_updates) # chk_update_btn.clicked.connect(self.check_updates)
home_label = QLabel( home_label = QLabel(
'<a href="{url}">User Guide</a>'.format(url=Endpoint.user_guide) '<a href="{url}">User Guide</a>'.format(url=Endpoint.user_guide))
)
home_label.setOpenExternalLinks(True) home_label.setOpenExternalLinks(True)
# buttons # buttons
btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self) btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self)
@ -171,7 +168,7 @@ class OptionsDialog(Dialog):
bottom_layout.addWidget(about_btn) bottom_layout.addWidget(about_btn)
bottom_layout.addWidget(home_label) bottom_layout.addWidget(home_label)
bottom_layout.addWidget(btnbox) bottom_layout.addWidget(btnbox)
#self.setLayout(self.main_layout) # self.setLayout(self.main_layout)
self.main_layout.addLayout(bottom_layout) self.main_layout.addLayout(bottom_layout)
# init from saved data # init from saved data
self.current_model = None self.current_model = None
@ -179,7 +176,8 @@ class OptionsDialog(Dialog):
self.current_model = get_model_byId(mw.col.models, self.model_id) self.current_model = get_model_byId(mw.col.models, self.model_id)
if self.current_model: if self.current_model:
self.models_button.setText( self.models_button.setText(
u'%s [%s]' % (_('CHOOSE_NOTE_TYPES'), self.current_model['name'])) u'%s [%s]' % (_('CHOOSE_NOTE_TYPES'),
self.current_model['name']))
# build fields -- dicts layout # build fields -- dicts layout
self.build_tabs_layout() self.build_tabs_layout()
@ -225,13 +223,18 @@ class OptionsDialog(Dialog):
''' '''
build dictionaryfields etc build dictionaryfields etc
''' '''
try: self.tab_widget.currentChanged.disconnect() try:
except Exception: pass self.tab_widget.currentChanged.disconnect()
except Exception:
pass
while len(self.tabs) > 0: while len(self.tabs) > 0:
self.removeTab(0, True) self.removeTab(0, True)
# tabs # tabs
conf = config.get_maps(self.current_model['id']) conf = config.get_maps(self.current_model['id'])
maps_list = {'list': [conf], 'def': 0} if isinstance(conf, list) else conf maps_list = {
'list': [conf],
'def': 0
} if isinstance(conf, list) else conf
for maps in maps_list['list']: for maps in maps_list['list']:
self.addTab(maps, False) self.addTab(maps, False)
self.tab_widget.currentChanged.connect(self.changedTab) self.tab_widget.currentChanged.connect(self.changedTab)
@ -241,27 +244,20 @@ class OptionsDialog(Dialog):
# size # size
self.resize( self.resize(
WIDGET_SIZE.dialog_width, WIDGET_SIZE.dialog_width,
min(max(3, len(self.current_model['flds'])+1), 14) * WIDGET_SIZE.map_max_height + WIDGET_SIZE.dialog_height_margin min(max(3,
) len(self.current_model['flds']) + 1), 14) *
WIDGET_SIZE.map_max_height + WIDGET_SIZE.dialog_height_margin)
self.save() self.save()
def addTab(self, maps=None, forcus=True): def addTab(self, maps=None, forcus=True):
i = len(self.tabs) i = len(self.tabs)
if isinstance(maps, list): if isinstance(maps, list):
maps = { maps = {'fields': maps, 'name': _('CONFIG_INDEX') % (i + 1)}
'fields': maps, tab = TabContent(self.current_model, maps['fields'] if maps else None,
'name': _('CONFIG_INDEX') % (i+1) self.dict_services)
}
tab = TabContent(
self.current_model,
maps['fields'] if maps else None,
self.dict_services
)
self.tabs.append(tab) self.tabs.append(tab)
self.tab_widget.addTab( self.tab_widget.addTab(
tab, tab, maps['name'] if maps else _('CONFIG_INDEX') % (i + 1))
maps['name'] if maps else _('CONFIG_INDEX') % (i+1)
)
if forcus: if forcus:
self.tab_widget.setCurrentIndex(i) self.tab_widget.setCurrentIndex(i)
@ -273,7 +269,7 @@ class OptionsDialog(Dialog):
del self.tabs[i] del self.tabs[i]
self.tab_widget.removeTab(i) self.tab_widget.removeTab(i)
tab.destroy() tab.destroy()
#for k in range(0, len(self.tabs)): # for k in range(0, len(self.tabs)):
# self.tab_widget.setTabText(k, _('CONFIG_INDEX') % (k+1)) # self.tab_widget.setTabText(k, _('CONFIG_INDEX') % (k+1))
def changedTab(self, i): def changedTab(self, i):
@ -289,12 +285,18 @@ class OptionsDialog(Dialog):
''' '''
show choose note type window show choose note type window
''' '''
edit = QPushButton(anki.lang._("Manage"), edit = QPushButton(
clicked=lambda: aqt.models.Models(mw, self)) anki.lang._("Manage"), clicked=lambda: aqt.models.Models(mw, self))
ret = StudyDeck(mw, names=lambda: sorted(mw.col.models.allNames()), ret = StudyDeck(
accept=anki.lang._("Choose"), title=anki.lang._("Choose Note Type"), mw,
help="_notes", parent=self, buttons=[edit], names=lambda: sorted(mw.col.models.allNames()),
cancel=True, geomKey="selectModel") accept=anki.lang._("Choose"),
title=anki.lang._("Choose Note Type"),
help="_notes",
parent=self,
buttons=[edit],
cancel=True,
geomKey="selectModel")
if ret.name: if ret.name:
model = mw.col.models.byName(ret.name) model = mw.col.models.byName(ret.name)
self.models_button.setText( self.models_button.setText(
@ -306,14 +308,13 @@ class OptionsDialog(Dialog):
if not self.current_model: if not self.current_model:
return return
data = dict() data = dict()
maps_list = { maps_list = {'list': [], 'def': self.tab_widget.currentIndex()}
'list': [],
'def': self.tab_widget.currentIndex()
}
for i, tab in enumerate(self.tabs): for i, tab in enumerate(self.tabs):
maps_list['list'].append({ maps_list['list'].append({
'fields': tab.data, 'fields':
'name': self.tab_widget.tabBar().tabText(i) tab.data,
'name':
self.tab_widget.tabBar().tabText(i)
}) })
current_model_id = str(self.current_model['id']) current_model_id = str(self.current_model['id'])
data[current_model_id] = maps_list data[current_model_id] = maps_list
@ -339,7 +340,7 @@ class TabContent(QScrollArea):
self.setWidgetResizable(True) self.setWidgetResizable(True)
self.setWidget(dicts) self.setWidget(dicts)
self.dicts_layout = dicts.layout() self.dicts_layout = dicts.layout()
#self.dicts_layout.setSizeConstraint(QLayout.SetFixedSize) # self.dicts_layout.setSizeConstraint(QLayout.SetFixedSize)
def build_layout(self): def build_layout(self):
''' '''
@ -372,7 +373,8 @@ class TabContent(QScrollArea):
self.ignore_all_check_btn.setEnabled(True) self.ignore_all_check_btn.setEnabled(True)
self.ignore_all_check_btn.setChecked(True) self.ignore_all_check_btn.setChecked(True)
self.dicts_layout.addWidget(self.ignore_all_check_btn, 0, 1) self.dicts_layout.addWidget(self.ignore_all_check_btn, 0, 1)
self.ignore_all_check_btn.clicked.connect(self.ignore_all_check_changed) self.ignore_all_check_btn.clicked.connect(
self.ignore_all_check_changed)
# Skip valued all # Skip valued all
self.skip_all_check_btn = QCheckBox(_('SELECT_ALL')) self.skip_all_check_btn = QCheckBox(_('SELECT_ALL'))
@ -389,15 +391,18 @@ class TabContent(QScrollArea):
name = fld['name'] name = fld['name']
if maps: if maps:
for j, each in enumerate(maps): for j, each in enumerate(maps):
if each.get('fld_ord', -1) == ord or each.get('fld_name', '') == name: if each.get('fld_ord', -1) == ord or each.get(
'fld_name', '') == name:
each['fld_name'] = name each['fld_name'] = name
each['fld_ord'] = ord each['fld_ord'] = ord
self.add_dict_layout(j, **each) self.add_dict_layout(j, **each)
break break
else: else:
self.add_dict_layout(i, fld_name=name, fld_ord=ord, word_checked=i==0) self.add_dict_layout(
i, fld_name=name, fld_ord=ord, word_checked=i == 0)
else: else:
self.add_dict_layout(i, fld_name=name, fld_ord=ord, word_checked=i==0) self.add_dict_layout(
i, fld_name=name, fld_ord=ord, word_checked=i == 0)
# update # update
self.ignore_all_update() self.ignore_all_update()
@ -410,21 +415,21 @@ class TabContent(QScrollArea):
word_checked = kwargs.get('word_checked', False) word_checked = kwargs.get('word_checked', False)
fld_name, fld_ord = ( fld_name, fld_ord = (
kwargs.get('fld_name', ''), #笔记类型的字段名 kwargs.get('fld_name', ''), # 笔记类型的字段名
kwargs.get('fld_ord', ''), #笔记类型的字段编号 kwargs.get('fld_ord', ''), # 笔记类型的字段编号
) )
dict_name, dict_unique, dict_fld_name, dict_fld_ord = ( dict_name, dict_unique, dict_fld_name, dict_fld_ord = (
kwargs.get('dict_name', ''), #字典名 kwargs.get('dict_name', ''), # 字典名
kwargs.get('dict_unique', ''), #字典ID kwargs.get('dict_unique', ''), # 字典ID
kwargs.get('dict_fld_name', ''), #对应字典的字段名 kwargs.get('dict_fld_name', ''), # 对应字典的字段名
kwargs.get('dcit_fld_ord', 0) #对应字典的字段编号 kwargs.get('dcit_fld_ord', 0) # 对应字典的字段编号
) )
ignore, skip, cloze = ( ignore, skip, cloze = (
kwargs.get('ignore', True), #忽略标志 kwargs.get('ignore', True), # 忽略标志
kwargs.get('skip_valued', True), #略过有值项标志 kwargs.get('skip_valued', True), # 略过有值项标志
kwargs.get('cloze_word', False), #单词填空 kwargs.get('cloze_word', False), # 单词填空
) )
# check # check
@ -436,25 +441,22 @@ class TabContent(QScrollArea):
# dict combox # dict combox
dict_combo = QComboBox() dict_combo = QComboBox()
dict_combo.setMinimumSize(WIDGET_SIZE.map_dict_width, 0) dict_combo.setMinimumSize(WIDGET_SIZE.map_dict_width, 0)
dict_combo.setMaximumSize( dict_combo.setMaximumSize(WIDGET_SIZE.map_dict_width,
WIDGET_SIZE.map_dict_width, WIDGET_SIZE.map_max_height)
WIDGET_SIZE.map_max_height dict_combo.setFocusPolicy(Qt.TabFocus | Qt.ClickFocus | Qt.StrongFocus
) | Qt.WheelFocus)
dict_combo.setFocusPolicy( ignore = not self.fill_dict_combo_options(dict_combo, dict_unique,
Qt.TabFocus | Qt.ClickFocus | Qt.StrongFocus | Qt.WheelFocus self._services) or ignore
)
ignore = not self.fill_dict_combo_options(dict_combo, dict_unique, self._services) or ignore
dict_unique = dict_combo.itemData(dict_combo.currentIndex()) dict_unique = dict_combo.itemData(dict_combo.currentIndex())
dict_combo.setEnabled(not word_checked and not ignore) dict_combo.setEnabled(not word_checked and not ignore)
# field combox # field combox
field_combo = QComboBox() field_combo = QComboBox()
field_combo.setMinimumSize(WIDGET_SIZE.map_field_width, 0) field_combo.setMinimumSize(WIDGET_SIZE.map_field_width, 0)
field_combo.setMaximumSize( field_combo.setMaximumSize(WIDGET_SIZE.map_field_width,
WIDGET_SIZE.map_field_width, WIDGET_SIZE.map_max_height)
WIDGET_SIZE.map_max_height
)
field_combo.setEnabled(not word_checked and not ignore) field_combo.setEnabled(not word_checked and not ignore)
self.fill_field_combo_options(field_combo, dict_name, dict_unique, dict_fld_name, dict_fld_ord) self.fill_field_combo_options(field_combo, dict_name, dict_unique,
dict_fld_name, dict_fld_ord)
# ignore # ignore
ignore_check_btn = QCheckBox(_("NOT_DICT_FIELD")) ignore_check_btn = QCheckBox(_("NOT_DICT_FIELD"))
@ -489,13 +491,14 @@ class TabContent(QScrollArea):
cloze_check_btn.setEnabled(not word_checked and not ignore) cloze_check_btn.setEnabled(not word_checked and not ignore)
if word_checked: if word_checked:
self._last_checkeds = [ self._last_checkeds = [
ignore_check_btn, dict_combo, ignore_check_btn, dict_combo, field_combo, skip_check_btn
field_combo, skip_check_btn
] ]
word_check_btn.clicked.connect(radio_btn_checked) word_check_btn.clicked.connect(radio_btn_checked)
if word_checked: if word_checked:
self._last_checkeds = None self._last_checkeds = None
radio_btn_checked() radio_btn_checked()
# ignor # ignor
def ignore_check_changed(): def ignore_check_changed():
word_checked = word_check_btn.isChecked() word_checked = word_check_btn.isChecked()
@ -504,20 +507,20 @@ class TabContent(QScrollArea):
field_combo.setEnabled(not word_checked and not ignore) field_combo.setEnabled(not word_checked and not ignore)
skip_check_btn.setEnabled(not word_checked and not ignore) skip_check_btn.setEnabled(not word_checked and not ignore)
cloze_check_btn.setEnabled(not word_checked and not ignore) cloze_check_btn.setEnabled(not word_checked and not ignore)
ignore_check_btn.stateChanged.connect(ignore_check_changed) ignore_check_btn.stateChanged.connect(ignore_check_changed)
ignore_check_btn.clicked.connect(self.ignore_all_update) ignore_check_btn.clicked.connect(self.ignore_all_update)
# skip # skip
skip_check_btn.clicked.connect(self.skip_all_update) skip_check_btn.clicked.connect(self.skip_all_update)
# dict # dict
def dict_combo_changed(index): def dict_combo_changed(index):
'''dict combo box index changed''' '''dict combo box index changed'''
self.fill_field_combo_options( self.fill_field_combo_options(
field_combo, field_combo, dict_combo.currentText(),
dict_combo.currentText(), dict_combo.itemData(index), field_combo.currentText(),
dict_combo.itemData(index), field_combo.itemData(field_combo.currentIndex()))
field_combo.currentText(),
field_combo.itemData(field_combo.currentIndex())
)
dict_combo.currentIndexChanged.connect(dict_combo_changed) dict_combo.currentIndexChanged.connect(dict_combo_changed)
self.dicts_layout.addWidget(word_check_btn, i + 1, 0) self.dicts_layout.addWidget(word_check_btn, i + 1, 0)
@ -528,7 +531,10 @@ class TabContent(QScrollArea):
self.dicts_layout.addWidget(cloze_check_btn, i + 1, 5) self.dicts_layout.addWidget(cloze_check_btn, i + 1, 5)
self._options.append({ self._options.append({
'model': {'fld_name': fld_name, 'fld_ord': fld_ord}, 'model': {
'fld_name': fld_name,
'fld_ord': fld_ord
},
'word_check_btn': word_check_btn, 'word_check_btn': word_check_btn,
'dict_combo': dict_combo, 'dict_combo': dict_combo,
'field_combo': field_combo, 'field_combo': field_combo,
@ -561,15 +567,18 @@ class TabContent(QScrollArea):
dict_combo.setCurrentIndex(i) dict_combo.setCurrentIndex(i)
return True return True
return False return False
return set_dict_combo_index() return set_dict_combo_index()
def fill_field_combo_options(self, field_combo, dict_combo_text, dict_combo_itemdata, dict_fld_name, dict_fld_ord): def fill_field_combo_options(self, field_combo, dict_combo_text,
dict_combo_itemdata, dict_fld_name,
dict_fld_ord):
'''setup field combobox''' '''setup field combobox'''
field_combo.clear() field_combo.clear()
field_combo.setEditable(False) field_combo.setEditable(False)
#if dict_combo_text in _sl('NOT_DICT_FIELD'): # if dict_combo_text in _sl('NOT_DICT_FIELD'):
# field_combo.setEnabled(False) # field_combo.setEnabled(False)
#el # el
if dict_combo_text in _sl('MDX_SERVER'): if dict_combo_text in _sl('MDX_SERVER'):
text = dict_fld_name if dict_fld_name else 'http://' text = dict_fld_name if dict_fld_name else 'http://'
field_combo.setEditable(True) field_combo.setEditable(True)
@ -594,16 +603,26 @@ class TabContent(QScrollArea):
maps = [] maps = []
for row in self._options: for row in self._options:
maps.append({ maps.append({
'fld_name': row['model']['fld_name'], 'fld_name':
'fld_ord': row['model']['fld_ord'], row['model']['fld_name'],
'word_checked': row['word_check_btn'].isChecked(), 'fld_ord':
'dict_name': row['dict_combo'].currentText().strip(), row['model']['fld_ord'],
'dict_unique': row['dict_combo'].itemData(row['dict_combo'].currentIndex()), 'word_checked':
'dict_fld_name': row['field_combo'].currentText().strip(), row['word_check_btn'].isChecked(),
'dict_fld_ord': row['field_combo'].itemData(row['field_combo'].currentIndex()), 'dict_name':
'ignore': row['ignore_check_btn'].isChecked(), row['dict_combo'].currentText().strip(),
'skip_valued': row['skip_check_btn'].isChecked(), 'dict_unique':
'cloze_word': row['cloze_check_btn'].isChecked() row['dict_combo'].itemData(row['dict_combo'].currentIndex()),
'dict_fld_name':
row['field_combo'].currentText().strip(),
'dict_fld_ord':
row['field_combo'].itemData(row['field_combo'].currentIndex()),
'ignore':
row['ignore_check_btn'].isChecked(),
'skip_valued':
row['skip_check_btn'].isChecked(),
'cloze_word':
row['cloze_check_btn'].isChecked()
}) })
return maps return maps
@ -635,8 +654,7 @@ class TabContent(QScrollArea):
class CTabBar(QTabBar): class CTabBar(QTabBar):
def __init__(self, parent=None):
def __init__(self, parent = None):
super(CTabBar, self).__init__(parent) super(CTabBar, self).__init__(parent)
# style # style
self.setTabsClosable(True) self.setTabsClosable(True)
@ -651,14 +669,17 @@ class CTabBar(QTabBar):
self._editor.installEventFilter(self) self._editor.installEventFilter(self)
def eventFilter(self, widget, event): def eventFilter(self, widget, event):
if ((event.type() == QEvent.MouseButtonPress and \ bhide = False
not self._editor.geometry().contains(event.globalPos()) \ if event.type() == QEvent.MouseButtonPress:
) or \ if not self._editor.geometry().contains(event.globalPos()):
(event.type() == QEvent.KeyPress and \ bhide = True
event.key() == Qt.Key_Escape) if not bhide:
): if event.type() == QEvent.KeyPress:
self.hideEditor() if event.key() == Qt.Key_Escape:
return True bhide = True
if bhide:
self.hideEditor()
return True
return QTabBar.eventFilter(self, widget, event) return QTabBar.eventFilter(self, widget, event)
def mouseDoubleClickEvent(self, event): def mouseDoubleClickEvent(self, event):

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -17,24 +17,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import time import time
from collections import defaultdict from collections import defaultdict
from aqt.qt import * from aqt.qt import *
from ..lang import _
from ..context import APP_ICON
from ..context import APP_ICON
from ..lang import _
__all__ = ['ProgressWindow'] __all__ = ['ProgressWindow']
_INFO_TEMPLATE = u''.join([ _INFO_TEMPLATE = u''.join([
u'<strong>' + _('QUERIED') + u'</strong>', u'<strong>' + _('QUERIED') + u'</strong>',
u'<p>' + 45 * u'-' + u'</p>', u'<p>' + 45 * u'-' + u'</p>',
u'<p>' + _('SUCCESS') + u' <b>{}</b> ' + _('WORDS') + u'</p>', u'<p>' + _('SUCCESS') + u' <b>{}</b> ' + _('WORDS') + u'</p>',
u'<p>' + _('SKIPED') + u' <b>{}</b> ' + _('WORDS') + u'</p>', u'<p>' + _('SKIPED') + u' <b>{}</b> ' + _('WORDS') + u'</p>',
u'<p>' + _('UPDATE') + u' <b>{}</b> ' + _('FIELDS') + u'</p>', u'<p>' + _('UPDATE') + u' <b>{}</b> ' + _('FIELDS') + u'</p>',
u'<p>' + _('FAILURE') + u' <b>{}</b> ' + _('WORDS') + u'</p>', u'<p>' + _('FAILURE') + u' <b>{}</b> ' + _('WORDS') + u'</p>',
]) ])
@ -66,15 +64,12 @@ class ProgressWindow(object):
self._msg_count.get('words_number', 0), self._msg_count.get('words_number', 0),
self._msg_count.get('fields_number', 0), self._msg_count.get('fields_number', 0),
self._msg_count.get('fails_number', 0), self._msg_count.get('fails_number', 0),
self._msg_count.get('skips_number', 0) self._msg_count.get('skips_number', 0))
) number_info = _INFO_TEMPLATE.format(words_number, skips_number,
number_info = _INFO_TEMPLATE.format( fields_number, fails_number)
words_number, self._update(
skips_number, label=number_info,
fields_number, value=words_number + skips_number + fails_number)
fails_number
)
self._update(label=number_info, value=words_number+skips_number+fails_number)
self._win.adjustSize() self._win.adjustSize()
self.app.processEvents() self.app.processEvents()
@ -95,9 +90,7 @@ class ProgressWindow(object):
self._win.setWindowTitle("FastWQ - Querying...") self._win.setWindowTitle("FastWQ - Querying...")
self._win.setModal(True) self._win.setModal(True)
self._win.setWindowFlags( self._win.setWindowFlags(
self._win.windowFlags() & self._win.windowFlags() & ~Qt.WindowContextHelpButtonHint)
~Qt.WindowContextHelpButtonHint
)
self._win.setWindowIcon(APP_ICON) self._win.setWindowIcon(APP_ICON)
self._win.setAutoReset(True) self._win.setAutoReset(True)
self._win.setAutoClose(True) self._win.setAutoClose(True)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,10 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from aqt.qt import * from aqt.qt import *
from .base import Dialog, WIDGET_SIZE
from ..context import config from ..context import config
from ..lang import _ from ..lang import _
from .base import Dialog
__all__ = ['SettingDialog'] __all__ = ['SettingDialog']
@ -36,7 +36,6 @@ class SettingDialog(Dialog):
self.setFixedWidth(400) self.setFixedWidth(400)
self.check_force_update = None self.check_force_update = None
self.check_ignore_accents = None self.check_ignore_accents = None
# self.check_auto_update = None
self.input_thread_number = None self.input_thread_number = None
self.build() self.build()
@ -53,11 +52,6 @@ class SettingDialog(Dialog):
layout.addWidget(check_ignore_accents) layout.addWidget(check_ignore_accents)
layout.addSpacing(10) layout.addSpacing(10)
# check_auto_update = QCheckBox(_("AUTO_UPDATE"))
# check_auto_update.setChecked(config.auto_update)
# layout.addWidget(check_auto_update)
# layout.addSpacing(10)
check_ighore_mdx_wordcase = QCheckBox(_("IGNORE_MDX_WORDCASE")) check_ighore_mdx_wordcase = QCheckBox(_("IGNORE_MDX_WORDCASE"))
check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase) check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase)
layout.addWidget(check_ighore_mdx_wordcase) layout.addWidget(check_ighore_mdx_wordcase)
@ -84,35 +78,55 @@ class SettingDialog(Dialog):
hbox.setStretchFactor(input_cloze_str, 2) hbox.setStretchFactor(input_cloze_str, 2)
layout.addLayout(hbox) layout.addLayout(hbox)
buttonBox = QDialogButtonBox(parent=self) hbox = QHBoxLayout()
buttonBox.setStandardButtons(QDialogButtonBox.Ok) okbtn = QDialogButtonBox(parent=self)
buttonBox.accepted.connect(self.accept) # 确定 okbtn.setStandardButtons(QDialogButtonBox.Ok)
okbtn.clicked.connect(self.accept)
resetbtn = QDialogButtonBox(parent=self)
resetbtn.setStandardButtons(QDialogButtonBox.Reset)
resetbtn.clicked.connect(self.reset)
hbox.setAlignment(Qt.AlignRight)
hbox.addSpacing(300)
hbox.addWidget(resetbtn)
hbox.addWidget(okbtn)
layout.addSpacing(48) layout.addSpacing(48)
layout.addWidget(buttonBox) layout.addLayout(hbox)
self.check_force_update = check_force_update self.check_force_update = check_force_update
self.check_ignore_accents = check_ignore_accents self.check_ignore_accents = check_ignore_accents
# self.check_auto_update = check_auto_update
self.check_ighore_mdx_wordcase = check_ighore_mdx_wordcase self.check_ighore_mdx_wordcase = check_ighore_mdx_wordcase
self.input_thread_number = input_thread_number self.input_thread_number = input_thread_number
self.input_cloze_str = input_cloze_str self.input_cloze_str = input_cloze_str
layout.setAlignment(Qt.AlignTop|Qt.AlignLeft) layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.setLayout(layout) self.setLayout(layout)
def accept(self): def accept(self):
self.save() self.save()
super(SettingDialog, self).accept() super(SettingDialog, self).accept()
def reset(self):
data = {
'force_update': False,
'ignore_accents': False,
'ignore_mdx_wordcase': False,
'thread_number': 16,
'cloze_str': '{{c1::%s}}'
}
config.update(data)
self.check_force_update.setChecked(config.force_update)
self.check_ignore_accents.setChecked(config.ignore_accents)
self.check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase)
self.input_thread_number.setValue(config.thread_number)
self.input_cloze_str.setText(config.cloze_str)
def save(self): def save(self):
data = { data = {
'force_update': self.check_force_update.isChecked(), 'force_update': self.check_force_update.isChecked(),
'ignore_accents': self.check_ignore_accents.isChecked(), 'ignore_accents': self.check_ignore_accents.isChecked(),
# 'auto_update': self.check_auto_update.isChecked(),
'ignore_mdx_wordcase': self.check_ighore_mdx_wordcase.isChecked(), 'ignore_mdx_wordcase': self.check_ighore_mdx_wordcase.isChecked(),
'thread_number': self.input_thread_number.value(), 'thread_number': self.input_thread_number.value(),
'cloze_str': self.input_cloze_str.text() 'cloze_str': self.input_cloze_str.text()
} }
config.update(data) config.update(data)

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -18,15 +18,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from anki.lang import currentLang from anki.lang import currentLang
try: try:
basestring basestring
except NameError: except NameError:
basestring = str basestring = str
__all__ = ['_', '_cl', '_sl'] __all__ = ['_', '_cl', '_sl']
# Language Define, [Key, zh_CN, en]
#Language Define, [Key, zh_CN, en]
_arr = [ _arr = [
['CHECK_FILENAME_LABEL', u'使用文件名作为标签', u'Use the Filename as Label'], ['CHECK_FILENAME_LABEL', u'使用文件名作为标签', u'Use the Filename as Label'],
['EXPORT_MEDIA', u'导出媒体文件', u'Export Media Files'], ['EXPORT_MEDIA', u'导出媒体文件', u'Export Media Files'],
@ -42,13 +42,19 @@ _arr = [
['QUERIED', u'查询', u'Queried'], ['QUERIED', u'查询', u'Queried'],
['FIELDS', u'字段', u'Fields'], ['FIELDS', u'字段', u'Fields'],
['WORDS', u'单词', u'Words'], ['WORDS', u'单词', u'Words'],
['NOT_DICT_FIELD', u'忽略', u'Ignore'], #不是字典字段 ['NOT_DICT_FIELD', u'忽略', u'Ignore'], # 不是字典字段
['NOTE_TYPE_FIELDS', u'<b>笔记字段</b>', u'<b>Note Fields</b>'], ['NOTE_TYPE_FIELDS', u'<b>笔记字段</b>', u'<b>Note Fields</b>'],
['DICTS', u'<b>字典</b>', u'<b>Dictionary</b>'], ['DICTS', u'<b>字典</b>', u'<b>Dictionary</b>'],
['DICT_FIELDS', u'<b>字典字段</b>', u'<b>Fields</b>'], ['DICT_FIELDS', u'<b>字典字段</b>', u'<b>Fields</b>'],
['RADIOS_DESC', u'<b>单选框选中为待查询单词字段</b>', u'<b> Select the field to be queried with single selection.</b>'], [
'RADIOS_DESC', u'<b>单选框选中为待查询单词字段</b>',
u'<b> Select the field to be queried with single selection.</b>'
],
['NO_QUERY_WORD', u'查询字段无单词', u'The query field is empty'], ['NO_QUERY_WORD', u'查询字段无单词', u'The query field is empty'],
['CSS_NOT_FOUND', u'没有找到CSS文件请手动选择', u'No CSS file found, please select one manually.'], [
'CSS_NOT_FOUND', u'没有找到CSS文件请手动选择',
u'No CSS file found, please select one manually.'
],
['ABOUT', u'关于', u'About'], ['ABOUT', u'关于', u'About'],
['REPOSITORY', u'项目地址', u'Project Repo'], ['REPOSITORY', u'项目地址', u'Project Repo'],
['FEEDBACK', u'反馈', u'Feedback'], ['FEEDBACK', u'反馈', u'Feedback'],
@ -60,7 +66,10 @@ _arr = [
['UPDATE', u'更新', u'Update'], ['UPDATE', u'更新', u'Update'],
['AUTO_UPDATE', u'自动检测新版本', u'Auto check new version'], ['AUTO_UPDATE', u'自动检测新版本', u'Auto check new version'],
['CHECK_UPDATE', u'检测更新', u'Check Update'], ['CHECK_UPDATE', u'检测更新', u'Check Update'],
['IGNORE_MDX_WORDCASE', u'忽略本地词典单词大小写', u'Ignore MDX dictionary word case'], [
'IGNORE_MDX_WORDCASE', u'忽略本地词典单词大小写',
u'Ignore MDX dictionary word case'
],
['FORCE_UPDATE', u'强制更新字段', u'Forced Updates of all fields'], ['FORCE_UPDATE', u'强制更新字段', u'Forced Updates of all fields'],
['IGNORE_ACCENTS', u'忽略声调', u'Ignore Accents'], ['IGNORE_ACCENTS', u'忽略声调', u'Ignore Accents'],
['SKIP_VALUED', u'跳过有值项', u'Skip non-empty'], ['SKIP_VALUED', u'跳过有值项', u'Skip non-empty'],
@ -68,7 +77,10 @@ _arr = [
['SETTINGS', u'参数', u'Settings'], ['SETTINGS', u'参数', u'Settings'],
['THREAD_NUMBER', u'线程数', u'Number of Threads'], ['THREAD_NUMBER', u'线程数', u'Number of Threads'],
['INITLIZING_DICT', u'初始化词典...', u'Initlizing Dictionary...'], ['INITLIZING_DICT', u'初始化词典...', u'Initlizing Dictionary...'],
['PLS_SET_DICTIONARY_FIELDS', u'请设置字典和字段', u'Please set the dictionary and fields.'], [
'PLS_SET_DICTIONARY_FIELDS', u'请设置字典和字段',
u'Please set the dictionary and fields.'
],
['CONFIG_INDEX', u'配置 %s', u'Config %s'], ['CONFIG_INDEX', u'配置 %s', u'Config %s'],
['SELECT_ALL', u'全选', u'All'], ['SELECT_ALL', u'全选', u'All'],
['DICTS_NAME', u'字典名称', u'Dictionary Name'], ['DICTS_NAME', u'字典名称', u'Dictionary Name'],
@ -80,7 +92,6 @@ _arr = [
['OPTIONS', u'选项', u'Options'], ['OPTIONS', u'选项', u'Options'],
['CLOZE_WORD', u'单词填空', u'Cloze word'], ['CLOZE_WORD', u'单词填空', u'Cloze word'],
['CLOZE_WORD_FORMAT', '单词填空格式', 'Cloze word formater'], ['CLOZE_WORD_FORMAT', '单词填空格式', 'Cloze word formater'],
['BRE_PRON', u'英式发音', u'British Pronunciation'], ['BRE_PRON', u'英式发音', u'British Pronunciation'],
['AME_PRON', u'美式发音', u'American Pronunciation'], ['AME_PRON', u'美式发音', u'American Pronunciation'],
['PRON', u'发音', u'Audio Pronunciation'], ['PRON', u'发音', u'Audio Pronunciation'],

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
from aqt.qt import *
try:
_encoding = QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QApplication.translate(context, text, disambig)
class Ui_DialogUpdates(object):
def setupUi(self, DialogUpdates):
DialogUpdates.setObjectName(u"DialogUpdates")
DialogUpdates.resize(500, 400)
self.verticalLayout = QVBoxLayout(DialogUpdates)
self.verticalLayout.setObjectName(u"verticalLayout")
self.labelUpdates = QLabel(DialogUpdates)
self.labelUpdates.setWordWrap(True)
self.labelUpdates.setOpenExternalLinks(True)
self.labelUpdates.setObjectName(u"labelUpdates")
self.verticalLayout.addWidget(self.labelUpdates)
self.textBrowser = QTextBrowser(DialogUpdates)
self.textBrowser.setObjectName(u"textBrowser")
self.verticalLayout.addWidget(self.textBrowser)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.update = QPushButton(DialogUpdates)
self.update.setObjectName(u"update")
self.horizontalLayout.addWidget(self.update, 0, Qt.AlignCenter)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi(DialogUpdates)
QMetaObject.connectSlotsByName(DialogUpdates)
def retranslateUi(self, DialogUpdates):
DialogUpdates.setWindowTitle(_translate("DialogUpdates", "FastWQ - Updater", None))
self.labelUpdates.setText(_translate(
"DialogUpdates",
"<html><head/><body>\
<p>A new version of {0} is available for download! </p>\
<p>Do you want to update {1}to version {2}?</p>\
<p>Changes from your version are listed below:</p>\
</body></html>",
None
))
self.update.setText(_translate("DialogUpdates", "Update", None))

View File

@ -1,311 +0,0 @@
try:
import httplib
except:
import http.client as httplib
try:
import urllib2
except:
import urllib.request as urllib2
import json
import os
import sys
import zipfile
import traceback
import io
import aqt
from aqt import mw
from aqt.qt import *
from aqt.utils import showInfo
from anki.hooks import addHook
from anki.utils import isMac, isWin
from ..context import APP_ICON
from .AnkiHub.updates import Ui_DialogUpdates
from .AnkiHub.markdown2 import markdown
__all__ = ['update']
# taken from Anki's aqt/profiles.py
def defaultBase():
path = mw.pm.addonFolder()
return os.path.dirname(os.path.abspath(path))
headers = {"User-Agent": "AnkiHub"}
dataPath = os.path.join(defaultBase(),'.fastwq_2.1.x_ankihub.json')
class DialogUpdates(QDialog, Ui_DialogUpdates):
def __init__(self, parent, data, oldData, callback):
parent = parent if parent else mw
QDialog.__init__(self, parent)
self.setModal(True)
self.setWindowFlags(
self.windowFlags() &
~Qt.WindowContextHelpButtonHint
)
self.setWindowIcon(APP_ICON)
self.setupUi(self)
totalSize = sum(map(lambda x:x['size'],data['assets']))
def answer():
self.update.setEnabled(False)
callback(self.appendHtml, self.finish)
self.html = u''
self.appendHtml(markdown(data['body']))
#if not automaticAnswer:
self.update.clicked.connect(lambda:answer())
fromVersion = ''
if 'tag_name' in oldData:
fromVersion = u'from {0} '.format(oldData['tag_name'])
self.labelUpdates.setText(
str(self.labelUpdates.text()).format(
data['name'],
fromVersion,
data['tag_name']))
def appendHtml(self,html='',temp=''):
self.html += html
self.textBrowser.setHtml(u'<html><body>{0}{1}{2}</body></html>'.format(self.html, temp, u'<div id="text_bottom"></div>'))
self.textBrowser.scrollToAnchor('text_bottom')
def finish(self):
self.hide()
self.destroy()
showInfo('Updated. Please restart Anki.')
pass
def installZipFile(data, fname):
#base = os.path.join(mw.pm.addonFolder(), 'fastwq')
base = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../'))
if fname.endswith(".py"):
path = os.path.join(base, fname)
with open(path, "wb") as file:
file.write(data)
file.close()
return True
# .zip file
try:
z = zipfile.ZipFile(io.BytesIO(data))
except zipfile.BadZipfile:
return False
for n in z.namelist():
if n.endswith("/"):
# folder; ignore
continue
# write
try:
z.extract(n, base)
except:
print(n)
return True
def asset(a):
return {
'url': a['browser_download_url'],
'size': a['size']
}
def updateSingle(repositories, path, data):
def callback(appendHtml, onReady):
for asset in data['assets']:
code = asset['url']
p, fname = os.path.split(code)
appendHtml(temp='<br />Downloading {1}: {0}%<br/>'.format(0,fname))
try:
urlthread = UrlThread(code)
urlthread.start()
urlthread.join()
response = urlthread.response#urllib2.urlopen(code)
meta = response.info()
file_size = int(meta.get("Content-Length"))
except:
appendHtml('Downloading file error!<br/>')
return
d = b''
dl = 0
i = 0
lastPercent = None
while True:
dkb = response.read(1024)
if not dkb:
break
dl += len(dkb)
d += dkb
if dl*100/file_size>i:
lastPercent = int(dl*100/file_size)
i = lastPercent+1
appendHtml(temp='<br />Downloading {1}: {0}%<br/>'.format(lastPercent,fname))
QApplication.instance().processEvents()
appendHtml('<br />Downloading {1}: 100%<br/>'.format(int(dl*100/file_size),fname))
appendHtml('Installing ...<br/>')
if not installZipFile(d, fname):
appendHtml('Corrupt file<br/>')
else:
repositories[path] = data
repositories[path]['update'] = 'ask'
with open(dataPath,'w') as f:
json.dump(repositories,f,indent=2)
f.close()
appendHtml('Done.<br/>')
onReady() # close the AnkiHub update window
return callback
def update(add=[], VERSION='v0.0.0', background=False, parent=None):
parent = parent if parent else mw
# progress win
if not background:
progresswin = QProgressDialog('Update Checking...', '', 0, 0, parent)
progresswin.setWindowModality(Qt.ApplicationModal)
progresswin.setCancelButton(None)
progresswin.setWindowFlags(
progresswin.windowFlags() &
~Qt.WindowContextHelpButtonHint
)
progresswin.setWindowTitle('FastWQ - Updater')
progresswin.setWindowIcon(APP_ICON)
progresswin.resize(280, 60)
progresswin.show()
else:
progresswin = None
#
conn = httplib.HTTPSConnection("api.github.com")
try:
with open(dataPath,'r') as f:
repositories = json.load(f)
f.close()
except:
repositories = {}
for a in add:
if a not in repositories:
repositories[a] = {
'id': 0,
'update': 'ask'
}
for path,repository in repositories.items():
username,repositoryName = path.split('/')
try:
urlthread = UrlThread("https://api.github.com/repos/{0}/releases/latest".format(path))
urlthread.start()
urlthread.join()
release = json.loads(urlthread.response.read())
except Exception as e:
release = {}
if 'id' in release:
if release['id'] != repository['id']:
data = {
'id': release['id'],
'name': repositoryName,
'tag_name': release['tag_name'],
'body': '### {0}\n'.format(release['name']) + release['body'],
'assets': [asset(release['assets'][1])],
'update': 'ask'
}
if 'tag_name' in repository:
oldVersion = map(int,repository['tag_name'][1:].split('.'))
oldVersion = [x for x in oldVersion]
while len(oldVersion)<3:
oldVersion.append(0)
else:
oldVersion = map(int,VERSION[1:].split('.'))#[0,0,0]
oldVersion = [x for x in oldVersion]
newVersion = map(int,data['tag_name'][1:].split('.'))
newVersion = [x for x in newVersion]
isMinor = len(newVersion)>2 and newVersion[2]>0
while len(newVersion)<3:
newVersion.append(0)
i = oldVersion[2]+1
if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]:
if isMinor:
i = 1
while i<newVersion[2]:
if progresswin and progresswin.wasCanceled():
break
try:
minorTagName = 'v{0}.{1}.{2}'.format(newVersion[0],oldVersion[1],i)
urlthread = UrlThread("https://api.github.com/repos/{0}/releases/tags/{1}".format(path,minorTagName))
urlthread.start()
urlthread.join()
responseData = urlthread.response.read()
minor = json.loads(responseData)
data['body'] += '\n\n### {0}\n'.format(minor['name']) + minor['body']
except:
pass
i += 1
if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]:
# new major release necessary!
# if the newest version is minor, fetch the additional assets from the major
if isMinor and (background or not progresswin.wasCanceled()):
try:
majorTagName = 'v{0}.{1}'.format(newVersion[0],newVersion[1])
urlthread = UrlThread(
"https://api.github.com/repos/{0}/releases/tags/{1}".format(path,majorTagName),
"https://api.github.com/repos/{0}/releases/tags/{1}.0".format(path,majorTagName)
)
urlthread.start()
urlthread.join()
responseData = urlthread.response.read()
major = json.loads(responseData)
data['body'] += '\n\n### {0}\n'.format(major['name']) + major['body']
except:
pass
if background or not progresswin.wasCanceled():
if progresswin:
progresswin.hide()
progresswin.destroy()
dialog = DialogUpdates(parent, data, repository, updateSingle(repositories, path, data))
dialog.exec_()
dialog.destroy()
else:
if progresswin:
progresswin.hide()
progresswin.destroy()
return 1
else:
if progresswin:
progresswin.hide()
progresswin.destroy()
return 0
if progresswin:
progresswin.hide()
progresswin.destroy()
return -1
class UrlThread(QThread):
def __init__(self, url, backurl=None):
super(UrlThread, self).__init__()
self.response = None
self.url = url
self.backurl = backurl
self.finished = False
def run(self):
try:
self.response = urllib2.urlopen(self.url)
except:
if self.backurl:
try:
self.response = urllib2.urlopen(self.backurl)
except:
pass
self.finished = True
def join(self):
while not self.finished:
QApplication.instance().processEvents()
self.wait(30)

View File

@ -1,17 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .readmdict import MDX, MDD
from struct import pack, unpack
from io import BytesIO
import re
import sys
import os
import sqlite3
import json import json
import os
import re
import sqlite3
import sys
# zlib compression is used for engine version >=2.0 # zlib compression is used for engine version >=2.0
import zlib import zlib
from io import BytesIO
from struct import pack, unpack
import chardet
from .readmdict import MDD, MDX
# LZO compression is used for engine version < 2.0 # LZO compression is used for engine version < 2.0
try: try:
import lzo import lzo
@ -110,16 +112,22 @@ class IndexBuilder(object):
def _replace_stylesheet(self, txt): def _replace_stylesheet(self, txt):
# substitute stylesheet definition # substitute stylesheet definition
encoding = 'utf-8'
if isinstance(txt, bytes):
encode_type = chardet.detect(txt)
encoding = encode_type['encoding']
txt = txt.decode(encoding)
txt_list = re.split('`\d+`', txt) txt_list = re.split('`\d+`', txt)
txt_tag = re.findall('`\d+`', txt) txt_tag = re.findall('`\d+`', txt)
txt_styled = txt_list[0] txt_styled = txt_list[0]
for j, p in enumerate(txt_list[1:]): for j, p in enumerate(txt_list[1:]):
style = self._stylesheet[txt_tag[j][1:-1]] style = self._stylesheet[txt_tag[j][1:-1]]
if p and p[-1] == '\n': if p and p[-1] == '\n':
txt_styled = txt_styled + style[0] + p.rstrip() + style[1] + '\r\n' txt_styled = txt_styled + style[0] + p.rstrip(
) + style[1] + '\r\n'
else: else:
txt_styled = txt_styled + style[0] + p + style[1] txt_styled = txt_styled + style[0] + p + style[1]
return txt_styled return txt_styled.encode(encoding)
def _make_mdx_index(self, db_name): def _make_mdx_index(self, db_name):
if os.path.exists(db_name): if os.path.exists(db_name):

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -17,29 +17,26 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import os import os
import re import re
import io
import shutil import shutil
import unicodedata import unicodedata
from collections import defaultdict from collections import defaultdict
from aqt.qt import * from aqt.qt import *
from aqt.utils import showInfo from aqt.utils import showInfo
from ..constants import Template from ..constants import Template
from ..context import config from ..context import config
from ..service import service_pool, QueryResult, copy_static_file from ..libs.snowballstemmer import stemmer
from ..service import QueryResult, copy_static_file, service_pool
from ..service.base import LocalService from ..service.base import LocalService
from ..utils import wrap_css from ..utils import wrap_css
from ..libs.snowballstemmer import stemmer
__all__ = [ __all__ = [
'InvalidWordException', 'update_note_fields', 'InvalidWordException', 'update_note_fields', 'update_note_field',
'update_note_field', 'promot_choose_css', 'add_to_tmpl', 'promot_choose_css', 'add_to_tmpl', 'query_flds', 'inspect_note'
'query_flds', 'inspect_note'
] ]
@ -127,19 +124,14 @@ def promot_choose_css(missed_css):
if not os.path.exists(filename) and not css['file'] in checked: if not os.path.exists(filename) and not css['file'] in checked:
checked.add(css['file']) checked.add(css['file'])
showInfo( showInfo(
Template.miss_css.format( Template.miss_css.format(dict=css['title'], css=css['file']))
dict=css['title'],
css=css['file']
)
)
try: try:
filepath = css['dict_path'][:css['dict_path'].rindex( filepath = css['dict_path'][:css['dict_path'].rindex(os.path.
os.path.sep)+1] sep) + 1]
filepath = QFileDialog.getOpenFileName( filepath = QFileDialog.getOpenFileName(
directory=filepath, directory=filepath,
caption=u'Choose css file', caption=u'Choose css file',
filter=u'CSS (*.css)' filter=u'CSS (*.css)')
)
if filepath: if filepath:
shutil.copy(filepath, filename) shutil.copy(filepath, filename)
wrap_css(filename) wrap_css(filename)
@ -161,15 +153,17 @@ def add_to_tmpl(note, **kwargs):
if js and js.strip(): if js and js.strip():
addings = js.strip() addings = js.strip()
if addings not in afmt: if addings not in afmt:
if not addings.startswith(u'<script') and not addings.endswith(u'/script>'): if not addings.startswith(u'<script') and not addings.endswith(
addings = u'\n<script type="text/javascript">\n{}\n</script>'.format(addings) u'/script>'):
addings = u'\n<script type="text/javascript">\n{}\n</script>'.format(
addings)
afmt += addings afmt += addings
if jsfile: if jsfile:
#new_jsfile = u'_' + \ # new_jsfile = u'_' + \
# jsfile if not jsfile.startswith(u'_') else jsfile # jsfile if not jsfile.startswith(u'_') else jsfile
#copy_static_file(jsfile, new_jsfile) # copy_static_file(jsfile, new_jsfile)
#addings = u'\r\n<script src="{}"></script>'.format(new_jsfile) # addings = u'\r\n<script src="{}"></script>'.format(new_jsfile)
#afmt += addings # afmt += addings
jsfile = jsfile if isinstance(jsfile, list) else [jsfile] jsfile = jsfile if isinstance(jsfile, list) else [jsfile]
for fn in jsfile: for fn in jsfile:
addings = ''' addings = '''
@ -204,17 +198,17 @@ def query_flds(note, fileds=None):
continue continue
if i == len(note.fields): if i == len(note.fields):
break break
#ignore field # ignore field
ignore = each.get('ignore', False) ignore = each.get('ignore', False)
if ignore: if ignore:
continue continue
#skip valued # skip valued
skip = each.get('skip_valued', False) skip = each.get('skip_valued', False)
if skip and len(note.fields[i]) != 0: if skip and len(note.fields[i]) != 0:
continue continue
#cloze # cloze
cloze = each.get('cloze_word', False) cloze = each.get('cloze_word', False)
#normal # normal
dict_unique = each.get('dict_unique', '').strip() dict_unique = each.get('dict_unique', '').strip()
dict_fld_ord = each.get('dict_fld_ord', -1) dict_fld_ord = each.get('dict_fld_ord', -1)
fld_ord = each.get('fld_ord', -1) fld_ord = each.get('fld_ord', -1)
@ -237,17 +231,17 @@ def query_flds(note, fileds=None):
success_num = 0 success_num = 0
result = defaultdict(QueryResult) result = defaultdict(QueryResult)
for task in tasks: for task in tasks:
#try: try:
service = services.get(task['k'], None) service = services.get(task['k'], None)
qr = service.active(task['f'], task['w']) qr = service.active(task['f'], task['w'])
if qr: if qr:
if task['cloze']: if task['cloze']:
qr['result'] = cloze_deletion(qr['result'], word) qr['result'] = cloze_deletion(qr['result'], word)
result.update({task['i']: qr}) result.update({task['i']: qr})
success_num += 1 success_num += 1
#except: except Exception as e:
# showInfo(_("NO_QUERY_WORD")) print(_("NO_QUERY_WORD"), e)
# pass pass
missed_css = list() missed_css = list()
for service in services.values(): for service in services.values():
@ -284,13 +278,15 @@ def cloze_deletion(text, cloze):
continue continue
word = text[s:e] word = text[s:e]
if _stemmer.stemWord(word).lower() == term: if _stemmer.stemWord(word).lower() == term:
l = len(cloze) ln = len(cloze)
w = word w = word
if w[:l].lower() == cloze.lower(): if w[:ln].lower() == cloze.lower():
e = s + l e = s + ln
w = word[:l] w = word[:ln]
result = result[:s+offset] + (config.cloze_str % w) + result[e+offset:] result = result[:s + offset] + (
offset += len(config.cloze_str)-2 config.cloze_str % w) + result[e + offset:]
offset += len(config.cloze_str) - 2
return result return result
_stemmer = stemmer('english') _stemmer = stemmer('english')

View File

@ -1,4 +1,4 @@
#-*- coding:utf-8 -*- # -*- coding:utf-8 -*-
# #
# Copyright (C) 2018 sthoo <sth201807@gmail.com> # Copyright (C) 2018 sthoo <sth201807@gmail.com>
# #
@ -19,6 +19,7 @@
import inspect import inspect
import os import os
import random
# use ntpath module to ensure the windows-style (e.g. '\\LDOCE.css') # use ntpath module to ensure the windows-style (e.g. '\\LDOCE.css')
# path can be processed on Unix platform. # path can be processed on Unix platform.
# However, anki version on mac platforms doesn't including this package? # However, anki version on mac platforms doesn't including this package?
@ -27,32 +28,33 @@ import re
import shutil import shutil
import sqlite3 import sqlite3
import urllib import urllib
import zlib
from collections import defaultdict
from functools import wraps
from hashlib import md5, sha1
import requests import requests
from bs4 import BeautifulSoup
from aqt import mw
from aqt.qt import QMutex, QThread
from ..context import config
from ..lang import _cl
from ..libs import MdxBuilder, StardictBuilder
from ..utils import MapDict, wrap_css
try: try:
import urllib2 import urllib2
except: except Exception:
import urllib.request as urllib2 import urllib.request as urllib2
import zlib
import random
from collections import defaultdict
from functools import wraps
from hashlib import md5
from hashlib import sha1
try: try:
from cookielib import CookieJar from cookielib import CookieJar
except: except Exception:
from http.cookiejar import CookieJar from http.cookiejar import CookieJar
from aqt import mw
from aqt.qt import QThread, QMutex
from bs4 import BeautifulSoup
from ..context import config
from ..libs import MdxBuilder, StardictBuilder
from ..utils import MapDict, wrap_css
from ..lang import _cl
try: try:
import threading as _threading import threading as _threading
@ -69,18 +71,14 @@ __all__ = [
def get_hex_name(prefix, val, suffix): def get_hex_name(prefix, val, suffix):
''' get sha1 hax name ''' ''' get sha1 hax name '''
hex_digest = sha1(val.encode('utf-8')).hexdigest().lower() hex_digest = sha1(val.encode('utf-8')).hexdigest().lower()
name = '.'.join([ name = '.'.join(['-'.join([prefix, hex_digest[:8], hex_digest[8:16], hex_digest[16:24], hex_digest[24:32], hex_digest[32:], ]), suffix, ])
'-'.join([
prefix, hex_digest[:8], hex_digest[8:16],
hex_digest[16:24], hex_digest[24:32], hex_digest[32:],
]),
suffix,
])
return name return name
def _is_method_or_func(object): def _is_method_or_func(object):
return inspect.isfunction(object) or inspect.ismethod(object) return inspect.isfunction(object) or inspect.ismethod(object)
def register(labels): def register(labels):
""" """
register the dict service with a labels, which will be shown in the dicts list. register the dict service with a labels, which will be shown in the dicts list.
@ -120,6 +118,8 @@ def export(labels):
export.EXPORT_INDEX += 1 export.EXPORT_INDEX += 1
return _deco return _deco
return _with return _with
export.EXPORT_INDEX = 0 export.EXPORT_INDEX = 0
@ -181,8 +181,9 @@ def with_styles(**styles):
return _deco return _deco
return _with return _with
# bs4 threading lock, overload protection
_BS_LOCKS = [_threading.Lock(), _threading.Lock()] _BS_LOCKS = [_threading.Lock(), _threading.Lock()] # bs4 threading lock, overload protection
def parse_html(html): def parse_html(html):
''' '''
@ -238,6 +239,7 @@ class Service(object):
@property @property
def unique(self): def unique(self):
return self._unique return self._unique
@unique.setter @unique.setter
def unique(self, value): def unique(self, value):
self._unique = value self._unique = value
@ -326,7 +328,7 @@ class WebService(Service):
if response.info().get('Content-Encoding') == 'gzip': if response.info().get('Content-Encoding') == 'gzip':
data = zlib.decompress(data, 16 + zlib.MAX_WBITS) data = zlib.decompress(data, 16 + zlib.MAX_WBITS)
return data return data
except: except Exception:
return u'' return u''
@classmethod @classmethod
@ -340,7 +342,7 @@ class WebService(Service):
'(KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36' '(KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36'
}).content) }).content)
return True return True
except Exception as e: except Exception:
pass pass
class TinyDownloadError(ValueError): class TinyDownloadError(ValueError):
@ -370,7 +372,7 @@ class WebService(Service):
be added onto the stream returned. This is helpful for some web be added onto the stream returned. This is helpful for some web
services that sometimes return MP3s that `mplayer` clips early. services that sometimes return MP3s that `mplayer` clips early.
""" """
DEFAULT_UA = 'Mozilla/5.0' DEFAULT_UA = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36'
DEFAULT_TIMEOUT = 3 DEFAULT_TIMEOUT = 3
PADDING = '\0' * 2**11 PADDING = '\0' * 2**11
@ -434,7 +436,7 @@ class WebService(Service):
try: try:
value_error.payload = response.read() value_error.payload = response.read()
response.close() response.close()
except: except Exception:
pass pass
raise value_error raise value_error
@ -475,7 +477,7 @@ class WebService(Service):
f.write(payload) f.write(payload)
f.close() f.close()
return True return True
except: except Exception:
return False return False
@ -516,7 +518,7 @@ class LocalService(Service):
def _get_builer(key, func=None): def _get_builer(key, func=None):
LocalService._mutex_builder.lock() LocalService._mutex_builder.lock()
key = md5(str(key).encode('utf-8')).hexdigest() key = md5(str(key).encode('utf-8')).hexdigest()
if not func is None: if not(func is None):
if not LocalService._mdx_builders[key]: if not LocalService._mdx_builders[key]:
worker = _DictBuildWorker(func) worker = _DictBuildWorker(func)
worker.start() worker.start()
@ -582,21 +584,22 @@ class MdxService(LocalService):
jsfile = re.findall(r'<script .*?src=[\'\"](.+?)[\'\"]', html, re.DOTALL) jsfile = re.findall(r'<script .*?src=[\'\"](.+?)[\'\"]', html, re.DOTALL)
return QueryResult(result=html, js=u'\n'.join(js), jsfile=jsfile) return QueryResult(result=html, js=u'\n'.join(js), jsfile=jsfile)
def _get_definition_mdx(self): def _get_definition_mdx(self):
"""according to the word return mdx dictionary page""" """according to the word return mdx dictionary page"""
content = self.builder.mdx_lookup(self.word, ignorecase=config.ignore_mdx_wordcase) ignorecase = config.ignore_mdx_wordcase and (self.word != self.word.lower() or self.word != self.word.upper())
content = self.builder.mdx_lookup(self.word, ignorecase=ignorecase)
str_content = "" str_content = ""
if len(content) > 0: if len(content) > 0:
for c in content: for c in content:
str_content += c.replace("\r\n","").replace("entry:/","") str_content += c.replace("\r\n", "").replace("entry:/", "")
return str_content return str_content
def _get_definition_mdd(self, word): def _get_definition_mdd(self, word):
"""according to the keyword(param word) return the media file contents""" """according to the keyword(param word) return the media file contents"""
word = word.replace('/', '\\') word = word.replace('/', '\\')
content = self.builder.mdd_lookup(word, ignorecase=config.ignore_mdx_wordcase) ignorecase = config.ignore_mdx_wordcase and (word != word.lower() or word != word.upper())
content = self.builder.mdd_lookup(word, ignorecase=ignorecase)
if len(content) > 0: if len(content) > 0:
return [content[0]] return [content[0]]
else: else:
@ -620,7 +623,7 @@ class MdxService(LocalService):
f.write(bytes_list[0]) f.write(bytes_list[0])
return savepath return savepath
except sqlite3.OperationalError as e: except sqlite3.OperationalError as e:
#showInfo(str(e)) print(e)
pass pass
return '' return ''
@ -677,7 +680,7 @@ class MdxService(LocalService):
# folder first, and it will also execute the wrap process to generate # folder first, and it will also execute the wrap process to generate
# the desired file. # the desired file.
if not os.path.exists(cssfile): if not os.path.exists(cssfile):
css_src = self.dict_path.replace(self._filename+u'.mdx', f) css_src = self.dict_path.replace(self._filename + u'.mdx', f)
if os.path.exists(css_src): if os.path.exists(css_src):
shutil.copy(css_src, cssfile) shutil.copy(css_src, cssfile)
else: else:
@ -700,17 +703,19 @@ class MdxService(LocalService):
if os.path.exists(savepath): if os.path.exists(savepath):
return savepath return savepath
try: try:
src_fn = self.dict_path.replace(self._filename+u'.mdx', basename) src_fn = self.dict_path.replace(self._filename + u'.mdx', basename)
if os.path.exists(src_fn): if os.path.exists(src_fn):
shutil.copy(src_fn, savepath) shutil.copy(src_fn, savepath)
return savepath return savepath
else: else:
bytes_list = self.builder.mdd_lookup(filepath_in_mdx, ignorecase=config.ignore_mdx_wordcase) ignorecase = config.ignore_mdx_wordcase and (filepath_in_mdx != filepath_in_mdx.lower() or filepath_in_mdx != filepath_in_mdx.upper())
bytes_list = self.builder.mdd_lookup(filepath_in_mdx, ignorecase=ignorecase)
if bytes_list: if bytes_list:
with open(savepath, 'wb') as f: with open(savepath, 'wb') as f:
f.write(bytes_list[0]) f.write(bytes_list[0])
return savepath return savepath
except sqlite3.OperationalError as e: except sqlite3.OperationalError as e:
print('save default file error', e)
pass pass
def save_media_files(self, data): def save_media_files(self, data):
@ -752,7 +757,7 @@ class StardictService(LocalService):
dict_path, dict_path,
service_wrap(StardictBuilder, dict_path, in_memory=False) service_wrap(StardictBuilder, dict_path, in_memory=False)
) )
#if self.builder: # if self.builder:
# self.builder.get_header() # self.builder.get_header()
@staticmethod @staticmethod
@ -772,7 +777,7 @@ class StardictService(LocalService):
@export([u'默认', u'Default']) @export([u'默认', u'Default'])
def fld_whole(self): def fld_whole(self):
#self.builder.check_build() # self.builder.check_build()
try: try:
result = self.builder[self.word] result = self.builder[self.word]
result = result.strip().replace('\r\n', '<br />')\ result = result.strip().replace('\r\n', '<br />')\
@ -788,7 +793,7 @@ class QueryResult(MapDict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(QueryResult, self).__init__(*args, **kwargs) super(QueryResult, self).__init__(*args, **kwargs)
# avoid return None # avoid return None
if self['result'] == None: if self['result'] is None:
self['result'] = "" self['result'] = ""
def set_styles(self, **kwargs): def set_styles(self, **kwargs):

View File

@ -51,6 +51,9 @@ class Cambridge(WebService):
if snd: if snd:
result['pronunciation'][pn+'mp3'] = cambridge_url_base + snd.get('data-src-mp3') result['pronunciation'][pn+'mp3'] = cambridge_url_base + snd.get('data-src-mp3')
header_found = True header_found = True
# 词性
pg = element.find('span', class_='posgram ico-bg')
#义 #义
body = element.find('div', class_='pos-body') body = element.find('div', class_='pos-body')
if body: if body:
@ -63,9 +66,11 @@ class Cambridge(WebService):
trans = tag.find('span', class_='trans') trans = tag.find('span', class_='trans')
es = tag.find_all('div', class_='examp emphasized') es = tag.find_all('div', class_='examp emphasized')
l.append( l.append(
u'<li>{0}{1}{2}{3}</li>'.format( u'<li>{0}{1}{2} {3}{4}</li>'.format(
'<span class="epp-xref">{0}</span>'.format(pg.get_text() if pg else ''),
u'<span class="epp-xref">{0}</span>'.format(i.get_text()) if i else u'', u'<span class="epp-xref">{0}</span>'.format(i.get_text()) if i else u'',
u'<b class="def">{0}</b>'.format(d.get_text()) if d else u'', u'<b class="def">{0}</b>'.format(d.get_text()) if d else u'',
u'<span class="trans">{0}</span>'.format(trans.get_text()) if trans else u'', u'<span class="trans">{0}</span>'.format(trans.get_text()) if trans else u'',
u''.join( u''.join(
u'<div class="examp">{0}</div>'.format(e.get_text()) if e else u'' u'<div class="examp">{0}</div>'.format(e.get_text()) if e else u''