def _create_gui(self): self.setWindowTitle(_("Preview")) qconnect(self.finished, self._on_finished) self.silentlyClose = True self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) self._web = AnkiWebView(title="previewer") self.vbox.addWidget(self._web) self.bbox = QDialogButtonBox() self._replay = self.bbox.addButton(_("Replay Audio"), QDialogButtonBox.ActionRole) self._replay.setAutoDefault(False) self._replay.setShortcut(QKeySequence("R")) self._replay.setToolTip(_("Shortcut key: %s" % "R")) qconnect(self._replay.clicked, self._on_replay_audio) both_sides_button = QCheckBox(_("Show Both Sides")) both_sides_button.setShortcut(QKeySequence("B")) both_sides_button.setToolTip(_("Shortcut key: %s" % "B")) self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole) self._show_both_sides = self.mw.col.conf.get("previewBothSides", False) both_sides_button.setChecked(self._show_both_sides) qconnect(both_sides_button.toggled, self._on_show_both_sides) self.vbox.addWidget(self.bbox) self.setLayout(self.vbox) restoreGeom(self, "preview")
def highlight_terms(webview: AnkiWebView, terms: List[str]): # FIXME: anki21 does not seem to support highlighting more than one # term at once. Likely a Qt bug / regression. # TODO: Perhaps choose to highlight the longest term on anki21. # TODO: Find a way to exclude UI text in editor pane from highlighting for term in terms: webview.findText(term)
def add_graphs_to_congrats(webview: AnkiWebView): page = basename(webview.page().url().path()) if page != "congrats.html": return graph_css = make_graph_css() graph_js = make_graph_js(get_active_congrats_graphs(), "deck:current") webview.eval(f""" const graphsContainer = document.createElement("div") graphsContainer.id = "graphsSection" graphsContainer.style = "text-align: center;" document.body.appendChild(graphsContainer) const loadGraphs = () => {{ {graph_css} {graph_js} }} const loadGraphScript = () => {{ const graphScript = document.createElement("script") graphScript.onload = loadGraphs graphScript.charset = "UTF-8" graphScript.src = "graphs.js" document.head.appendChild(graphScript) }} const protobufScript = document.createElement("script") protobufScript.onload = loadGraphScript protobufScript.charset = "UTF-8" protobufScript.src = "../js/vendor/protobuf.min.js" document.head.appendChild(protobufScript) """)
def setupUi(self, About): About.setObjectName("About") About.resize(410, 664) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(About.sizePolicy().hasHeightForWidth()) About.setSizePolicy(sizePolicy) self.vboxlayout = QtWidgets.QVBoxLayout(About) self.vboxlayout.setContentsMargins(0, 0, 0, 0) self.vboxlayout.setObjectName("vboxlayout") self.label = AnkiWebView(About) self.label.setProperty("url", QtCore.QUrl("about:blank")) self.label.setObjectName("label") self.vboxlayout.addWidget(self.label) self.buttonBox = QtWidgets.QDialogButtonBox(About) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.vboxlayout.addWidget(self.buttonBox) self.retranslateUi(About) self.buttonBox.accepted.connect(About.accept) self.buttonBox.rejected.connect(About.reject) QtCore.QMetaObject.connectSlotsByName(About)
def setup_preview(self) -> None: pform = self.pform self.preview_web = AnkiWebView(title="card layout") pform.verticalLayout.addWidget(self.preview_web) pform.verticalLayout.setStretch(1, 99) pform.preview_front.isChecked() qconnect(pform.preview_front.clicked, self.on_preview_toggled) qconnect(pform.preview_back.clicked, self.on_preview_toggled) pform.preview_settings.setText( f"{tr.card_templates_preview_settings()} {downArrow()}") qconnect(pform.preview_settings.clicked, self.on_preview_settings) self.preview_web.stdHtml( self.mw.reviewer.revHtml(), css=["css/reviewer.css"], js=[ "js/mathjax.js", "js/vendor/mathjax/tex-chtml.js", "js/reviewer.js", ], context=self, ) self.preview_web.set_bridge_command(self._on_bridge_cmd, self) if self._isCloze(): nums = list(self.note.cloze_numbers_in_fields()) if self.ord + 1 not in nums: # current card is empty nums.append(self.ord + 1) self.cloze_numbers = sorted(nums) self.setup_cloze_number_box() else: self.cloze_numbers = [] self.pform.cloze_number_combo.setHidden(True)
def _showQuestion(self) -> None: self._reps += 1 self.state = "question" self.typedAnswer: str = None c = self.card # grab the question and play audio q = c.q() # play audio? if c.autoplay(): AnkiWebView.setPlaybackRequiresGesture(False) sounds = c.question_av_tags() gui_hooks.reviewer_will_play_question_sounds(c, sounds) av_player.play_tags(sounds) else: AnkiWebView.setPlaybackRequiresGesture(True) av_player.clear_queue_and_maybe_interrupt() sounds = [] gui_hooks.reviewer_will_play_question_sounds(c, sounds) av_player.play_tags(sounds) # render & update bottom q = self._mungeQA(q) q = gui_hooks.card_will_show(q, c, "reviewQuestion") bodyclass = theme_manager.body_classes_for_card_ord(c.ord) self.web.eval(f"_showQuestion({json.dumps(q)},'{bodyclass}');") self._drawFlag() self._drawMark() self._showAnswerButton() self.mw.web.setFocus() # user hook gui_hooks.reviewer_did_show_question(c)
def focusInEvent(self, evt): AnkiWebView.focusInEvent(self, evt) if evt.reason() == Qt.TabFocusReason: self.eval("focusField(0);") elif evt.reason() == Qt.BacktabFocusReason: n = len(self.editor.note.fields) - 1 self.eval("focusField(%d);" % n)
def _create_gui(self): self.setWindowTitle(tr(TR.ACTIONS_PREVIEW)) qconnect(self.finished, self._on_finished) self.silentlyClose = True self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) self._web = AnkiWebView(title="previewer") self.vbox.addWidget(self._web) self.bbox = QDialogButtonBox() self._replay = self.bbox.addButton(tr(TR.ACTIONS_REPLAY_AUDIO), QDialogButtonBox.ActionRole) self._replay.setAutoDefault(False) self._replay.setShortcut(QKeySequence("R")) self._replay.setToolTip(tr(TR.ACTIONS_SHORTCUT_KEY, val="R")) qconnect(self._replay.clicked, self._on_replay_audio) both_sides_button = QCheckBox(tr(TR.QT_MISC_BACK_SIDE_ONLY)) both_sides_button.setShortcut(QKeySequence("B")) both_sides_button.setToolTip(tr(TR.ACTIONS_SHORTCUT_KEY, val="B")) self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole) self._show_both_sides = self.mw.col.conf.get("previewBothSides", False) both_sides_button.setChecked(self._show_both_sides) qconnect(both_sides_button.toggled, self._on_show_both_sides) self.vbox.addWidget(self.bbox) self.setLayout(self.vbox) restoreGeom(self, "preview")
def setupWebviews(self): pform = self.pform pform.frontWeb = AnkiWebView(title="card layout front") pform.frontPrevBox.addWidget(pform.frontWeb) pform.backWeb = AnkiWebView(title="card layout back") pform.backPrevBox.addWidget(pform.backWeb) jsinc = [ "jquery.js", "browsersel.js", "mathjax/conf.js", "mathjax/MathJax.js", "reviewer.js", ] pform.frontWeb.stdHtml( self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self, ) pform.backWeb.stdHtml( self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self, ) pform.frontWeb.set_bridge_command(self._on_bridge_cmd, self) pform.backWeb.set_bridge_command(self._on_bridge_cmd, self)
def setup_preview(self): pform = self.pform self.preview_web = AnkiWebView(title="card layout") pform.verticalLayout.addWidget(self.preview_web) pform.verticalLayout.setStretch(1, 99) pform.preview_front.isChecked() qconnect(pform.preview_front.clicked, self.on_preview_toggled) qconnect(pform.preview_back.clicked, self.on_preview_toggled) pform.preview_settings.setText( tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS) + " " + downArrow() ) qconnect(pform.preview_settings.clicked, self.on_preview_settings) jsinc = [ "jquery.js", "browsersel.js", "mathjax/conf.js", "mathjax/MathJax.js", "reviewer.js", ] self.preview_web.stdHtml( self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self, ) self.preview_web.set_bridge_command(self._on_bridge_cmd, self) if self._isCloze(): nums = list(self.note.cloze_numbers_in_fields()) if self.ord + 1 not in nums: # current card is empty nums.append(self.ord + 1) self.cloze_numbers = sorted(nums) self.setup_cloze_number_box() else: self.cloze_numbers = [] self.pform.cloze_number_combo.setHidden(True)
def _create_gui(self) -> None: self.setWindowTitle(tr.actions_preview()) self.close_shortcut = QShortcut(QKeySequence("Ctrl+Shift+P"), self) qconnect(self.close_shortcut.activated, self.close) qconnect(self.finished, self._on_finished) self.silentlyClose = True self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) self._web = AnkiWebView(title="previewer") self.vbox.addWidget(self._web) self.bbox = QDialogButtonBox() self._replay = self.bbox.addButton( tr.actions_replay_audio(), QDialogButtonBox.ButtonRole.ActionRole) self._replay.setAutoDefault(False) self._replay.setShortcut(QKeySequence("R")) self._replay.setToolTip(tr.actions_shortcut_key(val="R")) qconnect(self._replay.clicked, self._on_replay_audio) both_sides_button = QCheckBox(tr.qt_misc_back_side_only()) both_sides_button.setShortcut(QKeySequence("B")) both_sides_button.setToolTip(tr.actions_shortcut_key(val="B")) self.bbox.addButton(both_sides_button, QDialogButtonBox.ButtonRole.ActionRole) self._show_both_sides = self.mw.col.get_config_bool( Config.Bool.PREVIEW_BOTH_SIDES) both_sides_button.setChecked(self._show_both_sides) qconnect(both_sides_button.toggled, self._on_show_both_sides) self.vbox.addWidget(self.bbox) self.setLayout(self.vbox) restoreGeom(self, "preview")
def _render_scheduled(self) -> None: self.cancel_timer() self._last_render = time.time() if not self._open: return c = self.card() func = "_showQuestion" if not c: txt = tr(TR.QT_MISC_PLEASE_SELECT_1_CARD) bodyclass = "" self._last_state = None else: if self._show_both_sides: self._state = "answer" elif self._card_changed: self._state = "question" currentState = self._state_and_mod() if currentState == self._last_state: # nothing has changed, avoid refreshing return # need to force reload even if answer txt = c.q(reload=True) if self._state == "answer": func = "_showAnswer" txt = c.a() txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt) bodyclass = theme_manager.body_classes_for_card_ord(c.ord) if c.autoplay(): AnkiWebView.setPlaybackRequiresGesture(False) if self._show_both_sides: # if we're showing both sides at once, remove any audio # from the answer that's appeared on the question already question_audio = c.question_av_tags() only_on_answer_audio = [ x for x in c.answer_av_tags() if x not in question_audio ] audio = question_audio + only_on_answer_audio elif self._state == "question": audio = c.question_av_tags() else: audio = c.answer_av_tags() av_player.play_tags(audio) else: AnkiWebView.setPlaybackRequiresGesture(True) av_player.clear_queue_and_maybe_interrupt() txt = self.mw.prepare_card_text_for_display(txt) txt = gui_hooks.card_will_show( txt, c, "preview" + self._state.capitalize()) self._last_state = self._state_and_mod() self._web.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass)) self._card_changed = False
class CardInfoDialog(QDialog): TITLE = "browser card info" GEOMETRY_KEY = "revlog" silentlyClose = True def __init__( self, parent: QWidget | None, mw: aqt.AnkiQt, card: Card | None, on_close: Callable | None = None, geometry_key: str | None = None, window_title: str | None = None, ) -> None: super().__init__(parent) self.mw = mw self._on_close = on_close self.GEOMETRY_KEY = geometry_key or self.GEOMETRY_KEY if window_title: self.setWindowTitle(window_title) self._setup_ui(card.id if card else None) self.show() def _setup_ui(self, card_id: CardId | None) -> None: self.mw.garbage_collect_on_dialog_finish(self) disable_help_button(self) restoreGeom(self, self.GEOMETRY_KEY) addCloseShortcut(self) setWindowIcon(self) self.web = AnkiWebView(title=self.TITLE) self.web.setVisible(False) self.web.load_ts_page("card-info") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.web) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) buttons.setContentsMargins(10, 0, 10, 10) layout.addWidget(buttons) qconnect(buttons.rejected, self.reject) self.setLayout(layout) self.web.eval( "const cardInfo = anki.cardInfo(document.getElementById('main'));" ) self.update_card(card_id) def update_card(self, card_id: CardId | None) -> None: self.web.eval( f"cardInfo.then((c) => c.$set({{ cardId: {json.dumps(card_id)} }}));" ) def reject(self) -> None: if self._on_close: self._on_close() self.web.cleanup() self.web = None saveGeom(self, self.GEOMETRY_KEY) return QDialog.reject(self)
def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(607, 556) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.web = AnkiWebView(Dialog) self.web.setProperty("url", QtCore.QUrl("about:blank")) self.web.setObjectName("web") self.verticalLayout.addWidget(self.web) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setContentsMargins(6, 6, 6, 6) self.horizontalLayout_3.setSpacing(8) self.horizontalLayout_3.setObjectName("horizontalLayout_3") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_3.addItem(spacerItem) self.groupBox_2 = QtWidgets.QGroupBox(Dialog) self.groupBox_2.setTitle("") self.groupBox_2.setObjectName("groupBox_2") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_2) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.groups = QtWidgets.QRadioButton(self.groupBox_2) self.groups.setChecked(True) self.groups.setObjectName("groups") self.horizontalLayout_2.addWidget(self.groups) self.all = QtWidgets.QRadioButton(self.groupBox_2) self.all.setObjectName("all") self.horizontalLayout_2.addWidget(self.all) self.horizontalLayout_3.addWidget(self.groupBox_2) self.groupBox = QtWidgets.QGroupBox(Dialog) self.groupBox.setTitle("") self.groupBox.setObjectName("groupBox") self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox) self.horizontalLayout.setObjectName("horizontalLayout") self.month = QtWidgets.QRadioButton(self.groupBox) self.month.setChecked(True) self.month.setObjectName("month") self.horizontalLayout.addWidget(self.month) self.year = QtWidgets.QRadioButton(self.groupBox) self.year.setObjectName("year") self.horizontalLayout.addWidget(self.year) self.life = QtWidgets.QRadioButton(self.groupBox) self.life.setObjectName("life") self.horizontalLayout.addWidget(self.life) self.horizontalLayout_3.addWidget(self.groupBox) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close) self.buttonBox.setObjectName("buttonBox") self.horizontalLayout_3.addWidget(self.buttonBox) self.verticalLayout.addLayout(self.horizontalLayout_3) self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.rejected.connect(Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog)
def __init__(self, parent: QWidget, editor: Editor) -> None: AnkiWebView.__init__(self, title="editor") self.editor = editor self.setAcceptDrops(True) self._markInternal = False clip = self.editor.mw.app.clipboard() qconnect(clip.dataChanged, self._onClipboardChange) gui_hooks.editor_web_view_did_init(self)
def show(self): if not self.shown: self.web = AnkiWebView(self.mw) self.web.setMaximumWidth(400) self.shown = self.mw.addDockable(_("Card Info"), self.web) self.shown.connect(self.shown, SIGNAL("visibilityChanged(bool)"), self._visChange) self._update()
def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.strip = self.editor.mw.pm.profile['stripHTML'] self.setAcceptDrops(True) self._markInternal = False clip = self.editor.mw.app.clipboard() clip.dataChanged.connect(self._onClipboardChange)
def getUserGuideTab(self): guide = AnkiWebView() guide._page.profile().setHttpUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36') guide._page._bridge.onCmd = attemptOpenLink html, url = self.getHTML() guide._page.setHtml(html, url) guide.setObjectName("tab_4") return guide
def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.strip = self.editor.mw.pm.profile["stripHTML"] self.setAcceptDrops(True) self._markInternal = False clip = self.editor.mw.app.clipboard() clip.dataChanged.connect(self._onClipboardChange)
def ui(self): self.web = AnkiWebView() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.web) layout.addLayout(self.ui_buttons()) return layout
def mouseReleaseEvent(self, evt): if not isMac and not isWin and evt.button() == Qt.MidButton: # middle click on x11; munge the clipboard before standard # handling mime = self.prepareClip(mode=QClipboard.Selection) AnkiWebView.mouseReleaseEvent(self, evt) self.restoreClip(mime, mode=QClipboard.Selection) else: AnkiWebView.mouseReleaseEvent(self, evt)
def startReport(self): self.report = QDialog(mw) self.report.setWindowTitle('List of Words - Report') self.report.layout = QVBoxLayout(self.report) self.webView = AnkiWebView() self.report.layout.addWidget(self.webView) self.printButton = self.saveButton = QPushButton("Save List") self.printButton.pressed.connect(self.printWordList)
def __init__(self, parent, editor): AnkiWebView.__init__(self, title="editor") self.editor = editor self.strip = self.editor.mw.pm.profile["stripHTML"] self.setAcceptDrops(True) self._markInternal = False clip = self.editor.mw.app.clipboard() qconnect(clip.dataChanged, self._onClipboardChange) gui_hooks.editor_web_view_did_init(self)
def addTab(self, t): c = self.connect w = QWidget() l = QHBoxLayout() l.setMargin(0) l.setSpacing(3) left = QWidget() # template area tform = aqt.forms.template.Ui_Form() tform.setupUi(left) tform.label1.setText(u" →") tform.label2.setText(u" →") tform.labelc1.setText(u" ↗") tform.labelc2.setText(u" ↘") if self.style().objectName() == "gtk+": # gtk+ requires margins in inner layout tform.tlayout1.setContentsMargins(0, 11, 0, 0) tform.tlayout2.setContentsMargins(0, 11, 0, 0) tform.tlayout3.setContentsMargins(0, 11, 0, 0) if len(self.cards) > 1: tform.groupBox_3.setTitle(_("Styling (shared between cards)")) c(tform.front, SIGNAL("textChanged()"), self.saveCard) c(tform.css, SIGNAL("textChanged()"), self.saveCard) c(tform.back, SIGNAL("textChanged()"), self.saveCard) l.addWidget(left, 5) # preview area right = QWidget() pform = aqt.forms.preview.Ui_Form() pform.setupUi(right) if self.style().objectName() == "gtk+": # gtk+ requires margins in inner layout pform.frontPrevBox.setContentsMargins(0, 11, 0, 0) pform.backPrevBox.setContentsMargins(0, 11, 0, 0) # for cloze notes, show that it's one of n cards if self.model['type'] == MODEL_CLOZE: cnt = len( self.mm.availOrds(self.model, joinFields(self.note.fields))) for g in pform.groupBox, pform.groupBox_2: g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1)) pform.frontWeb = AnkiWebView() pform.frontPrevBox.addWidget(pform.frontWeb) pform.backWeb = AnkiWebView() pform.backPrevBox.addWidget(pform.backWeb) def linkClicked(url): openLink(url) for wig in pform.frontWeb, pform.backWeb: wig.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks) c(wig, SIGNAL("linkClicked(QUrl)"), linkClicked) l.addWidget(right, 5) w.setLayout(l) self.forms.append({'tform': tform, 'pform': pform}) self.tabs.addTab(w, t['name'])
def __init__( self, parent, template_fmts, callback, ): AnkiWebView.__init__(self, parent=parent, title="minifier") self.set_bridge_command(self.bridge_cmd, parent) self.template_fmts = template_fmts self.callback = callback
def displaygrid(self, units, timeNow): autoModTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") currentTimeTemp = autoModTime.replace(":", "") currentTime = currentTimeTemp.replace(" ", "-") self.generate(units, timeNow, autoModTime) self.win = QDialog(mw) self.wv = AnkiWebView() self.wv.setHtml(self.html) self.wv.show() #self.savepng(currentTime) return 0
def _renderPreview(self) -> None: self.cancelPreviewTimer() c = self.rendered_card = self.note.ephemeral_card( self.ord, custom_note_type=self.model, custom_template=self.current_template(), fill_empty=self.fill_empty_action_toggled, ) ti = self.maybeTextInput bodyclass = theme_manager.body_classes_for_card_ord( c.ord, self.night_mode_is_enabled ) if not self.have_autoplayed: self.preview_web.eval("ankimedia._reset();") if not c.autoplay(): self.preview_web.eval("ankimedia.autoplay = false;") if self.pform.preview_front.isChecked(): q = ti(self.mw.prepare_card_text_for_display(c.question())) q = gui_hooks.card_will_show(q, c, "clayoutQuestion") text = q else: a = ti(self.mw.prepare_card_text_for_display(c.answer()), type="a") a = gui_hooks.card_will_show(a, c, "clayoutAnswer") text = a self.preview_web.eval("ankimedia.skip_front = true;") # use _showAnswer to avoid the longer delay self.preview_web.eval(f"_showAnswer({json.dumps(text)},'{bodyclass}');") self.preview_web.eval( f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});" ) if not self.have_autoplayed: self.have_autoplayed = True if c.autoplay(): AnkiWebView.setPlaybackRequiresGesture(False) if self.pform.preview_front.isChecked(): audio = c.question_av_tags() else: audio = c.answer_av_tags() av_player.play_tags(audio) else: AnkiWebView.setPlaybackRequiresGesture(True) av_player.clear_queue_and_maybe_interrupt() self.updateCardNames()
class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(531, 345) self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout_2.setObjectName("verticalLayout_2") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.fields = QtWidgets.QComboBox(Dialog) self.fields.setObjectName("fields") self.gridLayout.addWidget(self.fields, 1, 2, 1, 2) self.label_2 = QtWidgets.QLabel(Dialog) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 2, 1, 1, 1) self.label = QtWidgets.QLabel(Dialog) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 1, 1, 1, 1) self.search = QtWidgets.QLineEdit(Dialog) self.search.setObjectName("search") self.gridLayout.addWidget(self.search, 2, 2, 1, 2) self.verticalLayout_2.addLayout(self.gridLayout) self.frame = QtWidgets.QFrame(Dialog) self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame.setFrameShadow(QtWidgets.QFrame.Raised) self.frame.setObjectName("frame") self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.webView = AnkiWebView(self.frame) self.webView.setProperty("url", QtCore.QUrl("about:blank")) self.webView.setObjectName("webView") self.verticalLayout.addWidget(self.webView) self.verticalLayout_2.addWidget(self.frame) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close) self.buttonBox.setObjectName("buttonBox") self.verticalLayout_2.addWidget(self.buttonBox) self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.rejected.connect(Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog) Dialog.setTabOrder(self.fields, self.webView) Dialog.setTabOrder(self.webView, self.buttonBox) def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_("Find Duplicates")) self.label_2.setText(_("Optional filter:")) self.label.setText(_("Search in:"))
def focusInEvent(self, evt): window = False if evt.reason() in (Qt.ActiveWindowFocusReason, Qt.PopupFocusReason): # editor area got focus again; need to tell js not to adjust cursor self.eval("mouseDown++;") window = True AnkiWebView.focusInEvent(self, evt) if evt.reason() == Qt.TabFocusReason: self.eval("focusField(0);") elif evt.reason() == Qt.BacktabFocusReason: n = len(self.editor.note.fields) - 1 self.eval("focusField(%d);" % n) elif window: self.eval("mouseDown--;")
def miMessage(text, parent=False): title = "Migaku" if parent is False: parent = aqt.mw.app.activeWindow() or aqt.mw icon = QIcon(join(addon_path, 'icons', 'migaku.png')) mb = QMessageBox(parent) mb.setWindowIcon(icon) mb.setWindowTitle(title) cb = QCheckBox("Don't show me the welcome screen again.") wv = AnkiWebView() wv._page._bridge.onCmd = attemptOpenLink wv.setFixedSize(680, 450) wv.page().setHtml(text) wide = QWidget() wide.setFixedSize(18, 18) mb.layout().addWidget(wv, 0, 1) mb.layout().addWidget(wide, 0, 2) mb.layout().setColumnStretch(0, 3) mb.layout().addWidget(cb, 1, 1) b = mb.addButton(QMessageBox.Ok) b.setFixedSize(100, 30) b.setDefault(True) mb.exec_() wv.deleteLater() if cb.isChecked(): return True else: return False
def __init__(self): self.mw = mw self.models = self.mw.col.models.all() self.window = QDialog(mw) self.window.setWindowTitle('List of Words - Choose') self.window.layout = QVBoxLayout(self.window) self.webView = AnkiWebView() self.window.layout.addWidget(self.webView) report = self.getHeisingStats() self.webView.stdHtml(report) self.window.show() self.window.exec_()
def __init__(self, parent): AnkiWebView.__init__(self, title = "Extractor") self.parent = parent addon_id = ADDON_NAME self.set_bridge_command(self._on_bridge_cmd, self) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.stdHtml(f"""<html><body id="htmlcontent"></body></html>""", css = [f"../_addons/{addon_id}/web/style.css"], js = [f"../_addons/{addon_id}/web/content.js", f"../_addons/{addon_id}/web/tinymce/tinymce.min.js"]) self.eval(f"""var absolute_base_url = "http://127.0.0.1:{mw.mediaServer.getPort()}/";""")
def onhanziStats(): mw.progress.start(immediate=True) rep = genhanziStats() d = QDialog(mw) l = QVBoxLayout() w = AnkiWebView() l.addWidget(w) css = "font{word-wrap:break-word;} div{display:none;}" w.stdHtml(rep, css) d.setLayout(l) d.resize(500, 400) restoreGeom(d, "hanzistats") mw.progress.finish() d.exec_() saveGeom(d, "hanzistats")
def __init__(self,reader): GenericProfile.__init__(self,reader) self.dockKanji = QtGui.QDockWidget(reader) self.dockKanji.setObjectName(fromUtf8("dockKanji")) self.dockWidgetContents = QtGui.QWidget() self.dockWidgetContents.setObjectName(fromUtf8("dockWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.dockWidgetContents) self.verticalLayout.setObjectName(fromUtf8("verticalLayout")) self.textField = AnkiWebView() self.textField.setAcceptDrops(False) self.textField.setObjectName("textField") self.verticalLayout.addWidget(self.textField) self.horizontalLayout_3 = QtGui.QHBoxLayout() self.horizontalLayout_3.setObjectName(fromUtf8("horizontalLayout_3")) self.verticalLayout.addLayout(self.horizontalLayout_3) self.dockKanji.setWidget(self.dockWidgetContents) reader.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockKanji) self.dockKanji.visibilityChanged.connect(self.onVisibilityChanged) self.dockKanji.setWindowTitle(translate("MainWindowReader", "Kanji", None)) self.textField.setLinkHandler(self.onAnchorClicked) # menu entries to toggle visibility of the Kanji dock self.actionToggleKanji = QtGui.QAction(reader) self.actionToggleKanji.setCheckable(True) self.actionToggleKanji.setObjectName("actionToggleKanji") self.actionToggleKanji.setText("&Kanji") self.actionToggleKanji.setToolTip("Toggle Kanji") reader.menuView.insertAction(reader.menuView.actions()[2],self.actionToggleKanji) QtCore.QObject.connect(self.actionToggleKanji, QtCore.SIGNAL("toggled(bool)"), self.dockKanji.setVisible)
def __init__(self,reader): GenericProfile.__init__(self,reader) self.history = [] self.currentIndex = 0 self.dockVocab = QtGui.QDockWidget(reader) self.dockVocab.setObjectName(fromUtf8("dockVocab")) self.dockWidgetContents = QtGui.QWidget() self.dockWidgetContents.setObjectName(fromUtf8("dockWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.dockWidgetContents) self.verticalLayout.setObjectName(fromUtf8("verticalLayout")) self.previousExpression = None self.textField = AnkiWebView() self.textField.setAcceptDrops(False) self.textField.setObjectName("textField") self.keyFilter = VocabKeyFilter() self.keyFilter.obj = self self.keyFilter.textField = self.textField self.textField.installEventFilter(self.keyFilter) self.verticalLayout.addWidget(self.textField) self.dockVocab.setWidget(self.dockWidgetContents) reader.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockVocab) self.dockVocab.visibilityChanged.connect(self.onVisibilityChanged) self.dockVocab.setWindowTitle(translate("MainWindowReader", "Vocabulary", None)) self.textField.setLinkHandler(self.onAnchorClicked) # menu entries to toggle visibility of the vocabulary dock self.actionToggleVocab = QtGui.QAction(reader) self.actionToggleVocab.setCheckable(True) self.actionToggleVocab.setObjectName("actionToggleVocab") self.actionToggleVocab.setText("&Vocabulary") self.actionToggleVocab.setToolTip("Toggle vocabulary") reader.menuView.insertAction(reader.menuView.actions()[2],self.actionToggleVocab) QtCore.QObject.connect(self.actionToggleVocab, QtCore.SIGNAL("toggled(bool)"), self.dockVocab.setVisible) self.dockVocab.installEventFilter(self.reader.keyFilter)
def onRevlog(self): data = self._revlogData() d = QDialog(self) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(data) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 400) restoreGeom(d, "revlog") d.exec_() saveGeom(d, "revlog")
class CardStats(object): def __init__(self, mw): self.mw = mw self.shown = False addHook("showQuestion", self._update) addHook("deckClosing", self.hide) addHook("reviewCleanup", self.hide) def show(self): if not self.shown: self.web = AnkiWebView(self.mw) self.web.setMaximumWidth(400) self.shown = self.mw.addDockable(_("Card Info"), self.web) self.shown.connect(self.shown, SIGNAL("visibilityChanged(bool)"), self._visChange) self._update() def hide(self): if self.shown: self.mw.rmDockable(self.shown) self.shown = None def _visChange(self, vis): if not vis: # schedule removal for after evt has finished self.mw.progress.timer(100, self.hide, False) def _update(self): if not self.shown: return txt = "" r = self.mw.reviewer d = self.mw.deck if r.card: txt += _("<h1>Current</h1>") txt += d.cardStats(r.card) lc = r.lastCard() if lc: txt += _("<h1>Last</h1>") txt += d.cardStats(lc) if not txt: txt = _("No current card or last card.") self.web.setHtml(""" <html><head> <style>table { font-size: 12px; } h1 { font-size: 14px; }</style> </head><body><center>%s</center></body></html>"""%txt)
def onKanjiStats(): mw.progress.start(immediate=True) rep = genKanjiStats() d = QDialog(mw) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(rep) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.resize(500, 400) restoreGeom(d, "kanjistats") mw.progress.finish() d.exec_() saveGeom(d, "kanjistats")
def onhanziStats(): mw.progress.start(immediate=True) rep = genhanziStats() d = QDialog(mw) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) css = "font{word-wrap:break-word;} div{display:none;}" w.stdHtml(rep, css) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.resize(500, 400) restoreGeom(d, "hanzistats") mw.progress.finish() d.exec_() saveGeom(d, "hanzistats")
def showCardInfo(self): if not self.card: return info, cs = self._cardInfoData() reps = self._revlogData(cs) d = QDialog(self) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(info + "<p>" + reps) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 400) restoreGeom(d, "revlog") d.exec_() saveGeom(d, "revlog")
def __init__(self, reviewer, card, parent=None): QDialog.__init__(self, parent=None) self.card = card self.reviewer = reviewer self.mw = reviewer.mw self.col = self.mw.col info, cs = self._cardInfoData() reps = self._revlogData(cs) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(info + "<p>" + reps) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), self, SLOT("reject()")) self.setLayout(l) self.setWindowModality(Qt.WindowModal) self.resize(500, 400) restoreGeom(self, "CardStatShowDialog")
def showHTML(html, modality=Qt.WindowModal): m = QMainWindow(mw.app.activeWindow()) d = QDialog(m) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.setWindowModality(modality) d.resize(500, 400) restoreGeom(d, "htmlview") w.stdHtml(html) if modality == Qt.WindowModal : d.exec_() else : d.show() saveGeom(d, "htmlview")
def updateWindow(self, html): if html is None: return False # build view webview = AnkiWebView() webview.stdHtml(html, mw.sharedCSS) webview.setLinkHandler(self.links) # Clear old layout if self.__layout: QObjectCleanupHandler().add(self.__layout) # build layout self.__layout = QVBoxLayout() self.__layout.setMargin(0) self.__layout.addWidget(webview) # Update window self.setLayout(self.__layout) self.update()
def dropEvent(self, evt): oldmime = evt.mimeData() # coming from this program? if evt.source(): if oldmime.hasHtml(): mime = QMimeData() mime.setHtml(_filterHTML(oldmime.html())) else: # old qt on linux won't give us html when dragging an image; # in that case just do the default action (which is to ignore # the drag) return AnkiWebView.dropEvent(self, evt) else: mime = self._processMime(oldmime) # create a new event with the new mime data and run it new = QDropEvent(evt.pos(), evt.possibleActions(), mime, evt.mouseButtons(), evt.keyboardModifiers()) evt.accept() QWebView.dropEvent(self, new) # tell the drop target to take focus so the drop contents are saved self.eval("dropTarget.focus();")
def __init__(self, parent, editor): AnkiWebView.__init__(self, parent) self.editor = editor self.errtxt = _("An error occured while opening %s") self.strip = self.editor.mw.config["stripHTML"]
class KanjiProfile(GenericProfile): name = "kanji" descriptor = "KANJI IN THIS TEXT" displayedName = "Kanji" languages = ["japanese"] sortIndex = 2 allowedTags = ['character', 'onyomi', 'kunyomi', 'glossary','ongroup','words'] def __init__(self,reader): GenericProfile.__init__(self,reader) self.dockKanji = QtGui.QDockWidget(reader) self.dockKanji.setObjectName(fromUtf8("dockKanji")) self.dockWidgetContents = QtGui.QWidget() self.dockWidgetContents.setObjectName(fromUtf8("dockWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.dockWidgetContents) self.verticalLayout.setObjectName(fromUtf8("verticalLayout")) self.textField = AnkiWebView() self.textField.setAcceptDrops(False) self.textField.setObjectName("textField") self.verticalLayout.addWidget(self.textField) self.horizontalLayout_3 = QtGui.QHBoxLayout() self.horizontalLayout_3.setObjectName(fromUtf8("horizontalLayout_3")) self.verticalLayout.addLayout(self.horizontalLayout_3) self.dockKanji.setWidget(self.dockWidgetContents) reader.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockKanji) self.dockKanji.visibilityChanged.connect(self.onVisibilityChanged) self.dockKanji.setWindowTitle(translate("MainWindowReader", "Kanji", None)) self.textField.setLinkHandler(self.onAnchorClicked) # menu entries to toggle visibility of the Kanji dock self.actionToggleKanji = QtGui.QAction(reader) self.actionToggleKanji.setCheckable(True) self.actionToggleKanji.setObjectName("actionToggleKanji") self.actionToggleKanji.setText("&Kanji") self.actionToggleKanji.setToolTip("Toggle Kanji") reader.menuView.insertAction(reader.menuView.actions()[2],self.actionToggleKanji) QtCore.QObject.connect(self.actionToggleKanji, QtCore.SIGNAL("toggled(bool)"), self.dockKanji.setVisible) def onVisibilityChanged(self,visible): self.actionToggleKanji.setChecked(self.dockKanji.isVisible()) def onAnchorClicked(self, url): command, index = url.split(':') if command == "jisho": self.reader.profiles["vocabulary"].onQuery([index]) #url = QtCore.QUrl(self.reader.preferences["linkToKanji"].format(index)) #QtGui.QDesktopServices().openUrl(url) else: index = int(index) commands = command.split("_") profile = commands.pop(0) self.runCommand(commands,self.definitions[index]) def onLookup(self,d,lengthMatched): if self.dockKanji.isVisible(): if 'japanese' in self.reader.languages: if lengthMatched == 0: self.definitions = self.reader.languages['japanese'].findCharacters(d['contentSample'][0]) if len(self.definitions) > 0: lengthMatched = 1 else: self.definitions = self.reader.languages['japanese'].findCharacters(d['contentSample'][:lengthMatched]) self.updateDefinitions() self.reader.updateVocabDefs('kanji') return lengthMatched def onShowDialogPreferences(self,dialog): GenericProfile.onShowDialogPreferences(self,dialog) def runCommand(self,cmd,definition): if cmd[0] == "copy": QtGui.QApplication.clipboard().setText(u'{character}\t{kunyomi}\t{onyomi}\t{glossary}'.format(**definition)) elif cmd[0] =="add": self.addFact(definition) elif cmd[0] == "addgroup": kanjigroups = os.path.join(self.reader.anki.collection().media.dir(),"Yomichan","KanjiGroups") if os.path.exists(kanjigroups): filename = os.path.join(kanjigroups,definition['ongroup']+".txt") with open(filename,'w') as fp: content = u"""### REGEXP ### .*[{0}]###v### ### SHUFFLE THIS TEXT ###""".format(definition['ongroup']) fp.write(content.encode('utf-8')) fp.close() d = dict() d['contentSample'] = definition['ongroup'] self.onLookup(d,len(d['contentSample'])) self.reader.profiles["vocabulary"].onQuery(list(definition['ongroup'])) def markup(self, definition): allCards = self.reader.plugin.fetchAllCards() words = u",".join([x for x in allCards["vocabulary"].keys() if definition['character'] in x]) return { 'character': definition['character'], 'onyomi': definition['onyomi'], 'kunyomi': definition['kunyomi'], 'glossary': definition['glossary'], 'summary': definition['character'], 'ongroup': definition['ongroup'], 'words': words } def buildDefBody(self, definition, index, allowOverwrite): links = '<a href="kanji_copy:{0}"><img src="qrc:///img/img/icon_copy_definition.png" align="right"></a>'.format(index) if (self.ankiIsFactValid('kanji', definition, index)): links += '<a href="kanji_add:{0}"><img src="qrc:///img/img/icon_add_expression.png" align="right"></a>'.format(index) readings = ', '.join([definition['kunyomi'], definition['onyomi']]) if definition['ongroup'] is not None: ongroup = u"""<a style="text-decoration:none;" href="kanji_addgroup:{0}">{1}</a>""".format(index,definition['ongroup']) else: ongroup = '' html = u""" <span class="links">{0}</span> <span class="expression"><a href="jisho:{1}">{1}</a><br></span> <span class="reading">[{2}]<br></span> <span class="glossary">{3}<br></span> <span class="ongroup">{4}<br></span> <br clear="all">""".format(links, definition['character'], readings, definition['glossary'],ongroup) return html
class Browser(QMainWindow): def __init__(self, mw): QMainWindow.__init__(self, mw) applyStyles(self) self.mw = mw self.col = self.mw.col self.currentRow = None self.lastFilter = "" self.form = aqt.forms.browser.Ui_Dialog() self.form.setupUi(self) restoreGeom(self, "editor", 0) restoreState(self, "editor") restoreSplitter(self.form.splitter_2, "editor2") restoreSplitter(self.form.splitter, "editor3") self.form.splitter_2.setChildrenCollapsible(False) self.form.splitter.setChildrenCollapsible(False) self.card = None self.setupToolbar() self.setupColumns() self.setupTable() self.setupMenus() self.setupSearch() self.setupTree() self.setupHeaders() self.setupHooks() self.setupEditor() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) self.form.searchEdit.setFocus() self.show() self.form.searchEdit.setText("deck:current is:recent") self.form.searchEdit.selectAll() self.onSearch() def setupToolbar(self): self.toolbarWeb = AnkiWebView() self.toolbarWeb.setFixedHeight(32) self.toolbar = BrowserToolbar(self.mw, self.toolbarWeb, self) self.form.verticalLayout_3.insertWidget(0, self.toolbarWeb) self.toolbar.draw() def setupMenus(self): # actions c = self.connect; f = self.form; s = SIGNAL("triggered()") c(f.actionReposition, s, self.reposition) c(f.actionReschedule, s, self.reschedule) c(f.actionCram, s, self.cram) c(f.actionChangeModel, s, self.onChangeModel) # edit c(f.actionOptions, s, self.onOptions) c(f.actionUndo, s, self.mw.onUndo) c(f.actionInvertSelection, s, self.invertSelection) c(f.actionSelectNotes, s, self.selectNotes) c(f.actionFindReplace, s, self.onFindReplace) c(f.actionFindDuplicates, s, self.onFindDupes) # jumps c(f.actionPreviousCard, s, self.onPreviousCard) c(f.actionNextCard, s, self.onNextCard) c(f.actionFind, s, self.onFind) c(f.actionNote, s, self.onNote) c(f.actionTags, s, self.onTags) c(f.actionCardList, s, self.onCardList) # help c(f.actionGuide, s, self.onHelp) runHook('browser.setupMenus', self) def updateFont(self): self.form.tableView.setFont(QFont( self.mw.pm.profile['editFontFamily'], self.mw.pm.profile['editFontSize'])) self.form.tableView.verticalHeader().setDefaultSectionSize( self.mw.pm.profile['editLineSize']) def closeEvent(self, evt): saveSplitter(self.form.splitter_2, "editor2") saveSplitter(self.form.splitter, "editor3") self.editor.saveNow() self.editor.setNote(None) saveGeom(self, "editor") saveState(self, "editor") saveHeader(self.form.tableView.horizontalHeader(), "editor") self.col.conf['activeCols'] = self.model.activeCols self.hide() aqt.dialogs.close("Browser") self.teardownHooks() self.mw.maybeReset() evt.accept() def keyPressEvent(self, evt): "Show answer on RET or register answer." if evt.key() == Qt.Key_Escape: self.close() elif self.mw.app.focusWidget() == self.form.tree: if evt.key() in (Qt.Key_Return, Qt.Key_Enter): item = self.form.tree.currentItem() self.onTreeClick(item, 0) def setupColumns(self): self.columns = [ ('question', _("Question")), ('answer', _("Answer")), ('template', _("Card")), ('deck', _("Card Deck")), ('ndeck', _("Note Deck")), ('noteFld', _("Sort Field")), ('noteCrt', _("Created")), ('noteMod', _("Edited")), ('cardMod', _("Reviewed")), ('cardDue', _("Due")), ('cardIvl', _("Interval")), ('cardEase', _("Ease")), ('cardReps', _("Reviews")), ('cardLapses', _("Lapses")), ] # Searching ###################################################################### def setupSearch(self): self.filterTimer = None self.connect(self.form.searchButton, SIGNAL("clicked()"), self.onSearch) self.connect(self.form.searchEdit, SIGNAL("returnPressed()"), self.onSearch) self.setTabOrder(self.form.searchEdit, self.form.tableView) self.compModel = QStringListModel() self.compModel.setStringList(self.mw.pm.profile['searchHistory']) self.searchComp = QCompleter(self.compModel, self.form.searchEdit) self.searchComp.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.searchComp.setCaseSensitivity(Qt.CaseInsensitive) self.form.searchEdit.setCompleter(self.searchComp) def onSearch(self, reset=True): "Careful: if reset is true, the current note is saved." txt = unicode(self.form.searchEdit.text()).strip() sh = self.mw.pm.profile['searchHistory'] if txt not in sh: sh.insert(0, txt) sh = sh[:30] self.compModel.setStringList(sh) self.mw.pm.profile['searchHistory'] = sh self.model.search(txt, reset) if not self.model.cards: # no row change will fire self.onRowChanged(None, None) # somewhat distracting # txt = _("No matches found.") # if not self.mw.pm.profile['fullSearch']: # txt += "<p>" + _( # _("If your cards have formatting, you may want <br>" # "to enable 'search within formatting' in the<br>" # "browser options.")) # tooltip(txt) def updateTitle(self): selected = len(self.form.tableView.selectionModel().selectedRows()) cur = len(self.model.cards) self.setWindowTitle(ngettext("Browser (%(cur)d card shown; %(sel)s)", "Browser (%(cur)d cards shown; %(sel)s)", cur) % { "cur": cur, "sel": ngettext("%d selected", "%d selected", selected) % selected }) return selected def onReset(self): self.editor.setNote(None) self.onSearch() # Table view & editor ###################################################################### def setupTable(self): self.model = DataModel(self) self.form.tableView.setSortingEnabled(True) self.form.tableView.setModel(self.model) self.form.tableView.selectionModel() self.form.tableView.setItemDelegate(StatusDelegate(self, self.model)) self.connect(self.form.tableView.selectionModel(), SIGNAL("selectionChanged(QItemSelection,QItemSelection)"), self.onRowChanged) def setupEditor(self): self.editor = aqt.editor.Editor( self.mw, self.form.fieldsArea, self) self.editor.stealFocus = False def onRowChanged(self, current, previous): "Update current note and hide/show editor." show = self.model.cards and self.updateTitle() == 1 self.form.splitter.widget(1).setShown(not not show) if not show: self.editor.setNote(None) else: self.card = self.model.getCard( self.form.tableView.selectionModel().currentIndex()) self.editor.setNote(self.card.note()) self.editor.card = self.card self.toolbar.draw() def refreshCurrentCard(self, note): self.model.refreshNote(note) # Headers & sorting ###################################################################### def setupHeaders(self): vh = self.form.tableView.verticalHeader() hh = self.form.tableView.horizontalHeader() if not isWin: vh.hide() hh.show() restoreHeader(hh, "editor") hh.setHighlightSections(False) hh.setMinimumSectionSize(50) hh.setMovable(True) self.setColumnSizes() hh.setContextMenuPolicy(Qt.CustomContextMenu) hh.connect(hh, SIGNAL("customContextMenuRequested(QPoint)"), self.onHeaderContext) self.setSortIndicator() hh.connect(hh, SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), self.onSortChanged) def onSortChanged(self, idx, ord): type = self.model.activeCols[idx] noSort = ("question", "answer", "template", "deck", "ndeck") if type in noSort: showInfo(_("Sorting on this column is not supported. Please " "choose another.")) type = self.col.conf['sortType'] if self.col.conf['sortType'] != type: self.col.conf['sortType'] = type # default to descending for non-text fields if type == "noteFld": ord = not ord self.col.conf['sortBackwards'] = ord self.onSearch() else: if self.col.conf['sortBackwards'] != ord: self.col.conf['sortBackwards'] = ord self.model.reverse() self.setSortIndicator() def setSortIndicator(self): hh = self.form.tableView.horizontalHeader() type = self.col.conf['sortType'] if type not in self.model.activeCols: hh.setSortIndicatorShown(False) return idx = self.model.activeCols.index(type) if self.col.conf['sortBackwards']: ord = Qt.DescendingOrder else: ord = Qt.AscendingOrder hh.blockSignals(True) hh.setSortIndicator(idx, ord) hh.blockSignals(False) hh.setSortIndicatorShown(True) def onHeaderContext(self, pos): gpos = self.form.tableView.mapToGlobal(pos) m = QMenu() for type, name in self.columns: a = m.addAction(name) a.setCheckable(True) a.setChecked(type in self.model.activeCols) a.connect(a, SIGNAL("toggled(bool)"), lambda b, t=type: self.toggleField(t)) m.exec_(gpos) def toggleField(self, type): self.model.beginReset() if type in self.model.activeCols: self.model.activeCols.remove(type) else: self.model.activeCols.append(type) self.setColumnSizes() # sorted field may have been hidden self.setSortIndicator() self.model.endReset() def setColumnSizes(self): hh = self.form.tableView.horizontalHeader() for c, i in enumerate(self.model.activeCols): if c == len(self.model.activeCols) - 1: hh.setResizeMode(c, QHeaderView.Stretch) else: hh.setResizeMode(c, QHeaderView.Interactive) # Filter tree ###################################################################### class CallbackItem(QTreeWidgetItem): def __init__(self, name, onclick): QTreeWidgetItem.__init__(self, [name]) self.onclick = onclick def setupTree(self): self.connect( self.form.tree, SIGNAL("itemClicked(QTreeWidgetItem*,int)"), self.onTreeClick) p = QPalette() p.setColor(QPalette.Base, QColor("#d6dde0")) self.form.tree.setPalette(p) self.buildTree() def buildTree(self): self.form.tree.clear() root = self.form.tree.invisibleRootItem() self._systemTagTree(root) self._decksTree(root) self._userTagTree(root) self.form.tree.expandToDepth(0) self.form.tree.setIndentation(15) def onTreeClick(self, item, col): if getattr(item, 'onclick', None): item.onclick() def setFilter(self, *args): if len(args) == 1: txt = args[0] else: txt = "" items = [] for c, a in enumerate(args): if c % 2 == 0: txt += a + ":" else: txt += a if " " in txt: txt = "'%s'" % txt items.append(txt) txt = "" txt = " ".join(items) if self.mw.app.keyboardModifiers() & Qt.ControlModifier: cur = unicode(self.form.searchEdit.text()) if cur: txt = cur + " " + txt self.form.searchEdit.setText(txt) self.onSearch() def _systemTagTree(self, root): tags = ( (_("Whole Collection"), "anki", ""), (_("Current Deck"), "deck16", "deck:current"), (_("New"), "plus16.png", "is:new"), (_("Learning"), "stock_new_template_red.png", "is:learn"), (_("Review"), "clock16.png", "is:review"), (_("Marked"), "star16.png", "tag:marked"), (_("Suspended"), "media-playback-pause.png", "is:suspended"), (_("Leech"), "emblem-important.png", "tag:leech")) for name, icon, cmd in tags: item = self.CallbackItem( name, lambda c=cmd: self.setFilter(c)) item.setIcon(0, QIcon(":/icons/" + icon)) root.addChild(item) return root def _userTagTree(self, root): for t in sorted(self.col.tags.all()): item = self.CallbackItem( t, lambda t=t: self.setFilter("tag", t)) item.setIcon(0, QIcon(":/icons/anki-tag.png")) root.addChild(item) def _decksTree(self, root): grps = self.col.sched.deckDueTree() def fillGroups(root, grps, head=""): for g in grps: item = self.CallbackItem( g[0], lambda g=g: self.setFilter( "deck", head+g[0])) item.setIcon(0, QIcon(":/icons/deck16.png")) root.addChild(item) fillGroups(item, g[4], g[0]+"::") fillGroups(root, grps) # Info ###################################################################### def showCardInfo(self): if not self.card: return info, cs = self._cardInfoData() reps = self._revlogData(cs) d = QDialog(self) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(info + "<p>" + reps) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 400) restoreGeom(d, "revlog") d.exec_() saveGeom(d, "revlog") def _cardInfoData(self): from anki.stats import CardStats cs = CardStats(self.col, self.card) rep = cs.report() rep = "<style>table * { font-size: 12px; }</style>" + rep m = self.card.model() rep = """ <div style='width: 400px; margin: 0 auto 0; border: 1px solid #000; padding: 3px; '>%s</div>""" % rep return rep, cs def onCardLink(self, url): if url == "sort": self.onChangeSortField() else: self.onRevlog() def onChangeSortField(self): from aqt.utils import chooseList m = self.card.model() fields = [f['name'] for f in m['flds']] mm = self.col.models idx = chooseList(_("Choose field to sort this model by:"), fields, mm.sortIdx(m)) if idx != mm.sortIdx(m): self.mw.progress.start() mm.setSortIdx(m, idx) self.mw.progress.finish() self.onSearch() def onRevlog(self): data = self._revlogData() d = QDialog(self) l = QVBoxLayout() l.setMargin(0) w = AnkiWebView() l.addWidget(w) w.stdHtml(data) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 400) restoreGeom(d, "revlog") d.exec_() saveGeom(d, "revlog") def _revlogData(self, cs): entries = self.mw.col.db.all( "select id/1000.0, ease, ivl, factor, time/1000.0, type " "from revlog where cid = ?", self.card.id) if not entries: return "" s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date") s += ("<th align=right>%s</th>" * 5) % ( _("Type"), _("Ease"), _("Interval"), _("Factor"), _("Time")) cnt = 0 for (date, ease, ivl, factor, taken, type) in reversed(entries): cnt += 1 s += "<tr><td>%s</td>" % time.strftime(_("<b>%Y-%m-%d</b> @ %H:%M"), time.localtime(date)) tstr = [_("Learn"), _("Review"), _("Relearn"), _("Cram"), _("Resched")][type] import anki.stats as st fmt = "<span style='color:%s'>%s</span>" if type == 0: tstr = fmt % (st.colLearn, tstr) elif type == 1: tstr = fmt % (st.colMature, tstr) elif type == 2: tstr = fmt % (st.colRelearn, tstr) elif type == 3: tstr = fmt % (st.colCram, tstr) else: tstr = fmt % ("#000", tstr) if ease == 1: ease = fmt % (st.colRelearn, ease) if ivl == 0: ivl = _("0d") elif ivl > 0: ivl = fmtTimeSpan(ivl*86400, short=True) else: ivl = cs.time(-ivl) s += ("<td align=right>%s</td>" * 5) % ( tstr, ease, ivl, "%d%%" % (factor/10) if factor else "", cs.time(taken)) + "</tr>" s += "</table>" if cnt != self.card.reps: s += '<div style="font-size: 12px;">' + _("""\ Note: Some of the history is missing. For more information, \ please see the browser documentation.""") + "</div>" return s # Menu helpers ###################################################################### def selectedCards(self): return [self.model.cards[idx.row()] for idx in self.form.tableView.selectionModel().selectedRows()] def selectedNotes(self): return self.col.db.list(""" select distinct nid from cards where id in %s""" % ids2str( [self.model.cards[idx.row()] for idx in self.form.tableView.selectionModel().selectedRows()])) def selectedNotesAsCards(self): return self.col.db.list( "select id from cards where nid in (%s)" % ",".join([str(s) for s in self.selectedNotes()])) def oneModelNotes(self): sf = self.selectedNotes() if not sf: return mods = self.col.db.scalar(""" select count(distinct mid) from notes where id in %s""" % ids2str(sf)) if mods > 1: showInfo(_("Please select cards from only one model.")) return return sf def onHelp(self): openHelp("Browser") # Misc menu options ###################################################################### def onChangeModel(self): return showInfo("not yet implemented") # given implicit card generation now, we need to fix model changing: # need to generate any unmapped cards nids = self.oneModelNotes() if nids: ChangeModel(self, nids) def cram(self): return showInfo("not yet implemented") self.close() self.mw.onCram(self.selectedCards()) # Card deletion ###################################################################### def deleteNotes(self): self.mw.checkpoint(_("Delete Notes")) self.model.beginReset() oldRow = self.form.tableView.selectionModel().currentIndex().row() self.col.remNotes(self.selectedNotes()) self.onSearch(reset=False) if len(self.model.cards): new = min(oldRow, len(self.model.cards) - 1) self.model.focusedCard = self.model.cards[new] self.model.endReset() self.mw.requireReset() # Deck change ###################################################################### def setDeck(self, initial=False): d = QDialog(self) d.setWindowModality(Qt.WindowModal) frm = aqt.forms.setgroup.Ui_Dialog() frm.setupUi(d) from aqt.tagedit import TagEdit te = TagEdit(d, type=1) frm.groupBox.layout().insertWidget(0, te) te.setCol(self.col) d.connect(d, SIGNAL("accepted()"), lambda: self._onSetDeck(frm, te)) self.setTabOrder(frm.setCur, te) self.setTabOrder(te, frm.setInitial) if initial: frm.setInitial.setChecked(True) d.show() te.setFocus() def _onSetDeck(self, frm, te): self.model.beginReset() self.mw.checkpoint(_("Set Deck")) mod = intTime() if frm.setCur.isChecked(): did = self.col.decks.id(unicode(te.text())) self.col.db.execute( "update cards set mod=?, did=? where id in " + ids2str( self.selectedCards()), mod, did) if frm.setInitial.isChecked(): self.col.db.execute( "update notes set mod=?, did=? where id in " + ids2str( self.selectedNotes()), mod, did) else: self.col.db.execute(""" update cards set mod=?, did=(select did from notes where id = cards.nid) where id in %s""" % ids2str(self.selectedCards()), mod) self.onSearch(reset=False) self.mw.requireReset() self.model.endReset() # Tags ###################################################################### def addTags(self, tags=None, label=None, prompt=None, func=None): if prompt is None: prompt = _("Enter tags to add:") if tags is None: (tags, r) = getTag(self, self.col, prompt) else: r = True if not r: return if func is None: func = self.col.tags.bulkAdd if label is None: label = _("Add Tags") if label: self.mw.checkpoint(label) func(self.selectedNotes(), tags) self.model.reset() self.mw.requireReset() def deleteTags(self, tags=None, label=None): if label is None: label = _("Delete Tags") self.addTags(tags, label, _("Enter tags to delete:"), func=self.col.tags.bulkRem) # Suspending and marking ###################################################################### def isSuspended(self): return not not (self.card and self.card.queue == -1) def onSuspend(self, sus=None): if sus is None: sus = not self.isSuspended() # focus lost hook may not have chance to fire self.editor.saveNow() c = self.selectedCards() if sus: self.col.sched.suspendCards(c) else: self.col.sched.unsuspendCards(c) self.model.reset() self.mw.requireReset() def isMarked(self): return not not (self.card and self.card.note().hasTag("Marked")) def onMark(self, mark=None): if mark is None: mark = not self.isMarked() if mark: self.addTags(tags="marked", label=False) else: self.deleteTags(tags="marked", label=False) # Repositioning ###################################################################### def reposition(self): cids = self.selectedCards() cids = self.col.db.list( "select id from cards where type = 0 and id in " + ids2str(cids)) if not cids: return showInfo(_("Only new cards can be repositioned.")) d = QDialog(self) d.setWindowModality(Qt.WindowModal) frm = aqt.forms.reposition.Ui_Dialog() frm.setupUi(d) (pmin, pmax) = self.col.db.first( "select min(due), max(due) from cards where type=0") txt = _("Queue top: %d") % pmin txt += "\n" + _("Queue bottom: %d") % pmax frm.label.setText(txt) if not d.exec_(): return self.model.beginReset() self.mw.checkpoint(_("Reposition")) self.col.sched.sortCards( cids, start=frm.start.value(), step=frm.step.value(), shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked()) self.onSearch(reset=False) self.mw.requireReset() self.model.endReset() # Rescheduling ###################################################################### def reschedule(self): d = QDialog(self) d.setWindowModality(Qt.WindowModal) frm = aqt.forms.reschedule.Ui_Dialog() frm.setupUi(d) if not d.exec_(): return self.model.beginReset() self.mw.checkpoint(_("Reschedule")) if frm.asNew.isChecked(): self.col.sched.forgetCards(self.selectedCards()) else: self.col.sched.reschedCards( self.selectedCards(), frm.min.value(), frm.max.value()) self.onSearch(reset=False) self.mw.requireReset() self.model.endReset() # Edit: selection ###################################################################### def selectNotes(self): nids = self.selectedNotes() self.form.searchEdit.setText("nid:"+",".join([str(x) for x in nids])) # clear the selection so we don't waste energy preserving it tv = self.form.tableView tv.selectionModel().clear() self.onSearch() tv.selectAll() def invertSelection(self): sm = self.form.tableView.selectionModel() items = sm.selection() self.form.tableView.selectAll() sm.select(items, QItemSelectionModel.Deselect | QItemSelectionModel.Rows) # Edit: undo ###################################################################### def setupHooks(self): addHook("undoState", self.onUndoState) addHook("reset", self.onReset) addHook("editTimer", self.refreshCurrentCard) addHook("editFocusLost", self.refreshCurrentCard) def teardownHooks(self): remHook("reset", self.onReset) remHook("editTimer", self.refreshCurrentCard) remHook("editFocusLost", self.refreshCurrentCard) remHook("undoState", self.onUndoState) def onUndoState(self, on): self.form.actionUndo.setEnabled(on) if on: self.form.actionUndo.setText(self.mw.form.actionUndo.text()) # Options ###################################################################### def onOptions(self): d = QDialog(self) frm = aqt.forms.browseropts.Ui_Dialog() frm.setupUi(d) frm.fontCombo.setCurrentFont(QFont( self.mw.pm.profile['editFontFamily'])) frm.fontSize.setValue(self.mw.pm.profile['editFontSize']) frm.lineSize.setValue(self.mw.pm.profile['editLineSize']) frm.fullSearch.setChecked(self.mw.pm.profile['fullSearch']) if d.exec_(): self.mw.pm.profile['editFontFamily'] = ( unicode(frm.fontCombo.currentFont().family())) self.mw.pm.profile['editFontSize'] = ( int(frm.fontSize.value())) self.mw.pm.profile['editLineSize'] = ( int(frm.lineSize.value())) self.mw.pm.profile['fullSearch'] = frm.fullSearch.isChecked() self.updateFont() # Edit: replacing ###################################################################### def onFindReplace(self): sf = self.selectedNotes() if not sf: return import anki.find fields = sorted(anki.find.fieldNames(self.col, downcase=False)) d = QDialog(self) frm = aqt.forms.findreplace.Ui_Dialog() frm.setupUi(d) d.setWindowModality(Qt.WindowModal) frm.field.addItems([_("All Fields")] + fields) self.connect(frm.buttonBox, SIGNAL("helpRequested()"), self.onFindReplaceHelp) if not d.exec_(): return if frm.field.currentIndex() == 0: field = None else: field = fields[frm.field.currentIndex()-1] self.mw.checkpoint(_("Find and Replace")) self.mw.progress.start() self.model.beginReset() try: changed = self.col.findReplace(sf, unicode(frm.find.text()), unicode(frm.replace.text()), frm.re.isChecked(), field, frm.ignoreCase.isChecked()) except sre_constants.error: ui.utils.showInfo(_("Invalid regular expression."), parent=self) return else: self.onSearch() self.mw.requireReset() finally: self.model.endReset() self.mw.progress.finish() showInfo(ngettext( "%(a)d of %(b)d note updated", "%(a)d of %(b)d notes updated", len(sf)) % { 'a': changed, 'b': len(sf), }) def onFindReplaceHelp(self): openHelp("Browser#FindReplace") # Edit: finding dupes ###################################################################### def onFindDupes(self): return showInfo("not yet implemented") win = QDialog(self) aqt = ankiqt.forms.finddupes.Ui_Dialog() dialog.setupUi(win) restoreGeom(win, "findDupes") fields = sorted(self.card.note.model.fieldModels, key=attrgetter("name")) # per-model data data = self.col.db.all(""" select fm.id, m.name || '>' || fm.name from fieldmodels fm, models m where fm.modelId = m.id""") data.sort(key=itemgetter(1)) # all-model data data2 = self.col.db.all(""" select fm.id, fm.name from fieldmodels fm""") byName = {} for d in data2: if d[1] in byName: byName[d[1]].append(d[0]) else: byName[d[1]] = [d[0]] names = byName.keys() names.sort() alldata = [(byName[n], n) for n in names] + data dialog.searchArea.addItems([d[1] for d in alldata]) # links dialog.webView.page().setLinkDelegationPolicy( QWebPage.DelegateAllLinks) self.connect(dialog.webView, SIGNAL("linkClicked(QUrl)"), self.dupeLinkClicked) def onFin(code): saveGeom(win, "findDupes") self.connect(win, SIGNAL("finished(int)"), onFin) def onClick(): idx = dialog.searchArea.currentIndex() data = alldata[idx] if isinstance(data[0], list): # all models fmids = data[0] else: # single model fmids = [data[0]] self.duplicatesReport(dialog.webView, fmids) self.connect(dialog.searchButton, SIGNAL("clicked()"), onClick) win.show() def duplicatesReport(self, web, fmids): self.col.startProgress(2) self.col.updateProgress(_("Finding...")) res = self.col.findDuplicates(fmids) t = "<html><body>" t += _("Duplicate Groups: %d") % len(res) t += "<p><ol>" for group in res: t += '<li><a href="%s">%s</a>' % ( "nid:" + ",".join(str(id) for id in group[1]), group[0]) t += "</ol>" t += "</body></html>" web.setHtml(t) self.col.finishProgress() def dupeLinkClicked(self, link): self.form.searchEdit.setText(link.toString()) self.onSearch() self.onNote() # Jumping ###################################################################### def _moveCur(self, dir): if not self.model.cards: return self.editor.saveNow() tv = self.form.tableView idx = tv.moveCursor(dir, Qt.NoModifier) tv.selectionModel().clear() tv.setCurrentIndex(idx) def onPreviousCard(self): self._moveCur(QAbstractItemView.MoveUp) self.editor.web.setFocus() def onNextCard(self): self._moveCur(QAbstractItemView.MoveDown) self.editor.web.setFocus() def onFind(self): self.form.searchEdit.setFocus() self.form.searchEdit.selectAll() def onNote(self): self.editor.focus() def onTags(self): self.form.tree.setFocus() def onCardList(self): self.form.tableView.setFocus()
def setupToolbar(self): self.toolbarWeb = AnkiWebView() self.toolbarWeb.setFixedHeight(32) self.toolbar = BrowserToolbar(self.mw, self.toolbarWeb, self) self.form.verticalLayout_3.insertWidget(0, self.toolbarWeb) self.toolbar.draw()
def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.errtxt = _("An error occured while opening %s") self.strip = self.editor.mw.pm.profile['stripHTML']
def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.strip = self.editor.mw.pm.profile['stripHTML']
def __init__(self, parent, editor): AnkiWebView.__init__(self, canFocus=True) self.editor = editor self.strip = self.editor.mw.pm.profile["stripHTML"]
class VocabularyProfile(GenericProfile): name = "vocabulary" displayedName = "Vocabulary" descriptor = "VOCABULARY IN THIS TEXT (EXPORT)" languages = ["japanese","chinese","korean"] sortIndex = 1 allowedTags = ['expression', 'term', 'source', 'kanji', 'hanja', 'reading', 'glossary', 'sentence','line','filename','summary','traditional','language','goo','defs','refs'] def __init__(self,reader): GenericProfile.__init__(self,reader) self.history = [] self.currentIndex = 0 self.dockVocab = QtGui.QDockWidget(reader) self.dockVocab.setObjectName(fromUtf8("dockVocab")) self.dockWidgetContents = QtGui.QWidget() self.dockWidgetContents.setObjectName(fromUtf8("dockWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.dockWidgetContents) self.verticalLayout.setObjectName(fromUtf8("verticalLayout")) self.previousExpression = None self.textField = AnkiWebView() self.textField.setAcceptDrops(False) self.textField.setObjectName("textField") self.keyFilter = VocabKeyFilter() self.keyFilter.obj = self self.keyFilter.textField = self.textField self.textField.installEventFilter(self.keyFilter) self.verticalLayout.addWidget(self.textField) self.dockVocab.setWidget(self.dockWidgetContents) reader.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockVocab) self.dockVocab.visibilityChanged.connect(self.onVisibilityChanged) self.dockVocab.setWindowTitle(translate("MainWindowReader", "Vocabulary", None)) self.textField.setLinkHandler(self.onAnchorClicked) # menu entries to toggle visibility of the vocabulary dock self.actionToggleVocab = QtGui.QAction(reader) self.actionToggleVocab.setCheckable(True) self.actionToggleVocab.setObjectName("actionToggleVocab") self.actionToggleVocab.setText("&Vocabulary") self.actionToggleVocab.setToolTip("Toggle vocabulary") reader.menuView.insertAction(reader.menuView.actions()[2],self.actionToggleVocab) QtCore.QObject.connect(self.actionToggleVocab, QtCore.SIGNAL("toggled(bool)"), self.dockVocab.setVisible) self.dockVocab.installEventFilter(self.reader.keyFilter) def updateSampleFromSelection(self): d = { "samplePosStart": 0, "contentSampleFlat": self.textField.selectedText(), "content": "" } self.onLookup(d,0,sentenceAndLine=False) def fixHtml(self,html,appendToHistory=True): if html.find(self.buildEmpty()) == -1 and appendToHistory: self.history.append((html,list(self.definitions),self.defBody)) back = len(self.history)>1 #self.currentIndex > 0 #forward = self.currentIndex < len(self.history)-1 if back: backHtml = "<a href='vocabulary_back:0'><<Back</a>" if back else "" forwardHtml = "" #"<a href='vocabulary_forward:0'>Forward>></a>" if forward else "" return u"<div>{1} {2}</div><br>{0}".format(html,backHtml,forwardHtml) else: return html def onVisibilityChanged(self,visible): self.actionToggleVocab.setChecked(self.dockVocab.isVisible()) def onAnchorClicked(self, url): command, index = url.split(':') if command == "jisho": url = QtCore.QUrl(self.reader.preferences["linkToVocab"].format(index)) QtGui.QDesktopServices().openUrl(url) elif command == "vocabulary_back": if len(self.history)>1: self.history.pop() html, definitions, body = self.history[-1] html = self.fixHtml(html,appendToHistory=False) self.textField.setHtml(html) self.definitions = definitions self.defBody = body elif command == "vocabulary_forward": self.textField.history().forward() else: if not index.startswith("void"): index = int(index) commands = command.split("_") profile = commands.pop(0) self.runCommand(commands,index) def onLookup(self,d,lengthMatched,sentenceAndLine=True): if self.dockVocab.isVisible(): lengthMatched = self.reader.findTerm(d) if sentenceAndLine: sentence, sentenceStart = reader_util.findSentence(d['content'], d['samplePosStart']) line, lineStart = reader_util.findLine(d['content'], d['samplePosStart']) else: sentence = line = "" for definition in self.definitions: definition['sentence'] = sentence definition['line'] = line definition['filename'] = self.reader.state.filename self.previousExpression = None self.reader.updateVocabDefs('vocabulary') return lengthMatched def onQuery(self,query): if self.dockVocab.isVisible(): lengthMatched = self.reader.findTerm(query,wildcards=True) for definition in self.definitions: definition['sentence'] = "" definition['line'] = "" definition['filename'] = self.reader.state.filename self.previousExpression = None self.reader.updateVocabDefs('vocabulary') return lengthMatched def onShowDialogPreferences(self,dialog): dialog.checkHideTranslation = QtGui.QCheckBox(dialog.tabAnki) dialog.checkHideTranslation.setObjectName(fromUtf8("checkHideTranslation")) dialog.verticalLayout_2.addWidget(dialog.checkHideTranslation) dialog.checkHideTranslation.setText(translate("DialogPreferences", "Hide translation, when an online dictionary entry is present", None)) GenericProfile.onShowDialogPreferences(self,dialog) def runCommand(self,cmds,index): if index >= len(self.definitions): return definition = self.definitions[index] if cmds[0] == "copy": if definition['reading']: result = u'{expression}\t{reading}\t{glossary}\n'.format(**definition) else: result = u'{expression}\t{glossary}\n'.format(**definition) if definition.get("defs"): text = self.reader.textContent.toPlainText() + "\n" self.reader.textContent.setPlainText(text + definition.get("defs").replace(u"<br>",u"\n")) QtGui.QApplication.clipboard().setText(result) elif cmds[0] == "goo": prefix = "http://dictionary.goo.ne.jp" self.reader.link = prefix + "/srch/jn/" + definition['expression'] + "/m1u/" page = urllib2.urlopen( urllib2.Request(url=prefix + "/srch/jn/" + definition['expression'] + "/m1u/", headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'})).read() soup = BeautifulSoup.BeautifulSoup(page) if not soup.find("div","contents-wrap-b"): lis = soup.find("div",id="NR-main").find("div","contents-wrap-a-in search").find("ul","list-search-a").findAll("li") for li in lis: hiragana = li.find("dt","search-ttl-a").contents[0].replace(u"\u2010",u"").replace(u"\u30fb",u"") idx = hiragana.find(u"\u3010") if idx>-1: hiragana = hiragana[:idx] self.reader.hiragana = [hiragana,definition['reading']] self.reader.html = soup.contents[0] if hiragana == definition['reading']: a = li.find("a") link = prefix + dict(a.attrs)["href"] page2 = urllib2.urlopen( urllib2.Request(url=link, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'})).read() soup = BeautifulSoup.BeautifulSoup(page2) if soup.find("div","contents-wrap-b"): definition['goo'] = '\n'.join(map(unicode,soup.findAll("ol","list-data-b"))) self.reader.preferences['onlineDicts']['goo'][definition['expression']+"["+(definition['reading'] or "")+"]"] = definition['goo'] self.updateDefinitions() else: if len(cmds)>1 and cmds[1] == "reading": definition = definition.copy() definition['summary'] = definition['reading'] definition['expression'] = definition['reading'] definition['reading'] = unicode() if cmds[0] == "add": self.addFact(definition) elif cmds[0] == "overwrite": self.overwriteFact(definition) def markup(self, definition): if definition.get('reading'): summary = u'{expression}[{reading}]'.format(**definition) else: summary = u'{expression}'.format(**definition) return { 'defs': definition.get("defs") or unicode(), 'refs': definition.get("refs") or unicode(), 'expression': definition['expression'], 'hanja': definition.get('hanja') or unicode(), 'reading': definition.get('reading') or unicode(), 'glossary': definition.get('glossary') or unicode(), 'gender': definition.get('gender') or unicode(), 'language': definition.get('language') or unicode(), 'sentence': definition.get('sentence') or unicode(), 'traditional': definition.get('traditional') or unicode(), 'line': definition.get('line') or unicode(), 'filename': definition.get('filename') or unicode(), 'goo': definition.get('goo') or unicode(), 'term': definition.get('term') or unicode(), 'source': definition.get('source') or unicode(), 'summary': summary } def buildDefBody(self, definition, index, allowOverwrite): reading = unicode() if(definition.get('language') == 'Japanese' and (definition['expression']+"["+(definition['reading'] or "")+"]") in self.reader.preferences['onlineDicts']['goo']): definition['goo'] = self.reader.preferences['onlineDicts']['goo'][definition['expression']+"["+(definition['reading'] or "")+"]"] if definition.get('reading'): reading = u'<span class="reading">[{0}]<br>'.format(definition['reading']) if definition.get('tags') == u'traditional': reading += u' (trad.)' reading += '</span>' rules = unicode() if definition.get('rules'): rules = ' < '.join(definition['rules']) rules = '<span class="rules">({0})<br></span>'.format(rules) gender = unicode() if definition.get('gender'): gender = '<span class="gender">{0}<br></span>'.format(definition['gender']) links = '<a href="vocabulary_copy:{0}"><img src="qrc:///img/img/icon_copy_definition.png" align="right"></a>'.format(index) markupExp = self.markup(definition) defReading = definition.copy() if defReading.get('reading'): defReading['expression'] = defReading['reading'] del defReading['reading'] markupReading = self.markup(defReading) if self.ankiIsFactValid('vocabulary', markupExp, index): links += u'<a href="vocabulary_add:{0}"><img src="qrc:///img/img/icon_add_expression.png" align="right"></a>'.format(index) else: if allowOverwrite: links += u'<a href="vocabulary_overwrite:{0}"><img src="qrc:///img/img/icon_overwrite_expression.png" align="right"></a>'.format(index) if markupReading is not None and definition.get('language') == 'Japanese': if self.ankiIsFactValid('vocabulary', markupReading, index): links += u'<a href="vocabulary_add_reading:{0}"><img src="qrc:///img/img/icon_add_reading.png" align="right"></a>'.format(index) elif markupExp is not None and markupReading['summary'] != markupExp['summary']: if allowOverwrite: links += u'<a href="vocabulary_overwrite_reading:{0}"><img src="qrc:///img/img/icon_overwrite_reading.png" align="right"></a>'.format(index) def glossary(hide): if hide: return u"""<a onclick='document.getElementById("glossary{1}").style.display="block";this.style.display="none"' href="javascript:void(0);">[Show English]<br></a><span class="glossary" id="glossary{1}" style="display:none;">{0}<br></span>""".format(definition['glossary'],index) else: return u'<span class="glossary" id="glossary">{0}<br></span>'.format(definition['glossary']) foundOnlineDictEntry = False if markupExp["defs"] != "": dictionaryEntries = u"<span class='online'>"+ markupExp["defs"] + " " + markupExp["refs"] + "</span>" foundOnlineDictEntry = True else: dictionaryEntries = "" if(definition.get("goo")): dictionaryEntries += u"<br><span class='online'>" + definition["goo"] + "</span><br>" foundOnlineDictEntry = True elif(definition.get('language') == 'Japanese'): dictionaryEntries += u'<br><a href="vocabulary_goo:{0}">[Goo]</a><br>'.format(index) if(definition.get('language') == 'Japanese'): expression = u'<span class="expression"><a href="jisho:{0}">{0}</a></span>'.format(definition["expression"]) reading = reading + '<br>' elif(definition.get('language') == 'German'): if self.previousExpression == definition['expression']: expression = '' else: expression = u'<span class="german">{0}</span><br>'.format(definition['expression'] + ' ' + gender) self.previousExpression = definition['expression'] else: expression = u'<span class="expression">{0}</span>'.format(definition['expression']) reading = reading + '<br>' html = u""" <span class="links">{0}</span> {1} {2} {3} {4} {5} <br clear="all">""".format(links, expression, reading, glossary(foundOnlineDictEntry and self.reader.preferences['hideTranslation']), rules,dictionaryEntries) if (definition.get('language') != 'German'): html = u"<hr>" + html return html
def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.errtxt = _("An error occured while opening %s") self.strip = True