def __init__( self, parent=None, languages=None, filename=None, preferences=None, anki=None, closed=None, updated=None ): QtGui.QMainWindow.__init__(self, parent) self.setupUi(self) self.textContent.mouseMoveEvent = self.onContentMouseMove self.textContent.mousePressEvent = self.onContentMousePress self.dockAnki.setEnabled(bool(anki)) if preferences: self.preferences = preferences else: self.preferences = Preferences() self.preferences.load() self.updateFinder = UpdateFinder() self.state = self.State() self.languages = languages self.addedFacts = list() self.anki = anki self.closed = closed self.updated = updated self.zoom = 0 self.applyPreferences() self.updateRecentFiles() self.updateDefinitions() if filename: self.openFile(filename) elif self.preferences.generalRecentLoad: filenames = self.preferences.recentFiles() if filenames: self.openFile(filenames[0]) if self.preferences.generalFindUpdates: self.updateFinder.start() bindings = [ (self.actionOpen, "triggered()", self.onActionOpen), (self.actionPreferences, "triggered()", self.onActionPreferences), (self.actionAbout, "triggered()", self.onActionAbout), (self.actionZoomIn, "triggered()", self.onActionZoomIn), (self.actionZoomOut, "triggered()", self.onActionZoomOut), (self.actionZoomReset, "triggered()", self.onActionZoomReset), (self.actionFind, "triggered()", self.onActionFind), (self.actionFindNext, "triggered()", self.onActionFindNext), (self.actionToggleWrap, "toggled(bool)", self.onActionToggleWrap), (self.actionCopyDefinition, "triggered()", self.onActionCopyDefinition), (self.actionCopyAllDefinitions, "triggered()", self.onActionCopyAllDefinitions), (self.actionCopySentence, "triggered()", self.onActionCopySentence), (self.actionHomepage, "triggered()", self.onActionHomepage), (self.actionFeedback, "triggered()", self.onActionFeedback), (self.textDefinitions, "anchorClicked(const QUrl&)", self.onDefinitionsAnchorClicked), (self.textDefinitionSearch, "returnPressed()", self.onDefinitionSearchReturn), (self.listDefinitions, "itemDoubleClicked(QListWidgetItem *)", self.onDefinitionDoubleClicked), (self.dockDefinitions, "visibilityChanged(bool)", self.onVisibilityChanged), (self.dockAnki, "visibilityChanged(bool)", self.onVisibilityChanged), (self.updateFinder, "updateSearchResult", self.onUpdaterSearchResult), ] for action, signal, callback in bindings: self.connect(action, QtCore.SIGNAL(signal), callback)
class MainWindowReader(QtGui.QMainWindow, Ui_MainWindowReader): class State: def __init__(self): self.filename = unicode() self.definitions = list() self.searchPosition = 0 self.searchText = unicode() self.scanPosition = 0 def __init__( self, parent=None, languages=None, filename=None, preferences=None, anki=None, closed=None, updated=None ): QtGui.QMainWindow.__init__(self, parent) self.setupUi(self) self.textContent.mouseMoveEvent = self.onContentMouseMove self.textContent.mousePressEvent = self.onContentMousePress self.dockAnki.setEnabled(bool(anki)) if preferences: self.preferences = preferences else: self.preferences = Preferences() self.preferences.load() self.updateFinder = UpdateFinder() self.state = self.State() self.languages = languages self.addedFacts = list() self.anki = anki self.closed = closed self.updated = updated self.zoom = 0 self.applyPreferences() self.updateRecentFiles() self.updateDefinitions() if filename: self.openFile(filename) elif self.preferences.generalRecentLoad: filenames = self.preferences.recentFiles() if filenames: self.openFile(filenames[0]) if self.preferences.generalFindUpdates: self.updateFinder.start() bindings = [ (self.actionOpen, "triggered()", self.onActionOpen), (self.actionPreferences, "triggered()", self.onActionPreferences), (self.actionAbout, "triggered()", self.onActionAbout), (self.actionZoomIn, "triggered()", self.onActionZoomIn), (self.actionZoomOut, "triggered()", self.onActionZoomOut), (self.actionZoomReset, "triggered()", self.onActionZoomReset), (self.actionFind, "triggered()", self.onActionFind), (self.actionFindNext, "triggered()", self.onActionFindNext), (self.actionToggleWrap, "toggled(bool)", self.onActionToggleWrap), (self.actionCopyDefinition, "triggered()", self.onActionCopyDefinition), (self.actionCopyAllDefinitions, "triggered()", self.onActionCopyAllDefinitions), (self.actionCopySentence, "triggered()", self.onActionCopySentence), (self.actionHomepage, "triggered()", self.onActionHomepage), (self.actionFeedback, "triggered()", self.onActionFeedback), (self.textDefinitions, "anchorClicked(const QUrl&)", self.onDefinitionsAnchorClicked), (self.textDefinitionSearch, "returnPressed()", self.onDefinitionSearchReturn), (self.listDefinitions, "itemDoubleClicked(QListWidgetItem *)", self.onDefinitionDoubleClicked), (self.dockDefinitions, "visibilityChanged(bool)", self.onVisibilityChanged), (self.dockAnki, "visibilityChanged(bool)", self.onVisibilityChanged), (self.updateFinder, "updateSearchResult", self.onUpdaterSearchResult), ] for action, signal, callback in bindings: self.connect(action, QtCore.SIGNAL(signal), callback) def applyPreferences(self): if self.preferences.uiReaderState != None: self.restoreState(QtCore.QByteArray.fromBase64(self.preferences.uiReaderState)) if self.preferences.uiReaderPosition != None: self.move(QtCore.QPoint(*self.preferences.uiReaderPosition)) if self.preferences.uiReaderSize != None: self.resize(QtCore.QSize(*self.preferences.uiReaderSize)) for tags in self.preferences.ankiTags: self.comboTags.addItem(tags) self.applyPreferencesContent() def applyPreferencesContent(self): palette = self.textContent.palette() palette.setColor(QtGui.QPalette.Base, QtGui.QColor(self.preferences.uiContentColorBg)) palette.setColor(QtGui.QPalette.Text, QtGui.QColor(self.preferences.uiContentColorFg)) self.textContent.setPalette(palette) font = self.textContent.font() font.setFamily(self.preferences.uiContentFontFamily) font.setPointSize(self.preferences.uiContentFontSize + self.zoom) self.textContent.setFont(font) self.actionToggleWrap.setChecked(self.preferences.uiContentWordWrap) def closeEvent(self, event): self.closeFile() self.preferences.uiReaderState = self.saveState().toBase64() self.preferences.save() if self.closed: self.closed() def keyPressEvent(self, event): if self.dockDefinitions.isVisible() and event.key() == QtCore.Qt.Key_Shift: 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.uiReaderPosition = (event.pos().x(), event.pos().y()) def resizeEvent(self, event): self.preferences.uiReaderSize = (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 not filename.isNull(): self.openFile(filename) def onActionPreferences(self): dialog = DialogPreferences(self, self.preferences, self.anki) if dialog.exec_() == QtGui.QDialog.Accepted: self.applyPreferencesContent() if self.updated: self.updated() def onActionAbout(self): dialog = 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): mode = QtGui.QPlainTextEdit.WidgetWidth if wrap else QtGui.QPlainTextEdit.NoWrap self.preferences.uiContentWordWrap = wrap self.textContent.setLineWrapMode(wrap) def onActionCopyDefinition(self): reader_util.copyDefinitions(self.state.definitions[:1]) def onActionCopyAllDefinitions(self): reader_util.copyDefinitions(self.state.definitions) def onActionCopySentence(self): content = unicode(self.textContent.toPlainText()) sentence = reader_util.findSentence(content, self.state.scanPosition) QtGui.QApplication.clipboard().setText(sentence) def onActionHomepage(self): url = QtCore.QUrl(constants["urlHomepage"]) QtGui.QDesktopServices().openUrl(url) def onActionFeedback(self): url = QtCore.QUrl(constants["urlFeedback"]) QtGui.QDesktopServices().openUrl(url) def onDefinitionsAnchorClicked(self, url): command, index = unicode(url.toString()).split(":") definition = self.state.definitions[int(index)] if command == "addFactExpression": markup = reader_util.buildFactMarkupExpression( definition.expression, definition.reading, definition.glossary, definition.sentence ) self.ankiAddFact(markup) if command == "addFactReading": markup = reader_util.buildFactMarkupReading(definition.reading, definition.glossary, definition.sentence) self.ankiAddFact(markup) elif command == "copyDefinition": reader_util.copyDefinitions([definition]) def onDefinitionSearchReturn(self): text = unicode(self.textDefinitionSearch.text()) definitions, length = self.language().wordSearch( text, self.preferences.searchResultMax, self.preferences.searchGroupByExp ) self.state.definitions = reader_util.convertDefinitions(definitions) self.updateDefinitions() def onDefinitionDoubleClicked(self, item): if self.anki: row = self.listDefinitions.row(item) self.anki.browseFact(self.addedFacts[row]) def onVisibilityChanged(self, visible): self.actionToggleAnki.setChecked(self.dockAnki.isVisible()) self.actionToggleDefinitions.setChecked(self.dockDefinitions.isVisible()) def onUpdaterSearchResult(self, result): if result and result > constants["version"]: QtGui.QMessageBox.information( self, "Yomichan", "A new version of Yomichan is available for download!\n\nYou can download this update ({0} > {1}) " 'from "Shared Plugins" in Anki or directly from the Yomichan homepage.'.format( constants["version"], result ), ) 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): filename = unicode(filename) try: with open(filename, "rb") 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.generalReadingsStrip: content = reader_util.stripContentReadings(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.split(filename)[1], encoding)) def closeFile(self): self.setWindowTitle("Yomichan") self.textContent.setPlainText(unicode()) self.updateRecentFile(False) self.state = self.State() def findText(self, text): content = self.textContent.toPlainText() index = content.indexOf(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, markup): if not self.anki: return False fields = reader_util.replaceMarkupInFields(self.preferences.ankiFields, markup) tags = self.anki.cleanupTags(unicode(self.comboTags.currentText())) tagIndex = self.comboTags.findText(tags) if tagIndex > 0: self.comboTags.removeItem(tagIndex) if tagIndex != 0: self.comboTags.insertItem(0, tags) self.preferences.updateFactTags(tags) factId = self.anki.addFact(fields, tags) if not factId: return False expression, reading = markup["%e"], markup["%r"] summary = expression if reading: summary = u"{0} [{1}]".format(expression, reading) self.addedFacts.append(factId) self.listDefinitions.addItem(summary) self.listDefinitions.setCurrentRow(self.listDefinitions.count() - 1) self.setStatus(u"Added expression {0}; {1} new fact(s) total".format(expression, len(self.addedFacts))) self.updateDefinitions() return True def ankiIsFactValid(self, markup): if not self.anki: return False fields = reader_util.replaceMarkupInFields(self.preferences.ankiFields, markup) return self.anki.canAddFact(fields) def updateSampleMouseEvent(self, event): cursor = self.textContent.cursorForPosition(event.pos()) self.state.scanPosition = cursor.position() requested = event.buttons() & QtCore.Qt.MidButton or event.modifiers() & QtCore.Qt.ShiftModifier if self.dockDefinitions.isVisible() and requested: self.updateSampleFromPosition() def updateSampleFromPosition(self): samplePosStart = self.state.scanPosition samplePosEnd = self.state.scanPosition + self.preferences.searchScanMax cursor = self.textContent.textCursor() content = unicode(self.textContent.toPlainText()) contentSample = content[samplePosStart:samplePosEnd] if not contentSample or unicode.isspace(contentSample[0]): cursor.clearSelection() self.textContent.setTextCursor(cursor) return contentSampleFlat = contentSample.replace("\n", unicode()) definitionsMatched, lengthMatched = self.language().wordSearch( contentSampleFlat, self.preferences.searchResultMax, self.preferences.searchGroupByExp ) sentence = reader_util.findSentence(content, samplePosStart) self.state.definitions = reader_util.convertDefinitions(definitionsMatched, sentence) self.updateDefinitions() lengthSelect = 0 if lengthMatched: for c in contentSample: lengthSelect += 1 if c != "\n": lengthMatched -= 1 if lengthMatched <= 0: break 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 not filenames: return for filename in filenames: self.menuOpenRecent.addAction(filename, (lambda fn=filename: self.openFile(fn))) 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 updateDefinitions(self): html = reader_util.buildDefinitionsHtml(self.state.definitions, self.ankiIsFactValid) self.textDefinitions.setHtml(html) def setStatus(self, status): self.statusBar.showMessage(status) def language(self): return self.languages["Japanese"]