def onTargetDeck(self): from aqt.tagedit import TagEdit t = self.card.template() d = QDialog(self) d.setWindowTitle("Anki") d.setMinimumWidth(400) l = QVBoxLayout() lab = QLabel(_("""\ Enter deck to place new %s cards in, or leave blank:""") % self.card.template()['name']) lab.setWordWrap(True) l.addWidget(lab) te = TagEdit(d, type=1) te.setCol(self.col) l.addWidget(te) if t['did']: te.setText(self.col.decks.get(t['did'])['name']) te.selectAll() bb = QDialogButtonBox(QDialogButtonBox.Close) bb.rejected.connect(d.close) l.addWidget(bb) d.setLayout(l) d.exec_() if not te.text().strip(): t['did'] = None else: t['did'] = self.col.decks.id(te.text())
def downloadMedia(url, editor): # Local file : just read the file content if url.startswith("file://"): try: url = url[7:] # On windows, paths tend to be prefixed by file:/// # rather than file://, so we remove redundant slash. if re.match(r'^/[A-Za-z]:\\', url): url = url[1:] return open(url, 'rb').read() except OSError: pass app = editor.mw.app # Show download dialog d = QDialog(editor.parentWindow) d.setWindowTitle("Downloading media (0.0%)") d.setWindowModality(Qt.WindowModal) vbox = QVBoxLayout() label = QLabel(url) label.setWordWrap(True) vbox.addWidget(label) d.setLayout(vbox) d.show() # Download chunk by chunk for progress bar try: response = urllib2.urlopen(url) totSize = int(response.info().getheader('Content-Length').strip()) currentRead = 0 chunk_size = 16384 chunks = [] while True: chunk = response.read(chunk_size) currentRead += len(chunk) if not chunk: break d.setWindowTitle("Downloading media (%.1f%%)" % (currentRead * 100.0 / totSize)) app.processEvents() chunks.append(chunk) return ''.join(chunks) except urllib2.URLError: return None finally: d.close() del d
def hyperlink_dialog(self): dialog = QDialog(self.parent_window) dialog.setWindowTitle("Create a hyperlink") dialog.resize(DIALOG_SIZE_X, DIALOG_SIZE_Y) ok_button_anchor = QPushButton("&OK", dialog) ok_button_anchor.setEnabled(False) ok_button_anchor.clicked.connect( lambda: self.insert_anchor(url_edit.text(), urltext_edit.text())) ok_button_anchor.clicked.connect(dialog.hide) ok_button_anchor.setAutoDefault(True) cancel_button_anchor = QPushButton("&Cancel", dialog) cancel_button_anchor.clicked.connect(dialog.hide) cancel_button_anchor.setAutoDefault(True) url_label = QLabel("Link to:") url_edit = QLineEdit() url_edit.setPlaceholderText("URL") url_edit.textChanged.connect(lambda: self.enable_ok_button( ok_button_anchor, url_edit.text(), urltext_edit.text())) urltext_label = QLabel("Text to display:") urltext_edit = QLineEdit() urltext_edit.setPlaceholderText("Text") urltext_edit.textChanged.connect(lambda: self.enable_ok_button( ok_button_anchor, url_edit.text(), urltext_edit.text())) button_box = QHBoxLayout() button_box.addStretch(1) button_box.addWidget(cancel_button_anchor) button_box.addWidget(ok_button_anchor) dialog_vbox = QVBoxLayout() dialog_vbox.addWidget(url_label) dialog_vbox.addWidget(url_edit) dialog_vbox.addWidget(urltext_label) dialog_vbox.addWidget(urltext_edit) dialog_vbox.addLayout(button_box) dialog.setLayout(dialog_vbox) # if user already selected text, put it in urltext_edit if self.selected_text: if self.selected_is_url: url_edit.setText(self.selected_text) urltext_edit.setFocus() else: urltext_edit.setText(self.selected_text) url_edit.setFocus() dialog.exec()
def getDefinitionChoiceDialog(aw, entries): d = QDialog(aw) grid = QGridLayout() # adds found definitions to dialog window for x in range(len(entries)): button = QPushButton(entries[x].word) button.clicked.connect(partial(buttonPressed, entries[x],d)) label = QLabel() label.setText(entries[x].shortDef) label.setWordWrap(True) grid.addWidget(button,x,0) grid.addWidget(label,x,1,1,5) d.setLayout(grid) return d
def onCheckMediaDB(self): self.progress.start(immediate=True) (nohave, unused, warnings) = self.col.media.check() self.progress.finish() # generate report report = "" if warnings: report += "\n".join(warnings) + "\n" if unused: if report: report += "\n\n\n" report += _( "In media folder but not used by any cards:") report += "\n" + "\n".join(unused) if nohave: if report: report += "\n\n\n" report += _( "Used on cards but missing from media folder:") report += "\n" + "\n".join(nohave) if not report: tooltip(_("No unused or missing files found.")) return # show report and offer to delete diag = QDialog(self) diag.setWindowTitle("Anki") layout = QVBoxLayout(diag) diag.setLayout(layout) text = QTextEdit() text.setReadOnly(True) text.setPlainText(report) layout.addWidget(text) box = QDialogButtonBox(QDialogButtonBox.Close) layout.addWidget(box) if unused: b = QPushButton(_("Delete Unused Files")) b.setAutoDefault(False) box.addButton(b, QDialogButtonBox.ActionRole) b.clicked.connect( lambda c, u=unused, d=diag: self.deleteUnused(u, d)) box.rejected.connect(diag.reject) diag.setMinimumHeight(400) diag.setMinimumWidth(500) restoreGeom(diag, "checkmediadb") diag.exec_() saveGeom(diag, "checkmediadb")
def chooseList(prompt, choices, startrow=0, parent=None): if not parent: parent = aqt.mw.app.activeWindow() d = QDialog(parent) d.setWindowModality(Qt.WindowModal) l = QVBoxLayout() d.setLayout(l) t = QLabel(prompt) l.addWidget(t) c = QListWidget() c.addItems(choices) c.setCurrentRow(startrow) l.addWidget(c) bb = QDialogButtonBox(QDialogButtonBox.Ok) bb.accepted.connect(d.accept) l.addWidget(bb) d.exec_() return c.currentRow()
def get_id_and_pass_from_user(mw: aqt.main.AnkiQt, username="", password="") -> Tuple[str, str]: diag = QDialog(mw) diag.setWindowTitle("Anki") diag.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # type: ignore diag.setWindowModality(Qt.WindowModal) vbox = QVBoxLayout() info_label = QLabel( without_unicode_isolation( tr(TR.SYNC_ACCOUNT_REQUIRED, link="https://ankiweb.net/account/register"))) info_label.setOpenExternalLinks(True) info_label.setWordWrap(True) vbox.addWidget(info_label) vbox.addSpacing(20) g = QGridLayout() l1 = QLabel(tr(TR.SYNC_ANKIWEB_ID_LABEL)) g.addWidget(l1, 0, 0) user = QLineEdit() user.setText(username) g.addWidget(user, 0, 1) l2 = QLabel(tr(TR.SYNC_PASSWORD_LABEL)) g.addWidget(l2, 1, 0) passwd = QLineEdit() passwd.setText(password) passwd.setEchoMode(QLineEdit.Password) g.addWidget(passwd, 1, 1) vbox.addLayout(g) bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # type: ignore bb.button(QDialogButtonBox.Ok).setAutoDefault(True) qconnect(bb.accepted, diag.accept) qconnect(bb.rejected, diag.reject) vbox.addWidget(bb) diag.setLayout(vbox) diag.show() accepted = diag.exec_() if not accepted: return ("", "") return (user.text().strip(), passwd.text())
def get_id_and_pass_from_user(mw: aqt.main.AnkiQt, username: str = "", password: str = "") -> tuple[str, str]: diag = QDialog(mw) diag.setWindowTitle("Anki") disable_help_button(diag) diag.setWindowModality(Qt.WindowModality.WindowModal) vbox = QVBoxLayout() info_label = QLabel( without_unicode_isolation( tr.sync_account_required( link="https://ankiweb.net/account/register"))) info_label.setOpenExternalLinks(True) info_label.setWordWrap(True) vbox.addWidget(info_label) vbox.addSpacing(20) g = QGridLayout() l1 = QLabel(tr.sync_ankiweb_id_label()) g.addWidget(l1, 0, 0) user = QLineEdit() user.setText(username) g.addWidget(user, 0, 1) l2 = QLabel(tr.sync_password_label()) g.addWidget(l2, 1, 0) passwd = QLineEdit() passwd.setText(password) passwd.setEchoMode(QLineEdit.EchoMode.Password) g.addWidget(passwd, 1, 1) vbox.addLayout(g) bb = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore bb.button(QDialogButtonBox.StandardButton.Ok).setAutoDefault(True) qconnect(bb.accepted, diag.accept) qconnect(bb.rejected, diag.reject) vbox.addWidget(bb) diag.setLayout(vbox) diag.show() accepted = diag.exec() if not accepted: return ("", "") return (user.text().strip(), passwd.text())
def showText(txt, parent=None, type="text", run=True, geomKey=None, \ minWidth=500, minHeight=400, title="Anki", copyBtn=False): if not parent: parent = aqt.mw.app.activeWindow() or aqt.mw diag = QDialog(parent) diag.setWindowTitle(title) layout = QVBoxLayout(diag) diag.setLayout(layout) text = QTextBrowser() text.setOpenExternalLinks(True) if type == "text": text.setPlainText(txt) else: text.setHtml(txt) layout.addWidget(text) box = QDialogButtonBox(QDialogButtonBox.Close) layout.addWidget(box) if copyBtn: def onCopy(): QApplication.clipboard().setText(text.toPlainText()) btn = QPushButton(_("Copy to Clipboard")) btn.clicked.connect(onCopy) box.addButton(btn, QDialogButtonBox.ActionRole) def onReject(): if geomKey: saveGeom(diag, geomKey) QDialog.reject(diag) box.rejected.connect(onReject) def onFinish(): if geomKey: saveGeom(diag, geomKey) box.accepted.connect(onFinish) diag.setMinimumHeight(minHeight) diag.setMinimumWidth(minWidth) if geomKey: restoreGeom(diag, geomKey) if run: diag.exec_() else: return diag, box
def _getUserPass(self): d = QDialog(self.mw) d.setWindowTitle("Anki") d.setWindowModality(Qt.WindowModal) vbox = QVBoxLayout() l = QLabel( _("""\ <h1>Account Required</h1> A free account is required to keep your collection synchronized. Please \ <a href="%s">sign up</a> for an account, then \ enter your details below.""") % "https://ankiweb.net/account/login") l.setOpenExternalLinks(True) l.setWordWrap(True) vbox.addWidget(l) vbox.addSpacing(20) g = QGridLayout() l1 = QLabel(_("AnkiWeb ID:")) g.addWidget(l1, 0, 0) user = QLineEdit() g.addWidget(user, 0, 1) l2 = QLabel(_("Password:")) g.addWidget(l2, 1, 0) passwd = QLineEdit() passwd.setEchoMode(QLineEdit.Password) g.addWidget(passwd, 1, 1) vbox.addLayout(g) bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.button(QDialogButtonBox.Ok).setAutoDefault(True) bb.accepted.connect(d.accept) bb.rejected.connect(d.reject) vbox.addWidget(bb) d.setLayout(vbox) d.show() accepted = d.exec_() u = user.text() p = passwd.text() if not accepted or not u or not p: return return (u, p)
def setup(self): addonconfig = mw.addonManager.getConfig(__name__) config = types.SimpleNamespace(**addonconfig['defaults']) if addonconfig.get("_debug_time", False): self.timepoint = lambda c: print("%s: %0.3f" % (c, time.time() - self.time)) else: self.timepoint = lambda _: None config.did = mw.col.conf['curDeck'] swin = QDialog(mw) vl = QVBoxLayout() fl = QHBoxLayout() deckcb = QComboBox() deckcb.addItems(sorted(mw.col.decks.allNames())) deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) fl.addWidget(QLabel("Deck: ")) deckcb.setCurrentText(mw.col.decks.get(config.did)['name']) def change_did(deckname): config.did = mw.col.decks.byName(deckname)['id'] deckcb.currentTextChanged.connect(change_did) fl.addWidget(deckcb) vl.addLayout(fl) frm = QGroupBox("Settings") vl.addWidget(frm) il = QVBoxLayout() fl = QHBoxLayout() field = QLineEdit() field.setPlaceholderText( "e.g. \"kanji\", \"hanzi\" or \"sentence-kanji\" (default: \"%s\")" % config.pattern) il.addWidget( QLabel("Pattern or Field names to search for (case insensitive):")) fl.addWidget(field) liter = QCheckBox("Match exactly") liter.setChecked(config.literal) fl.addWidget(liter) il.addLayout(fl) stint = QSpinBox() stint.setRange(1, 65536) stint.setValue(config.interval) il.addWidget(QLabel("Card interval considered strong:")) il.addWidget(stint) ttcol = QSpinBox() ttcol.setRange(1, 99) ttcol.setValue(config.thin) il.addWidget(QLabel("Number of Columns in the in-app table:")) il.addWidget(ttcol) wtcol = QSpinBox() wtcol.setRange(1, 99) wtcol.setValue(config.wide) il.addWidget(QLabel("Number of Columns in the exported table:")) il.addWidget(wtcol) groupby = QComboBox() groupby.addItems([ *("None, sorted by " + x.pretty_value() for x in SortOrder), *(x.name for x in data.groups), ]) groupby.setCurrentIndex(config.groupby) il.addWidget(QLabel("Group by:")) il.addWidget(groupby) shnew = QCheckBox("Show units not yet seen") shnew.setChecked(config.unseen) il.addWidget(shnew) toolt = QCheckBox("Show informational tooltips") toolt.setChecked(config.tooltips) il.addWidget(toolt) frm.setLayout(il) hl = QHBoxLayout() vl.addLayout(hl) gen = QPushButton("Generate", clicked=swin.accept) hl.addWidget(gen) cls = QPushButton("Close", clicked=swin.reject) hl.addWidget(cls) swin.setLayout(vl) swin.setTabOrder(gen, cls) swin.setTabOrder(cls, field) swin.setTabOrder(field, liter) swin.setTabOrder(liter, stint) swin.setTabOrder(stint, ttcol) swin.setTabOrder(ttcol, wtcol) swin.setTabOrder(wtcol, groupby) swin.setTabOrder(groupby, shnew) swin.setTabOrder(shnew, toolt) swin.resize(500, 400) if swin.exec_(): mw.progress.start(immediate=True) if len(field.text().strip()) != 0: config.pattern = field.text().lower() config.pattern = config.pattern.split() config.literal = liter.isChecked() config.interval = stint.value() config.thin = ttcol.value() config.wide = wtcol.value() config.groupby = groupby.currentIndex() config.unseen = shnew.isChecked() config.tooltips = toolt.isChecked() self.makegrid(config) mw.progress.finish() self.win.show()
class KanjiGrid: def __init__(self, mw): if mw: self.menuAction = QAction("Generate Kanji/Hanzi Grid", mw, triggered=self.setup) mw.form.menuTools.addSeparator() mw.form.menuTools.addAction(self.menuAction) def generate(self, config, units, timeNow, saveMode=False): def kanjitile(char, index, count=0, avg_interval=0, missing=False): tile = "" score = "NaN" if avg_interval: score = round(scoreAdjust(avg_interval / config.interval), 2) if missing: colour = "#888" else: colour = "#000" if count != 0: bgcolour = hsvrgbstr( scoreAdjust(avg_interval / config.interval) / 2) elif missing: bgcolour = "#EEE" else: bgcolour = "#FFF" if config.tooltips: tooltip = "Character: %s" % unicodedata.name(char) if count: tooltip += " | Count: %s | " % count tooltip += "Avg Interval: %s | Score: %s | " % (round( avg_interval, 2), score) tooltip += "Background: %s | Index: %s" % (bgcolour, index) tile += "\t<td style=\"background:%s;\" title=\"%s\">" % ( bgcolour, tooltip) else: tile += "\t<td style=\"background:%s;\">" % (bgcolour) tile += "<a href=\"http://jisho.org/search/%s%%20%%23kanji\" style=\"color:%s;\">%s</a></td>\n" % ( char, colour, char) return tile deckname = mw.col.decks.name(config.did).rsplit('::', 1)[-1] if saveMode: cols = config.wide else: cols = config.thin self.html = "<!doctype html><html><head><meta charset=\"UTF-8\" /><title>Anki Kanji Grid</title>" self.html += "<style type=\"text/css\">body{background-color:#FFF;}table{margin-left:auto;margin-right:auto;}.maintable{width:85%;}td{text-align:center;vertical-align:top;}.key{display:inline-block;width:3em}a,a:visited{color:#000;text-decoration:none;}</style>" self.html += "</head>\n" self.html += "<body>\n" self.html += "<span style=\"font-size: 3em;color: #888;\">%s</span><br>\n" % deckname self.html += "<div style=\"margin-bottom: 24pt;padding: 20pt;\"><p style=\"float: left\">Key:</p>" self.html += "<p style=\"float: right\">Weak " # keycolors = (hsvrgbstr(n/6.0) for n in range(6+1)) for c in [n / 6.0 for n in range(6 + 1)]: self.html += "<span class=\"key\" style=\"background-color: %s;\"> </span>" % hsvrgbstr( c / 2) self.html += " Strong</p></div>\n" self.html += "<div style=\"clear: both;\"><br><hr style=\"border-style: dashed;border-color: #666;width: 60%;\"><br></div>\n" self.html += "<div style=\"text-align: center;\">\n" if config.groupby >= len(SortOrder): groups = data.groups[config.groupby - len(SortOrder)] gc = 0 kanji = [u.value for u in units.values()] for i in range(1, len(groups.data)): self.html += "<h2 style=\"color:#888;\">%s</h2>\n" % groups.data[ i][0] table = "<table class=\"maintable\"><tr>\n" count = -1 for unit in [ units[c] for c in groups.data[i][1] if c in kanji ]: if unit.count != 0 or config.unseen: count += 1 if count % cols == 0 and count != 0: table += "</tr>\n<tr>\n" table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</tr></table>\n" n = count + 1 t = len(groups.data[i][1]) gc += n if config.unseen: table += "<details><summary>Missing</summary><table style=\"max-width:75%;\"><tr>\n" count = -1 for char in [ c for c in groups.data[i][1] if c not in kanji ]: count += 1 if count % cols == 0 and count != 0: table += "</tr>\n<tr>\n" table += kanjitile(char, count, missing=True) if count == -1: table += "<td><b style=\"color:#CCC\">None</b></td>" table += "</tr></table></details>\n" self.html += "<h4 style=\"color:#888;\">%d of %d - %0.2f%%</h4>\n" % ( n, t, n * 100.0 / t) self.html += table chars = reduce(lambda x, y: x + y, dict(groups.data).values()) self.html += "<h2 style=\"color:#888;\">%s</h2>" % groups.data[0][0] table = "<table class=\"maintable\"><tr>\n" count = -1 for unit in [u for u in units.values() if u.value not in chars]: if unit.count != 0 or config.unseen: count += 1 if count % cols == 0 and count != 0: table += "</tr>\n<tr>\n" table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</tr></table>\n" n = count + 1 self.html += "<h4 style=\"color:#888;\">%d of %d - %0.2f%%</h4>\n" % ( n, gc, n * 100.0 / gc) self.html += table self.html += "<style type=\"text/css\">.datasource{font-style:italic;font-size:0.75em;margin-top:1em;overflow-wrap:break-word;}.datasource a{color:#1034A6;}</style><span class=\"datasource\">Data source: " + ' '.join( "<a href=\"{}\">{}</a>".format(w, urllib.parse.unquote(w)) if re.match("https?://", w) else w for w in groups.source.split(' ')) + "</span>" else: table = "<table class=\"maintable\"><tr>\n" unitsList = { SortOrder.NONE: sorted(units.values(), key=lambda unit: (unit.idx, unit.count)), SortOrder.UNICODE: sorted(units.values(), key=lambda unit: (unicodedata.name(unit.value), unit.count)), SortOrder.SCORE: sorted(units.values(), key=lambda unit: (scoreAdjust(unit.avg_interval / config .interval), unit.count), reverse=True), SortOrder.FREQUENCY: sorted(units.values(), key=lambda unit: (unit.count, scoreAdjust(unit.avg_interval / config.interval)), reverse=True), }[SortOrder(config.groupby)] count = -1 for unit in unitsList: if unit.count != 0 or config.unseen: count += 1 if count % cols == 0 and count != 0: table += "</tr>\n<tr>\n" table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</tr></table>\n" self.html += "<h4 style=\"color:#888;\">%d total unique characters</h4>\n" % ( count + 1) self.html += table self.html += "</div></body></html>\n" def displaygrid(self, config, units, timeNow): self.generate(config, units, timeNow) self.timepoint("HTML generated") self.win = QDialog(mw) self.wv = KanjiGridWebView() vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.wv) self.wv.stdHtml(self.html) hl = QHBoxLayout() vl.addLayout(hl) sh = QPushButton("Save HTML", clicked=lambda: self.savehtml(config)) hl.addWidget(sh) sp = QPushButton("Save Image", clicked=self.savepng) hl.addWidget(sp) bb = QPushButton("Close", clicked=self.win.reject) hl.addWidget(bb) self.win.setLayout(vl) self.win.resize(500, 400) self.timepoint("Window complete") return 0 def savehtml(self, config): fileName = QFileDialog.getSaveFileName( self.win, "Save Page", QStandardPaths.standardLocations( QStandardPaths.DesktopLocation)[0], "Web Page (*.html *.htm)")[0] if fileName != "": mw.progress.start(immediate=True) if ".htm" not in fileName: fileName += ".html" with open(fileName, 'w', encoding='utf-8') as fileOut: (units, timeNow) = self.kanjigrid(config) self.generate(config, units, timeNow, True) fileOut.write(self.html) mw.progress.finish() showInfo("Page saved to %s!" % os.path.abspath(fileOut.name)) def savepng(self): fileName = QFileDialog.getSaveFileName( self.win, "Save Page", QStandardPaths.standardLocations( QStandardPaths.DesktopLocation)[0], "Portable Network Graphics (*.png)")[0] if fileName != "": mw.progress.start(immediate=True) if ".png" not in fileName: fileName += ".png" oldsize = self.wv.size() self.wv.resize(self.wv.page().contentsSize().toSize()) # the file will be saved after the page gets redrawn (KanjiGridWebView.eventFilter) self.wv.save_png = (fileName, oldsize) def kanjigrid(self, config): dids = [config.did] for _, id_ in mw.col.decks.children(config.did): dids.append(id_) self.timepoint("Decks selected") cids = mw.col.db.list( "select id from cards where did in %s or odid in %s" % (ids2str(dids), ids2str(dids))) self.timepoint("Cards selected") units = dict() notes = dict() timeNow = time.time() for i in cids: card = mw.col.getCard(i) if card.nid not in notes.keys(): keys = card.note().keys() unitKey = set() matches = operator.eq if config.literal else operator.contains for keyword in config.pattern: for key in keys: if matches(key.lower(), keyword): unitKey.update(set(card.note()[key])) break notes[card.nid] = unitKey else: unitKey = notes[card.nid] if unitKey is not None: for ch in unitKey: addUnitData(units, ch, i, card, config.kanjionly, timeNow) self.timepoint("Units created") return units, timeNow def makegrid(self, config): self.time = time.time() self.timepoint("Start") (units, timeNow) = self.kanjigrid(config) if units is not None: self.displaygrid(config, units, timeNow) def setup(self): addonconfig = mw.addonManager.getConfig(__name__) config = types.SimpleNamespace(**addonconfig['defaults']) if addonconfig.get("_debug_time", False): self.timepoint = lambda c: print("%s: %0.3f" % (c, time.time() - self.time)) else: self.timepoint = lambda _: None config.did = mw.col.conf['curDeck'] swin = QDialog(mw) vl = QVBoxLayout() fl = QHBoxLayout() deckcb = QComboBox() deckcb.addItems(sorted(mw.col.decks.allNames())) deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) fl.addWidget(QLabel("Deck: ")) deckcb.setCurrentText(mw.col.decks.get(config.did)['name']) def change_did(deckname): config.did = mw.col.decks.byName(deckname)['id'] deckcb.currentTextChanged.connect(change_did) fl.addWidget(deckcb) vl.addLayout(fl) frm = QGroupBox("Settings") vl.addWidget(frm) il = QVBoxLayout() fl = QHBoxLayout() field = QLineEdit() field.setPlaceholderText( "e.g. \"kanji\", \"hanzi\" or \"sentence-kanji\" (default: \"%s\")" % config.pattern) il.addWidget( QLabel("Pattern or Field names to search for (case insensitive):")) fl.addWidget(field) liter = QCheckBox("Match exactly") liter.setChecked(config.literal) fl.addWidget(liter) il.addLayout(fl) stint = QSpinBox() stint.setRange(1, 65536) stint.setValue(config.interval) il.addWidget(QLabel("Card interval considered strong:")) il.addWidget(stint) ttcol = QSpinBox() ttcol.setRange(1, 99) ttcol.setValue(config.thin) il.addWidget(QLabel("Number of Columns in the in-app table:")) il.addWidget(ttcol) wtcol = QSpinBox() wtcol.setRange(1, 99) wtcol.setValue(config.wide) il.addWidget(QLabel("Number of Columns in the exported table:")) il.addWidget(wtcol) groupby = QComboBox() groupby.addItems([ *("None, sorted by " + x.pretty_value() for x in SortOrder), *(x.name for x in data.groups), ]) groupby.setCurrentIndex(config.groupby) il.addWidget(QLabel("Group by:")) il.addWidget(groupby) shnew = QCheckBox("Show units not yet seen") shnew.setChecked(config.unseen) il.addWidget(shnew) toolt = QCheckBox("Show informational tooltips") toolt.setChecked(config.tooltips) il.addWidget(toolt) frm.setLayout(il) hl = QHBoxLayout() vl.addLayout(hl) gen = QPushButton("Generate", clicked=swin.accept) hl.addWidget(gen) cls = QPushButton("Close", clicked=swin.reject) hl.addWidget(cls) swin.setLayout(vl) swin.setTabOrder(gen, cls) swin.setTabOrder(cls, field) swin.setTabOrder(field, liter) swin.setTabOrder(liter, stint) swin.setTabOrder(stint, ttcol) swin.setTabOrder(ttcol, wtcol) swin.setTabOrder(wtcol, groupby) swin.setTabOrder(groupby, shnew) swin.setTabOrder(shnew, toolt) swin.resize(500, 400) if swin.exec_(): mw.progress.start(immediate=True) if len(field.text().strip()) != 0: config.pattern = field.text().lower() config.pattern = config.pattern.split() config.literal = liter.isChecked() config.interval = stint.value() config.thin = ttcol.value() config.wide = wtcol.value() config.groupby = groupby.currentIndex() config.unseen = shnew.isChecked() config.tooltips = toolt.isChecked() self.makegrid(config) mw.progress.finish() self.win.show()
def setup(self): addonconfig = mw.addonManager.getConfig(__name__) config = types.SimpleNamespace(**addonconfig['defaults']) if addonconfig.get("_debug_time", False): self.timepoint = lambda c: print("%s: %0.3f" % (c, time.time()-self.time)) else: self.timepoint = lambda _: None config.did = mw.col.conf['curDeck'] swin = QDialog(mw) vl = QVBoxLayout() fl = QHBoxLayout() deckcb = QComboBox() deckcb.addItems(sorted(mw.col.decks.allNames())) deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) fl.addWidget(QLabel("Deck: ")) deckcb.setCurrentText(mw.col.decks.get(config.did)['name']) def change_did(deckname): config.did = mw.col.decks.byName(deckname)['id'] deckcb.currentTextChanged.connect(change_did) fl.addWidget(deckcb) vl.addLayout(fl) frm = QGroupBox("Settings") vl.addWidget(frm) il = QVBoxLayout() fl = QHBoxLayout() field = QLineEdit() field.setPlaceholderText("e.g. \"kanji\" or \"sentence-kanji\" (default: \"%s\")" % config.pattern) il.addWidget(QLabel("Pattern or Field names to search for (case insensitive):")) fl.addWidget(field) liter = QCheckBox("Match exactly") liter.setChecked(config.literal) fl.addWidget(liter) il.addLayout(fl) stint = QSpinBox() stint.setRange(1, 65536) stint.setValue(config.interval) il.addWidget(QLabel("Card interval considered strong:")) il.addWidget(stint) ttcol = QSpinBox() ttcol.setRange(1, 99) ttcol.setValue(config.thin) il.addWidget(QLabel("Number of Columns:")) coll = QHBoxLayout() coll.addWidget(QLabel("In-app:")) coll.addWidget(ttcol) wtcol = QSpinBox() wtcol.setRange(1, 99) wtcol.setValue(config.wide) coll.addWidget(QLabel("Exported:")) coll.addWidget(wtcol) itcol = QCheckBox("Don't care") itcol.setChecked(addonconfig['defaults'].get("autothinwide", False)) def disableEnableColumnSettings(state): ttcol.setEnabled(state != Qt.Checked) wtcol.setEnabled(state != Qt.Checked) itcol.stateChanged.connect(disableEnableColumnSettings) disableEnableColumnSettings(itcol.checkState()) coll.addWidget(itcol) il.addLayout(coll) groupby = QComboBox() groupby.addItems([ *("None, sorted by " + x.pretty_value() for x in SortOrder), *(x.name for x in data.groups), ]) groupby.setCurrentIndex(config.groupby) il.addWidget(QLabel("Group by:")) il.addWidget(groupby) shnew = QCheckBox("Show units not yet seen") shnew.setChecked(config.unseen) il.addWidget(shnew) toolt = QCheckBox("Show informational tooltips") toolt.setChecked(config.tooltips) il.addWidget(toolt) frm.setLayout(il) hl = QHBoxLayout() vl.addLayout(hl) gen = QPushButton("Generate", clicked=swin.accept) hl.addWidget(gen) cls = QPushButton("Close", clicked=swin.reject) hl.addWidget(cls) swin.setLayout(vl) swin.setTabOrder(gen, cls) swin.setTabOrder(cls, field) swin.setTabOrder(field, liter) swin.setTabOrder(liter, stint) swin.setTabOrder(stint, ttcol) swin.setTabOrder(ttcol, wtcol) swin.setTabOrder(wtcol, groupby) swin.setTabOrder(groupby, shnew) swin.setTabOrder(shnew, toolt) swin.resize(500, 400) if swin.exec_(): mw.progress.start(immediate=True) if len(field.text().strip()) != 0: config.pattern = field.text().lower() config.pattern = config.pattern.split() config.literal = liter.isChecked() config.interval = stint.value() config.thin = ttcol.value() config.wide = wtcol.value() config.groupby = groupby.currentIndex() config.unseen = shnew.isChecked() config.tooltips = toolt.isChecked() config.autothinwide = itcol.isChecked() self.makegrid(config) mw.progress.finish() self.win.show()
class KanjiGrid: def __init__(self, mw): if mw: self.menuAction = QAction("Generate Kanji Grid", mw, triggered=self.setup) mw.form.menuTools.addSeparator() mw.form.menuTools.addAction(self.menuAction) def generate(self, config, units, saveMode=False): def kanjitile(char, index, count=0, avg_interval=0, missing=False): tile = "" score = "NaN" if avg_interval: score = round(scoreAdjust(avg_interval / config.interval), 2) if missing: colour = "#888" else: colour = "#000" if count != 0: bgcolour = hsvrgbstr(scoreAdjust(avg_interval / config.interval)/2) elif missing: bgcolour = "#EEE" else: bgcolour = "#FFF" if config.tooltips: tooltip = "Character: %s" % unicodedata.name(char) if count: tooltip += " | Count: %s | " % count tooltip += "Avg Interval: %s | Score: %s | " % (round(avg_interval, 2), score) tooltip += "Background: %s | Index: %s" % (bgcolour, index) tile += "\t<div style=\"background:%s;\" title=\"%s\">" % (bgcolour, tooltip) else: tile += "\t<div style=\"background:%s;\">" % (bgcolour) tile += "<a href=\"http://jisho.org/search/%s%%20%%23kanji\" style=\"color:%s;\">%s</a></div>\n" % (char, colour, char) return tile deckname = mw.col.decks.name(config.did).rsplit('::', 1)[-1] if saveMode: cols = config.wide else: cols = config.thin self.html = "<!doctype html><html><head><meta charset=\"UTF-8\" /><title>Anki Kanji Grid</title>" self.html += "<style type=\"text/css\">body{background-color:#FFF;}.maintable{width:85%%;}.maintable,.missingtable{margin-left:auto;margin-right:auto;display:grid;grid-template-columns:repeat(%s, 1fr);text-align:left;}.maintable > *,.missingtable > *{text-align:center;vertical-align:top;margin:1px;line-height:1.5em;}.key{display:inline-block;width:3em}a,a:visited{color:#000;text-decoration:none;}</style>" % cols if config.autothinwide: self.html += "<style type=\"text/css\">.maintable,.missingtable{display:block;font-size:0px}.maintable > *,.missingtable > *{display:inline-block;font-size:initial;width:1.5em;}</style>" self.html += "</head>\n" self.html += "<body>\n" self.html += "<span style=\"font-size: 3em;color: #888;\">Kanji Grid - %s</span><br>\n" % deckname self.html += "<div style=\"margin-bottom: 24pt;padding: 20pt;\"><p style=\"float: left\">Key:</p>" self.html += "<p style=\"float: right\">Weak " # keycolors = (hsvrgbstr(n/6.0) for n in range(6+1)) for c in [n/6.0 for n in range(6+1)]: self.html += "<span class=\"key\" style=\"background-color: %s;\"> </span>" % hsvrgbstr(c/2) self.html += " Strong</p></div>\n" self.html += "<div style=\"clear: both;\"><br><hr style=\"border-style: dashed;border-color: #666;width: 60%;\"><br></div>\n" self.html += "<div style=\"text-align: center;\">\n" if config.groupby >= len(SortOrder): groups = data.groups[config.groupby - len(SortOrder)] gc = 0 kanji = [u.value for u in units.values()] for i in range(1, len(groups.data)): self.html += "<h2 style=\"color:#888;\">%s Kanji</h2>\n" % groups.data[i][0] table = "<div class=\"maintable\">\n" count = -1 for unit in [units[c] for c in groups.data[i][1] if c in kanji]: if unit.count != 0 or config.unseen: count += 1 table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</div>\n" n = count+1 t = len(groups.data[i][1]) gc += n if config.unseen: table += "<details><summary>Missing kanji</summary><div class=\"missingtable\" style=\"max-width:75%;\">\n" count = -1 for char in [c for c in groups.data[i][1] if c not in kanji]: count += 1 table += kanjitile(char, count, missing=True) if count == -1: table += "<b style=\"color:#CCC\">None</b>" table += "</div></details>\n" self.html += "<h4 style=\"color:#888;\">%d of %d - %0.2f%%</h4>\n" % (n, t, n*100.0/t) self.html += table chars = reduce(lambda x, y: x+y, dict(groups.data).values()) self.html += "<h2 style=\"color:#888;\">%s Kanji</h2>" % groups.data[0][0] table = "<div class=\"maintable\">\n" count = -1 for unit in [u for u in units.values() if u.value not in chars]: if unit.count != 0 or config.unseen: count += 1 table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</div>\n" n = count+1 self.html += "<h4 style=\"color:#888;\">%d of %d - %0.2f%%</h4>\n" % (n, gc, n*100.0/gc) self.html += table self.html += "<style type=\"text/css\">.datasource{font-style:italic;font-size:0.75em;margin-top:1em;overflow-wrap:break-word;}.datasource a{color:#1034A6;}</style><span class=\"datasource\">Data source: " + ' '.join("<a href=\"{}\">{}</a>".format(w, urllib.parse.unquote(w)) if re.match("https?://", w) else w for w in groups.source.split(' ')) + "</span>" else: table = "<div class=\"maintable\">\n" unitsList = { SortOrder.NONE: sorted(units.values(), key=lambda unit: (unit.idx, unit.count)), SortOrder.UNICODE: sorted(units.values(), key=lambda unit: (unicodedata.name(unit.value), unit.count)), SortOrder.SCORE: sorted(units.values(), key=lambda unit: (scoreAdjust(unit.avg_interval / config.interval), unit.count), reverse=True), SortOrder.FREQUENCY: sorted(units.values(), key=lambda unit: (unit.count, scoreAdjust(unit.avg_interval / config.interval)), reverse=True), }[SortOrder(config.groupby)] count = -1 for unit in unitsList: if unit.count != 0 or config.unseen: count += 1 table += kanjitile(unit.value, count, unit.count, unit.avg_interval) table += "</div>\n" self.html += "<h4 style=\"color:#888;\">%d total unique kanji</h4>\n" % (count+1) self.html += table self.html += "</div></body></html>\n" self.timepoint("HTML generated") def displaygrid(self, config, units): self.generate(config, units) self.win = QDialog(mw) self.wv = KanjiGridWebView() vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.wv) self.wv.stdHtml(self.html) hl = QHBoxLayout() vl.addLayout(hl) sh = QPushButton("Save HTML", clicked=lambda: self.savehtml(config)) hl.addWidget(sh) sp = QPushButton("Save Image", clicked=self.savepng) hl.addWidget(sp) bb = QPushButton("Close", clicked=self.win.reject) hl.addWidget(bb) self.win.setLayout(vl) self.win.resize(500, 400) self.timepoint("Window complete") return 0 def savehtml(self, config): fileName = QFileDialog.getSaveFileName(self.win, "Save Page", QStandardPaths.standardLocations(QStandardPaths.DesktopLocation)[0], "Web Page (*.html *.htm)")[0] if fileName != "": mw.progress.start(immediate=True) if ".htm" not in fileName: fileName += ".html" with open(fileName, 'w', encoding='utf-8') as fileOut: self.time = time.time() self.timepoint("HTML start") units = self.kanjigrid(config) self.generate(config, units, True) fileOut.write(self.html) mw.progress.finish() showInfo("Page saved to %s!" % os.path.abspath(fileOut.name)) def savepng(self): fileName = QFileDialog.getSaveFileName(self.win, "Save Page", QStandardPaths.standardLocations(QStandardPaths.DesktopLocation)[0], "Portable Network Graphics (*.png)")[0] if fileName != "": mw.progress.start(immediate=True) if ".png" not in fileName: fileName += ".png" oldsize = self.wv.size() self.wv.resize(self.wv.page().contentsSize().toSize()) # the file will be saved after the page gets redrawn (KanjiGridWebView.eventFilter) self.wv.save_png = (fileName, oldsize) def kanjigrid(self, config): dids = [config.did] for _, id_ in mw.col.decks.children(config.did): dids.append(id_) self.timepoint("Decks selected") cids = mw.col.db.list("select id from cards where did in %s or odid in %s" % (ids2str(dids), ids2str(dids))) self.timepoint("Cards selected") units = dict() notes = dict() for i in cids: card = mw.col.getCard(i) if card.nid not in notes.keys(): keys = card.note().keys() unitKey = set() matches = operator.eq if config.literal else operator.contains for keyword in config.pattern: for key in keys: if matches(key.lower(), keyword): unitKey.update(set(card.note()[key])) break notes[card.nid] = unitKey else: unitKey = notes[card.nid] if unitKey is not None: for ch in unitKey: addUnitData(units, ch, i, card, config.kanjionly) self.timepoint("Units created") return units def makegrid(self, config): self.time = time.time() self.timepoint("Start") units = self.kanjigrid(config) if units is not None: self.displaygrid(config, units) def setup(self): addonconfig = mw.addonManager.getConfig(__name__) config = types.SimpleNamespace(**addonconfig['defaults']) if addonconfig.get("_debug_time", False): self.timepoint = lambda c: print("%s: %0.3f" % (c, time.time()-self.time)) else: self.timepoint = lambda _: None config.did = mw.col.conf['curDeck'] swin = QDialog(mw) vl = QVBoxLayout() fl = QHBoxLayout() deckcb = QComboBox() deckcb.addItems(sorted(mw.col.decks.allNames())) deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) fl.addWidget(QLabel("Deck: ")) deckcb.setCurrentText(mw.col.decks.get(config.did)['name']) def change_did(deckname): config.did = mw.col.decks.byName(deckname)['id'] deckcb.currentTextChanged.connect(change_did) fl.addWidget(deckcb) vl.addLayout(fl) frm = QGroupBox("Settings") vl.addWidget(frm) il = QVBoxLayout() fl = QHBoxLayout() field = QLineEdit() field.setPlaceholderText("e.g. \"kanji\" or \"sentence-kanji\" (default: \"%s\")" % config.pattern) il.addWidget(QLabel("Pattern or Field names to search for (case insensitive):")) fl.addWidget(field) liter = QCheckBox("Match exactly") liter.setChecked(config.literal) fl.addWidget(liter) il.addLayout(fl) stint = QSpinBox() stint.setRange(1, 65536) stint.setValue(config.interval) il.addWidget(QLabel("Card interval considered strong:")) il.addWidget(stint) ttcol = QSpinBox() ttcol.setRange(1, 99) ttcol.setValue(config.thin) il.addWidget(QLabel("Number of Columns:")) coll = QHBoxLayout() coll.addWidget(QLabel("In-app:")) coll.addWidget(ttcol) wtcol = QSpinBox() wtcol.setRange(1, 99) wtcol.setValue(config.wide) coll.addWidget(QLabel("Exported:")) coll.addWidget(wtcol) itcol = QCheckBox("Don't care") itcol.setChecked(addonconfig['defaults'].get("autothinwide", False)) def disableEnableColumnSettings(state): ttcol.setEnabled(state != Qt.Checked) wtcol.setEnabled(state != Qt.Checked) itcol.stateChanged.connect(disableEnableColumnSettings) disableEnableColumnSettings(itcol.checkState()) coll.addWidget(itcol) il.addLayout(coll) groupby = QComboBox() groupby.addItems([ *("None, sorted by " + x.pretty_value() for x in SortOrder), *(x.name for x in data.groups), ]) groupby.setCurrentIndex(config.groupby) il.addWidget(QLabel("Group by:")) il.addWidget(groupby) shnew = QCheckBox("Show units not yet seen") shnew.setChecked(config.unseen) il.addWidget(shnew) toolt = QCheckBox("Show informational tooltips") toolt.setChecked(config.tooltips) il.addWidget(toolt) frm.setLayout(il) hl = QHBoxLayout() vl.addLayout(hl) gen = QPushButton("Generate", clicked=swin.accept) hl.addWidget(gen) cls = QPushButton("Close", clicked=swin.reject) hl.addWidget(cls) swin.setLayout(vl) swin.setTabOrder(gen, cls) swin.setTabOrder(cls, field) swin.setTabOrder(field, liter) swin.setTabOrder(liter, stint) swin.setTabOrder(stint, ttcol) swin.setTabOrder(ttcol, wtcol) swin.setTabOrder(wtcol, groupby) swin.setTabOrder(groupby, shnew) swin.setTabOrder(shnew, toolt) swin.resize(500, 400) if swin.exec_(): mw.progress.start(immediate=True) if len(field.text().strip()) != 0: config.pattern = field.text().lower() config.pattern = config.pattern.split() config.literal = liter.isChecked() config.interval = stint.value() config.thin = ttcol.value() config.wide = wtcol.value() config.groupby = groupby.currentIndex() config.unseen = shnew.isChecked() config.tooltips = toolt.isChecked() config.autothinwide = itcol.isChecked() self.makegrid(config) mw.progress.finish() self.win.show()