def on_lookup_wadoku_expression(): init_nachschlagen() try: # No argument means expression mw.nachschlagen.wadoku() except ValueError as ve: showInfo(str(ve))
def __init__(self, app, profileManager, opts, args): QMainWindow.__init__(self) self.state = "startup" self.opts = opts aqt.mw = self self.app = app self.pm = profileManager # init rest of app self.safeMode = self.app.queryKeyboardModifiers() & Qt.ShiftModifier try: self.setupUI() self.setupAddons() except: showInfo(_("Error during startup:\n%s") % traceback.format_exc()) sys.exit(1) # must call this after ui set up if self.safeMode: tooltip(_("Shift key was held down. Skipping automatic " "syncing and add-on loading.")) # were we given a file to import? if args and args[0]: self.onAppMsg(args[0]) # Load profile in a timer so we can let the window finish init and not # close on profile load error. if isWin: fn = self.setupProfileAfterWebviewsLoaded else: fn = self.setupProfile self.progress.timer(10, fn, False, requiresCollection=False)
def onSmartAnswer(): card = mw.reviewer.card if not card: showInfo('Not in review now!') return easebutton = mw.col.sched.answerButtons(card) mw.reviewer._answerCard(easebutton)
def loadProfile(self): # show main window if self.pm.profile['mainWindowState']: restoreGeom(self, "mainWindow") restoreState(self, "mainWindow") else: self.resize(500, 400) # toolbar needs to be retranslated self.toolbar.draw() # show and raise window for osx self.show() self.activateWindow() self.raise_() # maybe sync (will load DB) self.onSync(auto=True) # import pending? if self.pendingImport: if self.pm.profile['key']: showInfo(_("""\ To import into a password protected profile, please open the profile before attempting to import.""")) else: import aqt.importing aqt.importing.importFile(self, self.pendingImport) self.pendingImport = None runHook("profileLoaded")
def disableObsoletePlugins(self): dir = self.pluginsFolder() native = _( "The %s plugin has been disabled, as Anki supports "+ "this natively now.") plugins = [ ("Custom Media Directory.py", (native % "custom media folder") + _(""" \ Please visit Settings>Preferences.""")), ("Regenerate Reading Field.py", _("""\ The regenerate reading field plugin has been disabled, as the Japanese \ support plugin supports this now. Please download the latest version.""")), ("Sync LaTeX with iPhone client.py", native % "sync LaTeX"), ("Incremental Reading.py", _("""The incremental reading plugin has been disabled because \ it needs updates.""")), ("Learn Mode.py", _("""\ The learn mode plugin has been disabled because it needs to be rewritten \ to work with this version of Anki.""")) ] for p in plugins: path = os.path.join(dir, p[0]) if os.path.exists(path): new = path.replace(".py", ".disabled") if os.path.exists(new): os.unlink(new) os.rename(path, new) showInfo(p[1])
def __init__(self): if USE_APPLESCRIPT is False: if not mw.col.conf.get(SETTING_TOKEN, False): # First run of the Plugin we did not save the access key yet client = EvernoteClient( consumer_key='scriptkiddi-2682', consumer_secret='965f1873e4df583c', sandbox=False ) request_token = client.get_request_token('https://fap-studios.de/anknotes/index.html') url = client.get_authorize_url(request_token) showInfo("We will open a Evernote Tab in your browser so you can allow access to your account") openLink(url) oauth_verifier = getText(prompt="Please copy the code that showed up, after allowing access, in here")[0] auth_token = client.get_access_token( request_token.get('oauth_token'), request_token.get('oauth_token_secret'), oauth_verifier) mw.col.conf[SETTING_TOKEN] = auth_token else: auth_token = mw.col.conf.get(SETTING_TOKEN, False) self.token = auth_token self.client = EvernoteClient(token=auth_token, sandbox=False) self.noteStore = self.client.get_note_store()
def _linkHandler(self, url): if ":" in url: (cmd, arg) = url.split(":") else: cmd = url if cmd == "open": self._selDeck(arg) elif cmd == "opts": self._showOptions(arg) elif cmd == "shared": self._onShared() elif cmd == "import": self.mw.onImport() elif cmd == "cram": self.mw.onCram() elif cmd == "create": showInfo(_("""\ To create a new deck, simply enter its name into any place that ask for \ a deck name, such as when adding notes, changing a card's deck while browsing, \ or importing text files.""")) elif cmd == "drag": draggedDeckDid, ontoDeckDid = arg.split(',') self._dragDeckOnto(draggedDeckDid, ontoDeckDid) elif cmd == "collapse": self._collapse(arg)
def check_command(command, command_type, options): if command_type in command: value = command[command_type] if value not in options: showInfo(error_message.format(command_type, value, command)) return False return True
def updateCollection(self): f = self.form d = self.mw.col if not isMac: wasAccel = self.mw.pm.glMode() != "software" wantAccel = f.hwAccel.isChecked() if wasAccel != wantAccel: if wantAccel: self.mw.pm.setGlMode("auto") else: self.mw.pm.setGlMode("software") showInfo(_("Changes will take effect when you restart Anki.")) qc = d.conf qc['dueCounts'] = f.showProgress.isChecked() qc['estTimes'] = f.showEstimates.isChecked() qc['newSpread'] = f.newSpread.currentIndex() qc['nightMode'] = f.nightMode.isChecked() qc['timeLim'] = f.timeLimit.value()*60 qc['collapseTime'] = f.lrnCutoff.value()*60 qc['addToCur'] = not f.useCurrent.currentIndex() qc['dayLearnFirst'] = f.dayLearnFirst.isChecked() self._updateDayCutoff() self._updateSchedVer(f.newSched.isChecked()) d.setMod()
def getVerse(self): ''' Lookup verse from ESV web-service and set results in current note. self should be set to Editor ''' #open ESV api connection and run query against reference esv = EsvSession() if not esv.query(self.note[REF_FIELD]): #query did not return successfully showInfo("No such passage: %s" % (self.note[REF_FIELD])) return #Retrive audio into a temporary folder path = namedtmp(os.path.basename(esv.esvMp3Link), True) urllib.urlretrieve(esv.esvMp3Link, path) #set note fields self.note[AUDIO_FIELD] = self._addMedia(path) self.note[TEXT_FIELD] = esv.esvText self.note[REF_FIELD] = esv.esvRef self.note[HINT_FIELD] = ",".join(esv.esvText.split()[:5]).replace(",", " ") #set 'y' for any additional card types that need to be generated for cardName in ADDTL_CARDS: self.note[cardName] = 'y' #reload note, makes things show up self.loadNote()
def fill_sounds(collection, view_key): if view_key == "deckBrowser": return showInfo(u"First select one of your decks") query_str = "deck:current" d_scanned = 0 d_success = 0 d_failed = 0 d_full = 0 notes = Finder(collection).findNotes(query_str) mw.progress.start(immediate=True) for noteId in notes: d_scanned = d_scanned+1 note = collection.getNote(noteId) note_dict = dict(note) # edit_function routines require a dict if has_field(Sound_fields, note_dict): if get_any(Sound_fields, note_dict)=="": s = sound(note_dict["Hanzi"]) if "" == s: d_failed = d_failed+1 else: set_all(Sound_fields, note_dict, to = s) d_success = d_success+1 # write back to note from dict and flush for f in Sound_fields: if note_dict.has_key(f) and note_dict[f] <> note[f]: note[f] = note_dict[f] note.flush() else: d_full = d_full+1 mw.progress.finish() msg_string = "%(empty)d/%(total)d empty Chinese notes in current deck<br>%(filled)d/%(empty)d notes were filled successfully<br>%(failed)d/%(empty)d notes fill failed."% {"empty":d_scanned-d_full, "total":d_scanned, "filled":d_success, "failed":d_failed} if d_failed>0: msg_string = msg_string+"<br><br>Please check your network connexion, or retry later." showInfo(msg_string)
def markdownconverter(arg = None): if arg is None: col = mw.col else: col = arg changed = 0 # Iterate over the cards ids = col.findCards("tag:Markdown") for id in ids: card = col.getCard(id) note = card.note() for (name, value) in note.items(): converted = '' converted = remade(value) #showInfo("%s:\n%s" % (name, converted) ) note[name] = converted note.delTag("Markdown") ++changed note.flush() # mw.reset() if arg is None: showInfo("Done: %d db" % changed) if changed > 0: return changed
def getSubscribeDecks(self, subs): for sub in subs: try: jsonResponse = self.server.getDeck(sub) # Uncomment this line to see data in retrieved deck #showInfo('Success! Result is ' + str(jsonResponse[0])) if len(jsonResponse) > 0: deck = jsonResponse[0] cards = deck['cards'] toFile = '' for card in cards: toFile += '%s; %s;\n' % (card['notes']['Front'], card['notes']['Back']) directory = os.path.dirname(__file__) filename = directory + '\import.txt' file = open(filename, 'w') file.write(toFile) file.close() self.importDeckFromCSV(filename, deck['name']) #self.deckCol.append(deck) # Adds retrieved deck to internal AnkiHub Deck Collection except HTTPError, e: showInfo(str('Subscription Download Error: %d - %s' % (e.code, str(json.loads(e.read()))))) except URLError, e: showInfo(str(e.args))
def connect(self, endpoint): def connectAction(): self.createLoadingScreen() self.username = mw.login.username.text() password = mw.login.password.text() try: jsonResponse = None if 'Login' in endpoint: jsonResponse = self.server.login(self.username, password) else: jsonResponse = self.server.signup(self.username, password) mw.login.close() self.username = jsonResponse['user']['username'] self.sessionToken = jsonResponse['user']['sessionToken'] showInfo('Success! Logged in as ' + jsonResponse['user']['username']) self.getSubscribeDecks(jsonResponse['user']['subscriptions']) self.processDecks() mw.loading.close() self.createSettings() except HTTPError, e: showInfo(str('%s Error: %d - %s' % (endpoint, e.code, json.loads(e.read())))) except URLError, e: showInfo(str(e.args))
def on_lookup_wadoku_selection(): init_nachschlagen() try: # Empty list (or possibly 'None') means selection mw.nachschlagen.wadoku([]) except ValueError as ve: showInfo(str(ve))
def clean(pl): if pl.poll() is not None: pl.wait() return False else: showInfo("Clean") return True
def setup_client(self): auth_token = SETTINGS.EVERNOTE.AUTH_TOKEN.fetch() if not auth_token: # First run of the Plugin we did not save the access key yet secrets = {'holycrepe': '36f46ea5dec83d4a', 'scriptkiddi-2682': '965f1873e4df583c'} client = EvernoteClient( consumer_key=EVERNOTE.API.CONSUMER_KEY, consumer_secret=secrets[EVERNOTE.API.CONSUMER_KEY], sandbox=EVERNOTE.API.IS_SANDBOXED ) request_token = client.get_request_token('https://fap-studios.de/anknotes/index.html') url = client.get_authorize_url(request_token) showInfo("We will open a Evernote Tab in your browser so you can allow access to your account") openLink(url) oauth_verifier = getText(prompt="Please copy the code that showed up, after allowing access, in here")[0] auth_token = client.get_access_token( request_token.get('oauth_token'), request_token.get('oauth_token_secret'), oauth_verifier) SETTINGS.EVERNOTE.AUTH_TOKEN.save(auth_token) else: client = EvernoteClient(token=auth_token, sandbox=EVERNOTE.API.IS_SANDBOXED) self.token = auth_token self.client = client log("Set up Evernote Client", 'client')
def my_keyHandler(self, evt): #global messageBuff global audio_speed, audio_replay key = unicode(evt.text()) if key == "0": audio_speed = 1.0 elif key == "[": audio_speed = max(0.1, audio_speed - 0.1) elif key == "]": audio_speed = min(4.0, audio_speed + 0.1) if key in "0[]": if audio_replay: play(audio_file) elif anki.sound.mplayerManager is not None: if anki.sound.mplayerManager.mplayer is not None: anki.sound.mplayerManager.mplayer.stdin.write("af_add scaletempo=stride=10:overlap=0.8\n") anki.sound.mplayerManager.mplayer.stdin.write(("speed_set %f \n" % audio_speed)) if key == "p": anki.sound.mplayerManager.mplayer.stdin.write("pause\n") elif key == "l": audio_replay = not audio_replay if audio_replay: showInfo("Auto Replay ON") else: showInfo("Auto Replay OFF") if key == "r": anki.sound.mplayerClear = True
def add_deck(self, pos): #TODO handle this case better if len(get_decks()) == len(self.app_settings.get('DeckEdit')): showInfo('exhausted all possible decks') return deck_edit_window = DeckEdit(self.app_settings, self) self._show_deck_edit(deck_edit_window)
def __init__(self): if not mw.col.conf.get(SETTING_TOKEN, False): # First run of the Plugin we did not save the access key yet client = EvernoteClient( consumer_key='scriptkiddi-2682', consumer_secret='965f1873e4df583c', sandbox=False ) request_token = client.get_request_token('http://brunomart.in/anknotes/index.html') url = client.get_authorize_url(request_token) showInfo("We will open a Evernote Tab in your browser so you can allow access to your account") openLink(url) oauth_verifier = getText(prompt="Please copy the code that showed up, after allowing access, in here")[0] secret_key = getText(prompt="protect your value with a password, it will be asked each time you import your notes")[0] auth_token = client.get_access_token( request_token.get('oauth_token'), request_token.get('oauth_token_secret'), oauth_verifier) mw.col.conf[SETTING_TOKEN] = encode(secret_key, auth_token) else: secret_key = getText(prompt="password")[0] auth_token_encoded = mw.col.conf.get(SETTING_TOKEN, False) auth_token = decode(secret_key, auth_token_encoded) self.token = auth_token self.client = EvernoteClient(token=auth_token, sandbox=False) self.noteStore = self.client.get_note_store()
def bulkReplace(self): """ Performs search-and-replace on selected notes """ nids = self.selectedNotes() if not nids: return # Allow undo self.mw.checkpoint(_("Fix word wrap on selected cards (remove non-breaking space)")) self.mw.progress.start() # Not sure if beginReset is required self.model.beginReset() changed = self.col.findReplace(nids, # Selected note IDs ' ', # from ' ', # to whitespace True, # treat as regular expression None, # all note fields True) # case insensitivity self.model.endReset() self.mw.progress.finish() # Display a progress meter showInfo(ngettext( "%(a)d of %(b)d note updated", "%(a)d of %(b)d notes updated", len(nids)) % { 'a': changed, 'b': len(nids), })
def onCloze(self): # check that the model is set up for cloze deletion if not re.search("{{(.*:)*cloze:", self.note.model()["tmpls"][0]["qfmt"]): if self.addMode: tooltip(_("Warning, cloze deletions will not work until " "you switch the type at the top to Cloze.")) else: showInfo( _( """\ To make a cloze deletion on an existing note, you need to change it \ to a cloze type first, via Edit>Change Note Type.""" ) ) return # find the highest existing cloze highest = 0 for name, val in self.note.items(): m = re.findall("\{\{c(\d+)::", val) if m: highest = max(highest, sorted([int(x) for x in m])[-1]) # reuse last? if not self.mw.app.keyboardModifiers() & Qt.AltModifier: highest += 1 # must start at 1 highest = max(1, highest) self.web.eval("wrap('{{c%d::', '}}');" % highest)
def __init__(self, app, profileManager, args): QMainWindow.__init__(self) self.state = "startup" aqt.mw = self self.app = app if isWin: self._xpstyle = QStyleFactory.create("WindowsXP") self.app.setStyle(self._xpstyle) self.pm = profileManager # running 2.0 for the first time? if self.pm.meta['firstRun']: # load the new deck user profile self.pm.load(self.pm.profiles()[0]) # upgrade if necessary from aqt.upgrade import Upgrader u = Upgrader(self) u.maybeUpgrade() self.pm.meta['firstRun'] = False self.pm.save() # init rest of app try: self.setupUI() self.setupAddons() except: showInfo(_("Error during startup:\n%s") % traceback.format_exc()) sys.exit(1) # were we given a file to import? if args and args[0]: self.onAppMsg(unicode(args[0], "utf8", "ignore")) # Load profile in a timer so we can let the window finish init and not # close on profile load error. self.progress.timer(10, self.setupProfile, False)
def remGroup(self): if self.conf['id'] == 1: showInfo(_("The default configuration can't be removed."), self) else: self.mw.col.decks.remConf(self.conf['id']) self.deck['conf'] = 1 self.loadConfs()
def loadImages(self): query = getTextFromHTML(self.parent.editor.note["Front"]) query = "".join(x for x in query if x.isalnum()) # NOTE: Google returns a different table-based page structure when using urllib with the above user agent. # Keep this in mind when editing the scraper. opener = urllib2.build_opener() opener.addheaders = [("Accept Language", "en-GB,en-US;q=0.8,en;q=0.6"), ("User-agent", "Mozilla/5.0")] try: page = opener.open("http://www.google.com/search?tbm=isch&q=" + query) except: # TODO print error msg self.close() showInfo("Error downloading images") return imageContainer = BeautifulSoup(page.read()).find("table", {"class": "images_table"}) images = imageContainer.findAll("img", limit=15) for image in images: imgFile = tempfile.NamedTemporaryFile(prefix=queryClean, suffix=".jpg") imgFile.seek(0) imgFile.write(urllib2.urlopen(image["src"]).read()) imgFile.flush() imgWidget = ClickableImage(imgFile) # self.destroyed.connect(imgWidget.close) # XXX: not working # mw.connect(self, SIGNAL("close"), imgWidget.close) mw.connect(imgWidget, SIGNAL("clicked"), self.selectImage) self.layout.addWidget(imgWidget) imgWidget.show()
def __init__(self, app, profileManager, args): QMainWindow.__init__(self) self.state = "startup" aqt.mw = self self.app = app self.pm = profileManager # running 2.0 for the first time? if self.pm.meta['firstRun']: # load the new deck user profile self.pm.load(self.pm.profiles()[0]) self.pm.meta['firstRun'] = False self.pm.save() # init rest of app self.safeMode = self.app.queryKeyboardModifiers() & Qt.ShiftModifier try: self.setupUI() self.setupAddons() except: showInfo(_("Error during startup:\n%s") % traceback.format_exc()) sys.exit(1) # must call this after ui set up if self.safeMode: tooltip(_("Shift key was held down. Skipping automatic " "syncing and add-on loading.")) # were we given a file to import? if args and args[0]: self.onAppMsg(args[0]) # Load profile in a timer so we can let the window finish init and not # close on profile load error. self.progress.timer(10, self.setupProfile, False)
def checkCollection(col=None, force=False): """Check for unreported cards and send them to beeminder.""" col = col or mw.col if col is None: return # reviews if REP_GOAL: reps = col.db.first("select count() from revlog")[0] last_timestamp = col.conf.get("beeminderRepTimestamp", 0) timestamp = col.db.first("select id/1000 from revlog order by id desc limit 1") if timestamp is not None: timestamp = timestamp[0] reportCards(col, reps, timestamp, "beeminderRepTotal", REP_GOAL, REP_OFFSET) if (force or timestamp != last_timestamp) and SEND_DATA: col.conf["beeminderRepTimestamp"] = timestamp col.setMod() # new cards if NEW_GOAL: new_cards = col.db.first("select count(distinct(cid)) from revlog where type = 0")[0] last_timestamp = col.conf.get("beeminderNewTimestamp", 0) timestamp = col.db.first("select id/1000 from revlog where type = 0 order by id desc limit 1") if timestamp is not None: timestamp = timestamp[0] reportCards(col, new_cards, timestamp, "beeminderNewTotal", NEW_GOAL, NEW_OFFSET) if (force or timestamp != last_timestamp) and SEND_DATA: col.conf["beeminderNewTimestamp"] = timestamp col.setMod() if force and (REP_GOAL or NEW_GOAL): showInfo("Synced with Beeminder.")
def loadProfile(self): # show main window if self.pm.profile['mainWindowState']: restoreGeom(self, "mainWindow") restoreState(self, "mainWindow") # toolbar needs to be retranslated self.toolbar.draw() # titlebar self.setWindowTitle("Anki - " + self.pm.name) # show and raise window for osx self.show() self.activateWindow() self.raise_() # maybe sync (will load DB) if self.pendingImport and os.path.basename( self.pendingImport).startswith("backup-"): # skip sync when importing a backup self.loadCollection() else: self.onSync(auto=True) # import pending? if self.pendingImport: if self.pm.profile['key']: showInfo(_("""\ To import into a password protected profile, please open the profile before attempting to import.""")) else: self.handleImport(self.pendingImport) self.pendingImport = None runHook("profileLoaded")
def getTransactions(self, gid): try: jsonResponse = self.server.getTransactions(gid) return jsonResponse except HTTPError, e: showInfo(str('Transaction Download Error: %d - %s' % (e.code, str(json.loads(e.read())))))
def onAppMsg(self, buf): if self.state == "startup": # try again in a second return self.progress.timer(1000, lambda: self.onAppMsg(buf), False) elif self.state == "profileManager": # can't raise window while in profile manager if buf == "raise": return self.pendingImport = buf return tooltip(_("Deck will be imported when a profile is opened.")) if not self.interactiveState() or self.progress.busy(): # we can't raise the main window while in profile dialog, syncing, etc if buf != "raise": showInfo(_("""\ Please ensure a profile is open and Anki is not busy, then try again."""), parent=None) return # raise window if isWin: # on windows we can raise the window by minimizing and restoring self.showMinimized() self.setWindowState(Qt.WindowActive) self.showNormal() else: # on osx we can raise the window. on unity the icon in the tray will just flash. self.activateWindow() self.raise_() if buf == "raise": return # import self.handleImport(buf)
def handleImport(self, path): import aqt.importing if not os.path.exists(path): return showInfo(_("Please use File>Import to import this file.")) aqt.importing.importFile(self, path)
def testFunction(): # get the number of cards in the current collection, which is stored in # the main window cardCount = mw.col.cardCount() # show a message box showInfo("Card count: %d" % cardCount)
def accept(self): f = self.form i = self.radioIdx spin = f.spin.value() if i == RADIO_NEW: self.deck["extendNew"] = spin self.mw.col.decks.save(self.deck) self.mw.col.sched.extendLimits(spin, 0) self.mw.reset() return QDialog.accept(self) elif i == RADIO_REV: self.deck["extendRev"] = spin self.mw.col.decks.save(self.deck) self.mw.col.sched.extendLimits(0, spin) self.mw.reset() return QDialog.accept(self) elif i == RADIO_CRAM: tags = self._getTags() # the rest create a filtered deck cur = self.mw.col.decks.byName(tr( TR.CUSTOM_STUDY_CUSTOM_STUDY_SESSION)) if cur: if not cur["dyn"]: showInfo(tr(TR.CUSTOM_STUDY_MUST_RENAME_DECK)) return QDialog.accept(self) else: # safe to empty self.mw.col.sched.empty_filtered_deck(cur["id"]) # reuse; don't delete as it may have children dyn = cur self.mw.col.decks.select(cur["id"]) else: did = self.mw.col.decks.new_filtered( tr(TR.CUSTOM_STUDY_CUSTOM_STUDY_SESSION)) dyn = self.mw.col.decks.get(did) # and then set various options if i == RADIO_FORGOT: dyn["terms"][0] = ["rated:%d:1" % spin, DYN_MAX_SIZE, DYN_RANDOM] dyn["resched"] = False elif i == RADIO_AHEAD: dyn["terms"][0] = ["prop:due<=%d" % spin, DYN_MAX_SIZE, DYN_DUE] dyn["resched"] = True elif i == RADIO_PREVIEW: dyn["terms"][0] = [ "is:new added:%s" % spin, DYN_MAX_SIZE, DYN_OLDEST ] dyn["resched"] = False elif i == RADIO_CRAM: type = f.cardType.currentRow() if type == TYPE_NEW: terms = "is:new " ord = DYN_ADDED dyn["resched"] = True elif type == TYPE_DUE: terms = "is:due " ord = DYN_DUE dyn["resched"] = True elif type == TYPE_REVIEW: terms = "-is:new " ord = DYN_RANDOM dyn["resched"] = True else: terms = "" ord = DYN_RANDOM dyn["resched"] = False dyn["terms"][0] = [(terms + tags).strip(), spin, ord] # add deck limit dyn["terms"][0][0] = 'deck:"%s" %s ' % (self.deck["name"], dyn["terms"][0][0]) self.mw.col.decks.save(dyn) # generate cards self.created_custom_study = True if not self.mw.col.sched.rebuild_filtered_deck(dyn["id"]): return showWarning( tr(TR.CUSTOM_STUDY_NO_CARDS_MATCHED_THE_CRITERIA_YOU)) self.mw.moveToState("overview") QDialog.accept(self)
def get_definition(editor): # ideally, users wouldn't have to do this, but the API limit is just 1000 calls/day. That could easily happen with just a few users. if (MERRIAM_WEBSTER_API_KEY == "YOUR_KEY_HERE"): message = "AutoDefine requires use of Merriam-Webster's Collegiate Dictionary with Audio API. To get functionality working:\n" message += "1. Go to www.dictionaryapi.com and sign up for an account, requesting access to the Collegiate Dictionary.\n" message += "2. In Anki, go to Tools>Add-Ons>AutoDefine>Edit... and replace YOUR_KEY_HERE with your unique API key.\n" showInfo(message) return # Random Anki loading editor.loadNote() editor.web.setFocus() editor.web.eval("focusField(0);") editor.web.eval("caretToEnd();") allFields = editor.note.fields word = allFields[0] url = "http://www.dictionaryapi.com/api/v1/references/collegiate/xml/" + word + "?key=" + MERRIAM_WEBSTER_API_KEY etree = ET.parse(urllib.urlopen(url)) allEntries = etree.findall("entry") definitionArray = [] if (INSERT_PRONUNCIATIONS): # Parse all unique pronunciations, and convert them to URLs as per http://goo.gl/nL0vte allSounds = [] for entry in allEntries: if entry.attrib["id"][:len(word)+1] == word + "[" or entry.attrib["id"] == word: for wav in entry.findall("sound/wav"): rawWav = wav.text # API-specific URL conversions if rawWav[:3] == "bix": midURL = "bix" elif rawWav[:2] == "gg": midURL = "gg" elif rawWav[:1].isdigit(): midURL = "number" else: midURL = rawWav[:1] wavURL = "http://media.merriam-webster.com/soundc11/" + midURL + "/" + rawWav allSounds.append(editor.urlToFile(wavURL).strip()) # we want to make this a non-duplicate set, so that we only get unique sound files. allSounds = OrderedSet(allSounds) for soundLocalFilename in reversed(allSounds): allFields[0] += '[sound:' + soundLocalFilename + ']' # Extract the type of word this is for entry in allEntries: if entry.attrib["id"][:len(word)+1] == word + "[" or entry.attrib["id"] == word: thisDef = entry.find("def") fl = entry.find("fl").text if fl == "verb": fl = "v." elif fl == "noun": fl = "n." elif fl == "adverb": fl = "adv." elif fl == "adjective": fl = "adj." thisDef.tail = "<b>" + fl + "</b>" # save the functional label (noun/verb/etc) in the tail # the <ssl> tag will contain the word 'obsolete' if the term is not in use anymore. However, for some reason, the tag # precedes the <dt> that it is associated with instead of being a child. We need to associate it here so that later # we can either remove or keep it regardless. previousWasSSL = False for child in thisDef: # this is a kind of poor way of going about things, but the ElementTree API doesn't seem to offer an alternative. if child.text == "obsolete" and child.tag == "ssl": previousWasSSL = True if previousWasSSL and child.tag == "dt": child.tail = "obsolete" previousWasSSL = False definitionArray.append(thisDef) toReturn = "" for definition in definitionArray: lastFunctionalLabel = "" toPrint = "" for dtTag in definition.findall("dt"): if dtTag.tail == "obsolete": dtTag.tail = "" #take away the tail word so that when printing it does not show up. if IGNORE_ARCHAIC: continue # We don't really care for 'verbal illustrations' or 'usage notes', even though they are occasionally useful. for usageNote in dtTag.findall("un"): dtTag.remove(usageNote) for verbalIllustration in dtTag.findall("vi"): dtTag.remove(verbalIllustration) # Directional cross reference doesn't make sense for us for dxTag in dtTag.findall("dx"): for dxtTag in dxTag.findall("dxt"): for dxnTag in dxtTag.findall("dxn"): dxtTag.remove(dxnTag) toPrint = ET.tostring(dtTag, "", "xml").strip() # extract raw XML from <dt>...</dt> toPrint = toPrint.replace("<sx>", "; ") # attempt to remove 'synonymous cross reference tag' and replace with semicolon toPrint = toPrint.replace("<dx>", "; ") # attempt to remove 'Directional cross reference tag' and replace with semicolon toPrint = re.sub('<[^>]*>', '', toPrint) # remove all other XML tags toPrint = re.sub(':', '', toPrint) # remove all colons, since they are usually useless and have been replaced with semicolons above toPrint = toPrint.replace(" ; ", "; ").strip() # erase space between semicolon and previous word, if exists, and strip any extraneous whitespace toPrint += "<br>\n" # add verb/noun/adjective if (lastFunctionalLabel != definition.tail): toPrint = definition.tail + " " + toPrint # but don't add an extra carriage return for the first definition #if (definition != definitionArray[0]): # toPrint = "<br>\n" + toPrint lastFunctionalLabel = definition.tail toReturn += toPrint # final cleanup of <sx> tag bs toReturn = toReturn.replace(".</b> ; ", ".</b> ") #<sx> as first definition after "n. " or "v. " toReturn = toReturn.replace("\n; ", "\n") #<sx> as first definition after newline allFields[1] = toReturn editor.web.eval("setFields(%s, %d);" % (allFields, 0)) # not sure exactly how saving works, but focusing different fields seems to help. editor.loadNote() editor.web.eval("focusField(0);") editor.web.eval("focusField(1);") editor.web.eval("focusField(0);") if (OPEN_IMAGES_IN_BROWSER): webbrowser.open("https://www.google.com/search?q= "+ word + "&safe=off&tbm=isch&tbs=isz:lt,islt:xga", 0, False)
def showDialog(self, currentCard=None): # Handle for dialog open without a current card from IR model did = None cid = None if not currentCard: deck = mw._selectedDeck() did = deck['id'] else: did = currentCard.did cid = currentCard.id cardDataList = self.getCardDataList(did, cid) if not cardDataList: showInfo(_('Please select an Incremental Reading deck.')) return d = QDialog(mw) l = QVBoxLayout() w = AnkiWebView() l.addWidget(w) script = ''' var cardList = new Array(); ''' index = 0 for cardData in cardDataList: index += 1 script += "card = new Object();" script += "card.id = " + str(cardData['id']) + ";" script += "card.title = '" + str(cardData['title']) + "';" script += "card.isCurrent = " + str(cardData['isCurrent']) + ";" script += "card.checkbox = document.createElement('input');" script += "card.checkbox.type = 'checkbox';" if cardData['isCurrent'] == 'true': script += "card.checkbox.setAttribute('checked', 'true');" script += "cardList[cardList.length] = card;" script += """ function buildCardData() { var container = document.getElementById('cardList'); container.innerHTML = ''; var list = document.createElement('div'); list.setAttribute('style','overflow:auto;'); var table = document.createElement('table'); list.appendChild(table); container.appendChild(list); var row; var col; var cardData; for (var i = 0; i < cardList.length; i++) { row = document.createElement('tr'); row.setAttribute('id','row' + i); cardData = cardList[i]; col = document.createElement('td'); col.setAttribute('style','width:4em;'); col.innerHTML = '' + i; row.appendChild(col); col = document.createElement('td'); col.setAttribute('style','width:10em;'); col.innerHTML = '' + cardData.id; row.appendChild(col); col = document.createElement('td'); col.setAttribute('style','width:30em;'); col.innerHTML = '' + cardData.title; row.appendChild(col); col = document.createElement('td'); col.setAttribute('style','width:2em;'); col.appendChild(cardData.checkbox); row.appendChild(col); table.appendChild(row); } } function reposition(origIndex, newIndex, isTopOfRange) { if (newIndex < 0 || newIndex > (cardList.length-1)) return -1; if (cardList[newIndex].checkbox.checked) return -1; if (isTopOfRange) { document.getElementById('newPos').value = newIndex; } var removedCards = cardList.splice(origIndex,1); cardList.splice(newIndex, 0, removedCards[0]); return newIndex; } function moveSelectedUp() { var topOfRange = -1; for (var i = 0; i < cardList.length; i++) { if (cardList[i].checkbox.checked) { if (topOfRange == -1) { topOfRange = i; } if (i == topOfRange) { if (document.getElementById('anchor').checked) { continue; //Don't move end of range if anchored. } else { reposition(i, i - 1, true); } } else { reposition(i, i - 1, false); } } } buildCardData(); } function moveSelectedDown() { var topOfRange = -1; var bottomOfRange = -1; for (var i = 0; i < cardList.length; i++) { if (cardList[i].checkbox.checked) { if (topOfRange == -1) { topOfRange = i; } bottomOfRange = i; } } for (var i = cardList.length-1; i > -1; i--) { if (cardList[i].checkbox.checked) { if (i == bottomOfRange && document.getElementById('anchor').checked) { continue; //Don't move end of range if anchored. } if (i == topOfRange) { reposition(i, i + 1, true); } else { reposition(i, i + 1, false); } } } buildCardData(); } function selectAll() { for (var i = 0; i < cardList.length; i++) { cardList[i].checkbox.checked = true; } } function selectNone() { for (var i = 0; i < cardList.length; i++) { cardList[i].checkbox.checked = false; } } function directMove() { var newIndex = document.getElementById('newPos').value; var topOfRange = -1; origIndex = -1; for (var i = 0; i < cardList.length; i++) { if (cardList[i].checkbox.checked) { if (topOfRange == -1) { topOfRange = i; } if (origIndex == -1) { origIndex = i; sizeOfMove = (newIndex - origIndex); } } } if (sizeOfMove < 0) { for (var i = 0; i < cardList.length; i++) { if (cardList[i].checkbox.checked) { if (i == topOfRange) { reposition(i, i + sizeOfMove, true); } else { reposition(i, i + sizeOfMove, false); } } } } else { for (var i = cardList.length-1; i > -1; i--) { if (cardList[i].checkbox.checked) { if (i == topOfRange) { reposition(i, i + sizeOfMove, true); } else { reposition(i, i + sizeOfMove, false); } } } } buildCardData(); } function updatePositions() { var cids = new Array(); for (var i=0; i < cardList.length; i++) { cids[cids.length] = parseInt(cardList[i].id); } return cids.join(); }; """ newPosField = "<span style='font-weight:bold'>Card Position: </span><input type='text' id='newPos' size='5' value='0' /> <span style='font-weight:bold'>of " + str( len(cardDataList)) + "</span> " newPosField += "<input type='button' value='Apply' onclick='directMove()' /> <span style='font-weight:bold'>Pin Top/Bottom? </span><input type='checkbox' id='anchor'/>" upDownButtons = "<input type='button' value='Move Up' onclick='moveSelectedUp()'/><input type='button' value='Move Down' onclick='moveSelectedDown()'/>" upDownButtons += "<input type='button' value='Select All' onclick='selectAll()'/><input type='button' value='Select None' onclick='selectNone()'/>" html = "<html><head><script>" + script + "</script></head><body onLoad='buildCardData()'>" html += "<p>" + newPosField html += "<p>" + upDownButtons html += "<div id='cardList'></div>" html += "</body></html>" w.stdHtml(html) bb = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Save) bb.accepted.connect(d.accept) bb.rejected.connect(d.reject) bb.setOrientation(Qt.Horizontal) l.addWidget(bb) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 500) choice = d.exec_() if choice == 1: if ANKI_21: cids = w.page().runJavaScript('updatePositions()', self.callback) else: cids = w.page().mainFrame().evaluateJavaScript( 'updatePositions()') self.repositionCards(cids) elif currentCard: self.repositionCard(currentCard, -1)
def onLangIdxChanged(self, idx): code = anki.lang.langs[idx][1] self.mw.pm.setLang(code) showInfo(_("Please restart Anki to complete language change."), parent=self)
def _on_accept(self): if self._handle_save_to_default: params = { 'num_audios': self.num_audios.text(), 'num_plays': self.num_plays.text(), 'num_copies': self.num_copies.text(), 'default_waiting_time': self.default_waiting_time.text(), 'additional_waiting_time': self.additional_waiting_time.text(), 'sample_rate': self.sample_rate.text(), } dump_file = open('params', 'wb') dump(params, dump_file) path = None directory = None if not self.advance_mode: dialog = SaveFileDialog(self.deck_selection.currentText().replace( "::", "_")) path = dialog.filename if path == None: return else: init_directory = expanduser("~/Desktop") directory = str( QFileDialog.getExistingDirectory( self, "Select directory to save outputs", init_directory, QFileDialog.ShowDirsOnly)) CustomMessageBox.showWithTimeout(1, "Start exporting", "Notification", icon=QMessageBox.Information, buttons=QMessageBox.Ok) combines = [] names = [] num_cps = [] if self.advance_mode: with open(self.path) as f: i = 0 for line in f: if i == 0: i += 1 continue splitted_fields = line.split(',') deck_name = splitted_fields[0] num_audios = int(splitted_fields[1].strip()) num_plays = int(splitted_fields[2].strip()) num_copies = int(splitted_fields[3].strip()) default_waiting_time = float(splitted_fields[4].strip()) additional_waiting_time = float(splitted_fields[5].strip()) mode = splitted_fields[6].strip() output_name = splitted_fields[7].strip() channel = channel_map[self.channel.currentText()] combines = generate_audio(deck_name, num_audios, num_plays, num_copies, default_waiting_time, additional_waiting_time, mode, self.change_channel, channel) if '.mp3' not in output_name: output_name += '.mp3' if num_copies == 1: combine = combines[0] if platform.system() == 'Windows': combine.export(directory + '\\' + output_name, format='mp3', parameters=['-ac', str(channel)]) else: combine.export(directory + '/' + output_name, format='mp3', parameters=['-ac', str(channel)]) else: for i in range(num_copies): combine = combines[i] new_name = output_name[:-4] + "-" + str(i + 1) + ".mp3" if platform.system() == 'Windows': combine.export( directory + '\\' + new_name, format='mp3', parameters=['-ac', str(channel)]) else: combine.export( directory + '/' + new_name, format='mp3', parameters=['-ac', str(channel)]) utils.showInfo("Export to audio successfully!") self.advance_mode = False self.csv_file_label.setText('') else: ## get values deck_name = self.deck_selection.currentText() try: num_audios = int(self.num_audios.text().strip()) num_plays = int(self.num_plays.text().strip()) num_copies = int(self.num_copies.text().strip()) default_waiting_time = float( self.default_waiting_time.text().strip()) additional_waiting_time = float( self.additional_waiting_time.text().strip()) _ = int(self.sample_rate.text().strip()) except Exception as e: utils.showInfo("You must enter a positive integer.") return if num_audios <= 0 or num_plays <= 0 or num_copies < 0: utils.showInfo("You must enter a positive integer.") return mode = self.mode.currentText() channel = channel_map[self.channel.currentText()] practice_mode = practice_modes.index( self.practice_mode.currentText()) combines = generate_audio(deck_name, num_audios, num_plays, num_copies, default_waiting_time, additional_waiting_time, mode, self.change_channel, channel, practice_mode) if len(combines) > 0: path = path[0].replace("::", "_") if num_copies == 1: combines[0].export(path, format='mp3', parameters=['-ac', str(channel)]) else: for i in range(num_copies): combine = combines[i] new_path = path[:-4] + "-" + str(i + 1) + ".mp3" combine.export(new_path, format='mp3', parameters=['-ac', str(channel)]) utils.showInfo("Export to audios successfully!") else: utils.showInfo("Cannot export to audios.")
def generate_audio(deck_name, num_audios, num_plays, num_copies, default_waiting_time, additional_waiting_time, mode, change_channel, channel, practice_mode=0): if isinstance(deck_name, str): deck_name = deck_name deck_name = deck_name.replace('"', '') deck_name = unicodedata.normalize('NFC', deck_name) deck = mw.col.decks.byName(deck_name) if deck == None: utils.showInfo("Deck {} does not exist.".format(deck_name)) decks = [] if len(mw.col.decks.children(deck['id'])) == 0: decks = [ deck_name, ] else: decks = [name for (name, _) in mw.col.decks.children(deck['id'])] deck_audios = [] for name in decks: query = 'deck:"{}"'.format(name) card_ids = mw.col.findCards(query=query) children_audios = [ ] ## each element of audios is a dict contain audios for front and back card for cid in card_ids: card = mw.col.getCard(cid) card_audios = [] audio_fields_dict = {} audio_fields_list = [] for field, value in card.note().items(): match = prog.findall(value) if match: audio_fields_dict[field] = [] audio_fields_list.append(field) if platform.system() == 'Windows': media_path = mw.col.path.rsplit( '\\', 1)[0] + '\\collection.media\\' else: media_path = mw.col.path.rsplit( '/', 1)[0] + '/collection.media/' for audio in match: file_path = media_path + audio audio_fields_dict[field].append(file_path) front_audio_fields, back_audio_fields = split_audio_fields( card, audio_fields_list) audio_dict = {} audio_dict['cid'] = cid audio_dict['practice_mode'] = practice_mode % 2 audio_dict['additional_waiting_time'] = additional_waiting_time audio_dict['default_waiting_time'] = default_waiting_time audio_dict['front'] = [] audio_dict['back'] = [] for faf in front_audio_fields: audio_dict['front'].extend(audio_fields_dict[faf]) for baf in back_audio_fields: audio_dict['back'].extend(audio_fields_dict[baf]) children_audios.append(audio_dict) deck_audios.append(children_audios) combines = [] if mode == 'Random subdecks': for _ in range(num_copies): audios = [] shuffle(deck_audios) for children_audios in deck_audios: shuffle(children_audios) for audio in children_audios: audios.append(audio) audios = group_audios(audios, num_plays, num_audios) combines.append( combine_audios(audios, channel, default_waiting_time, change_channel, additional_waiting_time, practice_mode)) else: for _ in range(num_copies): audios = [] for children_audios in deck_audios: for audio in children_audios: audios.append(audio) if mode == 'Random all': shuffle(audios) audios = group_audios(audios, num_plays, num_audios) elif mode == 'Overview': audios = group_audios(audios, num_plays, num_audios, overview=True) combines.append( combine_audios(audios, channel, default_waiting_time, change_channel, additional_waiting_time, practice_mode)) return combines
def joinCard(isPrev=False, isNext=False): if mw.state == "review" and mw.reviewer.card != None and ( mw.reviewer.card.model()["name"] == "movies2anki (add-on)" or mw.reviewer.card.model()["name"].startswith( "movies2anki - subs2srs")): m = re.match( r"^(.*?)_(\d+\.\d\d\.\d\d\.\d+)-(\d+\.\d\d\.\d\d\.\d+).*$", mw.reviewer.card.note()["Audio"]) card_prefix, time_start, time_end = m.groups() time_start = timeToSeconds(time_start) time_end = timeToSeconds(time_end) cards = sorted(mw.col.findCards("deck:current ", order=True)) card_idx = cards.index(mw.reviewer.card.id) prev_card_idx = card_idx - 1 if card_idx - 1 > 0 else 0 next_card_idx = card_idx + 1 if next_card_idx >= len(cards): next_card_idx = len(cards) - 1 if (isPrev and mw.col.getCard(cards[prev_card_idx]).id == mw.reviewer.card.id) or \ (isNext and mw.col.getCard(cards[next_card_idx]).id == mw.reviewer.card.id): tooltip("Nothing to do.") return curr_card = mw.col.getCard(cards[card_idx]).note() prev_card = mw.col.getCard(cards[prev_card_idx]).note() next_card = mw.col.getCard(cards[next_card_idx]).note() if (isPrev and prev_card["Source"] != curr_card["Source"]) or ( isNext and curr_card["Source"] != next_card["Source"]): showInfo( "Cards can't be joined due to the Source field difference.") return curr_card_audio = curr_card["Audio"] prev_card_audio = prev_card["Audio"] next_card_audio = next_card["Audio"] m = re.match(r"^.*?_(\d+\.\d\d\.\d\d\.\d+)-(\d+\.\d\d\.\d\d\.\d+).*$", curr_card_audio) curr_time_start, curr_time_end = m.groups() curr_time_start = timeToSeconds(curr_time_start) curr_time_end = timeToSeconds(curr_time_end) m = re.match(r"^.*?_(\d+\.\d\d\.\d\d\.\d+)-(\d+\.\d\d\.\d\d\.\d+).*$", prev_card_audio) prev_time_start, prev_time_end = m.groups() prev_time_start = timeToSeconds(prev_time_start) prev_time_end = timeToSeconds(prev_time_end) m = re.match(r"^.*?_(\d+\.\d\d\.\d\d\.\d+)-(\d+\.\d\d\.\d\d\.\d+).*$", next_card_audio) next_time_start, next_time_end = m.groups() next_time_start = timeToSeconds(next_time_start) next_time_end = timeToSeconds(next_time_end) if isPrev: time_start = prev_time_start if isNext: time_end = next_time_end c = mw.reviewer.card.note() for name, val in c.items(): if name == "Id": c[name] = "%s_%s-%s" % (card_prefix, secondsToTime(time_start), secondsToTime(time_end)) elif name == "Audio": c[name] = "%s_%s-%s.mp3" % (card_prefix, secondsToTime(time_start), secondsToTime(time_end)) elif name == "Video": c[name] = "%s_%s-%s.mp4" % (card_prefix, secondsToTime(time_start), secondsToTime(time_end)) elif name == "Source": pass elif name == "Path": pass elif name == "Audio Sound": c["Audio Sound"] = "" elif name == "Video Sound": c["Video Sound"] = "" else: if isPrev: c[name] = prev_card[name] + "<br>" + c[name] else: c[name] = c[name] + "<br>" + next_card[name] mw.checkpoint(_("Delete")) c.flush() if isPrev: cd = prev_card else: cd = next_card cnt = len(cd.cards()) mw.col.remNotes([cd.id]) mw.reset() tooltip( ngettext("Note joined and its %d card deleted.", "Note joined and its %d cards deleted.", cnt) % cnt)
def success(out: OpChangesWithCount) -> None: if out.count: tooltip(tr.browsing_notes_updated(count=out.count), parent=parent) else: showInfo(tr.browsing_tag_rename_warning_empty(), parent=parent)
def migaku(text): showInfo(text, False, "", "info", "Migaku Dictionary Add-on")
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 showWarning(self): if self.state.isChecked(): showInfo( """Warning! Whistling Dixie is a dangerous addon that may not be compatible with other addons. Even if they are compatible now, it does not guarantee that it will always be compatible. Please backup/export your database first to prevent unknown addon conflict(s).""" )
def import_model_create(name): showInfo("create model : " + name) todo()
def showDialog(self, currentCard=None): if currentCard: did = currentCard.did else: did = mw._selectedDeck()['id'] cardInfo = self._getCardInfo(did) if not cardInfo: showInfo('Please select an Incremental Reading deck.') return dialog = QDialog(mw) layout = QVBoxLayout() self.cardListWidget = QListWidget() self.cardListWidget.setAlternatingRowColors(True) self.cardListWidget.setSelectionMode( QAbstractItemView.ExtendedSelection) self.cardListWidget.setWordWrap(True) posWidth = len(str(len(cardInfo) + 1)) for i, card in enumerate(cardInfo, start=1): text = '❰ {} ❱\t{}'.format( str(i).zfill(posWidth), stripHTML(card['title'])) item = QListWidgetItem(text) item.setData(Qt.UserRole, card) self.cardListWidget.addItem(item) upButton = QPushButton('Up') upButton.clicked.connect(self._moveUp) downButton = QPushButton('Down') downButton.clicked.connect(self._moveDown) topButton = QPushButton('Top') topButton.clicked.connect(self._moveToTop) bottomButton = QPushButton('Bottom') bottomButton.clicked.connect(self._moveToBottom) randomizeButton = QPushButton('Randomize') randomizeButton.clicked.connect(self._randomize) controlsLayout = QHBoxLayout() controlsLayout.addWidget(topButton) controlsLayout.addWidget(upButton) controlsLayout.addWidget(downButton) controlsLayout.addWidget(bottomButton) controlsLayout.addStretch() controlsLayout.addWidget(randomizeButton) buttonBox = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Save) buttonBox.accepted.connect(dialog.accept) buttonBox.rejected.connect(dialog.reject) buttonBox.setOrientation(Qt.Horizontal) layout.addLayout(controlsLayout) layout.addWidget(self.cardListWidget) layout.addWidget(buttonBox) dialog.setLayout(layout) dialog.setWindowModality(Qt.WindowModal) dialog.resize(500, 500) choice = dialog.exec_() if choice == 1: cids = [] for i in range(self.cardListWidget.count()): card = self.cardListWidget.item(i).data(Qt.UserRole) cids.append(card['id']) self.reorder(cids)
def javaScriptAlert(self, frame: Any, text: str) -> None: showInfo(text)
def import_model_delete(id): showInfo("delete model : " + id) raise Exception("Fonction non créée")
def findRecursive(self, index): from .forms import findtreeitems item = index.internalPointer() TAG_TYPE = item.fullname self.found = {} self.found[TAG_TYPE] = {} d = QDialog(self.browser) frm = findtreeitems.Ui_Dialog() frm.setupUi(d) # Restore btn states frm.input.setText(self.finder.get('txt', '')) frm.input.setFocus() frm.cb_case.setChecked(self.finder.get('case', 0)) for idx, func in enumerate( (frm.btn_contains, frm.btn_exactly, frm.btn_startswith, frm.btn_endswith, frm.btn_regexp)): func.setChecked(0) if self.finder.get('radio', 0) == idx: func.setChecked(2) if not d.exec_(): return txt = frm.input.text() if not txt: return txt = unicodedata.normalize("NFC", txt) options = Qt.MatchRecursive if txt == 'vote for pedro': mw.pm.profile['Blitzkrieg.VFP'] = True from .alt import disabledDebugStuff disabledDebugStuff() self.finder['txt'] = txt self.finder['case'] = frm.cb_case.isChecked() if self.finder['case']: options |= Qt.MatchCaseSensitive if frm.btn_exactly.isChecked(): options |= Qt.MatchExactly self.finder['radio'] = 1 elif frm.btn_startswith.isChecked(): options |= Qt.MatchStartsWith self.finder['radio'] = 2 elif frm.btn_endswith.isChecked(): options |= Qt.MatchEndsWith self.finder['radio'] = 3 elif frm.btn_regexp.isChecked(): options |= Qt.MatchRegExp self.finder['radio'] = 4 else: options |= Qt.MatchContains self.finder['radio'] = 0 self.expandAllChildren(index, True) for idx in self.findItems(txt, options): itm = idx.internalPointer() if itm.type == TAG_TYPE: itm.background = QBrush(Qt.cyan) self.found[TAG_TYPE][itm.fullname] = True if not self.found[TAG_TYPE]: showInfo("Found nothing, nada, zilch!")
def infoMsg(msg): showInfo(msg) printf(msg)
def show_card_count(message=""): cardCount = mw.col.cardCount() showInfo(("Card count: %d" % cardCount) + message)
def _selectedDeck(self) -> Optional[Dict[str, Any]]: did = self.col.decks.selected() if not self.col.decks.nameOrNone(did): showInfo(_("Please select a deck.")) return None return self.col.decks.get(did)
def on_run_ocr(browser: Browser): time_start = time.time() selected_nids = browser.selectedNotes() config = mw.addonManager.getConfig(__name__) num_notes = len(selected_nids) num_batches = ceil(num_notes / config["batch_size"]) if num_notes == 0: showInfo("No cards selected.") return elif askUser(f"Are you sure you wish to run OCR processing on {num_notes} notes?") is False: return if config.get("tesseract_install_valid") is not True and config.get("text_output_location") == "new_field": showInfo( f"Note that because this addon changes the note template, you will see a warning about changing the " f"database and uploading to AnkiWeb. \n " f"This is normal, and will be shown each time you modify a note template.\n" f"This message will be only be shown once.") config["tesseract_install_valid"] = True # Stop the above msg appearing multiple times mw.addonManager.writeConfig(__name__, config) try: progress = mw.progress progress.start(immediate=True, min=0, max=num_batches) progress.update(value=0, max=num_batches, label="Starting OCR processing...") except TypeError: # old version of Qt/Anki progress = None ocr = OCR(col=mw.col, progress=progress, languages=config["languages"], text_output_location=config["text_output_location"], tesseract_exec_pth=config["tesseract_exec_path"] if config["override_tesseract_exec"] else None, batch_size=config["batch_size"], num_threads=config["num_threads"], use_batching=config["use_batching"], use_multithreading=config["use_multithreading"]) try: ocr.run_ocr_on_notes(note_ids=selected_nids) if progress: progress.finish() time_taken = time.time() - time_start log_messages = logger.handlers[0].flush() showInfo( f"Processed OCR for {num_notes} notes in {round(time_taken, 1)}s ({round(time_taken / num_notes, 1)}s per note)\n" f"{log_messages}") except pytesseract.TesseractNotFoundError: if progress: progress.finish() showCritical(text=f"Could not find a valid Tesseract-OCR installation! \n" f"Please visit the addon page in at https://ankiweb.net/shared/info/450181164 for" f" install instructions") except (RuntimeError, Exception) as exc: from . import __version__ as anki_ocr_version from anki.buildinfo import version as anki_version import sys import platform if progress: progress.finish() msg = f"Error encountered during processing. Debug info: \n" \ f"Anki Version: {anki_version} , AnkiOCR Version: {anki_ocr_version}\n" \ f"Platform: {platform.system()} , Python Version: {sys.version}" log_messages = logger.handlers[0].flush() if len(log_messages) > 0: msg += f"Logging message generated during processing:\n{log_messages}" exception_str: List[str] = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__) msg += "".join(exception_str) showInfo(msg) finally: browser.model.reset() mw.requireReset()
def bulk_fill_defs(): prompt = PROMPT_TEMPLATE.format( field_names='<i>definition</i> and <i>alternative</i>', extra_info='', ) progress_msg_template = ''' <b>Processing:</b> %(hanzi)s<br> <b>Chinese notes:</b> %(has_fields)d<br> <b>Translated:</b> %(filled)d<br> <b>Failed:</b> %(failed)d''' field_groups = ['meaning', 'english', 'german', 'french'] if not askUser(prompt): return n_targets = 0 d_success = 0 d_failed = 0 failed_hanzi = [] note_ids = Finder(mw.col).findNotes('deck:current') mw.progress.start(immediate=True, min=0, max=len(note_ids)) for i, nid in enumerate(note_ids): note = mw.col.getNote(nid) copy = dict(note) hanzi = get_hanzi(copy) if has_any_field(copy, field_groups) and hanzi: n_targets += 1 if all_fields_empty(copy, field_groups): result = fill_all_defs(hanzi, copy) if result: d_success += 1 else: d_failed += 1 if d_failed < 20: failed_hanzi += [hanzi] msg = progress_msg_template % { 'hanzi': hanzi, 'has_fields': n_targets, 'filled': d_success, 'failed': d_failed, } mw.progress.update(label=msg, value=i) save_note(note, copy) msg = ''' <b>Translation complete</b><br> <b>Chinese notes:</b> %(has_fields)d<br> <b>Translated:</b> %(filled)d<br> <b>Failed:</b> %(failed)d''' % { 'has_fields': n_targets, 'filled': d_success, 'failed': d_failed, } if d_failed > 0: msg += ( '<div>Translation failures may come either from connection issues ' "(if you're using an online translation service), or because some " 'words are not it the dictionary (for local dictionaries).</div>' '<div>The following notes failed: ' + ', '.join(failed_hanzi) + '</div>') mw.progress.finish() showInfo(msg)
def bulk_fill_sound(): prompt = PROMPT_TEMPLATE.format( field_names='<i>Sound</i>', extra_info=( '<div>There will be a 5 second delay between each sound request,' ' so this may take a while.</div>'), ) if not askUser(prompt): return d_has_fields = 0 d_already_had_sound = 0 d_success = 0 d_failed = 0 note_ids = Finder(mw.col).findNotes('deck:current') mw.progress.start(immediate=True, min=0, max=len(note_ids)) for i, nid in enumerate(note_ids): orig = mw.col.getNote(nid) copy = dict(orig) if has_any_field(copy, ['sound', 'mandarinSound', 'cantoneseSound']) and has_field( config['fields']['hanzi'], copy): d_has_fields += 1 hanzi = get_first(config['fields']['hanzi'], copy) if all_fields_empty(copy, ['sound', 'mandarinSound', 'cantoneseSound']): msg = ''' <b>Processing:</b> %(hanzi)s<br> <b>Updated:</b> %(d_success)d notes<br> <b>Failed:</b> %(d_failed)d notes''' % { 'hanzi': get_hanzi(copy), 'd_success': d_success, 'd_failed': d_failed, } mw.progress.update(label=msg, value=i) s, f = fill_sound(hanzi, copy) d_success += s d_failed += f save_note(orig, copy) sleep(5) else: d_already_had_sound += 1 mw.progress.finish() msg = ''' %(d_success)d new pronunciations downloaded %(d_failed)d downloads failed %(have)d/%(d_has_fields)d notes now have pronunciation''' % { 'd_success': d_success, 'd_failed': d_failed, 'have': d_already_had_sound + d_success, 'd_has_fields': d_has_fields, } if d_failed > 0: msg += ('TTS is taken from an online source. ' 'It may not always be fully responsive. ' 'Please check your network connexion, or retry later.') showInfo(msg)
def onlyOneSelected(self): dirs = self.selectedAddons() if len(dirs) != 1: showInfo(_("Please select a single add-on first.")) return return dirs[0]
def accept(self): f = self.form i = self.radioIdx spin = f.spin.value() if i == RADIO_NEW: self.deck['extendNew'] = spin self.mw.col.decks.save(self.deck) self.mw.col.sched.extendLimits(spin, 0) self.mw.reset() return QDialog.accept(self) elif i == RADIO_REV: self.deck['extendRev'] = spin self.mw.col.decks.save(self.deck) self.mw.col.sched.extendLimits(0, spin) self.mw.reset() return QDialog.accept(self) elif i == RADIO_CRAM: tags = self._getTags() # the rest create a filtered deck cur = self.mw.col.decks.byName(_("Custom Study Session")) if cur: if not cur['dyn']: showInfo("Please rename the existing Custom Study deck first.") return QDialog.accept(self) else: # safe to empty self.mw.col.sched.emptyDyn(cur['id']) # reuse; don't delete as it may have children dyn = cur self.mw.col.decks.select(cur['id']) else: did = self.mw.col.decks.newDyn(_("Custom Study Session")) dyn = self.mw.col.decks.get(did) # and then set various options if i == RADIO_FORGOT: dyn['delays'] = [1] dyn['terms'][0] = ['rated:%d:1' % spin, DYN_MAX_SIZE, DYN_RANDOM] dyn['resched'] = False elif i == RADIO_AHEAD: dyn['delays'] = None dyn['terms'][0] = ['prop:due<=%d' % spin, DYN_MAX_SIZE, DYN_DUE] dyn['resched'] = True elif i == RADIO_PREVIEW: dyn['delays'] = None dyn['terms'][0] = [ 'is:new added:%s' % spin, DYN_MAX_SIZE, DYN_OLDEST ] dyn['resched'] = False elif i == RADIO_CRAM: dyn['delays'] = None type = f.cardType.currentRow() if type == TYPE_NEW: terms = "is:new " ord = DYN_ADDED dyn['resched'] = True elif type == TYPE_DUE: terms = "is:due " ord = DYN_DUE dyn['resched'] = True else: terms = "" ord = DYN_RANDOM dyn['resched'] = False dyn['terms'][0] = [(terms + tags).strip(), spin, ord] # add deck limit dyn['terms'][0][0] = "deck:\"%s\" %s " % (self.deck['name'], dyn['terms'][0][0]) # generate cards if not self.mw.col.sched.rebuildDyn(): return showWarning( _("No cards matched the criteria you provided.")) self.mw.moveToState("overview") QDialog.accept(self)
def _selectedDeck(self): did = self.col.decks.selected() if not self.col.decks.nameOrNone(did): showInfo(_("Please select a deck.")) return return self.col.decks.get(did)
def accept(self): self.exporter.includeSched = (self.frm.includeSched.isChecked()) self.exporter.includeMedia = (self.frm.includeMedia.isChecked()) self.exporter.includeTags = (self.frm.includeTags.isChecked()) if not self.frm.deck.currentIndex(): #position 0 means: all decks. self.exporter.did = None else: name = self.decks[self.frm.deck.currentIndex()] self.exporter.did = self.col.decks.id(name) if self.isVerbatim: name = time.strftime("-%Y-%m-%d@%H-%M-%S", time.localtime(time.time())) deck_name = _("collection") + name else: # Get deck name and remove invalid filename characters deck_name = self.decks[self.frm.deck.currentIndex()] deck_name = re.sub('[\\\\/?<>:*|"^]', '_', deck_name) if not self.isVerbatim and self.isApkg and self.exporter.includeSched and self.col.schedVer( ) == 2: showInfo( "Please switch to the regular scheduler before exporting a single deck .apkg with scheduling." ) return filename = '{0}{1}'.format(deck_name, self.exporter.ext) while 1: file = getSaveFile(self, _("Export"), "export", self.exporter.key, self.exporter.ext, fname=filename) if not file: return if checkInvalidFilename(os.path.basename(file), dirsep=False): continue break self.hide() if file: self.mw.progress.start(immediate=True) try: f = open(file, "wb") f.close() except (OSError, IOError) as e: showWarning(_("Couldn't save file: %s") % str(e)) else: os.unlink(file) exportedMedia = lambda cnt: self.mw.progress.update( label=ngettext("Exported %d media file", "Exported %d media files", cnt) % cnt) addHook("exportedMediaFiles", exportedMedia) self.exporter.exportInto(file) remHook("exportedMediaFiles", exportedMedia) period = 3000 if self.isVerbatim: msg = _("Collection exported.") else: if self.isTextNote: msg = ngettext( "%d note exported.", "%d notes exported.", self.exporter.count) % self.exporter.count else: msg = ngettext( "%d card exported.", "%d cards exported.", self.exporter.count) % self.exporter.count tooltip(msg, period=period) finally: self.mw.progress.finish() QDialog.accept(self)
def showA(ar): showInfo(json.dumps(ar, ensure_ascii=False))
def settings(self): addon_path = dirname(__file__) images = join(addon_path, 'user_files/images') audio = join(addon_path, 'user_files/audio_video') self.header_checkbox = QCheckBox("Header") self.header_checkbox.setFixedWidth(102) self.header_checkbox.setToolTip( """Shows a random line in \"Header Texts\" list. You can edit the list by pressing the \"Header Texts\" button down belw.""" ) self.image_checkbox = QCheckBox("Image") self.image_checkbox.setFixedWidth(102) self.image_checkbox.setToolTip( """Shows a random image from images folder. You can add images by copying images to the \"images\" foler in add-on folder. open the folder by pressing \"Open Images Folder\" button down below.""" ) self.audio_checkbox = QCheckBox("Audio/Video") self.audio_checkbox.setFixedWidth(102) self.audio_checkbox.setToolTip( """Plays a random audio from \"audio\" folder in add-on folder. You can add audio by copying them to the \"audio\" folder in add-on folder. open the folder by pressing \"Open Audio Folder\" button down below.""" ) line1 = QHBoxLayout() line1.addWidget(self.header_checkbox) line1.addWidget(self.image_checkbox) line1.addWidget(self.audio_checkbox) line1.addStretch() self.show_onAgain = QCheckBox("Again") self.show_onHard = QCheckBox("Hard") self.show_onGood = QCheckBox("Good") self.show_onEasy = QCheckBox("Easy") line2 = QHBoxLayout() line2.addWidget(self.show_onAgain) line2.addWidget(self.show_onHard) line2.addWidget(self.show_onGood) line2.addWidget(self.show_onEasy) viewChance_label = QLabel("Pop-Up Chance") viewChance_label.setToolTip( "Changes the posibility of pop-up window popping up.") viewChance_label.setFixedWidth(210) self.view_chance = QSpinBox() self.view_chance.setFixedWidth(210) self.view_chance.setRange(1, 100) self.view_chance.setSuffix("%") viewChance_holder = QHBoxLayout() viewChance_holder.addWidget(viewChance_label) viewChance_holder.addWidget(self.view_chance) SA_view_chance_label = QLabel("Show Answer Pop-Up Chance") SA_view_chance_label.setToolTip( "Changes the posibility of pop-up window popping up on show answer.\nSet on 0 to disable." ) SA_view_chance_label.setFixedWidth(210) self.SA_view_chance = QSpinBox() self.SA_view_chance.setFixedWidth(210) self.SA_view_chance.setRange(0, 100) self.SA_view_chance.setSuffix("%") SA_view_chance_holder = QHBoxLayout() SA_view_chance_holder.addWidget(SA_view_chance_label) SA_view_chance_holder.addWidget(self.SA_view_chance) headerTextSize_label = QLabel("Header Font Size") headerTextSize_label.setToolTip( "Changes font size for the header text.") headerTextSize_label.setFixedWidth(210) self.headerText_fontSize = QSpinBox() self.headerText_fontSize.setFixedWidth(210) self.headerText_fontSize.setMinimum(1) self.headerText_fontSize.setSuffix("px") header_fontSize_holder = QHBoxLayout() header_fontSize_holder.addWidget(headerTextSize_label) header_fontSize_holder.addWidget(self.headerText_fontSize) headerFontStyle_label = QLabel("Header Font") headerFontStyle_label.setToolTip("Changes header font style.") headerFontStyle_label.setFixedWidth(210) self.headerText_fontStyle = QFontComboBox() self.headerText_fontStyle.setFixedWidth(210) header_fontStyle_holder = QHBoxLayout() header_fontStyle_holder.addWidget(headerFontStyle_label) header_fontStyle_holder.addWidget(self.headerText_fontStyle) imagesFolder_button = QPushButton("Open Images Folder") imagesFolder_button.clicked.connect(lambda: self.open_file(images)) audioFolder_button = QPushButton("Open Audio/Video Folder") audioFolder_button.clicked.connect(lambda: self.open_file(audio)) headerTexts_button = QPushButton("Header Texts") headerTexts_window = QDialog() headerTexts_window.setWindowIcon(QIcon(addon_path + "/icon.png")) headerTexts_window.setWindowTitle("Header Texts") header_tabs = QTabWidget() self.SA_headerTexts_textEditor = QPlainTextEdit() self.SA_headerTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) SA_layout = QVBoxLayout() SA_layout.addWidget(self.SA_headerTexts_textEditor) SA_tab = QWidget() SA_tab.setLayout(SA_layout) header_tabs.addTab(SA_tab, "Show Answer") self.again_headerTexts_textEditor = QPlainTextEdit() self.again_headerTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) again_layout = QVBoxLayout() again_layout.addWidget(self.again_headerTexts_textEditor) again_tab = QWidget() again_tab.setLayout(again_layout) header_tabs.addTab(again_tab, "Again") self.hard_headerTexts_textEditor = QPlainTextEdit() self.hard_headerTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) hard_layout = QVBoxLayout() hard_layout.addWidget(self.hard_headerTexts_textEditor) hard_tab = QWidget() hard_tab.setLayout(hard_layout) header_tabs.addTab(hard_tab, "Hard") self.good_headerTexts_textEditor = QPlainTextEdit() self.good_headerTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) good_layout = QVBoxLayout() good_layout.addWidget(self.good_headerTexts_textEditor) good_tab = QWidget() good_tab.setLayout(good_layout) header_tabs.addTab(good_tab, "Good") self.easy_headerTexts_textEditor = QPlainTextEdit() self.easy_headerTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) easy_layout = QVBoxLayout() easy_layout.addWidget(self.easy_headerTexts_textEditor) easy_tab = QWidget() easy_tab.setLayout(easy_layout) header_tabs.addTab(easy_tab, "Easy") headers_vbox = QVBoxLayout() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Close) buttons.rejected.connect(headerTexts_window.close) headers_vbox.addWidget(header_tabs) headers_vbox.addWidget(buttons) headerTexts_window.setLayout(headers_vbox) headerTexts_button.clicked.connect(lambda: headerTexts_window.exec()) windowTtitles_button = QPushButton("Window Title Texts") windowTitles_window = QDialog() windowTitles_window.setWindowIcon(QIcon(addon_path + "/icon.png")) windowTitles_window.setWindowTitle("Window Title Texts") windowTitle_tabs = QTabWidget() self.SA_windowTitles_textEditor = QPlainTextEdit() self.SA_windowTitles_textEditor.setWordWrapMode(QTextOption.NoWrap) SA_layout = QVBoxLayout() SA_layout.addWidget(self.SA_windowTitles_textEditor) SA_tab = QWidget() SA_tab.setLayout(SA_layout) windowTitle_tabs.addTab(SA_tab, "Show Answer") self.again_windowTitles_textEditor = QPlainTextEdit() self.again_windowTitles_textEditor.setWordWrapMode(QTextOption.NoWrap) again_layout = QVBoxLayout() again_layout.addWidget(self.again_windowTitles_textEditor) again_tab = QWidget() again_tab.setLayout(again_layout) windowTitle_tabs.addTab(again_tab, "Again") self.hard_windowTitles_textEditor = QPlainTextEdit() self.hard_windowTitles_textEditor.setWordWrapMode(QTextOption.NoWrap) hard_layout = QVBoxLayout() hard_layout.addWidget(self.hard_windowTitles_textEditor) hard_tab = QWidget() hard_tab.setLayout(hard_layout) windowTitle_tabs.addTab(hard_tab, "Hard") self.good_windowTitles_textEditor = QPlainTextEdit() self.good_windowTitles_textEditor.setWordWrapMode(QTextOption.NoWrap) good_layout = QVBoxLayout() good_layout.addWidget(self.good_windowTitles_textEditor) good_tab = QWidget() good_tab.setLayout(good_layout) windowTitle_tabs.addTab(good_tab, "Good") self.easy_windowTitles_textEditor = QPlainTextEdit() self.easy_windowTitles_textEditor.setWordWrapMode(QTextOption.NoWrap) easy_layout = QVBoxLayout() easy_layout.addWidget(self.easy_windowTitles_textEditor) easy_tab = QWidget() easy_tab.setLayout(easy_layout) windowTitle_tabs.addTab(easy_tab, "Easy") windowTitles_vbox = QVBoxLayout() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Close) buttons.rejected.connect(windowTitles_window.close) windowTitles_vbox.addWidget(windowTitle_tabs) windowTitles_vbox.addWidget(buttons) windowTitles_window.setLayout(windowTitles_vbox) windowTtitles_button.clicked.connect( lambda: windowTitles_window.exec()) buttonTexts_button = QPushButton("Button Texts") buttonTexts_window = QDialog() buttonTexts_window.setWindowIcon(QIcon(addon_path + "/icon.png")) buttonTexts_window.setWindowTitle("Button Texts") button_tabs = QTabWidget() self.SA_buttonTexts_textEditor = QPlainTextEdit() self.SA_buttonTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) SA_layout = QVBoxLayout() SA_layout.addWidget(self.SA_buttonTexts_textEditor) SA_tab = QWidget() SA_tab.setLayout(SA_layout) button_tabs.addTab(SA_tab, "Show Answer") self.again_buttonTexts_textEditor = QPlainTextEdit() self.again_buttonTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) again_layout = QVBoxLayout() again_layout.addWidget(self.again_buttonTexts_textEditor) again_tab = QWidget() again_tab.setLayout(again_layout) button_tabs.addTab(again_tab, "Again") self.hard_buttonTexts_textEditor = QPlainTextEdit() self.hard_buttonTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) hard_layout = QVBoxLayout() hard_layout.addWidget(self.hard_buttonTexts_textEditor) hard_tab = QWidget() hard_tab.setLayout(hard_layout) button_tabs.addTab(hard_tab, "Hard") self.good_buttonTexts_textEditor = QPlainTextEdit() self.good_buttonTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) good_layout = QVBoxLayout() good_layout.addWidget(self.good_buttonTexts_textEditor) good_tab = QWidget() good_tab.setLayout(good_layout) button_tabs.addTab(good_tab, "Good") self.easy_buttonTexts_textEditor = QPlainTextEdit() self.easy_buttonTexts_textEditor.setWordWrapMode(QTextOption.NoWrap) easy_layout = QVBoxLayout() easy_layout.addWidget(self.easy_buttonTexts_textEditor) easy_tab = QWidget() easy_tab.setLayout(easy_layout) button_tabs.addTab(easy_tab, "Easy") buttons_vbox = QVBoxLayout() buttons = QDialogButtonBox() buttons.setStandardButtons(QDialogButtonBox.Close) buttons.rejected.connect(buttonTexts_window.close) buttons_vbox.addWidget(button_tabs) buttons_vbox.addWidget(buttons) buttonTexts_window.setLayout(buttons_vbox) buttonTexts_button.clicked.connect(lambda: buttonTexts_window.exec()) apply_button = QPushButton("Apply") apply_button.clicked.connect(self.onApply) apply_button.clicked.connect( lambda: showInfo("<div style='color: red;\ font-size: 15px;'> Changes will take effect after you restart anki. </div>", title="Review Pop-Up Settings")) apply_button.clicked.connect(lambda: self.hide()) cancel_button = QPushButton("Cancel") cancel_button.clicked.connect(lambda: self.hide()) bottom_line = QHBoxLayout() bottom_line.addWidget(apply_button) bottom_line.addWidget(cancel_button) self.layout = QVBoxLayout() self.layout.addLayout(line1) self.layout.addLayout(line2) self.layout.addLayout(viewChance_holder) self.layout.addLayout(SA_view_chance_holder) self.layout.addLayout(header_fontStyle_holder) self.layout.addLayout(header_fontSize_holder) self.layout.addWidget(imagesFolder_button) self.layout.addWidget(audioFolder_button) self.layout.addWidget(headerTexts_button) self.layout.addWidget(windowTtitles_button) self.layout.addWidget(buttonTexts_button) self.layout.addLayout(bottom_line)