def display_result(ilist): """Format and Display the report on a showText window.""" r = "1min Interval Report:\n\n" r += "Deck\tCount\t% Total\t% in Learn\n" for l in ilist: r += "%s\t%d\t%0.1f\t%0.1f\n" % (l[0], l[1], l[2], l[3]) showText(r)
def bulkCopy(nids): mw.progress.start(immediate=True) for nid in nids: note = mw.col.getNote(nid) word = note[srcField] try : with warnings.catch_warnings(): warnings.simplefilter("error") mw.progress.update(label=word) GetExamples(word) g = Google() g.write(word) dp = DictionaryParser(word) dp.format() except : # mw.progress.clear() # utils.showInfo("Exception " + word) txt = "<h3>" + word + "</h3>" txt += "<div style='white-space: pre-wrap'>" + traceback.format_exc() + "</div>" utils.showText(txt, type="html") pass mw.progress.finish()
def onTimeout(self): error = cgi.escape(self.pool) self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "Pyaudio not" in error: return showWarning(_("Please install PyAudio")) if "install mplayer" in error: return showWarning(_("Please install mplayer")) if "no default output" in error: return showWarning(_("Please connect a microphone, and ensure " "other programs are not using the audio device.")) stdText = _("""\ An error occurred. It may have been caused by a harmless bug, <br> or your deck may have a problem. <p>To confirm it's not a problem with your deck, please run <b>Tools > Check Database</b>. <p>If that doesn't fix the problem, please copy the following<br> into a bug report:""") pluginText = _("""\ An error occurred in an add-on.<br> Please post on the add-on forum:<br>%s<br>""") pluginText %= "https://groups.google.com/forum/#!forum/anki-addons" if "addon" in error: txt = pluginText else: txt = stdText # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html")
def import_from_json(): path = getFile(mw, "Org file to import", cb=None, dir=expanduser("~")) if not path: return with open(path, 'r') as f: content = f.read().decode('utf-8') entries = json.loads(content) import itertools get_deck = lambda e: e['deck'] entries = sorted(entries, key=get_deck) mw.checkpoint(_("Import")) logs = [] for deck_name, entries in itertools.groupby(entries, get_deck): # FIXME: If required we could group by model name also! importer = JsonImporter(mw.col, path, MODEL_NAME, deck_name) importer.initMapping() importer.run(list(entries)) if importer.log: logs.append('\n'.join(importer.log)) txt = _("Importing complete.") + "\n" txt += '\n'.join(logs) showText(txt) mw.reset()
def onTimeout(self): error = self.pool self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "Pyaudio not" in error: return showWarning(_("Please install PyAudio")) if "install mplayer" in error: return showWarning(_("Please install mplayer")) if "no default output" in error: return showWarning(_("Please connect a microphone.")) stdText = _("""\ An error occurred. It may have been caused by a harmless bug, <br> or your deck may have a problem. <p>To confirm it's not a problem with your deck, please run <b>Tools > Maintenance > Check Database</b>. <p>If that doesn't fix the problem, please copy the following<br> into a bug report:""") pluginText = _("""\ An error occurred in an add-on. Please contact the add-on author.<br>""") if "addon" in error: txt = pluginText else: txt = stdText # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html")
def lookupPronunciation(expr): """ Show the pronunciation when the user does a manual lookup """ ret = getPronunciations(expr) thehtml = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> <HTML> <HEAD> <style> body { font-size: 30px; } </style> <TITLE>Pronunciations</TITLE> <meta charset="UTF-8" /> </HEAD> <BODY> %s </BODY> </HTML> """ % ( "<br/><br/>\n".join(ret) ) showText(thehtml, type="html")
def accept(self): self.importer.importMode = 1 self.mw.pm.profile['importMode'] = self.importer.importMode self.importer.allowHTML = True self.mw.pm.profile['allowHTML'] = self.importer.allowHTML did = self.deck.selectedId() if did != self.importer.model['did']: self.importer.model['did'] = did self.mw.col.models.save(self.importer.model) self.mw.col.decks.select(did) self.mw.progress.start(immediate=True) self.mw.checkpoint(_("Import")) self.importer.run() self.mw.progress.finish() txt = _("Importing complete.") + "\n" if self.importer.log: txt += "\n".join(self.importer.log) self.close() showText(txt) self.mw.reset()
def _sync(self, auth=None): # to avoid gui widgets being garbage collected in the worker thread, # run gc in advance self._didFullUp = False self._didError = False gc.collect() # create the thread, setup signals and start running t = self.thread = SyncThread( self.pm.collectionPath(), self.pm.profile['syncKey'], auth=auth, media=self.pm.profile['syncMedia']) t.event.connect(self.onEvent) self.label = _("Connecting...") self.mw.progress.start(immediate=True, label=self.label) self.sentBytes = self.recvBytes = 0 self._updateLabel() self.thread.start() while not self.thread.isFinished(): self.mw.app.processEvents() self.thread.wait(100) self.mw.progress.finish() if self.thread.syncMsg: showText(self.thread.syncMsg) if self.thread.uname: self.pm.profile['syncUser'] = self.thread.uname def delayedInfo(): if self._didFullUp and not self._didError: showInfo(_("""\ Your collection was successfully uploaded to AnkiWeb. If you use any other devices, please sync them now, and choose \ to download the collection you have just uploaded from this computer. \ After doing so, future reviews and added cards will be merged \ automatically.""")) self.mw.progress.timer(1000, delayedInfo, False)
def onTimeout(self): error = cgi.escape(self.pool) self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "Pyaudio not" in error: return showWarning(_("Please install PyAudio")) if "install mplayer" in error: return showWarning(_("Please install mplayer")) if "no default output" in error: return showWarning( _("Please connect a microphone, and ensure " "other programs are not using the audio device.") ) if "invalidTempFolder" in error: return showWarning(self.tempFolderMsg()) if "disk I/O error" in error: return showWarning( _( """\ An error occurred while accessing the database. Possible causes: - Antivirus, firewall, backup, or synchronization software may be \ interfering with Anki. Try disabling such software and see if the \ problem goes away. - Your disk may be full. - The Documents/Anki folder may be on a network drive. - Files in the Documents/Anki folder may not be writeable. - Your hard disk may have errors. It's a good idea to run Tools>Check Database to ensure your collection \ is not corrupt. """ ) ) stdText = _( """\ An error occurred. It may have been caused by a harmless bug, <br> or your deck may have a problem. <p>To confirm it's not a problem with your deck, please run <b>Tools > Check Database</b>. <p>If that doesn't fix the problem, please copy the following<br> into a bug report:""" ) pluginText = _( """\ An error occurred in an add-on.<br> Please post on the add-on forum:<br>%s<br>""" ) pluginText %= "https://anki.tenderapp.com/discussions/add-ons" if "addon" in error: txt = pluginText else: txt = stdText # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html")
def buildCards(): msg = "" mw.progress.start(immediate=True) for i in range(len(feeds_info)): msg += feeds_info[i]["DECK"] + ":\n" msg += buildCard(**feeds_info[i]) + "\n" mw.progress.finish() utils.showText(msg)
def onCheckDB(self): "True if no problems" self.progress.start(immediate=True) ret, ok = self.col.fixIntegrity() self.progress.finish() if not ok: showText(ret) else: tooltip(ret) self.reset() return ret
def alertUser(self): text ='%d new card(s) added. %d card(s) were duplicates' % ( len(self.added), len(self.duplicates)) text += '\n\nNEW CARDS:\n==========\n' for card in self.added: text += stripHTML(card.front) + '\n' text += '\nDUPLICATES:\n===========\n' for card in self.duplicates: text += stripHTML(card.front) + '\n' showText(text)
def pause(self): self.responsive = False self.diag, box = showText('Pausing speech recognition, say "RESUME" to continue', run=False) def onReject(self): self.diag = None self.diag.connect(box, SIGNAL("rejected()"), lambda:onReject(self)) self.diag.show()
def register(again=False): """Register the new importer with Anki's importers.""" addonDir = os.path.dirname(os.path.abspath(__file__)) libsDir = os.path.join(addonDir, "lib") if libsDir not in sys.path: sys.path.append(libsDir) from . import spreadsheet reload(spreadsheet) importer = (("Spreadsheet Import Plus (*.xlsx)", spreadsheet.SpreadsheetImporter), ) if again: importing.Importers = importing.Importers[0:-1] + importer showText(repr(importing.Importers)) else: importing.Importers += importer
def on_future_done(fut: Future) -> None: mw.col.db.begin() timer.stop() try: out: SyncOutput = fut.result() except Exception as err: handle_sync_error(mw, err) return on_done() mw.pm.set_host_number(out.host_number) if out.server_message: showText(out.server_message) if out.required == out.NO_CHANGES: # all done return on_done() else: full_sync(mw, out, on_done)
def _sync(self, auth=None): # to avoid gui widgets being garbage collected in the worker thread, # run gc in advance self._didFullUp = False self._didError = False gc.collect() # create the thread, setup signals and start running t = self.thread = SyncThread(self.pm.collectionPath(), self.pm.profile['syncKey'], auth=auth, media=self.pm.profile['syncMedia']) t.event.connect(self.onEvent) self.label = _("Connecting...") prog = self.mw.progress.start(immediate=True, label=self.label, cancellable=True) self.sentBytes = self.recvBytes = 0 self._updateLabel() self.thread.start() while not self.thread.isFinished(): if prog.ankiCancel: self.thread.flagAbort() # make sure we don't display 'upload success' msg self._didFullUp = False # abort may take a while self.mw.progress.update(_("Stopping...")) self.mw.app.processEvents() self.thread.wait(100) self.mw.progress.finish() if self.thread.syncMsg: showText(self.thread.syncMsg) if self.thread.uname: self.pm.profile['syncUser'] = self.thread.uname def delayedInfo(): if self._didFullUp and not self._didError: showInfo( _("""\ Your collection was successfully uploaded to AnkiWeb. If you use any other devices, please sync them now, and choose \ to download the collection you have just uploaded from this computer. \ After doing so, future reviews and added cards will be merged \ automatically.""")) self.mw.progress.timer(1000, delayedInfo, False)
def _on_render_latex(self): self.progress_dialog = self.mw.progress.start() try: out = self.mw.col.media.render_all_latex( self._on_render_latex_progress) if self.progress_dialog.wantCancel: return finally: self.mw.progress.finish() self.progress_dialog = None if out is not None: nid, err = out self.mw.browser_search(SearchTerm(nid=nid)) showText(err, type="html") else: tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED))
def join_thread(self) -> None: """ Gather up the results of a completed extract thread, and start the next one if appropriate. """ assert self.extract_thread is not None, "Tried to join a nonexistent thread!" if self.handle_thread_exception(): return None if not self.extract_thread.notes: # This is probably a mistake or misconfiguration. To avoid deleting # all the user's existing notes to "sync" the collection, abort now. showWarning( f"No notes were found in the wiki {self.extract_thread.wiki_name}. " f"Please check your add-on configuration. " f"Your collection has not been updated.") self.reject() return None # This is a set union, with object equality defined by the ID. Any # notes with an ID matching one already used in a previous wiki will be # discarded here. self.notes.update(self.extract_thread.notes) self.warnings.extend(self.extract_thread.warnings) self.form.wikiProgressBar.setValue(self.form.wikiProgressBar.value() + 1) if self.wikis: # If there are any more wikis, handle the next one. # Eventually, parallelizing this might be nice # (might also not improve performance). return self.extract() else: # When all are completed... if self.warnings: showText(f"*** {len(self.warnings)} " f"{pluralize('warning', len(self.warnings))}: ***\n" + '\n'.join(self.warnings)) if (not self.warnings) or askUser("Continue syncing?"): return self.sync() else: self.accept() self.mw.reset() return tooltip("Sync canceled.")
def showDebugInfo(): from aqt import mw from anki import version as anki_version from aqt.utils import showInfo, showText txt = """ <h2>Bug Status: Affected</h2> <p>It seems like your system is affected by a module import bug.</p> <p>To report your findings, please copy the debug info below and post it <a href="https://anki.tenderapp.com/discussions/add-ons/35343">here</a>. (no account needed). Feel free to uninstall "Import Bug Test" once you're done</p> <p>Thanks so much for your help in tracking this issue down!</p> <p><b>Debug info</b>:</p> """ txt += "<div style='white-space: pre-wrap'>" if not anki_version.startswith("2.0"): from aqt.utils import supportText txt += "\n" + supportText() + "\n" addmgr = mw.addonManager txt += "Add-ons:\n\n" + "\n".join( addmgr.annotatedName(d) for d in addmgr.allAddons()) + "\n\n" else: from aqt import appVersion from aqt.qt import QT_VERSION_STR, PYQT_VERSION_STR txt += '<p>' + "Version %s" % appVersion + '\n' txt += ("Qt %s PyQt %s\n\n") % (QT_VERSION_STR, PYQT_VERSION_STR) txt += "Add-ons:\n\n" + repr(mw.addonManager.files()) + "\n\n" txt += "Import Errors:\n\n" txt += "\n\n".join(error[0] + ":\n" + error[1] for error in errors) txt += "</div>" kwargs = dict(title="Import Bug Test", type="html") if not anki_version.startswith("2.0"): kwargs["copyBtn"] = True showText(txt, **kwargs)
def _on_render_latex(self): self.progress_dialog = self.mw.progress.start() try: out = self.mw.col.media.render_all_latex(self._on_render_latex_progress) if self.progress_dialog.wantCancel: return finally: self.mw.progress.finish() self.progress_dialog = None if out is not None: nid, err = out browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText("nid:%d" % nid) browser.onSearchActivated() showText(err, type="html") else: tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED))
def importToAnki(dialog, temp_file_path): mw.progress.finish() if temp_file_path is not None: mw.progress.start(immediate=True, label="Importing...") dialog.setupImporter(temp_file_path) dialog.selectDeck() dialog.importer.run() mw.progress.finish() txt = _("Importing complete.") + "\n" if dialog.importer.log: txt += "\n".join(dialog.importer.log) os.remove(temp_file_path) else: txt = "Nothing to import!" showText(txt)
def touch_media(self, *args, **kwargs): media = [] m = mw.col.media.dir() for f in os.listdir(m): fpath = join(m, f) if isfile(fpath): #TEST: set to 1979 # os.utime(fpath,(315550000,315550000)) if os.stat(fpath).st_mtime < DOS_EPOCH_TIME: t = time.time() os.utime(fpath, (t, t)) media.append(f) if media: lst = "\n".join(media) showText("Incorrect mod time before 1980, auto updated:\n\n%s" % lst)
def run(self): try: config = mw.addonManager.getConfig(__name__) cookies = {} if config["qlts"]: cookies = {"qlts": config["qlts"]} elif config["cookies"]: from http.cookies import SimpleCookie C = SimpleCookie() C.load(config["cookies"]) cookies = {key: morsel.value for key, morsel in C.items()} r = requests.get(self.url, verify=False, headers=headers, cookies=cookies) r.raise_for_status() regex = re.escape('window.Quizlet["setPasswordData"]') if re.search(regex, r.text): self.error = True self.errorCode = 403 return regex = re.escape('window.Quizlet["cardsModeData"] = ') regex += r'(.+?)' regex += re.escape('; QLoad("Quizlet.cardsModeData");') data = re.search(regex, r.text).group(1).strip() self.results = json.loads(data) title = re.search(r'<title>(.*?) \| Quizlet</title>', r.text).group(1).strip() self.results['title'] = title except requests.HTTPError as e: self.error = True self.errorCode = e.response.status_code self.errorMessage = e.response.text except ValueError as e: self.error = True self.errorReason = ("Invalid json: {0}".format(e)) showText(_("Invalid json: ") + repr(e))
def _on_render_latex(self) -> None: self.progress_dialog = self.mw.progress.start() try: out = self.mw.col.media.render_all_latex( self._on_render_latex_progress) if self.progress_dialog.wantCancel: return finally: self.mw.progress.finish() self.progress_dialog = None if out is not None: nid, err = out aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid), )) showText(err, type="html") else: tooltip(tr.media_check_all_latex_rendered())
def pause(self): self.responsive = False self.diag, box = showText( 'Pausing speech recognition, say "RESUME" to continue', run=False) def onReject(self): self.diag = None self.diag.connect(box, SIGNAL("rejected()"), lambda: onReject(self)) self.diag.show()
def onDone(future: Future): self.progress.finish() ret, ok = future.result() if not ok: showText(ret) else: tooltip(ret) # if an error has directed the user to check the database, # silently clean up any broken reset hooks which distract from # the underlying issue while True: try: self.reset() break except Exception as e: print("swallowed exception in reset hook:", e) continue
def accept(self): self.importer.mapping = self.mapping if not self.importer.mappingOk(): showWarning(_("The first field of the note type must be mapped.")) return self.importer.importMode = self.frm.importMode.currentIndex() self.mw.pm.profile["importMode"] = self.importer.importMode self.importer.allowHTML = self.frm.allowHTML.isChecked() self.mw.pm.profile["allowHTML"] = self.importer.allowHTML if self.frm.tagModifiedCheck.isChecked(): self.importer.tagModified = self.frm.tagModifiedTag.toPlainText() self.mw.pm.profile["tagModified"] = self.importer.tagModified did = self.deck.selectedId() if did != self.importer.model["did"]: self.importer.model["did"] = did self.mw.col.models.save(self.importer.model, updateReqs=False) self.mw.col.decks.select(did) self.mw.progress.start(immediate=True) self.mw.checkpoint(_("Import")) try: self.importer.run() except UnicodeDecodeError: showUnicodeWarning() return except Exception as e: msg = _("Import failed.\n") err = repr(str(e)) if "1-character string" in err: msg += err elif "invalidTempFolder" in err: msg += self.mw.errorHandler.tempFolderMsg() else: msg += traceback.format_exc() showText(msg) return finally: self.mw.progress.finish() txt = _("Importing complete.") + "\n" if self.importer.log: txt += "\n".join(self.importer.log) self.close() showText(txt) self.mw.reset()
def ImportToAnki(model_name, import_to_deck, *args, **kwargs): # get file file = kwargs.get("file", None) if not file: file = getFile(mw, _("Import"), None, key="import", filter=Importers[0][0]) if not file: return file = str(file) # check default model try: model = mw.col.models.byName(model_name) if not model: raise Exception("没有找到【{}】".format(model_name)) except: importFile(mw, settings.deck_template_file) try: model = mw.col.models.byName(model_name) except: model = None importer = TextImporter(mw.col, file) importer.delimiter = "\t" importer.importMode = 0 importer.allowHTML = True importer.model = model did = mw.col.decks.id(import_to_deck) mw.col.conf['curDeck'] = did importer.model['did'] = did mw.col.decks.select(did) importer.mapping = [kwargs.get("first")] importer.run() mw.reset() txt = _("Importing complete.") + "\n" if importer.log: txt += "\n".join(importer.log) showText(txt)
def onTimeout(self) -> None: error = html.escape(self.pool) self.pool = "" self.mw.progress.clear() if "AbortSchemaModification" in error: return if "DeprecationWarning" in error: return if "10013" in error: showWarning(tr.qt_misc_your_firewall_or_antivirus_program_is()) return if "invalidTempFolder" in error: showWarning(self.tempFolderMsg()) return if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error or "Errno 28" in error: showWarning(tr.qt_misc_your_computers_storage_may_be_full()) return if "disk I/O error" in error: showWarning(markdown(tr.errors_accessing_db())) return must_close = False if "PanicException" in error: must_close = True txt = markdown( "**A fatal error occurred, and Anki must close. Please report this message on the forums.**" ) error = f"{supportText() + self._addonText(error)}\n{error}" elif self.mw.addonManager.dirty: txt = markdown(tr.errors_addons_active_popup()) error = f"{supportText() + self._addonText(error)}\n{error}" else: txt = markdown(tr.errors_standard_popup()) error = f"{supportText()}\n{error}" # show dialog txt = f"{txt}<div style='white-space: pre-wrap'>{error}</div>" showText(txt, type="html", copyBtn=True) if must_close: sys.exit(1)
def fix_occlbug(): mw.progress.start(immediate=True, label="Please wait, this should not take long") nids = find_occlbug_affected_notes() tag_notes(nids) mw.progress.finish() if len(nids) > 0: showText(""" PLEASE READ THIS CAREFULLY. You will only see this message once. (You can find the text in the addon page though) Sorry!! There was a bug in previous version of the addon 'Image Style Editor'. When editing image sizes in image occlusion notes, it made all the cards of the note have the same occlusion masks. A total of %d notes were found affected by the bug, and they were tagged "image-style-occl-bugged". To fix your notes, please do the following: 1. Click Browse, search for "tag:image-style-occl-bugged" (without quotes) 2. For each note, remove "image-style-occl-bugged" tag, then press 'Edit Image Occlusion'(Ctrl + Shift + O) on top right. 3. Move one of the masks by 1mm. There needs to be a change to the note. 4. Click 'Edit Cards' on the bottom. """%len(nids))
def accept(self): self.importer.mapping = self.mapping if not self.importer.mappingOk(): showWarning( _("The first field of the note type must be mapped.")) return self.importer.importMode = self.frm.importMode.currentIndex() self.mw.pm.profile['importMode'] = self.importer.importMode self.importer.allowHTML = self.frm.allowHTML.isChecked() self.mw.pm.profile['allowHTML'] = self.importer.allowHTML did = self.deck.selectedId() if did != self.importer.model['did']: self.importer.model['did'] = did self.mw.col.models.save(self.importer.model) self.mw.col.decks.select(did) self.mw.progress.start(immediate=True) self.mw.checkpoint(_("Import")) try: self.importer.run() except UnicodeDecodeError: showUnicodeWarning() return except Exception as e: msg = _("Import failed.\n") err = repr(str(e)) if "1-character string" in err: msg += err elif "invalidTempFolder" in err: msg += self.mw.errorHandler.tempFolderMsg() else: msg += str(traceback.format_exc(), "ascii", "replace") showText(msg) return finally: self.mw.progress.finish() txt = _("Importing complete.") + "\n" if self.importer.log: txt += "\n".join(self.importer.log) self.close() showText(txt) self.mw.reset()
def onCheckDB(self): "True if no problems" self.progress.start(immediate=True) ret, ok = self.col.fixIntegrity() self.progress.finish() if not ok: showText(ret) else: tooltip(ret) # if an error has directed the user to check the database, # silently clean up any broken reset hooks which distract from # the underlying issue while True: try: self.reset() break except Exception as e: print("swallowed exception in reset hook:", e) continue return ret
def select_model(self): if not self.mod_list: showText(_trans("USER DEFINED TEMPLATE ALERT"), self, "html", title=_trans("AnKindle")) importFile(mw, DEFAULT_TEMPLATE) edit = QPushButton(_trans("USE LATEST TEMPLATE"), clicked=lambda x: importFile(mw, DEFAULT_TEMPLATE)) ret = StudyDeck( mw, names=lambda: sorted([f['name'] for f in self.mod_list]), accept=anki.lang._("Choose"), title=_trans("NOTE TYPE"), parent=self, buttons=[edit], help='', cancel=True) return ret
def on_future_done(fut) -> None: timer.stop() ret, ok = fut.result() if not ok: showText(ret) else: tooltip(ret) # if an error has directed the user to check the database, # silently clean up any broken reset hooks which distract from # the underlying issue n = 0 while n < 10: try: mw.reset() break except Exception as e: print("swallowed exception in reset hook:", e) n += 1 continue
def onTimeout(self): error = html.escape(self.pool) self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "10013" in error: return showWarning( _("Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki." )) if "install mplayer" in error: return showWarning( _("Sound and video on cards will not function until mpv or mplayer is installed." )) if "no default input" in error.lower(): return showWarning( _("Please connect a microphone, and ensure " "other programs are not using the audio device.")) if "invalidTempFolder" in error: return showWarning(self.tempFolderMsg()) if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error or "Errno 28" in error: return showWarning( _("Your computer's storage may be full. Please delete some unneeded files, then try again." )) if "disk I/O error" in error: showWarning(markdown(tr(FString.ERRORS_ACCESSING_DB))) return if self.mw.addonManager.dirty: txt = markdown(tr(FString.ERRORS_ADDONS_ACTIVE_POPUP)) error = supportText() + self._addonText(error) + "\n" + error else: txt = markdown(tr(FString.ERRORS_STANDARD_POPUP)) error = supportText() + "\n" + error # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html", copyBtn=True)
def onTimeout(self): error = self.pool self.pool = "" stdText = _("""\ An error occurred. It may have been caused by a harmless bug, <br> or your deck may have a problem. <p>To confirm it's not a problem with your deck, please run <b>Tools > Advanced > Check Database</b>. <p>If that doesn't fix the problem, please copy the following<br> into a bug report:""") pluginText = _("""\ An error occurred in a plugin. Please contact the plugin author.<br> Please do not file a bug report with Anki.<br>""") self.mw.progress.clear() if "abortSchemaMod" in error: return if "addon" in error: txt = pluginText else: txt = stdText # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html")
def lookupPronunciation(expr): """ Show the pronunciation when the user does a manual lookup """ ret = getPronunciations(expr) thehtml = """ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> <HTML> <HEAD> <style> body { font-size: 30px; } </style> <TITLE>Pronunciations</TITLE> <meta charset="UTF-8" /> </HEAD> <BODY> %s </BODY> </HTML> """ % ("<br/><br/>\n".join(ret)) showText(thehtml, type="html")
def debug(): card = mw.col.sched.getCard() #showText(str(card.nid), title="anki-extended-scmhash debug output") note = card.note() #showText(str(note.model), title="anki-extended-scmhash debug output") model = note.model() # debugtext = str(model['tmpls'][0]['name']) # debugtext = str(model['css']) debugtext = str(model['name']) + str(model['tmpls'][0]['name']) \ + ", qfmt:\n" + str(model['tmpls'][0]['qfmt']) # showText(debugtext, title="anki-extended-scmhash debug output") s = str(model['css']) for f in model['flds']: s += f['name'] for t in model['tmpls']: s += t['name'] for fmt in ('qfmt', 'afmt'): s += t[fmt] showText(s, title="anki-extended-scmhash debug output")
def onTimeout(self) -> None: error = html.escape(self.pool) self.pool = "" self.mw.progress.clear() if "AbortSchemaModification" in error: return if "DeprecationWarning" in error: return if "10013" in error: showWarning(tr.qt_misc_your_firewall_or_antivirus_program_is()) return if "no default input" in error.lower(): showWarning(tr.qt_misc_please_connect_a_microphone_and_ensure()) return if "invalidTempFolder" in error: showWarning(self.tempFolderMsg()) return if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error or "Errno 28" in error: showWarning(tr.qt_misc_your_computers_storage_may_be_full()) return if "disk I/O error" in error: showWarning(markdown(tr.errors_accessing_db())) return if self.mw.addonManager.dirty: txt = markdown(tr.errors_addons_active_popup()) error = f"{supportText() + self._addonText(error)}\n{error}" else: txt = markdown(tr.errors_standard_popup()) error = f"{supportText()}\n{error}" # show dialog txt = f"{txt}<div style='white-space: pre-wrap'>{error}</div>" showText(txt, type="html", copyBtn=True)
def onTimeout(self) -> None: error = html.escape(self.pool) self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "DeprecationWarning" in error: return if "10013" in error: showWarning(tr(TR.QT_MISC_YOUR_FIREWALL_OR_ANTIVIRUS_PROGRAM_IS)) return if "no default input" in error.lower(): showWarning(tr(TR.QT_MISC_PLEASE_CONNECT_A_MICROPHONE_AND_ENSURE)) return if "invalidTempFolder" in error: showWarning(self.tempFolderMsg()) return if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error or "Errno 28" in error: showWarning(tr(TR.QT_MISC_YOUR_COMPUTERS_STORAGE_MAY_BE_FULL)) return if "disk I/O error" in error: showWarning(markdown(tr(TR.ERRORS_ACCESSING_DB))) return if self.mw.addonManager.dirty: txt = markdown(tr(TR.ERRORS_ADDONS_ACTIVE_POPUP)) error = supportText() + self._addonText(error) + "\n" + error else: txt = markdown(tr(TR.ERRORS_STANDARD_POPUP)) error = supportText() + "\n" + error # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html", copyBtn=True)
def test_names(): """Go through the collection and show possible new names Search the cards for sounds or images with file names that look like MD5 hashes, rename the files and change the notes. """ test_string = u'' nids = mw.col.db.list("select id from notes") for nid in progress(nids, "Dehashilating", "This is all wrong!"): n = mw.col.getNote(nid) for (name, value) in n.items(): rs = re.search(hash_name_pat, value) if None == rs: continue try: new_name_ = new_media_name(rs.group(1), rs.group(2), n) except ValueError: continue test_string += u'{0}{1} → {2}\n'.format( rs.group(1), rs.group(2), new_name_) if (test_string): showText('These new names will be used:\n' + test_string) return test_string
def on_clicked(self): if os.path.isfile(self.help_text_or_file): if IS_PY3K: with open(self.help_text_or_file, encoding="utf-8") as f: text = f.read() else: with open(self.help_text_or_file) as f: text = f.read() text = text.decode("utf-8") else: text = self.help_text_or_file dlg, box = showText(text , self.parent(), "html", title=anki.lang._("Help"), run=False) online_template_doc = QPushButton(_trans("MORE_DOC"), dlg) online_template_doc.clicked.connect(partial(openLink, ONLINE_DOC_URL)) dlg.layout().insertWidget(1, online_template_doc) dlg.exec_()
def __init__(self, parent, updater=None): super(_SharedFrame, self).__init__(parent) self.l_h_widgets = QHBoxLayout(self) wx = WeChatButton( self, os.path.join(os.path.dirname(__file__), "resource", "AnKindle.jpg")) wx.setIcon( os.path.join(os.path.dirname(__file__), "resource", "wechat.png")) wx.setObjectName("wx") self.l_h_widgets.addWidget(wx) vt = VoteButton(self, ADDON_CD) vt.setObjectName("vt") vt.setIcon( os.path.join(os.path.dirname(__file__), "resource", "upvote.png")) self.l_h_widgets.addWidget(vt) mr = MoreAddonButton(self) mr.setObjectName("mr") mr.setIcon( os.path.join(os.path.dirname(__file__), "resource", "more.png")) self.l_h_widgets.addWidget(mr) self.help_btn = _HelpBtn(self) self.l_h_widgets.addSpacerItem( QSpacerItem( 10, 10, QSizePolicy.Expanding, QSizePolicy.Minimum, )) self.l_h_widgets.addWidget(self.help_btn) if updater: up_btn = UpgradeButton(self, updater) up_btn.setIcon( os.path.join(os.path.dirname(__file__), "resource", "update.png")) self.l_h_widgets.addWidget(up_btn) if isWin: up_btn.clicked.disconnect() up_btn.clicked.connect( lambda: showText(_trans("WIN UPDATE") % ADDON_CD, parent, title=_trans("ANKINDLE")))
def onEmptyCards(self): self.progress.start(immediate=True) cids = self.col.emptyCids() if not cids: self.progress.finish() tooltip(_("No empty cards.")) return report = self.col.emptyCardReport(cids) self.progress.finish() part1 = ngettext("%d card", "%d cards", len(cids)) % len(cids) part1 = _("%s to delete:") % part1 diag, box = showText(part1 + "\n\n" + report, run=False) box.addButton(_("Delete Cards"), QDialogButtonBox.AcceptRole) box.button(QDialogButtonBox.Close).setDefault(True) def onDelete(): QDialog.accept(diag) self.checkpoint(_("Delete Empty")) self.col.remCards(cids) tooltip(ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids)) self.reset() diag.connect(box, SIGNAL("accepted()"), onDelete) diag.show()
def onEvent(self, evt, *args): pu = self.mw.progress.update if evt == "badAuth": tooltip( _("AnkiWeb ID or password was incorrect; please try again."), parent=self.mw) # blank the key so we prompt user again self.pm.profile['syncKey'] = None self.pm.save() elif evt == "corrupt": pass elif evt == "newKey": self.pm.profile['syncKey'] = args[0] self.pm.save() elif evt == "offline": tooltip(_("Syncing failed; internet offline.")) elif evt == "upbad": self._didFullUp = False self._checkFailed() elif evt == "sync": m = None; t = args[0] if t == "login": m = _("Syncing...") elif t == "upload": self._didFullUp = True m = _("Uploading to AnkiWeb...") elif t == "download": m = _("Downloading from AnkiWeb...") elif t == "sanity": m = _("Checking...") elif t == "findMedia": m = _("Checking media...") elif t == "upgradeRequired": showText(_("""\ Please visit AnkiWeb, upgrade your deck, then try again.""")) if m: self.label = m self._updateLabel() elif evt == "syncMsg": self.label = args[0] self._updateLabel() elif evt == "error": self._didError = True showText(_("Syncing failed:\n%s")% self._rewriteError(args[0])) elif evt == "clockOff": self._clockOff() elif evt == "checkFailed": self._checkFailed() elif evt == "mediaSanity": showWarning(_("""\ A problem occurred while syncing media. Please use Tools>Check Media, then \ sync again to correct the issue.""")) elif evt == "noChanges": pass elif evt == "fullSync": self._confirmFullSync() elif evt == "downloadClobber": showInfo(_("Your AnkiWeb collection does not contain any cards. Please sync again and choose 'Upload' instead.")) elif evt == "send": # posted events not guaranteed to arrive in order self.sentBytes = max(self.sentBytes, int(args[0])) self._updateLabel() elif evt == "recv": self.recvBytes = max(self.recvBytes, int(args[0])) self._updateLabel()
def showMessages(mw, data): showText(data["msg"], parent=mw, type="html") mw.pm.meta["lastMsg"] = data["msgId"]
def advimport(): Log('-'*80) filename = getFile(mw, "Select file to import", None, key="import") if len(filename) == 0: showText("invalid filename", mw, type="text", run=True) return lines = [] n = 0 with open(filename) as f: reader = unicode_csv_reader(f) for i in range(N_HEADER_LINES): n += 1 reader.next() for row in reader: #print row n += 1 lines.append((n, row)) for n, line in lines: #Log("--"*5) data = [] _chapt = line[0] _sect = line[1] _keywords = line[2] _question = line[3] _solution = line[4] _type = line[5] _subtype = line[6] _symb = SYMBOLS.get(_type, "") _rests = line[7:] print "L%03i:"%n, if not _type: print "!!! No type, skipping" continue elif _type == u"rule": print " Rule ", model = "Rule" key = _question data = [key, _question, _solution, _chapt, _sect, _type, _symb] elif _type == u"pron": print " Pronoun ", model = "Simple" key = _solution data = [key, _question, _solution, _chapt, _sect, _type, _symb] elif _type == u"wend": print " Sentence ", model = "Simple" key = _solution data = [key, _question, _solution, _chapt, _sect, _type, _symb] elif _type == u"prep": print " Prepos ", model = "Simple" key = _solution data = [key, _question, _solution, _chapt, _sect, _type, _symb] elif _type == u"adv": print " Adverb ", model = "Simple" key = _solution data = [key, _question, _solution, _chapt, _sect, _type, _symb] elif _type == u"nom": # Noun print " Noun ", model = "Noun" key = _solution lst = _solution.split(' ') art = lst.pop(0) noun = " ".join(lst) if not _subtype or _subtype == u"": if art == "el": _subtype = u"♂" elif art == "la": _subtype = u"♀" elif art == "los": _subtype = u"♂♂/♂♀" elif art == "las": _subtype = u"♀♀" elif art == "el/la": _subtype = u"♂/♀" elif _subtype[0] in ["F", "f"]: _subtype = u"♀" elif _subtype[0] in ["M", "m"]: _subtype = u"♂" data = [key, _question, _solution, _chapt, _sect, _type, _subtype, _symb] elif _type == u"verb": print " Verb ", modus = _rests[0] temp = _rests[1] forms = _rests[2:] for ii, f in enumerate(forms): _ = f.split('|') if len(_)==2: for i, (c, e) in enumerate(zip(["stem", "ext"], _)): _[i] = '<span class="%s">%s</span>' % (c, e) for i, x in enumerate(_): _[i] = _[i].replace("[", '<span class="irr">') _[i] = _[i].replace("]", '</span>') forms[ii] = "".join(_) model = "Verb" key = "%s (%s; %s)" % (_solution, modus, temp) jsforms = '''{'sg1':'%s','sg2':'%s','sg3':'%s','pl1':'%s','pl2':'%s','pl3':'%s'}''' % tuple(forms) #Log("JSF", jsforms) _question = _question.replace("[", '<span class="prp">') _question = _question.replace("]", '</span>') _solution = _solution.replace("[", '<span class="prp">') _solution = _solution.replace("]", '</span>') #print _question data = [key, _question, _solution, _chapt, _sect, _type, _subtype, _symb, modus, temp, jsforms] elif _type == u"adj": print " Adjective ", s = _solution def decline(stem, exts=['_o', '_a', '_os', '_as'], wrap=('<b>', '</b>')): return [stem+wrap[0]+_+wrap[1] for _ in exts] if '[' in s: _subtype = 'IRR' i = s.find('[') stem = s[:i] exts = s[i+1:s.find(']')].split('|') #Log("ir1: ", i, stem, exts, len(exts)) if len(exts)==4: pass elif len(exts)==2: exts = [exts[0], exts[0], exts[1], exts[1]] elif len(exts)==3: exts = [exts[0], exts[1], exts[2], exts[2]] else: #TODO exts = ['???']*4 elif '|' in s: _subtype = 'IRR' stem = '' exts = s.split('|') if len(exts)==4: pass elif len(exts)==2: exts = [exts[0], exts[0], exts[1], exts[1]] elif len(exts)==3: exts = [exts[0], exts[1], exts[2], exts[2]] else: exts = ['???']*4 elif s[-1]=='o': _subtype = '-o' stem = s[:-1] exts = ['_o', '_a', '_os', '_as'] elif s[-1]=='e': _subtype = '-e' stem = s[:-1] exts = ['e', 'e', '_es', '_es'] elif s[-4:]=='ista': _subtype = '-ista' stem = s[:-4] exts = ['ist_a', 'ist_a', 'ist_as', 'ist_as'] elif s[-2:] == u'ón': _subtype = u'-ón' stem = s[:-2] exts = [u'*ón_', '*on_a', '*on_es', '*on_as'] elif s[-5:] == 'erior': _subtype = '-erior' stem = s[:-5] exts = [u'erior', 'erior', 'erior_s', 'erior_s'] elif s[-2:] == u'or': _subtype = '-or' stem = s[:-2] exts = [u'or', 'or_a', 'or_es', 'or_as'] elif s[-1] == 'z': _subtype = '-z' stem = s[:-1] exts = [u'*z', '*z', '*c_es', '*c_es'] else: # consonant at end: _subtype = '-CONS' stem = s exts = ['', '', '_es', '_es'] print '!!!! >> check this:', stem, exts ,"\n ", #decl = decline(stem, exts, wrap=('<span class="ext">', '</span>')) decl = decline(stem, exts, wrap=('', '')) #decl = [_.replace('_', '') for _ in decl] for i, d in enumerate(decl): while d.find('*')>=0: fi = d.find('*') #print fi, d d = d[:fi] + '<span class="irr">' + d[fi+1] + '</span>' + d[fi+2:] if '_' in d: d = d.replace('_', '<span class="ext">') + '</span>' decl[i] = d #print decl #Log(stem, exts, decl) model = "Adjectiv" key = stem + exts[0] # use masculine form sg as key key = key.replace('*', '').replace('_','') jsforms = '''{'MSg':'%s','FSg':'%s','MPl':'%s','FPl':'%s'}''' % tuple(decl) data = [key, _question, key, _chapt, _sect, _type, _subtype, _symb, jsforms] else: print "!!! Unknown type, skipping" continue if len(data) > 0: print data[1], " | ", data[2] with codecs.open('multiimport.tsv', 'w', encoding='utf-8') as f: #data = [_.encode("utf8") for _ in data] s = "\t".join(data) #f.write(s.decode("utf8")) f.write(s) #print s did = mw.col.decks.byName(deck_name)['id'] mw.col.decks.select(did) m = mw.col.models.byName(model) mw.col.conf['curModel'] = m['id'] cdeck = mw.col.decks.current() cdeck['mid'] = m['id'] mw.col.decks.save(cdeck) mw.col.models.setCurrent(m) m['did'] = did mw.col.models.save(m) mw.reset() ti = TextImporter(mw.col,'multiimport.tsv') ti.delimiter = '\t' ti.allowHTML = True ti.initMapping() ti.run() #os.remove('multiimport.tsv') print('-'*80)
def showMessages(mw, data): showText(data['msg'], parent=mw, type="html") mw.pm.meta['lastMsg'] = data['msgId']
def onTimeout(self): error = html.escape(self.pool) self.pool = "" self.mw.progress.clear() if "abortSchemaMod" in error: return if "10013" in error: return showWarning(_("Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki.")) if "Pyaudio not" in error: return showWarning(_("Please install PyAudio")) if "install mplayer" in error: return showWarning(_("Sound and video on cards will not function until mpv or mplayer is installed.")) if "no default input" in error.lower(): return showWarning(_("Please connect a microphone, and ensure " "other programs are not using the audio device.")) if "invalidTempFolder" in error: return showWarning(self.tempFolderMsg()) if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error: return showWarning(_("Your computer's storage may be full. Please delete some unneeded files, then try again.")) if "disk I/O error" in error: return showWarning(_("""\ An error occurred while accessing the database. Possible causes: - Antivirus, firewall, backup, or synchronization software may be \ interfering with Anki. Try disabling such software and see if the \ problem goes away. - Your disk may be full. - The Documents/Anki folder may be on a network drive. - Files in the Documents/Anki folder may not be writeable. - Your hard disk may have errors. It's a good idea to run Tools>Check Database to ensure your collection \ is not corrupt. """)) stdText = _("""\ <h1>Error</h1> <p>An error occurred. Please use <b>Tools > Check Database</b> to see if \ that fixes the problem.</p> <p>If problems persist, please report the problem on our \ <a href="https://help.ankiweb.net">support site</a>. Please copy and paste \ the information below into your report.</p>""") pluginText = _("""\ <h1>Error</h1> <p>An error occurred. Please start Anki while holding down the shift \ key, which will temporarily disable the add-ons you have installed.</p> <p>If the issue only occurs when add-ons are enabled, please use the \ Tools>Add-ons menu item to disable some add-ons and restart Anki, \ repeating until you discover the add-on that is causing the problem.</p> <p>When you've discovered the add-on that is causing the problem, please \ report the issue on the <a href="https://help.ankiweb.net/discussions/add-ons/">\ add-ons section</a> of our support site. <p>Debug info:</p> """) if self.mw.addonManager.dirty: txt = pluginText error = supportText() + self._addonText(error) + "\n" + error else: txt = stdText error = supportText() + "\n" + error # show dialog txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" showText(txt, type="html", copyBtn=True)
def importFile(mw, file): importerClass = None done = False for i in importing.Importers: if done: break for mext in re.findall(r"[( ]?\*\.(.+?)[) ]", i[0]): if file.endswith("." + mext): importerClass = i[1] done = True break if not importerClass: # if no matches, assume TSV importerClass = importing.Importers[0][1] importer = importerClass(mw.col, file) # need to show import dialog? if importer.needMapper: # make sure we can load the file first mw.progress.start(immediate=True) try: importer.open() except UnicodeDecodeError: showUnicodeWarning() return except Exception as e: msg = repr(str(e)) if msg == "'unknownFormat'": showWarning(_("Unknown file format.")) else: msg = _("Import failed. Debugging info:\n") msg += str(traceback.format_exc()) showText(msg) return finally: mw.progress.finish() diag = ImportDialog(mw, importer) else: # if it's an apkg/zip, first test it's a valid file if importer.__class__.__name__ == "AnkiPackageImporter": try: z = zipfile.ZipFile(importer.file) z.getinfo("collection.anki2") except: showWarning(invalidZipMsg()) return # we need to ask whether to import/replace if not setupApkgImport(mw, importer): return mw.progress.start(immediate=True) try: try: importer.run() finally: mw.progress.finish() except zipfile.BadZipfile: showWarning(invalidZipMsg()) except Exception as e: err = repr(str(e)) if "invalidFile" in err: msg = _("""\ Invalid file. Please restore from backup.""") showWarning(msg) elif "invalidTempFolder" in err: showWarning(mw.errorHandler.tempFolderMsg()) elif "readonly" in err: showWarning(_("""\ Unable to import from a read-only file.""")) else: msg = _("Import failed.\n") msg += str(traceback.format_exc()) showText(msg) else: log = "\n".join(importer.log) if "\n" not in log: tooltip(log) else: showText(log) mw.reset()
def dehashilate(): """Go through the collection and clean up MD5-ish names Search the cards for sounds or images with file names that look like MD5 hashes, rename the files and change the notes. """ mdir = mw.col.media.dir() new_names_dict = {} rename_exec_list = [] bad_mv_text = u'' mw.checkpoint(_("Dehashilate")) nids = mw.col.db.list("select id from notes") for nid in progress(nids, "Dehashilating", "This is all wrong!"): n = mw.col.getNote(nid) for (name, value) in n.items(): for match in re.findall(hash_name_pat, value): rs = re.search(hash_name_pat, value) if None == rs: # Should be redundant with the for match ...: # loop. RAS 2012-06-23 continue old_name = '{0}{1}'.format(rs.group(1), rs.group(2)) try: new_name = new_names_dict[old_name] except KeyError: try: new_name = new_media_name(rs.group(1), rs.group(2), n) except ValueError: continue do_rename = True else: do_rename = False if do_rename: src = os.path.join(mdir, old_name) dst = os.path.join(mdir, new_name) try: os.rename(src, dst) except OSError: # print u'Problem movivg {0} → {1}\n'.format(src, dst) bad_mv_text += u'{0} → {1}\n'.format(src, dst) else: new_names_dict[old_name] = new_name n[name] = value.replace(old_name, new_name) n.flush() rename_exec_list.append(dict(nid=nid, flds=n.joinedFields())) mw.col.db.executemany("update notes set flds =:flds where id =:nid", rename_exec_list) # This is a bit of voodo code. Without it the cards weren't # synced. Maybe this helps. (Cribbed from anki.find, but don't # keep extra list of nids.) RAS 2012-06-20 # And it doesn't work. RAS 2012-07-13 # """File # "/home/roland/Anki-tests/addons/dehashilator/dehashilator.py", # line 268, in dehashilate # mw.col.updateFieldCache([re_dict[nids] for re_dict in # rename_exec_list]) # TypeError: unhashable type: 'list'""" # mw.col.updateFieldCache([re_dict[nids] for re_dict in rename_exec_list]) mw.reset() if bad_mv_text: showText(_(u'These files weren’t renamed:\n') + bad_mv_text)