update license file
This commit is contained in:
parent
d9e53562a4
commit
d151e91b04
14
LICENSE
Normal file
14
LICENSE
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Copyright 2011-2019 Alex Yatskov
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:c7716fdf1e98bc42f89e2cb2e8a7511bd050b1b781fe185d8a84b3612fa192f2
|
|
||||||
size 21696512
|
|
@ -1,115 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
import operator
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
class Dictionary:
|
|
||||||
def __init__(self, filename, index=True):
|
|
||||||
self.db = sqlite3.connect(filename)
|
|
||||||
self.indices = set()
|
|
||||||
|
|
||||||
|
|
||||||
def findTerm(self, text, wildcards=False):
|
|
||||||
self.requireIndex('Vocab', 'expression')
|
|
||||||
self.requireIndex('Vocab', 'reading')
|
|
||||||
self.requireIndex('VocabGloss', 'vocabId')
|
|
||||||
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
|
|
||||||
definitions = []
|
|
||||||
cursor.execute('SELECT * FROM Vocab WHERE expression {0} ? OR reading=?'.format('LIKE' if wildcards else '='), (text, text))
|
|
||||||
for vocabId, expression, reading, tags in cursor.fetchall():
|
|
||||||
tags = tags.split()
|
|
||||||
|
|
||||||
cursor.execute('SELECT glossary From VocabGloss WHERE vocabId=?', (vocabId,))
|
|
||||||
glossary = map(operator.itemgetter(0), cursor)
|
|
||||||
|
|
||||||
#
|
|
||||||
# TODO: Handle addons through data.
|
|
||||||
#
|
|
||||||
|
|
||||||
addons = []
|
|
||||||
for tag in tags:
|
|
||||||
if tag.startswith('v5') and tag != 'v5':
|
|
||||||
addons.append('v5')
|
|
||||||
elif tag.startswith('vs-'):
|
|
||||||
addons.append('vs')
|
|
||||||
|
|
||||||
definitions.append({
|
|
||||||
'id': vocabId,
|
|
||||||
'expression': expression,
|
|
||||||
'reading': reading,
|
|
||||||
'glossary': glossary,
|
|
||||||
'tags': tags + addons,
|
|
||||||
'addons': addons
|
|
||||||
})
|
|
||||||
|
|
||||||
return definitions
|
|
||||||
|
|
||||||
|
|
||||||
def findKanji(self, text):
|
|
||||||
assert len(text) == 1
|
|
||||||
|
|
||||||
self.requireIndex('Kanji', 'character')
|
|
||||||
self.requireIndex('KanjiGloss', 'kanjiId')
|
|
||||||
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
|
|
||||||
cursor.execute('SELECT * FROM Kanji WHERE character=? LIMIT 1', text)
|
|
||||||
query = cursor.fetchone()
|
|
||||||
if query is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
kanjiId, character, kunyomi, onyomi = query
|
|
||||||
cursor.execute('SELECT glossary From KanjiGloss WHERE kanjiId=?', (kanjiId,))
|
|
||||||
glossary = map(operator.itemgetter(0), cursor)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': kanjiId,
|
|
||||||
'character': character,
|
|
||||||
'kunyomi': [] if kunyomi is None else kunyomi.split(),
|
|
||||||
'onyomi': [] if onyomi is None else onyomi.split(),
|
|
||||||
'glossary': glossary
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def requireIndex(self, table, column):
|
|
||||||
name = 'index_{0}_{1}'.format(table, column)
|
|
||||||
if not self.hasIndex(name):
|
|
||||||
self.buildIndex(name, table, column)
|
|
||||||
|
|
||||||
|
|
||||||
def buildIndex(self, name, table, column):
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
cursor.execute('CREATE INDEX {0} ON {1}({2})'.format(name, table, column))
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def hasIndex(self, name):
|
|
||||||
if name in self.indices:
|
|
||||||
return True
|
|
||||||
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
cursor.execute('SELECT * FROM sqlite_master WHERE name=?', (name,))
|
|
||||||
if len(cursor.fetchall()) == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.indices.update([name])
|
|
||||||
return True
|
|
@ -1,96 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
# This module is based on Rikaichan code written by Jonathan Zarate
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class Translator:
|
|
||||||
def __init__(self, deinflector, dictionary):
|
|
||||||
self.deinflector = deinflector
|
|
||||||
self.dictionary = dictionary
|
|
||||||
|
|
||||||
|
|
||||||
def findTerm(self, text, wildcards=False):
|
|
||||||
if wildcards:
|
|
||||||
text = re.sub(u'[\**]', u'%', text)
|
|
||||||
text = re.sub(u'[\??]', u'_', text)
|
|
||||||
|
|
||||||
groups = {}
|
|
||||||
for i in xrange(len(text), 0, -1):
|
|
||||||
term = text[:i]
|
|
||||||
|
|
||||||
dfs = self.deinflector.deinflect(term, lambda term: [d['tags'] for d in self.dictionary.findTerm(term)])
|
|
||||||
if dfs is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for df in dfs:
|
|
||||||
self.processTerm(groups, **df)
|
|
||||||
|
|
||||||
definitions = groups.values()
|
|
||||||
definitions = sorted(
|
|
||||||
definitions,
|
|
||||||
reverse=True,
|
|
||||||
key=lambda d: (
|
|
||||||
len(d['source']),
|
|
||||||
'P' in d['tags'],
|
|
||||||
-len(d['rules']),
|
|
||||||
d['expression']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
length = 0
|
|
||||||
for result in definitions:
|
|
||||||
length = max(length, len(result['source']))
|
|
||||||
|
|
||||||
return definitions, length
|
|
||||||
|
|
||||||
|
|
||||||
def findKanji(self, text):
|
|
||||||
processed = {}
|
|
||||||
results = []
|
|
||||||
for c in text:
|
|
||||||
if c not in processed:
|
|
||||||
match = self.dictionary.findKanji(c)
|
|
||||||
if match is not None:
|
|
||||||
results.append(match)
|
|
||||||
processed[c] = match
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def processTerm(self, groups, source, tags, rules=[], root='', wildcards=False):
|
|
||||||
for entry in self.dictionary.findTerm(root, wildcards):
|
|
||||||
if entry['id'] in groups:
|
|
||||||
continue
|
|
||||||
|
|
||||||
matched = len(tags) == 0
|
|
||||||
for tag in tags:
|
|
||||||
if tag in entry['tags']:
|
|
||||||
matched = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if matched:
|
|
||||||
groups[entry['id']] = {
|
|
||||||
'expression': entry['expression'],
|
|
||||||
'reading': entry['reading'],
|
|
||||||
'glossary': entry['glossary'],
|
|
||||||
'tags': entry['tags'],
|
|
||||||
'source': source,
|
|
||||||
'rules': rules
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
import codecs
|
|
||||||
import json
|
|
||||||
import operator
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class Preferences(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.filename = os.path.expanduser(u'~/.yomichan.json')
|
|
||||||
self.defaults = os.path.join(os.path.dirname(__file__), u'defaults.json')
|
|
||||||
self.settings = {}
|
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
return self.settings.get(name)
|
|
||||||
|
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
|
||||||
self.settings[name] = value
|
|
||||||
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
with codecs.open(self.defaults, 'rb', 'utf-8') as fp:
|
|
||||||
self.settings = json.load(fp)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.path.exists(self.filename):
|
|
||||||
with codecs.open(self.filename, 'rb', 'utf-8') as fp:
|
|
||||||
self.settings.update(json.load(fp))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
with codecs.open(self.filename, 'wb', 'utf-8') as fp:
|
|
||||||
json.dump(self.settings, fp, indent=4, sort_keys=True)
|
|
||||||
|
|
||||||
|
|
||||||
def filePosition(self, filename):
|
|
||||||
matches = filter(lambda f: f['path'] == filename, self['recentFiles'])
|
|
||||||
return 0 if len(matches) == 0 else matches[0]['position']
|
|
||||||
|
|
||||||
|
|
||||||
def recentFiles(self):
|
|
||||||
return map(operator.itemgetter('path'), self['recentFiles'])
|
|
||||||
|
|
||||||
|
|
||||||
def updateFactTags(self, tags):
|
|
||||||
if tags in self['tags']:
|
|
||||||
self['tags'].remove(tags)
|
|
||||||
self['tags'].insert(0, tags)
|
|
||||||
|
|
||||||
|
|
||||||
def updateRecentFile(self, filename, position):
|
|
||||||
self['recentFiles'] = filter(lambda f: f['path'] != filename, self['recentFiles'])
|
|
||||||
self['recentFiles'].insert(0, {'path': filename, 'position': position})
|
|
||||||
|
|
||||||
|
|
||||||
def clearRecentFiles(self):
|
|
||||||
self['recentFiles'] = []
|
|
@ -1,226 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
|
||||||
import copy
|
|
||||||
import gen.preferences_ui
|
|
||||||
|
|
||||||
|
|
||||||
class DialogPreferences(QtGui.QDialog, gen.preferences_ui.Ui_DialogPreferences):
|
|
||||||
def __init__(self, parent, preferences, anki):
|
|
||||||
QtGui.QDialog.__init__(self, parent)
|
|
||||||
self.setupUi(self)
|
|
||||||
|
|
||||||
self.accepted.connect(self.onAccept)
|
|
||||||
self.buttonColorBg.clicked.connect(self.onButtonColorBgClicked)
|
|
||||||
self.buttonColorFg.clicked.connect(self.onButtonColorFgClicked)
|
|
||||||
self.comboBoxDeck.currentIndexChanged.connect(self.onDeckChanged)
|
|
||||||
self.comboBoxModel.currentIndexChanged.connect(self.onModelChanged)
|
|
||||||
self.comboFontFamily.currentFontChanged.connect(self.onFontFamilyChanged)
|
|
||||||
self.radioButtonKanji.toggled.connect(self.onProfileChanged)
|
|
||||||
self.radioButtonVocab.toggled.connect(self.onProfileChanged)
|
|
||||||
self.spinFontSize.valueChanged.connect(self.onFontSizeChanged)
|
|
||||||
self.tableFields.itemChanged.connect(self.onFieldsChanged)
|
|
||||||
|
|
||||||
self.preferences = preferences
|
|
||||||
self.anki = anki
|
|
||||||
|
|
||||||
self.dataToDialog()
|
|
||||||
|
|
||||||
|
|
||||||
def dataToDialog(self):
|
|
||||||
self.checkCheckForUpdates.setChecked(self.preferences['checkForUpdates'])
|
|
||||||
self.checkRememberTextContent.setChecked(self.preferences['rememberTextContent'])
|
|
||||||
self.checkAllowEditing.setChecked(self.preferences['allowEditing'])
|
|
||||||
self.checkLoadRecentFile.setChecked(self.preferences['loadRecentFile'])
|
|
||||||
self.checkStripReadings.setChecked(self.preferences['stripReadings'])
|
|
||||||
self.spinScanLength.setValue(self.preferences['scanLength'])
|
|
||||||
self.checkEnableAnkiConnect.setChecked(self.preferences['enableAnkiConnect'])
|
|
||||||
|
|
||||||
self.updateSampleText()
|
|
||||||
font = self.textSample.font()
|
|
||||||
self.comboFontFamily.setCurrentFont(font)
|
|
||||||
self.spinFontSize.setValue(font.pointSize())
|
|
||||||
|
|
||||||
if self.anki is not None:
|
|
||||||
self.tabAnki.setEnabled(True)
|
|
||||||
self.profiles = copy.deepcopy(self.preferences['profiles'])
|
|
||||||
self.profileToDialog()
|
|
||||||
|
|
||||||
|
|
||||||
def dialogToData(self):
|
|
||||||
self.preferences['checkForUpdates'] = self.checkCheckForUpdates.isChecked()
|
|
||||||
self.preferences['rememberTextContent'] = self.checkRememberTextContent.isChecked()
|
|
||||||
self.preferences['allowEditing'] = self.checkAllowEditing.isChecked()
|
|
||||||
self.preferences['loadRecentFile'] = self.checkLoadRecentFile.isChecked()
|
|
||||||
self.preferences['scanLength'] = self.spinScanLength.value()
|
|
||||||
self.preferences['stripReadings'] = self.checkStripReadings.isChecked()
|
|
||||||
self.preferences['enableAnkiConnect'] = self.checkEnableAnkiConnect.isChecked()
|
|
||||||
self.preferences['firstRun'] = False
|
|
||||||
|
|
||||||
if self.anki is not None:
|
|
||||||
self.dialogToProfile()
|
|
||||||
self.preferences['profiles'] = self.profiles
|
|
||||||
|
|
||||||
|
|
||||||
def dialogToProfile(self):
|
|
||||||
self.setActiveProfile({
|
|
||||||
'deck': unicode(self.comboBoxDeck.currentText()),
|
|
||||||
'model': unicode(self.comboBoxModel.currentText()),
|
|
||||||
'fields': self.ankiFields()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def profileToDialog(self):
|
|
||||||
profile, name = self.activeProfile()
|
|
||||||
|
|
||||||
deck = u'' if profile is None else profile['deck']
|
|
||||||
model = u'' if profile is None else profile['model']
|
|
||||||
|
|
||||||
self.comboBoxDeck.blockSignals(True)
|
|
||||||
self.comboBoxDeck.clear()
|
|
||||||
self.comboBoxDeck.addItems(self.anki.deckNames())
|
|
||||||
self.comboBoxDeck.setCurrentIndex(self.comboBoxDeck.findText(deck))
|
|
||||||
self.comboBoxDeck.blockSignals(False)
|
|
||||||
|
|
||||||
self.comboBoxModel.blockSignals(True)
|
|
||||||
self.comboBoxModel.clear()
|
|
||||||
self.comboBoxModel.addItems(self.anki.modelNames())
|
|
||||||
self.comboBoxModel.setCurrentIndex(self.comboBoxModel.findText(model))
|
|
||||||
self.comboBoxModel.blockSignals(False)
|
|
||||||
|
|
||||||
allowedTags = {
|
|
||||||
'vocab': ['expression', 'reading', 'glossary', 'sentence'],
|
|
||||||
'kanji': ['character', 'onyomi', 'kunyomi', 'glossary'],
|
|
||||||
}[name]
|
|
||||||
|
|
||||||
allowedTags = map(lambda t: '<strong>{' + t + '}<strong>', allowedTags)
|
|
||||||
self.labelTags.setText('Allowed tags are {0}'.format(', '.join(allowedTags)))
|
|
||||||
|
|
||||||
self.updateAnkiFields()
|
|
||||||
|
|
||||||
|
|
||||||
def updateSampleText(self):
|
|
||||||
palette = self.textSample.palette()
|
|
||||||
palette.setColor(QtGui.QPalette.Base, QtGui.QColor(self.preferences['bgColor']))
|
|
||||||
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(self.preferences['fgColor']))
|
|
||||||
self.textSample.setPalette(palette)
|
|
||||||
|
|
||||||
font = self.textSample.font()
|
|
||||||
font.setFamily(self.preferences['fontFamily'])
|
|
||||||
font.setPointSize(self.preferences['fontSize'])
|
|
||||||
self.textSample.setFont(font)
|
|
||||||
|
|
||||||
|
|
||||||
def setAnkiFields(self, fields, fieldsPrefs):
|
|
||||||
if fields is None:
|
|
||||||
fields = []
|
|
||||||
|
|
||||||
self.tableFields.blockSignals(True)
|
|
||||||
self.tableFields.setRowCount(len(fields))
|
|
||||||
|
|
||||||
for i, name in enumerate(fields):
|
|
||||||
columns = []
|
|
||||||
|
|
||||||
itemName = QtGui.QTableWidgetItem(name)
|
|
||||||
itemName.setFlags(QtCore.Qt.ItemIsSelectable)
|
|
||||||
columns.append(itemName)
|
|
||||||
|
|
||||||
itemValue = QtGui.QTableWidgetItem(fieldsPrefs.get(name, u''))
|
|
||||||
columns.append(itemValue)
|
|
||||||
|
|
||||||
for j, column in enumerate(columns):
|
|
||||||
self.tableFields.setItem(i, j, column)
|
|
||||||
|
|
||||||
self.tableFields.blockSignals(False)
|
|
||||||
|
|
||||||
|
|
||||||
def ankiFields(self):
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
for i in range(0, self.tableFields.rowCount()):
|
|
||||||
itemName = unicode(self.tableFields.item(i, 0).text())
|
|
||||||
itemValue = unicode(self.tableFields.item(i, 1).text())
|
|
||||||
result[itemName] = itemValue
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def onAccept(self):
|
|
||||||
self.dialogToData()
|
|
||||||
|
|
||||||
|
|
||||||
def onButtonColorFgClicked(self):
|
|
||||||
color, ok = QtGui.QColorDialog.getRgba(self.preferences['fgColor'], self)
|
|
||||||
if ok:
|
|
||||||
self.preferences['fgColor'] = color
|
|
||||||
self.updateSampleText()
|
|
||||||
|
|
||||||
|
|
||||||
def onButtonColorBgClicked(self):
|
|
||||||
color, ok = QtGui.QColorDialog.getRgba(self.preferences['bgColor'], self)
|
|
||||||
if ok:
|
|
||||||
self.preferences['bgColor'] = color
|
|
||||||
self.updateSampleText()
|
|
||||||
|
|
||||||
|
|
||||||
def onFontFamilyChanged(self, font):
|
|
||||||
self.preferences['fontFamily'] = unicode(font.family())
|
|
||||||
self.updateSampleText()
|
|
||||||
|
|
||||||
|
|
||||||
def onFontSizeChanged(self, size):
|
|
||||||
self.preferences['fontSize'] = size
|
|
||||||
self.updateSampleText()
|
|
||||||
|
|
||||||
|
|
||||||
def onModelChanged(self, index):
|
|
||||||
self.updateAnkiFields()
|
|
||||||
self.dialogToProfile()
|
|
||||||
|
|
||||||
|
|
||||||
def onDeckChanged(self, index):
|
|
||||||
self.dialogToProfile()
|
|
||||||
|
|
||||||
|
|
||||||
def onFieldsChanged(self, item):
|
|
||||||
self.dialogToProfile()
|
|
||||||
|
|
||||||
|
|
||||||
def onProfileChanged(self, data):
|
|
||||||
self.profileToDialog()
|
|
||||||
|
|
||||||
|
|
||||||
def updateAnkiFields(self):
|
|
||||||
modelName = self.comboBoxModel.currentText()
|
|
||||||
fieldNames = self.anki.modelFieldNames(modelName) or []
|
|
||||||
|
|
||||||
profile, name = self.activeProfile()
|
|
||||||
fields = {} if profile is None else profile['fields']
|
|
||||||
|
|
||||||
self.setAnkiFields(fieldNames, fields)
|
|
||||||
|
|
||||||
|
|
||||||
def activeProfile(self):
|
|
||||||
name = 'vocab' if self.radioButtonVocab.isChecked() else 'kanji'
|
|
||||||
return self.profiles.get(name), name
|
|
||||||
|
|
||||||
|
|
||||||
def setActiveProfile(self, profile):
|
|
||||||
name = 'vocab' if self.radioButtonVocab.isChecked() else 'kanji'
|
|
||||||
self.profiles[name] = profile
|
|
@ -1,604 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
|
||||||
import about
|
|
||||||
import constants
|
|
||||||
import gen.reader_ui
|
|
||||||
import os
|
|
||||||
import preferences
|
|
||||||
import reader_util
|
|
||||||
import updates
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindowReader(QtGui.QMainWindow, gen.reader_ui.Ui_MainWindowReader):
|
|
||||||
class State:
|
|
||||||
def __init__(self):
|
|
||||||
self.filename = u''
|
|
||||||
self.searchText = u''
|
|
||||||
self.kanjiDefs = []
|
|
||||||
self.vocabDefs = []
|
|
||||||
self.scanPosition = 0
|
|
||||||
self.searchPosition = 0
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, parent, preferences, language, filename=None, anki=None, closed=None):
|
|
||||||
QtGui.QMainWindow.__init__(self, parent)
|
|
||||||
self.setupUi(self)
|
|
||||||
|
|
||||||
self.textContent.mouseMoveEvent = self.onContentMouseMove
|
|
||||||
self.textContent.mousePressEvent = self.onContentMousePress
|
|
||||||
self.dockAnki.setEnabled(anki is not None)
|
|
||||||
|
|
||||||
self.facts = []
|
|
||||||
self.anki = anki
|
|
||||||
self.closed = closed
|
|
||||||
self.language = language
|
|
||||||
self.preferences = preferences
|
|
||||||
self.state = self.State()
|
|
||||||
self.updates = updates.UpdateFinder()
|
|
||||||
self.zoom = 0
|
|
||||||
|
|
||||||
self.applyPreferences()
|
|
||||||
self.updateRecentFiles()
|
|
||||||
self.updateVocabDefs()
|
|
||||||
self.updateKanjiDefs()
|
|
||||||
|
|
||||||
if filename is not None:
|
|
||||||
self.openFile(filename)
|
|
||||||
elif self.preferences['rememberTextContent']:
|
|
||||||
self.textContent.setPlainText(self.preferences['textContent'])
|
|
||||||
elif self.preferences['loadRecentFile']:
|
|
||||||
filenames = self.preferences.recentFiles()
|
|
||||||
if len(filenames) > 0 and os.path.isfile(filenames[0]):
|
|
||||||
self.openFile(filenames[0])
|
|
||||||
|
|
||||||
self.actionAbout.triggered.connect(self.onActionAbout)
|
|
||||||
self.actionFind.triggered.connect(self.onActionFind)
|
|
||||||
self.actionFindNext.triggered.connect(self.onActionFindNext)
|
|
||||||
self.actionHomepage.triggered.connect(self.onActionHomepage)
|
|
||||||
self.actionKindleDeck.triggered.connect(self.onActionKindleDeck)
|
|
||||||
self.actionWordList.triggered.connect(self.onActionWordList)
|
|
||||||
self.actionOpen.triggered.connect(self.onActionOpen)
|
|
||||||
self.actionPreferences.triggered.connect(self.onActionPreferences)
|
|
||||||
self.actionToggleWrap.toggled.connect(self.onActionToggleWrap)
|
|
||||||
self.actionZoomIn.triggered.connect(self.onActionZoomIn)
|
|
||||||
self.actionZoomOut.triggered.connect(self.onActionZoomOut)
|
|
||||||
self.actionZoomReset.triggered.connect(self.onActionZoomReset)
|
|
||||||
self.dockAnki.visibilityChanged.connect(self.onVisibilityChanged)
|
|
||||||
self.dockKanji.visibilityChanged.connect(self.onVisibilityChanged)
|
|
||||||
self.dockVocab.visibilityChanged.connect(self.onVisibilityChanged)
|
|
||||||
self.listDefinitions.itemDoubleClicked.connect(self.onDefinitionDoubleClicked)
|
|
||||||
self.textKanjiDefs.anchorClicked.connect(self.onKanjiDefsAnchorClicked)
|
|
||||||
self.textKanjiSearch.returnPressed.connect(self.onKanjiDefSearchReturn)
|
|
||||||
self.textVocabDefs.anchorClicked.connect(self.onVocabDefsAnchorClicked)
|
|
||||||
self.textVocabSearch.returnPressed.connect(self.onVocabDefSearchReturn)
|
|
||||||
self.updates.updateResult.connect(self.onUpdaterSearchResult)
|
|
||||||
|
|
||||||
if self.preferences['checkForUpdates']:
|
|
||||||
self.updates.start()
|
|
||||||
|
|
||||||
|
|
||||||
def applyPreferences(self):
|
|
||||||
if self.preferences['windowState'] is not None:
|
|
||||||
self.restoreState(QtCore.QByteArray.fromBase64(self.preferences['windowState']))
|
|
||||||
if self.preferences['windowPosition'] is not None:
|
|
||||||
self.move(QtCore.QPoint(*self.preferences['windowPosition']))
|
|
||||||
if self.preferences['windowSize'] is not None:
|
|
||||||
self.resize(QtCore.QSize(*self.preferences['windowSize']))
|
|
||||||
|
|
||||||
self.comboTags.addItems(self.preferences['tags'])
|
|
||||||
self.applyPreferencesContent()
|
|
||||||
|
|
||||||
if self.preferences['firstRun']:
|
|
||||||
QtGui.QMessageBox.information(
|
|
||||||
self,
|
|
||||||
'Yomichan',
|
|
||||||
'This may be the first time you are running Yomichan.\n' \
|
|
||||||
'Please take some time to configure this extension.'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.onActionPreferences()
|
|
||||||
|
|
||||||
|
|
||||||
def applyPreferencesContent(self):
|
|
||||||
palette = self.textContent.palette()
|
|
||||||
palette.setColor(QtGui.QPalette.Base, QtGui.QColor(self.preferences['bgColor']))
|
|
||||||
palette.setColor(QtGui.QPalette.Text, QtGui.QColor(self.preferences['fgColor']))
|
|
||||||
self.textContent.setPalette(palette)
|
|
||||||
|
|
||||||
self.textContent.setReadOnly(not self.preferences['allowEditing'])
|
|
||||||
self.textContent.setAttribute(QtCore.Qt.WA_InputMethodEnabled)
|
|
||||||
|
|
||||||
font = self.textContent.font()
|
|
||||||
font.setFamily(self.preferences['fontFamily'])
|
|
||||||
font.setPointSize(self.preferences['fontSize'] + self.zoom)
|
|
||||||
self.textContent.setLineWrapMode(QtGui.QPlainTextEdit.WidgetWidth if self.preferences['wordWrap'] else QtGui.QPlainTextEdit.NoWrap)
|
|
||||||
self.textContent.setFont(font)
|
|
||||||
|
|
||||||
self.actionToggleWrap.setChecked(self.preferences['wordWrap'])
|
|
||||||
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
self.closeFile()
|
|
||||||
self.preferences['windowState'] = str(self.saveState().toBase64())
|
|
||||||
self.preferences.save()
|
|
||||||
|
|
||||||
if self.anki is not None:
|
|
||||||
self.anki.stopEditing()
|
|
||||||
|
|
||||||
if self.closed is not None:
|
|
||||||
self.closed()
|
|
||||||
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Shift:
|
|
||||||
self.updateSampleFromPosition()
|
|
||||||
elif ord('0') <= event.key() <= ord('9'):
|
|
||||||
index = event.key() - ord('0') - 1
|
|
||||||
if index < 0:
|
|
||||||
index = 9
|
|
||||||
if event.modifiers() & QtCore.Qt.ShiftModifier:
|
|
||||||
if event.modifiers() & QtCore.Qt.ControlModifier:
|
|
||||||
self.executeKanjiCommand('addKanji', index)
|
|
||||||
else:
|
|
||||||
if event.modifiers() & QtCore.Qt.ControlModifier:
|
|
||||||
self.executeVocabCommand('addVocabExp', index)
|
|
||||||
if event.modifiers() & QtCore.Qt.AltModifier:
|
|
||||||
self.executeVocabCommand('addVocabReading', index)
|
|
||||||
elif event.key() == ord('[') and self.state.scanPosition > 0:
|
|
||||||
self.state.scanPosition -= 1
|
|
||||||
self.updateSampleFromPosition()
|
|
||||||
elif event.key() == ord(']') and self.state.scanPosition < len(self.textContent.toPlainText()) - 1:
|
|
||||||
self.state.scanPosition += 1
|
|
||||||
self.updateSampleFromPosition()
|
|
||||||
|
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
|
||||||
if event.mimeData().hasUrls():
|
|
||||||
event.acceptProposedAction()
|
|
||||||
|
|
||||||
|
|
||||||
def dropEvent(self, event):
|
|
||||||
url = event.mimeData().urls()[0]
|
|
||||||
self.openFile(url.toLocalFile())
|
|
||||||
|
|
||||||
|
|
||||||
def moveEvent(self, event):
|
|
||||||
self.preferences['windowPosition'] = event.pos().x(), event.pos().y()
|
|
||||||
|
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
|
||||||
self.preferences['windowSize'] = event.size().width(), event.size().height()
|
|
||||||
|
|
||||||
|
|
||||||
def onActionOpen(self):
|
|
||||||
filename = QtGui.QFileDialog.getOpenFileName(
|
|
||||||
parent=self,
|
|
||||||
caption='Select a file to open',
|
|
||||||
filter='Text files (*.txt);;All files (*.*)'
|
|
||||||
)
|
|
||||||
if filename:
|
|
||||||
self.openFile(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionKindleDeck(self):
|
|
||||||
filename = QtGui.QFileDialog.getOpenFileName(
|
|
||||||
parent=self,
|
|
||||||
caption='Select a Kindle deck to import',
|
|
||||||
filter='Deck files (*.db)'
|
|
||||||
)
|
|
||||||
if filename:
|
|
||||||
words = reader_util.extractKindleDeck(filename)
|
|
||||||
self.importWordList(words)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionWordList(self):
|
|
||||||
filename = QtGui.QFileDialog.getOpenFileName(
|
|
||||||
parent=self,
|
|
||||||
caption='Select a word list file to import',
|
|
||||||
filter='Text files (*.txt);;All files (*.*)'
|
|
||||||
)
|
|
||||||
if filename:
|
|
||||||
words = reader_util.extractWordList(filename)
|
|
||||||
self.importWordList(words)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionPreferences(self):
|
|
||||||
dialog = preferences.DialogPreferences(self, self.preferences, self.anki)
|
|
||||||
if dialog.exec_() == QtGui.QDialog.Accepted:
|
|
||||||
self.applyPreferencesContent()
|
|
||||||
|
|
||||||
|
|
||||||
def onActionAbout(self):
|
|
||||||
dialog = about.DialogAbout(self)
|
|
||||||
dialog.exec_()
|
|
||||||
|
|
||||||
|
|
||||||
def onActionZoomIn(self):
|
|
||||||
font = self.textContent.font()
|
|
||||||
if font.pointSize() < 72:
|
|
||||||
font.setPointSize(font.pointSize() + 1)
|
|
||||||
self.textContent.setFont(font)
|
|
||||||
self.zoom += 1
|
|
||||||
|
|
||||||
|
|
||||||
def onActionZoomOut(self):
|
|
||||||
font = self.textContent.font()
|
|
||||||
if font.pointSize() > 1:
|
|
||||||
font.setPointSize(font.pointSize() - 1)
|
|
||||||
self.textContent.setFont(font)
|
|
||||||
self.zoom -= 1
|
|
||||||
|
|
||||||
|
|
||||||
def onActionZoomReset(self):
|
|
||||||
if self.zoom:
|
|
||||||
font = self.textContent.font()
|
|
||||||
font.setPointSize(font.pointSize() - self.zoom)
|
|
||||||
self.textContent.setFont(font)
|
|
||||||
self.zoom = 0
|
|
||||||
|
|
||||||
|
|
||||||
def onActionFind(self):
|
|
||||||
searchText = self.state.searchText
|
|
||||||
|
|
||||||
cursor = self.textContent.textCursor()
|
|
||||||
if cursor.hasSelection():
|
|
||||||
searchText = cursor.selectedText()
|
|
||||||
|
|
||||||
searchText, ok = QtGui.QInputDialog.getText(self, 'Find', 'Search text:', text=searchText)
|
|
||||||
if searchText and ok:
|
|
||||||
self.findText(searchText)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionFindNext(self):
|
|
||||||
if self.state.searchText:
|
|
||||||
self.findText(self.state.searchText)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionToggleWrap(self, wrap):
|
|
||||||
self.preferences['wordWrap'] = wrap
|
|
||||||
mode = QtGui.QPlainTextEdit.WidgetWidth if self.preferences['wordWrap'] else QtGui.QPlainTextEdit.NoWrap
|
|
||||||
self.textContent.setLineWrapMode(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def onActionHomepage(self):
|
|
||||||
url = QtCore.QUrl('https://foosoft.net/projects/yomichan')
|
|
||||||
QtGui.QDesktopServices().openUrl(url)
|
|
||||||
|
|
||||||
|
|
||||||
def onVocabDefsAnchorClicked(self, url):
|
|
||||||
command, index = unicode(url.toString()).split(':')
|
|
||||||
self.executeVocabCommand(command, int(index))
|
|
||||||
|
|
||||||
|
|
||||||
def onKanjiDefsAnchorClicked(self, url):
|
|
||||||
command, index = unicode(url.toString()).split(':')
|
|
||||||
self.executeKanjiCommand(command, int(index))
|
|
||||||
|
|
||||||
|
|
||||||
def onVocabDefSearchReturn(self):
|
|
||||||
text = unicode(self.textVocabSearch.text())
|
|
||||||
self.state.vocabDefs, length = self.language.findTerm(text, True)
|
|
||||||
self.updateVocabDefs()
|
|
||||||
if self.dockKanji.isVisible():
|
|
||||||
self.state.kanjiDefs = self.language.findKanji(text)
|
|
||||||
self.updateKanjiDefs()
|
|
||||||
|
|
||||||
|
|
||||||
def onKanjiDefSearchReturn(self):
|
|
||||||
text = unicode(self.textKanjiSearch.text())
|
|
||||||
self.state.kanjiDefs = self.language.findKanji(text)
|
|
||||||
self.updateKanjiDefs()
|
|
||||||
|
|
||||||
|
|
||||||
def onDefinitionDoubleClicked(self, item):
|
|
||||||
if self.anki is not None:
|
|
||||||
row = self.listDefinitions.row(item)
|
|
||||||
self.anki.browseNote(self.facts[row])
|
|
||||||
|
|
||||||
|
|
||||||
def onVisibilityChanged(self, visible):
|
|
||||||
self.actionToggleAnki.setChecked(self.dockAnki.isVisible())
|
|
||||||
self.actionToggleVocab.setChecked(self.dockVocab.isVisible())
|
|
||||||
self.actionToggleKanji.setChecked(self.dockKanji.isVisible())
|
|
||||||
|
|
||||||
|
|
||||||
def onUpdaterSearchResult(self, versions):
|
|
||||||
if versions['latest'] > constants.c['appVersion']:
|
|
||||||
dialog = updates.DialogUpdates(self, versions)
|
|
||||||
dialog.exec_()
|
|
||||||
|
|
||||||
|
|
||||||
def onContentMouseMove(self, event):
|
|
||||||
QtGui.QPlainTextEdit.mouseMoveEvent(self.textContent, event)
|
|
||||||
self.updateSampleMouseEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
def onContentMousePress(self, event):
|
|
||||||
QtGui.QPlainTextEdit.mousePressEvent(self.textContent, event)
|
|
||||||
self.updateSampleMouseEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
def openFile(self, filename):
|
|
||||||
try:
|
|
||||||
filename = unicode(filename)
|
|
||||||
with open(filename) as fp:
|
|
||||||
content = fp.read()
|
|
||||||
except IOError:
|
|
||||||
self.setStatus(u'Failed to load file {0}'.format(filename))
|
|
||||||
QtGui.QMessageBox.critical(self, 'Yomichan', 'Cannot open file for read')
|
|
||||||
return
|
|
||||||
|
|
||||||
self.closeFile()
|
|
||||||
|
|
||||||
self.state.filename = filename
|
|
||||||
self.state.scanPosition = self.preferences.filePosition(filename)
|
|
||||||
if self.state.scanPosition > len(content):
|
|
||||||
self.state.scanPosition = 0
|
|
||||||
|
|
||||||
self.updateRecentFile()
|
|
||||||
self.updateRecentFiles()
|
|
||||||
|
|
||||||
content, encoding = reader_util.decodeContent(content)
|
|
||||||
if self.preferences['stripReadings']:
|
|
||||||
content = reader_util.stripReadings(content)
|
|
||||||
|
|
||||||
self.textContent.setPlainText(content)
|
|
||||||
if self.state.scanPosition > 0:
|
|
||||||
cursor = self.textContent.textCursor()
|
|
||||||
cursor.setPosition(self.state.scanPosition)
|
|
||||||
self.textContent.setTextCursor(cursor)
|
|
||||||
self.textContent.centerCursor()
|
|
||||||
|
|
||||||
self.setStatus(u'Loaded file {0}'.format(filename))
|
|
||||||
self.setWindowTitle(u'Yomichan - {0} ({1})'.format(os.path.basename(filename), encoding))
|
|
||||||
|
|
||||||
|
|
||||||
def closeFile(self):
|
|
||||||
if self.preferences['rememberTextContent']:
|
|
||||||
self.preferences['textContent'] = unicode(self.textContent.toPlainText())
|
|
||||||
|
|
||||||
self.setWindowTitle('Yomichan')
|
|
||||||
self.textContent.setPlainText(u'')
|
|
||||||
self.updateRecentFile(False)
|
|
||||||
self.state = self.State()
|
|
||||||
|
|
||||||
|
|
||||||
def findText(self, text):
|
|
||||||
content = unicode(self.textContent.toPlainText())
|
|
||||||
index = content.find(unicode(text), self.state.searchPosition)
|
|
||||||
|
|
||||||
if index == -1:
|
|
||||||
wrap = self.state.searchPosition != 0
|
|
||||||
self.state.searchPosition = 0
|
|
||||||
if wrap:
|
|
||||||
self.findText(text)
|
|
||||||
else:
|
|
||||||
QtGui.QMessageBox.information(self, 'Yomichan', 'Search text not found')
|
|
||||||
else:
|
|
||||||
self.state.searchPosition = index + len(text)
|
|
||||||
cursor = self.textContent.textCursor()
|
|
||||||
cursor.setPosition(index, QtGui.QTextCursor.MoveAnchor)
|
|
||||||
cursor.setPosition(self.state.searchPosition, QtGui.QTextCursor.KeepAnchor)
|
|
||||||
self.textContent.setTextCursor(cursor)
|
|
||||||
|
|
||||||
self.state.searchText = text
|
|
||||||
|
|
||||||
|
|
||||||
def ankiAddFact(self, profile, markup):
|
|
||||||
if markup is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.anki is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
profile = self.preferences['profiles'].get(profile)
|
|
||||||
if profile is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
fields = reader_util.formatFields(profile['fields'], markup)
|
|
||||||
tagsSplit = reader_util.splitTags(unicode(self.comboTags.currentText()))
|
|
||||||
tagsJoined = ' '.join(tagsSplit)
|
|
||||||
|
|
||||||
tagIndex = self.comboTags.findText(tagsJoined)
|
|
||||||
if tagIndex > 0:
|
|
||||||
self.comboTags.removeItem(tagIndex)
|
|
||||||
if tagIndex != 0:
|
|
||||||
self.comboTags.insertItem(0, tagsJoined)
|
|
||||||
self.preferences.updateFactTags(tagsJoined)
|
|
||||||
|
|
||||||
factId = self.anki.addNote(profile['deck'], profile['model'], fields, tagsSplit, None)
|
|
||||||
if factId is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.facts.append(factId)
|
|
||||||
self.listDefinitions.addItem(markup['summary'])
|
|
||||||
self.listDefinitions.setCurrentRow(self.listDefinitions.count() - 1)
|
|
||||||
self.setStatus(u'Added fact {0}; {1} new fact(s) total'.format(markup['summary'], len(self.facts)))
|
|
||||||
|
|
||||||
self.updateVocabDefs(scroll=True)
|
|
||||||
self.updateKanjiDefs(scroll=True)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def ankiIsFactValid(self, profile, markup):
|
|
||||||
if markup is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.anki is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
profile = self.preferences['profiles'].get(profile)
|
|
||||||
if profile is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
fields = reader_util.formatFields(profile['fields'], markup)
|
|
||||||
return self.anki.canAddNote(profile['deck'], profile['model'], fields)
|
|
||||||
|
|
||||||
|
|
||||||
def executeVocabCommand(self, command, index):
|
|
||||||
if index >= len(self.state.vocabDefs):
|
|
||||||
return
|
|
||||||
|
|
||||||
definition = self.state.vocabDefs[index]
|
|
||||||
if command == 'addVocabExp':
|
|
||||||
markup = reader_util.markupVocabExp(definition)
|
|
||||||
self.ankiAddFact('vocab', markup)
|
|
||||||
if command == 'addVocabReading':
|
|
||||||
markup = reader_util.markupVocabReading(definition)
|
|
||||||
self.ankiAddFact('vocab', markup)
|
|
||||||
elif command == 'copyVocabDef':
|
|
||||||
reader_util.copyVocabDef(definition)
|
|
||||||
|
|
||||||
|
|
||||||
def executeKanjiCommand(self, command, index):
|
|
||||||
if index >= len(self.state.kanjiDefs):
|
|
||||||
return
|
|
||||||
|
|
||||||
definition = self.state.kanjiDefs[index]
|
|
||||||
if command == 'addKanji':
|
|
||||||
markup = reader_util.markupKanji(definition)
|
|
||||||
self.ankiAddFact('kanji', markup)
|
|
||||||
elif command == 'copyKanjiDef':
|
|
||||||
reader_util.copyKanjiDef(definition)
|
|
||||||
|
|
||||||
|
|
||||||
def updateSampleMouseEvent(self, event):
|
|
||||||
cursor = self.textContent.cursorForPosition(event.pos())
|
|
||||||
self.state.scanPosition = cursor.position()
|
|
||||||
if event.buttons() & QtCore.Qt.MidButton or event.modifiers() & QtCore.Qt.ShiftModifier:
|
|
||||||
self.updateSampleFromPosition()
|
|
||||||
|
|
||||||
|
|
||||||
def updateSampleFromPosition(self):
|
|
||||||
samplePosStart = self.state.scanPosition
|
|
||||||
samplePosEnd = self.state.scanPosition + self.preferences['scanLength']
|
|
||||||
|
|
||||||
content = unicode(self.textContent.toPlainText())
|
|
||||||
contentSample = content[samplePosStart:samplePosEnd]
|
|
||||||
contentSampleFlat = contentSample.replace(u'\n', u'')
|
|
||||||
|
|
||||||
cursor = self.textContent.textCursor()
|
|
||||||
|
|
||||||
if len(contentSampleFlat) == 0:
|
|
||||||
cursor.clearSelection()
|
|
||||||
self.textContent.setTextCursor(cursor)
|
|
||||||
return
|
|
||||||
|
|
||||||
lengthMatched = 0
|
|
||||||
if self.dockVocab.isVisible():
|
|
||||||
self.state.vocabDefs, lengthMatched = self.language.findTerm(contentSampleFlat)
|
|
||||||
sentence = reader_util.findSentence(content, samplePosStart)
|
|
||||||
for definition in self.state.vocabDefs:
|
|
||||||
definition['sentence'] = sentence
|
|
||||||
self.updateVocabDefs()
|
|
||||||
|
|
||||||
if self.dockKanji.isVisible():
|
|
||||||
if lengthMatched == 0:
|
|
||||||
self.state.kanjiDefs = self.language.findKanji(contentSampleFlat[0])
|
|
||||||
if len(self.state.kanjiDefs) > 0:
|
|
||||||
lengthMatched = 1
|
|
||||||
else:
|
|
||||||
self.state.kanjiDefs = self.language.findKanji(contentSampleFlat[:lengthMatched])
|
|
||||||
self.updateKanjiDefs()
|
|
||||||
|
|
||||||
lengthSelect = 0
|
|
||||||
for c in contentSample:
|
|
||||||
if lengthMatched <= 0:
|
|
||||||
break
|
|
||||||
lengthSelect += 1
|
|
||||||
if c != u'\n':
|
|
||||||
lengthMatched -= 1
|
|
||||||
|
|
||||||
cursor.setPosition(samplePosStart, QtGui.QTextCursor.MoveAnchor)
|
|
||||||
cursor.setPosition(samplePosStart + lengthSelect, QtGui.QTextCursor.KeepAnchor)
|
|
||||||
self.textContent.setTextCursor(cursor)
|
|
||||||
|
|
||||||
|
|
||||||
def clearRecentFiles(self):
|
|
||||||
self.preferences.clearRecentFiles()
|
|
||||||
self.updateRecentFiles()
|
|
||||||
|
|
||||||
|
|
||||||
def updateRecentFiles(self):
|
|
||||||
self.menuOpenRecent.clear()
|
|
||||||
|
|
||||||
filenames = self.preferences.recentFiles()
|
|
||||||
if len(filenames) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
self.menuOpenRecent.addAction(filename, lambda f=filename: self.openFile(f))
|
|
||||||
|
|
||||||
self.menuOpenRecent.addSeparator()
|
|
||||||
self.menuOpenRecent.addAction('Clear file history', self.clearRecentFiles)
|
|
||||||
|
|
||||||
|
|
||||||
def updateRecentFile(self, addIfNeeded=True):
|
|
||||||
if self.state.filename:
|
|
||||||
if addIfNeeded or self.state.filename in self.preferences.recentFiles():
|
|
||||||
self.preferences.updateRecentFile(self.state.filename, self.state.scanPosition)
|
|
||||||
|
|
||||||
|
|
||||||
def updateDefs(self, defs, builder, control, **options):
|
|
||||||
scrollbar = control.verticalScrollBar()
|
|
||||||
position = scrollbar.sliderPosition()
|
|
||||||
|
|
||||||
html = builder(defs, self.ankiIsFactValid)
|
|
||||||
control.setHtml(html)
|
|
||||||
|
|
||||||
if options.get('scroll', False):
|
|
||||||
scrollbar.setSliderPosition(position)
|
|
||||||
|
|
||||||
|
|
||||||
def updateVocabDefs(self, **options):
|
|
||||||
self.updateDefs(
|
|
||||||
self.state.vocabDefs,
|
|
||||||
reader_util.buildVocabDefs,
|
|
||||||
self.textVocabDefs,
|
|
||||||
**options
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def updateKanjiDefs(self, **options):
|
|
||||||
self.updateDefs(
|
|
||||||
self.state.kanjiDefs,
|
|
||||||
reader_util.buildKanjiDefs,
|
|
||||||
self.textKanjiDefs,
|
|
||||||
**options
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def importWordList(self, words):
|
|
||||||
self.state.vocabDefs = []
|
|
||||||
self.state.kanjiDefs = []
|
|
||||||
|
|
||||||
for word in words:
|
|
||||||
if self.dockVocab.isVisible():
|
|
||||||
self.state.vocabDefs += self.language.dictionary.findTerm(word)
|
|
||||||
|
|
||||||
if self.dockKanji.isVisible():
|
|
||||||
self.state.kanjiDefs += self.language.findKanji(word)
|
|
||||||
|
|
||||||
self.updateVocabDefs(scroll=True)
|
|
||||||
self.updateKanjiDefs(scroll=True)
|
|
||||||
|
|
||||||
|
|
||||||
def setStatus(self, status):
|
|
||||||
self.statusBar.showMessage(status)
|
|
@ -1,293 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
|
||||||
import re
|
|
||||||
import codecs
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
def decodeContent(content):
|
|
||||||
encodings = ['utf-8', 'shift_jis', 'euc-jp', 'utf-16']
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
for encoding in encodings:
|
|
||||||
try:
|
|
||||||
return content.decode(encoding), encoding
|
|
||||||
except UnicodeDecodeError, e:
|
|
||||||
errors[encoding] = e[2]
|
|
||||||
|
|
||||||
encoding = sorted(errors, key=errors.get, reverse=True)[0]
|
|
||||||
return content.decode(encoding, 'replace'), encoding
|
|
||||||
|
|
||||||
|
|
||||||
def stripReadings(content):
|
|
||||||
return re.sub(u'《[^》]+》', u'', content)
|
|
||||||
|
|
||||||
|
|
||||||
def findSentence(content, position):
|
|
||||||
quotesFwd = {u'「': u'」', u'『': u'』', u"'": u"'", u'"': u'"'}
|
|
||||||
quotesBwd = {u'」': u'「', u'』': u'『', u"'": u"'", u'"': u'"'}
|
|
||||||
terminators = u'。..??!!'
|
|
||||||
|
|
||||||
quoteStack = []
|
|
||||||
|
|
||||||
start = 0
|
|
||||||
for i in xrange(position, start, -1):
|
|
||||||
c = content[i]
|
|
||||||
|
|
||||||
if not quoteStack and (c in terminators or c in quotesFwd or c == '\n'):
|
|
||||||
start = i + 1
|
|
||||||
break
|
|
||||||
|
|
||||||
if quoteStack and c == quoteStack[0]:
|
|
||||||
quoteStack.pop()
|
|
||||||
elif c in quotesBwd:
|
|
||||||
quoteStack.insert(0, quotesBwd[c])
|
|
||||||
|
|
||||||
quoteStack = []
|
|
||||||
|
|
||||||
end = len(content)
|
|
||||||
for i in xrange(position, end):
|
|
||||||
c = content[i]
|
|
||||||
|
|
||||||
if not quoteStack:
|
|
||||||
if c in terminators:
|
|
||||||
end = i + 1
|
|
||||||
break
|
|
||||||
elif c in quotesBwd:
|
|
||||||
end = i
|
|
||||||
break
|
|
||||||
|
|
||||||
if quoteStack and c == quoteStack[0]:
|
|
||||||
quoteStack.pop()
|
|
||||||
elif c in quotesFwd:
|
|
||||||
quoteStack.insert(0, quotesFwd[c])
|
|
||||||
|
|
||||||
return content[start:end].strip()
|
|
||||||
|
|
||||||
|
|
||||||
def formatFields(fields, markup):
|
|
||||||
result = {}
|
|
||||||
for field, value in fields.items():
|
|
||||||
try:
|
|
||||||
result[field] = value.format(**markup)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def splitTags(tags):
|
|
||||||
return filter(lambda tag: tag.strip(), re.split('[;,\s]', tags))
|
|
||||||
|
|
||||||
|
|
||||||
def markupVocabExp(definition):
|
|
||||||
if definition['reading']:
|
|
||||||
summary = u'{expression} [{reading}]'.format(**definition)
|
|
||||||
else:
|
|
||||||
summary = u'{expression}'.format(**definition)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'expression': definition['expression'],
|
|
||||||
'reading': definition['reading'] or u'',
|
|
||||||
'glossary': '; '.join(definition['glossary']),
|
|
||||||
'sentence': definition.get('sentence'),
|
|
||||||
'summary': summary
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def markupVocabReading(definition):
|
|
||||||
if definition['reading']:
|
|
||||||
return {
|
|
||||||
'expression': definition['reading'],
|
|
||||||
'reading': u'',
|
|
||||||
'glossary': '; '.join(definition['glossary']),
|
|
||||||
'sentence': definition.get('sentence'),
|
|
||||||
'summary': definition['reading']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def copyVocabDef(definition):
|
|
||||||
glossary = '; '.join(definition['glossary'])
|
|
||||||
if definition['reading']:
|
|
||||||
result = u'{0}\t{1}\t{2}\n'.format(definition['expression'], definition['reading'], glossary)
|
|
||||||
else:
|
|
||||||
result = u'{0}\t{1}\n'.format(definition['expression'], glossary)
|
|
||||||
|
|
||||||
QtGui.QApplication.clipboard().setText(result)
|
|
||||||
|
|
||||||
|
|
||||||
def markupKanji(definition):
|
|
||||||
return {
|
|
||||||
'character': definition['character'],
|
|
||||||
'onyomi': ', '.join(definition['onyomi']),
|
|
||||||
'kunyomi': ', '.join(definition['kunyomi']),
|
|
||||||
'glossary': ', '.join(definition['glossary']),
|
|
||||||
'summary': definition['character']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def copyKanjiDef(definition):
|
|
||||||
result = u'{0}\t{1}\t{2}\t{3}'.format(
|
|
||||||
definition['character'],
|
|
||||||
', '.join(definition['kunyomi']),
|
|
||||||
', '.join(definition['onyomi']),
|
|
||||||
', '.join(definition['glossary'])
|
|
||||||
)
|
|
||||||
|
|
||||||
QtGui.QApplication.clipboard().setText(result)
|
|
||||||
|
|
||||||
|
|
||||||
def buildDefHeader():
|
|
||||||
palette = QtGui.QApplication.palette()
|
|
||||||
toolTipBg = palette.color(QtGui.QPalette.Window).name()
|
|
||||||
toolTipFg = palette.color(QtGui.QPalette.WindowText).name()
|
|
||||||
|
|
||||||
return u'''
|
|
||||||
<html><head><style>
|
|
||||||
body {{ background-color: {0}; color: {1}; font-size: 11pt; }}
|
|
||||||
span.expression {{ font-size: 15pt; }}
|
|
||||||
</style></head><body>'''.format(toolTipBg, toolTipFg)
|
|
||||||
|
|
||||||
|
|
||||||
def buildDefFooter():
|
|
||||||
return '</body></html>'
|
|
||||||
|
|
||||||
|
|
||||||
def buildEmpty():
|
|
||||||
return u'''
|
|
||||||
<p>No definitions to display.</p>
|
|
||||||
<p>Mouse over text with the <em>middle mouse button</em> or <em>shift key</em> pressed to search.</p>
|
|
||||||
<p>You can also also input terms in the search box below.'''
|
|
||||||
|
|
||||||
|
|
||||||
def buildVocabDef(definition, index, query):
|
|
||||||
reading = u''
|
|
||||||
if definition['reading']:
|
|
||||||
reading = u'<span class="reading">[{0}]<br></span>'.format(definition['reading'])
|
|
||||||
|
|
||||||
rules = u''
|
|
||||||
if definition.get('rules'):
|
|
||||||
rules = ' < '.join(definition['rules'])
|
|
||||||
rules = '<span class="rules">({0})<br></span>'.format(rules)
|
|
||||||
|
|
||||||
links = '<a href="copyVocabDef:{0}"><img src="://img/img/icon_copy_definition.png" align="right"></a>'.format(index)
|
|
||||||
if query is not None:
|
|
||||||
if query('vocab', markupVocabExp(definition)):
|
|
||||||
links += '<a href="addVocabExp:{0}"><img src="://img/img/icon_add_expression.png" align="right"></a>'.format(index)
|
|
||||||
if query('vocab', markupVocabReading(definition)):
|
|
||||||
links += '<a href="addVocabReading:{0}"><img src="://img/img/icon_add_reading.png" align="right"></a>'.format(index)
|
|
||||||
|
|
||||||
glossary = u'<ol>'
|
|
||||||
for g in definition['glossary']:
|
|
||||||
glossary += u'<li>{0}</li>'.format(g)
|
|
||||||
glossary += u'</ol>'
|
|
||||||
|
|
||||||
expression = u'<span class="expression">'
|
|
||||||
if 'P' in definition['tags']:
|
|
||||||
expression += u'<strong>{}</strong>'.format(definition['expression'])
|
|
||||||
else:
|
|
||||||
expression += definition['expression']
|
|
||||||
expression += u'</span>'
|
|
||||||
|
|
||||||
html = u'''
|
|
||||||
<span class="links">{links}</span>
|
|
||||||
{expression}
|
|
||||||
<span class="reading">{reading}</span>
|
|
||||||
<span class="rules">{rules}</span>
|
|
||||||
<span class="glossary">{glossary}<br></span>
|
|
||||||
<br clear="all">'''.format(
|
|
||||||
links = links,
|
|
||||||
expression = expression,
|
|
||||||
reading = reading,
|
|
||||||
glossary = glossary,
|
|
||||||
rules = rules
|
|
||||||
)
|
|
||||||
|
|
||||||
return html
|
|
||||||
|
|
||||||
|
|
||||||
def buildVocabDefs(definitions, query):
|
|
||||||
html = buildDefHeader()
|
|
||||||
if len(definitions) > 0:
|
|
||||||
for i, definition in enumerate(definitions):
|
|
||||||
html += buildVocabDef(definition, i, query)
|
|
||||||
else:
|
|
||||||
html += buildEmpty()
|
|
||||||
|
|
||||||
return html + buildDefFooter()
|
|
||||||
|
|
||||||
|
|
||||||
def buildKanjiDef(definition, index, query):
|
|
||||||
links = '<a href="copyKanjiDef:{0}"><img src="://img/img/icon_copy_definition.png" align="right"></a>'.format(index)
|
|
||||||
if query is not None and query('kanji', markupKanji(definition)):
|
|
||||||
links += '<a href="addKanji:{0}"><img src="://img/img/icon_add_expression.png" align="right"></a>'.format(index)
|
|
||||||
|
|
||||||
readings = ', '.join(definition['kunyomi'] + definition['onyomi'])
|
|
||||||
glossary = ', '.join(definition['glossary'])
|
|
||||||
|
|
||||||
html = u'''
|
|
||||||
<span class="links">{links}</span>
|
|
||||||
<span class="expression">{expression}<br></span>
|
|
||||||
<span class="reading">[{reading}]<br></span>
|
|
||||||
<span class="glossary">{glossary}<br></span>
|
|
||||||
<br clear="all">'''.format(
|
|
||||||
links = links,
|
|
||||||
expression = definition['character'],
|
|
||||||
reading = readings,
|
|
||||||
glossary = glossary
|
|
||||||
)
|
|
||||||
|
|
||||||
return html
|
|
||||||
|
|
||||||
|
|
||||||
def buildKanjiDefs(definitions, query):
|
|
||||||
html = buildDefHeader()
|
|
||||||
|
|
||||||
if len(definitions) > 0:
|
|
||||||
for i, definition in enumerate(definitions):
|
|
||||||
html += buildKanjiDef(definition, i, query)
|
|
||||||
else:
|
|
||||||
html += buildEmpty()
|
|
||||||
|
|
||||||
return html + buildDefFooter()
|
|
||||||
|
|
||||||
|
|
||||||
def extractKindleDeck(filename):
|
|
||||||
words = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
with sqlite3.connect(unicode(filename)) as db:
|
|
||||||
for row in db.execute('select word from WORDS'):
|
|
||||||
words.append(row[0])
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return words
|
|
||||||
|
|
||||||
|
|
||||||
def extractWordList(filename):
|
|
||||||
words = []
|
|
||||||
|
|
||||||
with codecs.open(unicode(filename), 'rb', 'utf-8') as fp:
|
|
||||||
words = re.split('[;,\s]', fp.read())
|
|
||||||
|
|
||||||
return filter(None, words)
|
|
@ -1,72 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
import constants
|
|
||||||
import gen.updates_ui
|
|
||||||
import json
|
|
||||||
import urllib2
|
|
||||||
|
|
||||||
|
|
||||||
class DialogUpdates(QtGui.QDialog, gen.updates_ui.Ui_DialogUpdates):
|
|
||||||
def __init__(self, parent, versions):
|
|
||||||
QtGui.QDialog.__init__(self, parent)
|
|
||||||
self.setupUi(self)
|
|
||||||
|
|
||||||
self.updateHtml(versions)
|
|
||||||
self.labelUpdates.setText(
|
|
||||||
unicode(self.labelUpdates.text()).format(
|
|
||||||
constants.c['appVersion'],
|
|
||||||
versions['latest']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def updateHtml(self, versions):
|
|
||||||
html = '<html><body>'
|
|
||||||
|
|
||||||
for update in versions['updates']:
|
|
||||||
version = update.get('version')
|
|
||||||
if version > constants.c['appVersion']:
|
|
||||||
html += '<strong>Version {0}</strong>'.format(version)
|
|
||||||
html += '<ul>'
|
|
||||||
for feature in update['features']:
|
|
||||||
html += '<li>{0}</li>'.format(feature)
|
|
||||||
html += '</ul>'
|
|
||||||
|
|
||||||
self.textBrowser.setHtml(html)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateFinder(QtCore.QThread):
|
|
||||||
updateResult = QtCore.pyqtSignal(dict)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
latest = constants.c['appVersion']
|
|
||||||
updates = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
fp = urllib2.urlopen('https://foosoft.net/projects/yomichan/dl/updates.json')
|
|
||||||
updates = json.loads(fp.read())
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for update in updates:
|
|
||||||
latest = max(latest, update.get('version'))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.updateResult.emit({'latest': latest, 'updates': updates})
|
|
97
yomichan.py
97
yomichan.py
@ -1,97 +0,0 @@
|
|||||||
#!/usr/bin/env python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2013 Alex Yatskov
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
|
||||||
from yomi_base import japanese
|
|
||||||
from yomi_base.anki_connect import AnkiConnect
|
|
||||||
from yomi_base.preference_data import Preferences
|
|
||||||
from yomi_base.reader import MainWindowReader
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
class Yomichan:
|
|
||||||
def __init__(self):
|
|
||||||
self.language = japanese.initLanguage()
|
|
||||||
|
|
||||||
self.preferences = Preferences()
|
|
||||||
self.preferences.load()
|
|
||||||
|
|
||||||
|
|
||||||
class YomichanPlugin(Yomichan):
|
|
||||||
def __init__(self):
|
|
||||||
Yomichan.__init__(self)
|
|
||||||
|
|
||||||
self.toolIconVisible = False
|
|
||||||
self.window = None
|
|
||||||
self.anki = anki_bridge.Anki()
|
|
||||||
self.parent = self.anki.window()
|
|
||||||
self.ankiConnect = AnkiConnect(self.anki, self.preferences)
|
|
||||||
|
|
||||||
separator = QtGui.QAction(self.parent)
|
|
||||||
separator.setSeparator(True)
|
|
||||||
self.anki.addUiAction(separator)
|
|
||||||
|
|
||||||
action = QtGui.QAction(QtGui.QIcon(':/img/img/icon_logo_32.png'), '&Yomichan...', self.parent)
|
|
||||||
action.setIconVisibleInMenu(True)
|
|
||||||
action.setShortcut('Ctrl+Y')
|
|
||||||
action.triggered.connect(self.onShowRequest)
|
|
||||||
self.anki.addUiAction(action)
|
|
||||||
|
|
||||||
|
|
||||||
def onShowRequest(self):
|
|
||||||
if self.window:
|
|
||||||
self.window.setVisible(True)
|
|
||||||
self.window.activateWindow()
|
|
||||||
else:
|
|
||||||
self.window = MainWindowReader(
|
|
||||||
self.parent,
|
|
||||||
self.preferences,
|
|
||||||
self.language,
|
|
||||||
None,
|
|
||||||
self.anki,
|
|
||||||
self.onWindowClose
|
|
||||||
)
|
|
||||||
self.window.show()
|
|
||||||
|
|
||||||
|
|
||||||
def onWindowClose(self):
|
|
||||||
self.window = None
|
|
||||||
|
|
||||||
|
|
||||||
class YomichanStandalone(Yomichan):
|
|
||||||
def __init__(self):
|
|
||||||
Yomichan.__init__(self)
|
|
||||||
|
|
||||||
self.application = QtGui.QApplication(sys.argv)
|
|
||||||
self.window = MainWindowReader(
|
|
||||||
None,
|
|
||||||
self.preferences,
|
|
||||||
self.language,
|
|
||||||
filename=sys.argv[1] if len(sys.argv) >= 2 else None
|
|
||||||
)
|
|
||||||
|
|
||||||
self.window.show()
|
|
||||||
self.application.exec_()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
instance = YomichanStandalone()
|
|
||||||
else:
|
|
||||||
from yomi_base import anki_bridge
|
|
||||||
instance = YomichanPlugin()
|
|
Loading…
Reference in New Issue
Block a user