def test_nextIvl(): d = getEmptyCol() f = d.newNote() f["Front"] = "one" f["Back"] = "two" d.addNote(f) d.reset() conf = d.decks.confForDid(1) conf["new"]["delays"] = [0.5, 3, 10] conf["lapse"]["delays"] = [1, 5, 9] d.decks.save(conf) c = d.sched.getCard() # new cards ################################################## ni = d.sched.nextIvl assert ni(c, 1) == 30 assert ni(c, 2) == 180 assert ni(c, 3) == 4 * 86400 d.sched.answerCard(c, 1) # cards in learning ################################################## assert ni(c, 1) == 30 assert ni(c, 2) == 180 assert ni(c, 3) == 4 * 86400 d.sched.answerCard(c, 2) assert ni(c, 1) == 30 assert ni(c, 2) == 600 assert ni(c, 3) == 4 * 86400 d.sched.answerCard(c, 2) # normal graduation is tomorrow assert ni(c, 2) == 1 * 86400 assert ni(c, 3) == 4 * 86400 # lapsed cards ################################################## c.type = CARD_TYPE_REV c.ivl = 100 c.factor = STARTING_FACTOR assert ni(c, 1) == 60 assert ni(c, 2) == 100 * 86400 assert ni(c, 3) == 100 * 86400 # review cards ################################################## c.queue = QUEUE_TYPE_REV c.ivl = 100 c.factor = STARTING_FACTOR # failing it should put it at 60s assert ni(c, 1) == 60 # or 1 day if relearn is false conf["lapse"]["delays"] = [] d.decks.save(conf) assert ni(c, 1) == 1 * 86400 # (* 100 1.2 86400)10368000.0 assert ni(c, 2) == 10368000 # (* 100 2.5 86400)21600000.0 assert ni(c, 3) == 21600000 # (* 100 2.5 1.3 86400)28080000.0 assert ni(c, 4) == 28080000 assert without_unicode_isolation(d.sched.nextIvlStr(c, 4)) == "10.8mo"
def _ensureProfile(self) -> None: "Create a new profile if none exists." self.create(_("User 1")) p = os.path.join(self.base, "README.txt") with open(p, "w", encoding="utf8") as file: file.write( without_unicode_isolation( tr(TR.PROFILES_FOLDER_README, link=appHelpSite + "#startupopts")))
def copy(self, notetype: NotetypeDict, add: bool = True) -> NotetypeDict: "Copy, save and return." cloned = copy.deepcopy(notetype) cloned["name"] = without_unicode_isolation( self.col.tr.notetypes_copy(val=cloned["name"])) cloned["id"] = 0 if add: self.add(cloned) return cloned
def _ensureProfile(self) -> None: "Create a new profile if none exists." self.create(tr.profiles_user_1()) p = os.path.join(self.base, "README.txt") with open(p, "w", encoding="utf8") as file: file.write( without_unicode_isolation( tr.profiles_folder_readme( link=f"{appHelpSite}files#startup-options", )) + "\n")
def copy(self, m: NoteType) -> NoteType: "Copy, save and return." m2 = copy.deepcopy(m) m2["name"] = without_unicode_isolation( self.col.tr(TR.NOTETYPES_COPY, val=m2["name"]) ) m2["id"] = 0 self.add(m2) return m2
def copy(self, m: NotetypeDict, add: bool = True) -> NotetypeDict: "Copy, save and return." m2 = copy.deepcopy(m) m2["name"] = without_unicode_isolation( self.col.tr.notetypes_copy(val=m2["name"])) m2["id"] = 0 if add: self.add(m2) return m2
def _load_deck(self) -> None: form = self.form deck = self.deck config = deck.config self.form.name.setText(deck.name) self.form.name.setPlaceholderText(deck.name) existing = deck.id != 0 if existing: build_label = tr.actions_rebuild() else: build_label = tr.decks_build() self.form.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setText(build_label) form.resched.setChecked(config.reschedule) self._onReschedToggled(0) term1: FilteredDeckConfig.SearchTerm = config.search_terms[0] form.search.setText(term1.search) form.order.setCurrentIndex(term1.order) form.limit.setValue(term1.limit) if self.col.sched_ver() == 1: if config.delays: form.steps.setText(self.listToUser(list(config.delays))) form.stepsOn.setChecked(True) else: form.steps.setVisible(False) form.stepsOn.setVisible(False) form.previewDelay.setValue(config.preview_delay) if len(config.search_terms) > 1: term2: FilteredDeckConfig.SearchTerm = config.search_terms[1] form.search_2.setText(term2.search) form.order_2.setCurrentIndex(term2.order) form.limit_2.setValue(term2.limit) show_second = existing else: show_second = False form.order_2.setCurrentIndex(5) form.limit_2.setValue(20) form.secondFilter.setChecked(show_second) form.filter2group.setVisible(show_second) self.set_custom_searches(self._desired_search_1, self._desired_search_2) self.setWindowTitle( without_unicode_isolation( tr.actions_options_for(val=self.deck.name))) gui_hooks.filtered_deck_dialog_did_load_deck(self, deck)
def _ensureProfile(self) -> None: "Create a new profile if none exists." self.create(tr(TR.PROFILES_USER_1)) p = os.path.join(self.base, "README.txt") with open(p, "w", encoding="utf8") as file: file.write( without_unicode_isolation( tr( TR.PROFILES_FOLDER_README, link=f"{appHelpSite}files?id=startup-options", )))
def updateTitle(self) -> None: selected = self.table.len_selection() cur = self.table.len() tr_title = ( tr.browsing_window_title_notes if self.table.is_notes_mode() else tr.browsing_window_title ) self.setWindowTitle( without_unicode_isolation(tr_title(total=cur, selected=selected)) )
def __init__( self, mw: AnkiQt, nt: NotetypeDict, parent: Optional[QWidget] = None, open_at: int = 0, ) -> None: QDialog.__init__(self, parent or mw) mw.garbage_collect_on_dialog_finish(self) self.mw = mw self.col = self.mw.col self.mm = self.mw.col.models self.model = nt self.mm._remove_from_cache(self.model["id"]) self.change_tracker = ChangeTracker(self.mw) self.setWindowTitle( without_unicode_isolation( tr.fields_fields_for(val=self.model["name"]))) if os.getenv("ANKI_EXPERIMENTAL_FIELDS_WEB"): form = aqt.forms.fields_web.Ui_Dialog() form.setupUi(self) self.webview = form.webview self.webview.set_title("fields") self.show() self.refresh() self.webview.set_bridge_command(self._on_bridge_cmd, self) self.activateWindow() return self.form = aqt.forms.fields.Ui_Dialog() self.form.setupUi(self) self.webview = None disable_help_button(self) self.form.buttonBox.button( QDialogButtonBox.StandardButton.Help).setAutoDefault(False) self.form.buttonBox.button( QDialogButtonBox.StandardButton.Cancel).setAutoDefault(False) self.form.buttonBox.button( QDialogButtonBox.StandardButton.Save).setAutoDefault(False) self.currentIdx: Optional[int] = None self.fillFields() self.setupSignals() self.form.fieldList.setDragDropMode( QAbstractItemView.DragDropMode.InternalMove) self.form.fieldList.dropEvent = self.onDrop # type: ignore[assignment] self.form.fieldList.setCurrentRow(open_at) self.exec()
def __init__( self, mw: AnkiQt, note: Note, ord=0, parent: Optional[QWidget] = None, fill_empty: bool = False, ): QDialog.__init__(self, parent or mw, Qt.Window) mw.setupDialogGC(self) self.mw = aqt.mw self.note = note self.ord = ord self.col = self.mw.col.weakref() self.mm = self.mw.col.models self.model = note.model() self.templates = self.model["tmpls"] self.fill_empty_action_toggled = fill_empty self.night_mode_is_enabled = self.mw.pm.night_mode() self.mobile_emulation_enabled = False self.have_autoplayed = False self.mm._remove_from_cache(self.model["id"]) self.mw.checkpoint(tr(TR.CARD_TEMPLATES_CARD_TYPES)) self.change_tracker = ChangeTracker(self.mw) self.setupTopArea() self.setupMainArea() self.setupButtons() self.setupShortcuts() self.setWindowTitle( without_unicode_isolation( tr(TR.CARD_TEMPLATES_CARD_TYPES_FOR, val=self.model["name"]) ) ) disable_help_button(self) v1 = QVBoxLayout() v1.addWidget(self.topArea) v1.addWidget(self.mainArea) v1.addLayout(self.buttons) v1.setContentsMargins(12, 12, 12, 12) self.setLayout(v1) gui_hooks.card_layout_will_show(self) self.redraw_everything() restoreGeom(self, "CardLayout") restoreSplitter(self.mainArea, "CardLayoutMainArea") self.setWindowModality(Qt.ApplicationModal) self.show() # take the focus away from the first input area when starting up, # as users tend to accidentally type into the template self.setFocus()
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 onAdvanced(self) -> None: nt = self.current_notetype() d = QDialog(self) disable_help_button(d) frm = aqt.forms.modelopts.Ui_Dialog() frm.setupUi(d) frm.latexsvg.setChecked(nt.get("latexsvg", False)) frm.latexHeader.setText(nt["latexPre"]) frm.latexFooter.setText(nt["latexPost"]) d.setWindowTitle( without_unicode_isolation(tr.actions_options_for(val=nt["name"]))) qconnect(frm.buttonBox.helpRequested, lambda: openHelp(HelpPage.LATEX)) restoreGeom(d, "modelopts") gui_hooks.models_advanced_will_show(d) d.exec_() saveGeom(d, "modelopts") nt["latexsvg"] = frm.latexsvg.isChecked() nt["latexPre"] = str(frm.latexHeader.toPlainText()) nt["latexPost"] = str(frm.latexFooter.toPlainText()) self.saveAndRefresh(nt)
def __init__(self, dlg, addon, conf) -> None: super().__init__(dlg) self.addon = addon self.conf = conf self.mgr = dlg.mgr self.form = aqt.forms.addonconf.Ui_Dialog() self.form.setupUi(self) restore = self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults) qconnect(restore.clicked, self.onRestoreDefaults) self.setupFonts() self.updateHelp() self.updateText(self.conf) restoreGeom(self, "addonconf") restoreSplitter(self.form.splitter, "addonconf") self.setWindowTitle( without_unicode_isolation( tr( TR.ADDONS_CONFIG_WINDOW_TITLE, name=self.mgr.addon_meta(addon).human_name(), ))) self.show()
def __init__(self, mw, first=False, search="", deck=None): QDialog.__init__(self, mw) self.mw = mw self.deck = deck or self.mw.col.decks.current() self.search = search self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) if first: label = tr(TR.DECKS_BUILD) else: label = tr(TR.ACTIONS_REBUILD) self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) disable_help_button(self) self.setWindowModality(Qt.WindowModal) qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK)) self.setWindowTitle( without_unicode_isolation( tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"]))) restoreGeom(self, "dyndeckconf") self.initialSetup() self.loadConf() if search: search = self.mw.col.build_search_string( search, SearchTerm(card_state=SearchTerm.CARD_STATE_DUE)) self.form.search.setText(search) search_2 = self.mw.col.build_search_string( search, SearchTerm(card_state=SearchTerm.CARD_STATE_NEW)) self.form.search_2.setText(search_2) self.form.search.selectAll() if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) self.show() self.exec_() saveGeom(self, "dyndeckconf")
def _setup_ui(self) -> None: self.setWindowModality(Qt.ApplicationModal) self.mw.garbage_collect_on_dialog_finish(self) self.setMinimumWidth(400) disable_help_button(self) restoreGeom(self, self.TITLE) addCloseShortcut(self) self.web = AnkiWebView(title=self.TITLE) self.web.setVisible(False) self.web.load_ts_page("deckoptions") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.web) self.setLayout(layout) self.web.eval(f"""const $deckOptions = anki.deckOptions( document.getElementById('main'), {self._deck["id"]});""") self.setWindowTitle( without_unicode_isolation( tr.actions_options_for(val=self._deck["name"]))) gui_hooks.deck_options_did_load(self)
def onAdvanced(self) -> None: nt = self.current_notetype() d = QDialog(self) d.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # type: ignore frm = aqt.forms.modelopts.Ui_Dialog() frm.setupUi(d) frm.latexsvg.setChecked(nt.get("latexsvg", False)) frm.latexHeader.setText(nt["latexPre"]) frm.latexFooter.setText(nt["latexPost"]) d.setWindowTitle( without_unicode_isolation( tr(TR.ACTIONS_OPTIONS_FOR, val=nt["name"]))) qconnect(frm.buttonBox.helpRequested, lambda: openHelp("math?id=latex")) restoreGeom(d, "modelopts") gui_hooks.models_advanced_will_show(d) d.exec_() saveGeom(d, "modelopts") nt["latexsvg"] = frm.latexsvg.isChecked() nt["latexPre"] = str(frm.latexHeader.toPlainText()) nt["latexPost"] = str(frm.latexFooter.toPlainText()) self.saveAndRefresh(nt)
def test_nextIvl(): col = getEmptyCol() note = col.newNote() note["Front"] = "one" note["Back"] = "two" col.addNote(note) col.reset() conf = col.decks.config_dict_for_deck_id(1) conf["new"]["delays"] = [0.5, 3, 10] conf["lapse"]["delays"] = [1, 5, 9] col.decks.save(conf) c = col.sched.getCard() # new cards ################################################## ni = col.sched.nextIvl assert ni(c, 1) == 30 assert ni(c, 2) == (30 + 180) // 2 assert ni(c, 3) == 180 assert ni(c, 4) == 4 * 86400 col.sched.answerCard(c, 1) # cards in learning ################################################## assert ni(c, 1) == 30 assert ni(c, 2) == (30 + 180) // 2 assert ni(c, 3) == 180 assert ni(c, 4) == 4 * 86400 col.sched.answerCard(c, 3) assert ni(c, 1) == 30 assert ni(c, 2) == (180 + 600) // 2 assert ni(c, 3) == 600 assert ni(c, 4) == 4 * 86400 col.sched.answerCard(c, 3) # normal graduation is tomorrow assert ni(c, 3) == 1 * 86400 assert ni(c, 4) == 4 * 86400 # lapsed cards ################################################## c.type = CARD_TYPE_RELEARNING c.ivl = 100 c.factor = STARTING_FACTOR c.flush() assert ni(c, 1) == 60 assert ni(c, 3) == 100 * 86400 assert ni(c, 4) == 101 * 86400 # review cards ################################################## c.type = CARD_TYPE_REV c.queue = QUEUE_TYPE_REV c.ivl = 100 c.factor = STARTING_FACTOR c.flush() # failing it should put it at 60s assert ni(c, 1) == 60 # or 1 day if relearn is false conf["lapse"]["delays"] = [] col.decks.save(conf) assert ni(c, 1) == 1 * 86400 # (* 100 1.2 86400)10368000.0 assert ni(c, 2) == 10368000 # (* 100 2.5 86400)21600000.0 assert ni(c, 3) == 21600000 # (* 100 2.5 1.3 86400)28080000.0 assert ni(c, 4) == 28080000 assert without_unicode_isolation(col.sched.nextIvlStr(c, 4)) == "10.8mo"
def show(mw: aqt.AnkiQt) -> QDialog: dialog = ClosableQDialog(mw) disable_help_button(dialog) mw.garbage_collect_on_dialog_finish(dialog) abt = aqt.forms.about.Ui_About() abt.setupUi(dialog) # Copy debug info ###################################################################### def addon_fmt(addmgr: AddonManager, addon: AddonMeta) -> str: if addon.installed_at: installed = time.strftime( "%Y-%m-%dT%H:%M", time.localtime(addon.installed_at) ) else: installed = "0" if addon.provided_name: name = addon.provided_name else: name = "''" user = addmgr.getConfig(addon.dir_name) default = addmgr.addonConfigDefaults(addon.dir_name) if user == default: modified = "''" else: modified = "mod" return f"{name} ['{addon.dir_name}', {installed}, '{addon.human_version}', {modified}]" def onCopy() -> None: addmgr = mw.addonManager active = [] activeids = [] inactive = [] for addon in addmgr.all_addon_meta(): if addon.enabled: active.append(addon_fmt(addmgr, addon)) if addon.ankiweb_id(): activeids.append(addon.dir_name) else: inactive.append(addon_fmt(addmgr, addon)) newline = "\n" info = f""" {supportText()} ===Add-ons (active)=== (add-on provided name [Add-on folder, installed at, version, is config changed]) {newline.join(sorted(active))} ===IDs of active AnkiWeb add-ons=== {" ".join(activeids)} ===Add-ons (inactive)=== (add-on provided name [Add-on folder, installed at, version, is config changed]) {newline.join(sorted(inactive))} """ info = f" {' '.join(info.splitlines(True))}" QApplication.clipboard().setText(info) tooltip(tr.about_copied_to_clipboard(), parent=dialog) btn = QPushButton(tr.about_copy_debug_info()) qconnect(btn.clicked, onCopy) abt.buttonBox.addButton(btn, QDialogButtonBox.ButtonRole.ActionRole) abt.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setFocus() # WebView contents ###################################################################### abouttext = "<center><img src='/_anki/imgs/anki-logo-thin.png'></center>" abouttext += f"<p>{tr.about_anki_is_a_friendly_intelligent_spaced()}" abouttext += f"<p>{tr.about_anki_is_licensed_under_the_agpl3()}" abouttext += f"<p>{tr.about_version(val=versionWithBuild())}<br>" abouttext += ("Python %s Qt %s PyQt %s<br>") % ( platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR, ) abouttext += ( without_unicode_isolation(tr.about_visit_website(val=aqt.appWebsite)) + "</span>" ) # automatically sorted; add new lines at the end allusers = sorted( ( "Aaron Harsh", "Alex Fraser", "Andreas Klauer", "Andrew Wright", "Aristotelis P.", "Bernhard Ibertsberger", "C. van Rooyen", "Charlene Barina", "Christian Krause", "Christian Rusche", "Dave Druelinger", "David Smith", "Dmitry Mikheev", "Dotan Cohen", "Emilio Wuerges", "Emmanuel Jarri", "Frank Harper", "Gregor Skumavc", "Guillem Palau Salvà", "H. Mijail", "Henrik Enggaard Hansen", "Houssam Salem", "Ian Lewis", "Immanuel Asmus", "Iroiro", "Jarvik7", "Jin Eun-Deok", "Jo Nakashima", "Johanna Lindh", "Joseph Lorimer", "Julien Baley", "Jussi Määttä", "Kieran Clancy", "LaC", "Laurent Steffan", "Luca Ban", "Luciano Esposito", "Marco Giancotti", "Marcus Rubeus", "Mari Egami", "Mark Wilbur", "Matthew Duggan", "Matthew Holtz", "Meelis Vasser", "Michael Jürges", "Michael Keppler", "Michael Montague", "Michael Penkov", "Michal Čadil", "Morteza Salehi", "Nathanael Law", "Nguyễn Hào Khôi", "Nick Cook", "Niklas Laxström", "Norbert Nagold", "Ole Guldberg", "Pcsl88", "Petr Michalec", "Piotr Kubowicz", "Richard Colley", "Roland Sieker", "Samson Melamed", "Silja Ijas", "Snezana Lukic", "Soren Bjornstad", "Stefaan De Pooter", "Susanna Björverud", "Sylvain Durand", "Tacutu", "Timm Preetz", "Timo Paulssen", "Ursus", "Victor Suba", "Volker Jansen", "Volodymyr Goncharenko", "Xtru", "Ádám Szegi", "赵金鹏", "黃文龍", "David Bailey", "Arman High", "Arthur Milchior", "Rai (Michael Pokorny)", "AMBOSS MD Inc.", "Erez Volk", "Tobias Predel", "Thomas Kahn", "zjosua", "Ijgnd", "Evandro Coan", "Alan Du", "ANH", "Junseo Park", "Gustavo Costa", "余时行", "叶峻峣", "RumovZ", "学习骇客", "ready-research", ) ) abouttext += "<p>" + tr.about_written_by_damien_elmes_with_patches( cont=", ".join(allusers) ) abouttext += f"<p>{tr.about_if_you_have_contributed_and_are()}" abouttext += f"<p>{tr.about_a_big_thanks_to_all_the()}" abt.label.setMinimumWidth(800) abt.label.setMinimumHeight(600) dialog.show() abt.label.stdHtml(abouttext, js=[]) return dialog
def __init__( self, mw: AnkiQt, search: Optional[str] = None, search_2: Optional[str] = None, deck: Optional[Deck] = None, ) -> None: """If 'deck' is an existing filtered deck, load and modify its settings. Otherwise, build a new one and derive settings from the current deck. """ QDialog.__init__(self, mw) self.mw = mw self.col = self.mw.col self.did: Optional[int] = None self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) self.initialSetup() self.old_deck = self.col.decks.current() if deck and deck["dyn"]: # modify existing dyn deck label = tr(TR.ACTIONS_REBUILD) self.deck = deck self.loadConf() elif self.old_deck["dyn"]: # create new dyn deck from other dyn deck label = tr(TR.DECKS_BUILD) self.loadConf(deck=self.old_deck) self.new_dyn_deck() else: # create new dyn deck from regular deck label = tr(TR.DECKS_BUILD) self.new_dyn_deck() self.loadConf() self.set_default_searches(self.old_deck["name"]) self.form.name.setText(self.deck["name"]) self.form.name.setPlaceholderText(self.deck["name"]) self.set_custom_searches(search, search_2) qconnect(self.form.search_button.clicked, self.on_search_button) qconnect(self.form.search_button_2.clicked, self.on_search_button_2) qconnect(self.form.hint_button.clicked, self.on_hint_button) blue = theme_manager.color(colors.LINK) grey = theme_manager.color(colors.DISABLED) self.setStyleSheet(f"""QPushButton[label] {{ padding: 0; border: 0 }} QPushButton[label]:hover {{ text-decoration: underline }} QPushButton[label="search"] {{ color: {blue} }} QPushButton[label="hint"] {{ color: {grey} }}""") disable_help_button(self) self.setWindowModality(Qt.WindowModal) qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK)) self.setWindowTitle( without_unicode_isolation( tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"]))) self.form.buttonBox.button(QDialogButtonBox.Ok).setText(label) if self.col.schedVer() == 1: self.form.secondFilter.setVisible(False) restoreGeom(self, "dyndeckconf") self.show()
def show(mw): dialog = ClosableQDialog(mw) dialog.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # type: ignore mw.setupDialogGC(dialog) abt = aqt.forms.about.Ui_About() abt.setupUi(dialog) # Copy debug info ###################################################################### def addon_fmt(addmgr: AddonManager, addon: AddonMeta) -> str: if addon.installed_at: installed = time.strftime("%Y-%m-%dT%H:%M", time.localtime(addon.installed_at)) else: installed = "0" if addon.provided_name: name = addon.provided_name else: name = "''" user = addmgr.getConfig(addon.dir_name) default = addmgr.addonConfigDefaults(addon.dir_name) if user == default: modified = "''" else: modified = "mod" return f"{name} ['{addon.dir_name}', {installed}, '{addon.human_version}', {modified}]" def onCopy(): addmgr = mw.addonManager active = [] activeids = [] inactive = [] for addon in addmgr.all_addon_meta(): if addon.enabled: active.append(addon_fmt(addmgr, addon)) if addon.ankiweb_id(): activeids.append(addon.dir_name) else: inactive.append(addon_fmt(addmgr, addon)) newline = "\n" info = f""" {supportText()} ===Add-ons (active)=== (add-on provided name [Add-on folder, installed at, version, is config changed]) {newline.join(sorted(active))} ===IDs of active AnkiWeb add-ons=== {" ".join(activeids)} ===Add-ons (inactive)=== (add-on provided name [Add-on folder, installed at, version, is config changed]) {newline.join(sorted(inactive))} """ info = " " + " ".join(info.splitlines(True)) QApplication.clipboard().setText(info) tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog) btn = QPushButton(tr(TR.ABOUT_COPY_DEBUG_INFO)) qconnect(btn.clicked, onCopy) abt.buttonBox.addButton(btn, QDialogButtonBox.ActionRole) abt.buttonBox.button(QDialogButtonBox.Ok).setFocus() # WebView contents ###################################################################### abouttext = "<center><img src='/_anki/imgs/anki-logo-thin.png'></center>" abouttext += "<p>" + tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED) abouttext += "<p>" + tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3) abouttext += "<p>" + tr(TR.ABOUT_VERSION, val=versionWithBuild()) + "<br>" abouttext += ("Python %s Qt %s PyQt %s<br>") % ( platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR, ) abouttext += (without_unicode_isolation( tr(TR.ABOUT_VISIT_WEBSITE, val=aqt.appWebsite)) + "</span>") # automatically sorted; add new lines at the end allusers = sorted(( "Aaron Harsh", "Alex Fraser", "Andreas Klauer", "Andrew Wright", "Aristotelis P.", "Bernhard Ibertsberger", "C. van Rooyen", "Charlene Barina", "Christian Krause", "Christian Rusche", "Dave Druelinger", "David Smith", "Dmitry Mikheev", "Dotan Cohen", "Emilio Wuerges", "Emmanuel Jarri", "Frank Harper", "Gregor Skumavc", "Guillem Palau Salvà", "H. Mijail", "Henrik Enggaard Hansen", "Houssam Salem", "Ian Lewis", "Immanuel Asmus", "Iroiro", "Jarvik7", "Jin Eun-Deok", "Jo Nakashima", "Johanna Lindh", "Joseph Lorimer", "Julien Baley", "Jussi Määttä", "Kieran Clancy", "LaC", "Laurent Steffan", "Luca Ban", "Luciano Esposito", "Marco Giancotti", "Marcus Rubeus", "Mari Egami", "Mark Wilbur", "Matthew Duggan", "Matthew Holtz", "Meelis Vasser", "Michael Jürges", "Michael Keppler", "Michael Montague", "Michael Penkov", "Michal Čadil", "Morteza Salehi", "Nathanael Law", "Nguyễn Hào Khôi", "Nick Cook", "Niklas Laxström", "Norbert Nagold", "Ole Guldberg", "Pcsl88", "Petr Michalec", "Piotr Kubowicz", "Richard Colley", "Roland Sieker", "Samson Melamed", "Silja Ijas", "Snezana Lukic", "Soren Bjornstad", "Stefaan De Pooter", "Susanna Björverud", "Sylvain Durand", "Tacutu", "Timm Preetz", "Timo Paulssen", "Ursus", "Victor Suba", "Volker Jansen", "Volodymyr Goncharenko", "Xtru", "Ádám Szegi", "赵金鹏", "黃文龍", "David Bailey", "Arman High", "Arthur Milchior", "Rai (Michael Pokorny)", "AMBOSS MD Inc.", "Erez Volk", "Tobias Predel", "Thomas Kahn", "zjosua", "Ijgnd", "Evandro Coan", "Alan Du", "ANH", "Junseo Park", "Gustavo Costa", "余时行", "叶峻峣", )) abouttext += "<p>" + tr(TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers)) abouttext += "<p>" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE) abouttext += "<p>" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE) abt.label.setMinimumWidth(800) abt.label.setMinimumHeight(600) dialog.show() abt.label.stdHtml(abouttext, js=[]) return dialog
def test_latex(): col = getEmptyCol() # change latex cmd to simulate broken build import anki.latex anki.latex.pngCommands[0][0] = "nolatex" # add a note with latex note = col.newNote() note["Front"] = "[latex]hello[/latex]" col.addNote(note) # but since latex couldn't run, there's nothing there assert len(os.listdir(col.media.dir())) == 0 # check the error message msg = note.cards()[0].q() assert "executing nolatex" in without_unicode_isolation(msg) assert "installed" in msg # check if we have latex installed, and abort test if we don't if not shutil.which("latex") or not shutil.which("dvipng"): print("aborting test; latex or dvipng is not installed") return # fix path anki.latex.pngCommands[0][0] = "latex" # check media db should cause latex to be generated col.media.render_all_latex() assert len(os.listdir(col.media.dir())) == 1 assert ".png" in note.cards()[0].q() # adding new notes should cause generation on question display note = col.newNote() note["Front"] = "[latex]world[/latex]" col.addNote(note) note.cards()[0].q() assert len(os.listdir(col.media.dir())) == 2 # another note with the same media should reuse note = col.newNote() note["Front"] = " [latex]world[/latex]" col.addNote(note) assert len(os.listdir(col.media.dir())) == 2 oldcard = note.cards()[0] assert ".png" in oldcard.q() # if we turn off building, then previous cards should work, but cards with # missing media will show a broken image anki.latex.build = False note = col.newNote() note["Front"] = "[latex]foo[/latex]" col.addNote(note) assert len(os.listdir(col.media.dir())) == 2 assert ".png" in oldcard.q() # turn it on again so other test don't suffer anki.latex.build = True # bad commands (result, msg) = _test_includes_bad_command("\\write18") assert result, msg (result, msg) = _test_includes_bad_command("\\readline") assert result, msg (result, msg) = _test_includes_bad_command("\\input") assert result, msg (result, msg) = _test_includes_bad_command("\\include") assert result, msg (result, msg) = _test_includes_bad_command("\\catcode") assert result, msg (result, msg) = _test_includes_bad_command("\\openout") assert result, msg (result, msg) = _test_includes_bad_command("\\write") assert result, msg (result, msg) = _test_includes_bad_command("\\loop") assert result, msg (result, msg) = _test_includes_bad_command("\\def") assert result, msg (result, msg) = _test_includes_bad_command("\\shipout") assert result, msg # inserting commands beginning with a bad name should not raise an error (result, msg) = _test_includes_bad_command("\\defeq") assert not result, msg # normal commands should not either (result, msg) = _test_includes_bad_command("\\emph") assert not result, msg