diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py
index 040c643fd..e503c712b 100644
--- a/qt/aqt/__init__.py
+++ b/qt/aqt/__init__.py
@@ -232,7 +232,7 @@ def setupLangAndBackend(
else:
qt_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
qt_lang = lang.replace("-", "_")
- if _qtrans.load("qtbase_" + qt_lang, qt_dir):
+ if _qtrans.load(f"qtbase_{qt_lang}", qt_dir):
app.installTranslator(_qtrans)
return anki.lang.current_i18n
@@ -249,7 +249,7 @@ class AnkiApp(QApplication):
appMsg = pyqtSignal(str)
- KEY = "anki" + checksum(getpass.getuser())
+ KEY = f"anki{checksum(getpass.getuser())}"
TMOUT = 30000
def __init__(self, argv: List[str]) -> None:
@@ -319,7 +319,7 @@ def parseArgs(argv: List[str]) -> Tuple[argparse.Namespace, List[str]]:
# as there's no such profile
if isMac and len(argv) > 1 and argv[1].startswith("-psn"):
argv = [argv[0]]
- parser = argparse.ArgumentParser(description="Anki " + appVersion)
+ parser = argparse.ArgumentParser(description=f"Anki {appVersion}")
parser.usage = "%(prog)s [OPTIONS] [file to import/add-on to install]"
parser.add_argument("-b", "--base", help="path to base folder", default="")
parser.add_argument("-p", "--profile", help="profile name to load", default="")
@@ -424,7 +424,7 @@ def run() -> None:
QMessageBox.critical(
None,
"Startup Error",
- "Please notify support of this error:\n\n" + traceback.format_exc(),
+ f"Please notify support of this error:\n\n{traceback.format_exc()}",
)
diff --git a/qt/aqt/about.py b/qt/aqt/about.py
index 0948f4473..d49a21a36 100644
--- a/qt/aqt/about.py
+++ b/qt/aqt/about.py
@@ -81,7 +81,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
(add-on provided name [Add-on folder, installed at, version, is config changed])
{newline.join(sorted(inactive))}
"""
- info = " " + " ".join(info.splitlines(True))
+ info = f" {' '.join(info.splitlines(True))}"
QApplication.clipboard().setText(info)
tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog)
@@ -93,9 +93,9 @@ def show(mw: aqt.AnkiQt) -> QDialog:
# WebView contents
######################################################################
abouttext = "
"
- abouttext += "" + tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED)
- abouttext += "
" + tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3)
- abouttext += "
" + tr(TR.ABOUT_VERSION, val=versionWithBuild()) + "
"
+ abouttext += f"
{tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED)}"
+ abouttext += f"
{tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3)}"
+ abouttext += f"
{tr(TR.ABOUT_VERSION, val=versionWithBuild())}
"
abouttext += ("Python %s Qt %s PyQt %s
") % (
platform.python_version(),
QT_VERSION_STR,
@@ -211,8 +211,8 @@ def show(mw: aqt.AnkiQt) -> QDialog:
abouttext += "
" + tr(
TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers)
)
- abouttext += "
" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)
- abouttext += "
" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)
+ abouttext += f"
{tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)}"
+ abouttext += f"
{tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)}"
abt.label.setMinimumWidth(800)
abt.label.setMinimumHeight(600)
dialog.show()
diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py
index 546e5f739..9f695ea20 100644
--- a/qt/aqt/addcards.py
+++ b/qt/aqt/addcards.py
@@ -84,7 +84,7 @@ class AddCards(QDialog):
self.helpButton.setAutoDefault(False)
bb.addButton(self.helpButton, QDialogButtonBox.HelpRole)
# history
- b = bb.addButton(tr(TR.ADDING_HISTORY) + " " + downArrow(), ar)
+ b = bb.addButton(f"{tr(TR.ADDING_HISTORY)} {downArrow()}", ar)
if isMac:
sc = "Ctrl+Shift+H"
else:
@@ -149,7 +149,7 @@ class AddCards(QDialog):
fields = note.fields
txt = htmlToTextLine(", ".join(fields))
if len(txt) > 30:
- txt = txt[:30] + "..."
+ txt = f"{txt[:30]}..."
line = tr(TR.ADDING_EDIT, val=txt)
line = gui_hooks.addcards_will_add_history_entry(line, note)
a = m.addAction(line)
diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py
index bb208bb85..58a06fdc9 100644
--- a/qt/aqt/addons.py
+++ b/qt/aqt/addons.py
@@ -309,7 +309,7 @@ class AddonManager:
meta = self.addon_meta(dir)
name = meta.human_name()
if not meta.enabled:
- name += " " + tr(TR.ADDONS_DISABLED)
+ name += f" {tr(TR.ADDONS_DISABLED)}"
return name
# Conflict resolution
@@ -741,11 +741,9 @@ class AddonsDialog(QDialog):
name = addon.human_name()
if not addon.enabled:
- return name + " " + tr(TR.ADDONS_DISABLED2)
+ return f"{name} {tr(TR.ADDONS_DISABLED2)}"
elif not addon.compatible():
- return (
- name + " " + tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon))
- )
+ return f"{name} {tr(TR.ADDONS_REQUIRES, val=self.compatible_string(addon))}"
return name
@@ -818,7 +816,7 @@ class AddonsDialog(QDialog):
if not addon:
return
if re.match(r"^\d+$", addon):
- openLink(aqt.appShared + f"info/{addon}")
+ openLink(f"{aqt.appShared}info/{addon}")
else:
showWarning(tr(TR.ADDONS_ADDON_WAS_NOT_DOWNLOADED_FROM_ANKIWEB))
@@ -864,7 +862,7 @@ class AddonsDialog(QDialog):
def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]:
if not paths:
- key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + f" (*{self.mgr.ext})"
+ key = f"{tr(TR.ADDONS_PACKAGED_ANKI_ADDON)} (*{self.mgr.ext})"
paths_ = getFile(
self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True
)
@@ -924,7 +922,7 @@ class GetAddons(QDialog):
saveGeom(self, "getaddons")
def onBrowse(self) -> None:
- openLink(aqt.appShared + "addons/2.1")
+ openLink(f"{aqt.appShared}addons/2.1")
def accept(self) -> None:
# get codes
@@ -946,7 +944,7 @@ def download_addon(client: HttpClient, id: int) -> Union[DownloadOk, DownloadErr
"Fetch a single add-on from AnkiWeb."
try:
resp = client.get(
- aqt.appShared + f"download/{id}?v=2.1&p={current_point_version}"
+ f"{aqt.appShared}download/{id}?v=2.1&p={current_point_version}"
)
if resp.status_code != 200:
return DownloadError(status_code=resp.status_code)
@@ -1107,7 +1105,7 @@ def show_log_to_user(parent: QWidget, log: List[DownloadLogEntry]) -> None:
text = tr(TR.ADDONS_ONE_OR_MORE_ERRORS_OCCURRED)
else:
text = tr(TR.ADDONS_DOWNLOAD_COMPLETE_PLEASE_RESTART_ANKI_TO)
- text += "
" + download_log_to_html(log)
+ text += f"
{download_log_to_html(log)}"
if have_problem:
showWarning(text, textFormat="rich", parent=parent)
@@ -1153,7 +1151,7 @@ def _fetch_update_info_batch(
"""Get update info from AnkiWeb.
Chunk must not contain more than 25 ids."""
- resp = client.get(aqt.appShared + "updates/" + ",".join(chunk) + "?v=3")
+ resp = client.get(f"{aqt.appShared}updates/{','.join(chunk)}?v=3")
if resp.status_code == 200:
return resp.json()
else:
@@ -1366,7 +1364,7 @@ class ConfigEditor(QDialog):
showInfo(msg)
return
except Exception as e:
- showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e))
+ showInfo(f"{tr(TR.ADDONS_INVALID_CONFIGURATION)} {repr(e)}")
return
if not isinstance(new_conf, dict):
@@ -1419,7 +1417,7 @@ def installAddonPackages(
if log:
log_html = "
".join(log)
if advise_restart:
- log_html += "
" + tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE)
+ log_html += f"
{tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE)}"
if len(log) == 1 and not strictly_modal:
tooltip(log_html, parent=parent)
else:
diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py
index 041e4f031..7d0a38be2 100644
--- a/qt/aqt/clayout.py
+++ b/qt/aqt/clayout.py
@@ -115,7 +115,7 @@ class CardLayout(QDialog):
self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
self.topAreaForm.setupUi(self.topArea)
self.topAreaForm.templateOptions.setText(
- tr(TR.ACTIONS_OPTIONS) + " " + downArrow()
+ f"{tr(TR.ACTIONS_OPTIONS)} {downArrow()}"
)
qconnect(self.topAreaForm.templateOptions.clicked, self.onMore)
qconnect(
@@ -305,7 +305,7 @@ class CardLayout(QDialog):
qconnect(pform.preview_front.clicked, self.on_preview_toggled)
qconnect(pform.preview_back.clicked, self.on_preview_toggled)
pform.preview_settings.setText(
- tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS) + " " + downArrow()
+ f"{tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS)} {downArrow()}"
)
qconnect(pform.preview_settings.clicked, self.on_preview_settings)
@@ -491,7 +491,7 @@ class CardLayout(QDialog):
text = a
# use _showAnswer to avoid the longer delay
- self.preview_web.eval("_showAnswer(%s,'%s');" % (json.dumps(text), bodyclass))
+ self.preview_web.eval(f"_showAnswer({json.dumps(text)},'{bodyclass}');")
self.preview_web.eval(
f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});"
)
@@ -522,14 +522,14 @@ class CardLayout(QDialog):
def answerRepl(match: Match) -> str:
res = self.mw.reviewer.correct("exomple", "an example")
if hadHR:
- res = "
" + res
+ res = f"
{res}"
return res
repl: Union[str, Callable]
if type == "q":
repl = ""
- repl = "%s" % repl
+ repl = f"{repl}"
else:
repl = answerRepl
return re.sub(r"\[\[type:.+?\]\]", repl, txt)
diff --git a/qt/aqt/customstudy.py b/qt/aqt/customstudy.py
index 8dade1388..e4bc605dc 100644
--- a/qt/aqt/customstudy.py
+++ b/qt/aqt/customstudy.py
@@ -59,7 +59,7 @@ class CustomStudy(QDialog):
def plus(num: Union[int, str]) -> str:
if num == 1000:
num = "1000+"
- return "" + str(num) + ""
+ return f"{str(num)}"
if idx == RADIO_NEW:
new = self.mw.col.sched.totalNewForCurrentDeck()
diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py
index 4cb0ae697..de69648b8 100644
--- a/qt/aqt/deckbrowser.py
+++ b/qt/aqt/deckbrowser.py
@@ -288,7 +288,7 @@ class DeckBrowser:
extra = tr(TR.DECKS_IT_HAS_CARD, count=count)
if askUser(
- tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val=deck["name"]) + " " + extra
+ f"{tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val=deck['name'])} {extra}"
):
return True
return False
@@ -332,4 +332,4 @@ class DeckBrowser:
)
def _onShared(self) -> None:
- openLink(aqt.appShared + "decks/")
+ openLink(f"{aqt.appShared}decks/")
diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py
index cd37a25ed..ee0fe2e47 100644
--- a/qt/aqt/editor.py
+++ b/qt/aqt/editor.py
@@ -130,7 +130,7 @@ class Editor:
None,
"fields",
tr(TR.EDITING_CUSTOMIZE_FIELDS),
- tr(TR.EDITING_FIELDS) + "...",
+ f"{tr(TR.EDITING_FIELDS)}...",
disables=False,
rightside=False,
),
@@ -138,7 +138,7 @@ class Editor:
None,
"cards",
tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL),
- tr(TR.EDITING_CARDS) + "...",
+ f"{tr(TR.EDITING_CARDS)}...",
disables=False,
rightside=False,
),
@@ -239,7 +239,7 @@ class Editor:
with open(path, "rb") as fp:
data = fp.read()
data64 = b"".join(base64.encodebytes(data).splitlines())
- return "data:%s;base64,%s" % (mime, data64.decode("ascii"))
+ return f"data:{mime};base64,{data64.decode('ascii')}"
def addButton(
self,
@@ -314,7 +314,7 @@ class Editor:
else:
imgelm = ""
if label or not imgelm:
- labelelm = """{}""".format(label or cmd)
+ labelelm = f"""{label or cmd}"""
else:
labelelm = ""
if id:
@@ -539,7 +539,7 @@ class Editor:
if err == 2:
cols[0] = "dupe"
- self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
+ self.web.eval(f"setBackgrounds({json.dumps(cols)});")
def showDupes(self) -> None:
aqt.dialogs.open(
@@ -734,23 +734,23 @@ class Editor:
self._wrapWithColour(self.fcolour)
def _updateForegroundButton(self) -> None:
- self.web.eval("setFGButton('%s')" % self.fcolour)
+ self.web.eval(f"setFGButton('{self.fcolour}')")
def onColourChanged(self) -> None:
self._updateForegroundButton()
self.mw.pm.profile["lastColour"] = self.fcolour
def _wrapWithColour(self, colour: str) -> None:
- self.web.eval("setFormat('forecolor', '%s')" % colour)
+ self.web.eval(f"setFormat('forecolor', '{colour}')")
# Audio/video/images
######################################################################
def onAddMedia(self) -> None:
extension_filter = " ".join(
- "*." + extension for extension in sorted(itertools.chain(pics, audio))
+ f"*.{extension}" for extension in sorted(itertools.chain(pics, audio))
)
- key = tr(TR.EDITING_MEDIA) + " (" + extension_filter + ")"
+ key = f"{tr(TR.EDITING_MEDIA)} ({extension_filter})"
def accept(file: str) -> None:
self.addMedia(file, canDelete=True)
@@ -770,7 +770,7 @@ class Editor:
except Exception as e:
showWarning(str(e))
return
- self.web.eval("setFormat('inserthtml', %s);" % json.dumps(html))
+ self.web.eval(f"setFormat('inserthtml', {json.dumps(html)});")
def _addMedia(self, path: str, canDelete: bool = False) -> str:
"Add to media folder and return local img or sound tag."
@@ -810,15 +810,15 @@ class Editor:
ext = fname.split(".")[-1].lower()
if ext in pics:
name = urllib.parse.quote(fname.encode("utf8"))
- return '
' % name
+ return f'
'
else:
av_player.play_file(fname)
- return "[sound:%s]" % html.escape(fname, quote=False)
+ return f"[sound:{html.escape(fname, quote=False)}]"
def urlToFile(self, url: str) -> Optional[str]:
l = url.lower()
for suffix in pics + audio:
- if l.endswith("." + suffix):
+ if l.endswith(f".{suffix}"):
return self._retrieveURL(url)
# not a supported type
return None
@@ -842,7 +842,7 @@ class Editor:
data = base64.b64decode(b64data, validate=True)
if ext == "jpeg":
ext = "jpg"
- return self._addPastedImage(data, "." + ext)
+ return self._addPastedImage(data, f".{ext}")
return ""
@@ -857,7 +857,7 @@ class Editor:
def _addPastedImage(self, data: bytes, ext: str) -> str:
# hash and write
csum = checksum(data)
- fname = "{}-{}{}".format("paste", csum, ext)
+ fname = f"paste-{csum}{ext}"
return self._addMediaFromData(fname, data)
def _retrieveURL(self, url: str) -> Optional[str]:
@@ -967,9 +967,7 @@ class Editor:
ext = "true"
else:
ext = "false"
- self.web.eval(
- "pasteHTML(%s, %s, %s);" % (json.dumps(html), json.dumps(internal), ext)
- )
+ self.web.eval(f"pasteHTML({json.dumps(html)}, {json.dumps(internal)}, {ext});")
def doDrop(self, html: str, internal: bool, extended: bool = False) -> None:
def pasteIfField(ret: bool) -> None:
@@ -1187,8 +1185,8 @@ class EditorWebView(AnkiWebView):
token = html.escape(token).replace("\t", " " * 4)
# if there's more than one consecutive space,
# use non-breaking spaces for the second one on
- def repl(match: Match) -> None:
- return match.group(1).replace(" ", " ") + " "
+ def repl(match: Match) -> str:
+ return f"{match.group(1).replace(' ', ' ')} "
token = re.sub(" ( +)", repl, token)
processed.append(token)
@@ -1247,7 +1245,7 @@ class EditorWebView(AnkiWebView):
if not mime.hasHtml():
return
html = mime.html()
- mime.setHtml("" + html)
+ mime.setHtml(f"{html}")
clip.setMimeData(mime)
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py
index fc40eaa3e..51e5ca14b 100644
--- a/qt/aqt/errors.py
+++ b/qt/aqt/errors.py
@@ -95,13 +95,13 @@ class ErrorHandler(QObject):
if self.mw.addonManager.dirty:
txt = markdown(tr(TR.ERRORS_ADDONS_ACTIVE_POPUP))
- error = supportText() + self._addonText(error) + "\n" + error
+ error = f"{supportText() + self._addonText(error)}\n{error}"
else:
txt = markdown(tr(TR.ERRORS_STANDARD_POPUP))
- error = supportText() + "\n" + error
+ error = f"{supportText()}\n{error}"
# show dialog
- txt = txt + "" + error + "
"
+ txt = f"{txt}{error}
"
showText(txt, type="html", copyBtn=True)
def _addonText(self, error: str) -> str:
@@ -113,6 +113,6 @@ class ErrorHandler(QObject):
mw.addonManager.addonName(i) for i in dict.fromkeys(reversed(matches))
]
# highlight importance of first add-on:
- addons[0] = "{}".format(addons[0])
+ addons[0] = f"{addons[0]}"
addons_str = ", ".join(addons)
- return tr(TR.ADDONS_POSSIBLY_INVOLVED, addons=addons_str) + "\n"
+ return f"{tr(TR.ADDONS_POSSIBLY_INVOLVED, addons=addons_str)}\n"
diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py
index 8cbbb29e1..e19668842 100644
--- a/qt/aqt/fields.py
+++ b/qt/aqt/fields.py
@@ -60,7 +60,7 @@ class FieldDialog(QDialog):
self.currentIdx = None
self.form.fieldList.clear()
for c, f in enumerate(self.model["flds"]):
- self.form.fieldList.addItem("{}: {}".format(c + 1, f["name"]))
+ self.form.fieldList.addItem(f"{c + 1}: {f['name']}")
def setupSignals(self) -> None:
f = self.form
@@ -244,7 +244,7 @@ class FieldDialog(QDialog):
fut.result()
except TemplateError as e:
# fixme: i18n
- showWarning("Unable to save changes: " + str(e))
+ showWarning(f"Unable to save changes: {str(e)}")
return
self.mw.reset()
tooltip("Changes saved.", parent=self.mw)
diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py
index 99db0bda7..d1cecc330 100644
--- a/qt/aqt/importing.py
+++ b/qt/aqt/importing.py
@@ -194,7 +194,7 @@ class ImportDialog(QDialog):
showUnicodeWarning()
return
except Exception as e:
- msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n"
+ msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n"
err = repr(str(e))
if "1-character string" in err:
msg += err
@@ -205,7 +205,7 @@ class ImportDialog(QDialog):
showText(msg)
return
else:
- txt = tr(TR.IMPORTING_IMPORTING_COMPLETE) + "\n"
+ txt = f"{tr(TR.IMPORTING_IMPORTING_COMPLETE)}\n"
if self.importer.log:
txt += "\n".join(self.importer.log)
self.close()
@@ -326,7 +326,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
if done:
break
for mext in re.findall(r"[( ]?\*\.(.+?)[) ]", i[0]):
- if file.endswith("." + mext):
+ if file.endswith(f".{mext}"):
importerClass = i[1]
done = True
break
@@ -352,7 +352,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
if msg == "'unknownFormat'":
showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT))
else:
- msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n"
+ msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n"
msg += str(traceback.format_exc())
showText(msg)
return
@@ -392,7 +392,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
elif "readonly" in err:
showWarning(tr(TR.IMPORTING_UNABLE_TO_IMPORT_FROM_A_READONLY))
else:
- msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n"
+ msg = f"{tr(TR.IMPORTING_FAILED_DEBUG_INFO)}\n"
msg += str(traceback.format_exc())
showText(msg)
else:
diff --git a/qt/aqt/main.py b/qt/aqt/main.py
index a774b1064..00217ff82 100644
--- a/qt/aqt/main.py
+++ b/qt/aqt/main.py
@@ -397,7 +397,7 @@ class AnkiQt(QMainWindow):
restoreGeom(self, "mainWindow")
restoreState(self, "mainWindow")
# titlebar
- self.setWindowTitle(self.pm.name + " - Anki")
+ self.setWindowTitle(f"{self.pm.name} - Anki")
# show and raise window for osx
self.show()
self.activateWindow()
@@ -489,7 +489,7 @@ class AnkiQt(QMainWindow):
)
else:
showWarning(
- tr(TR.ERRORS_UNABLE_OPEN_COLLECTION) + "\n" + traceback.format_exc()
+ f"{tr(TR.ERRORS_UNABLE_OPEN_COLLECTION)}\n{traceback.format_exc()}"
)
# clean up open collection if possible
try:
@@ -643,14 +643,14 @@ class AnkiQt(QMainWindow):
def moveToState(self, state: str, *args: Any) -> None:
# print("-> move from", self.state, "to", state)
oldState = self.state or "dummy"
- cleanup = getattr(self, "_" + oldState + "Cleanup", None)
+ cleanup = getattr(self, f"_{oldState}Cleanup", None)
if cleanup:
# pylint: disable=not-callable
cleanup(state)
self.clearStateShortcuts()
self.state = state
gui_hooks.state_will_change(state, oldState)
- getattr(self, "_" + state + "State")(oldState, *args)
+ getattr(self, f"_{state}State")(oldState, *args)
if state != "resetRequired":
self.bottomWeb.show()
gui_hooks.state_did_change(state, oldState)
@@ -730,14 +730,13 @@ class AnkiQt(QMainWindow):
i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH)
b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume")
self.web.stdHtml(
- """
-
-"""
- % (i, b),
+""",
context=web_context,
)
self.bottomWeb.hide()
@@ -755,7 +754,7 @@ class AnkiQt(QMainWindow):
id: str = "",
extra: str = "",
) -> str:
- class_ = "but " + class_
+ class_ = f"but {class_}"
if key:
key = tr(TR.ACTIONS_SHORTCUT_KEY, val=key)
else:
@@ -989,7 +988,7 @@ title="%s" %s>%s""" % (
def setStateShortcuts(self, shortcuts: List[Tuple[str, Callable]]) -> None:
gui_hooks.state_shortcuts_will_change(self.state, shortcuts)
# legacy hook
- runHook(self.state + "StateShortcuts", shortcuts)
+ runHook(f"{self.state}StateShortcuts", shortcuts)
self.stateShortcuts = self.applyShortcuts(shortcuts)
def clearStateShortcuts(self) -> None:
@@ -1285,7 +1284,7 @@ title="%s" %s>%s""" % (
if not existed:
f.write(b"nid\tmid\tfields\n")
for id, mid, flds in col.db.execute(
- "select id, mid, flds from notes where id in %s" % ids2str(nids)
+ f"select id, mid, flds from notes where id in {ids2str(nids)}"
):
fields = splitFields(flds)
f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8"))
@@ -1471,9 +1470,9 @@ title="%s" %s>%s""" % (
buf = ""
for c, line in enumerate(text.strip().split("\n")):
if c == 0:
- buf += ">>> %s\n" % line
+ buf += f">>> {line}\n"
else:
- buf += "... %s\n" % line
+ buf += f"... {line}\n"
try:
to_append = buf + (self._output or "")
to_append = gui_hooks.debug_console_did_evaluate_python(
@@ -1616,7 +1615,7 @@ title="%s" %s>%s""" % (
self.mediaServer.start()
def baseHTML(self) -> str:
- return '' % self.serverURL()
+ return f''
def serverURL(self) -> str:
return "http://127.0.0.1:%d/" % self.mediaServer.getPort()
diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py
index 923200062..9f3177a6e 100644
--- a/qt/aqt/mediasrv.py
+++ b/qt/aqt/mediasrv.py
@@ -33,7 +33,7 @@ def _getExportFolder() -> str:
return webInSrcFolder
elif isMac:
dir = os.path.dirname(os.path.abspath(__file__))
- return os.path.abspath(dir + "/../../Resources/web")
+ return os.path.abspath(f"{dir}/../../Resources/web")
else:
if os.environ.get("TEST_TARGET"):
# running tests in bazel; we have no data
@@ -110,7 +110,7 @@ def allroutes(pathin: str) -> Response:
isdir = os.path.isdir(os.path.join(directory, path))
except ValueError:
return flask.make_response(
- "Path for '%s - %s' is too long!" % (directory, path),
+ f"Path for '{directory} - {path}' is too long!",
HTTPStatus.BAD_REQUEST,
)
@@ -121,13 +121,13 @@ def allroutes(pathin: str) -> Response:
# protect against directory transversal: https://security.openstack.org/guidelines/dg_using-file-paths.html
if not fullpath.startswith(directory):
return flask.make_response(
- "Path for '%s - %s' is a security leak!" % (directory, path),
+ f"Path for '{directory} - {path}' is a security leak!",
HTTPStatus.FORBIDDEN,
)
if isdir:
return flask.make_response(
- "Path for '%s - %s' is a directory (not supported)!" % (directory, path),
+ f"Path for '{directory} - {path}' is a directory (not supported)!",
HTTPStatus.FORBIDDEN,
)
@@ -221,7 +221,7 @@ def _redirectWebExports(path: str) -> Tuple[str, str]:
addMgr = aqt.mw.addonManager
except AttributeError as error:
if devMode:
- print("_redirectWebExports: %s" % error)
+ print(f"_redirectWebExports: {error}")
return None
try:
diff --git a/qt/aqt/models.py b/qt/aqt/models.py
index 634e21a33..e03a08fe3 100644
--- a/qt/aqt/models.py
+++ b/qt/aqt/models.py
@@ -128,7 +128,7 @@ class Models(QDialog):
self.models = notetypes
for m in self.models:
mUse = tr(TR.BROWSING_NOTE_COUNT, count=m.use_count)
- item = QListWidgetItem("%s [%s]" % (m.name, mUse))
+ item = QListWidgetItem(f"{m.name} [{mUse}]")
self.form.modelsList.addItem(item)
self.form.modelsList.setCurrentRow(row)
diff --git a/qt/aqt/mpv.py b/qt/aqt/mpv.py
index 94556f1ad..8bffad40f 100644
--- a/qt/aqt/mpv.py
+++ b/qt/aqt/mpv.py
@@ -119,9 +119,9 @@ class MPVBase:
"""Prepare the argument list for the mpv process."""
self.argv = [self.executable]
self.argv += self.default_argv
- self.argv += ["--input-ipc-server=" + self._sock_filename]
+ self.argv += [f"--input-ipc-server={self._sock_filename}"]
if self.window_id is not None:
- self.argv += ["--wid=" + str(self.window_id)]
+ self.argv += [f"--wid={str(self.window_id)}"]
def _start_process(self):
"""Start the mpv process."""
@@ -258,7 +258,7 @@ class MPVBase:
buf = buf[newline + 1 :]
if self.debug:
- sys.stdout.write("<<< " + data.decode("utf8", "replace"))
+ sys.stdout.write(f"<<< {data.decode('utf8', 'replace')}")
message = self._parse_message(data)
self._handle_message(message)
@@ -298,7 +298,7 @@ class MPVBase:
self._event_queue.put(message)
else:
- raise MPVCommunicationError("invalid message %r" % message)
+ raise MPVCommunicationError(f"invalid message {message!r}")
def _send_message(self, message, timeout=None):
"""Send a message/command to the mpv process, message must be a
@@ -308,7 +308,7 @@ class MPVBase:
data = self._compose_message(message)
if self.debug:
- sys.stdout.write(">>> " + data.decode("utf8", "replace"))
+ sys.stdout.write(f">>> {data.decode('utf8', 'replace')}")
# Request/response cycles are coordinated across different threads, so
# that they don't get mixed up. This makes it possible to use commands
@@ -371,7 +371,7 @@ class MPVBase:
self._send_message(message, timeout)
return self._get_response(timeout)
except MPVCommandError as e:
- raise MPVCommandError("%r: %s" % (message["command"], e))
+ raise MPVCommandError(f"{message['command']!r}: {e}")
except Exception as e:
if _retry:
print("mpv timed out, restarting")
@@ -512,7 +512,7 @@ class MPV(MPVBase):
return
if message["event"] == "property-change":
- name = "property-" + message["name"]
+ name = f"property-{message['name']}"
else:
name = message["event"]
@@ -527,7 +527,7 @@ class MPV(MPVBase):
try:
self.command("enable_event", name)
except MPVCommandError:
- raise MPVError("no such event %r" % name)
+ raise MPVError(f"no such event {name!r}")
self._callbacks.setdefault(name, []).append(callback)
@@ -538,12 +538,12 @@ class MPV(MPVBase):
try:
callbacks = self._callbacks[name]
except KeyError:
- raise MPVError("no callbacks registered for event %r" % name)
+ raise MPVError(f"no callbacks registered for event {name!r}")
try:
callbacks.remove(callback)
except ValueError:
- raise MPVError("callback %r not registered for event %r" % (callback, name))
+ raise MPVError(f"callback {callback!r} not registered for event {name!r}")
def register_property_callback(self, name, callback):
"""Register a function `callback` for the property-change event on
@@ -556,9 +556,9 @@ class MPV(MPVBase):
# Apparently observe_property does not check it :-(
proplist = self.command("get_property", "property-list")
if name not in proplist:
- raise MPVError("no such property %r" % name)
+ raise MPVError(f"no such property {name!r}")
- self._callbacks.setdefault("property-" + name, []).append(callback)
+ self._callbacks.setdefault(f"property-{name}", []).append(callback)
# 'observe_property' expects some kind of id which can be used later
# for unregistering with 'unobserve_property'.
@@ -572,15 +572,15 @@ class MPV(MPVBase):
property-change event on property `name`.
"""
try:
- callbacks = self._callbacks["property-" + name]
+ callbacks = self._callbacks[f"property-{name}"]
except KeyError:
- raise MPVError("no callbacks registered for property %r" % name)
+ raise MPVError(f"no callbacks registered for property {name!r}")
try:
callbacks.remove(callback)
except ValueError:
raise MPVError(
- "callback %r not registered for property %r" % (callback, name)
+ f"callback {callback!r} not registered for property {name!r}"
)
serial = self._property_serials.pop((name, callback))
diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py
index 949c2e8c1..723528602 100644
--- a/qt/aqt/overview.py
+++ b/qt/aqt/overview.py
@@ -80,7 +80,7 @@ class Overview:
elif url == "decks":
self.mw.moveToState("deckBrowser")
elif url == "review":
- openLink(aqt.appShared + "info/%s?v=%s" % (self.sid, self.sidVer))
+ openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}")
elif url == "studymore" or url == "customStudy":
self.onStudyMore()
elif url == "unbury":
@@ -180,8 +180,8 @@ class Overview:
def _desc(self, deck: Dict[str, Any]) -> str:
if deck["dyn"]:
desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR)
- desc += " " + tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO)
- desc += " " + tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK)
+ desc += f" {tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO)}"
+ desc += f" {tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK)}"
else:
desc = deck.get("desc", "")
if deck.get("md", False):
@@ -192,7 +192,7 @@ class Overview:
dyn = "dyn"
else:
dyn = ""
- return '%s
' % (dyn, desc)
+ return f'{desc}
'
def _table(self) -> Optional[str]:
counts = list(self.mw.col.sched.counts())
diff --git a/qt/aqt/previewer.py b/qt/aqt/previewer.py
index fc5718c6d..0e6a1dc98 100644
--- a/qt/aqt/previewer.py
+++ b/qt/aqt/previewer.py
@@ -214,9 +214,9 @@ class Previewer(QDialog):
av_player.clear_queue_and_maybe_interrupt()
txt = self.mw.prepare_card_text_for_display(txt)
- txt = gui_hooks.card_will_show(txt, c, "preview" + self._state.capitalize())
+ txt = gui_hooks.card_will_show(txt, c, f"preview{self._state.capitalize()}")
self._last_state = self._state_and_mod()
- self._web.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass))
+ self._web.eval(f"{func}({json.dumps(txt)},'{bodyclass}');")
self._card_changed = False
def _on_show_both_sides(self, toggle: bool) -> None:
diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py
index bf540a84b..8c5ee1018 100644
--- a/qt/aqt/profiles.py
+++ b/qt/aqt/profiles.py
@@ -522,7 +522,7 @@ create table if not exists profiles
without_unicode_isolation(
tr(
TR.PROFILES_FOLDER_README,
- link=appHelpSite + "files?id=startup-options",
+ link=f"{appHelpSite}files?id=startup-options",
)
)
)
diff --git a/qt/aqt/progress.py b/qt/aqt/progress.py
index 098f64d85..d5002208e 100644
--- a/qt/aqt/progress.py
+++ b/qt/aqt/progress.py
@@ -45,7 +45,7 @@ class ProgressManager:
def handler() -> None:
if requiresCollection and not self.mw.col:
# no current collection; timer is no longer valid
- print("Ignored progress func as collection unloaded: %s" % repr(func))
+ print(f"Ignored progress func as collection unloaded: {repr(func)}")
return
if not self._levels:
diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py
index 69b692eb7..3702d2db5 100644
--- a/qt/aqt/reviewer.py
+++ b/qt/aqt/reviewer.py
@@ -97,9 +97,7 @@ class Reviewer:
mins = int(round(elapsed[0] / 60))
part2 = tr(TR.STUDYING_MINUTE, count=mins)
fin = tr(TR.STUDYING_FINISH)
- diag = askUserDialog(
- "%s %s" % (part1, part2), [tr(TR.STUDYING_CONTINUE), fin]
- )
+ diag = askUserDialog(f"{part1} {part2}", [tr(TR.STUDYING_CONTINUE), fin])
diag.setIcon(QMessageBox.Information)
if diag.run() == fin:
return self.mw.moveToState("deckBrowser")
@@ -142,15 +140,13 @@ class Reviewer:
fade = ""
if self.mw.pm.video_driver() == VideoDriver.Software:
fade = ""
- return """
+ return f"""
★
⚑
-{}
+{fade}
-{}
-""".format(
- fade, extra
- )
+{extra}
+"""
def _initWeb(self) -> None:
self._reps = 0
@@ -207,7 +203,7 @@ class Reviewer:
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
- self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
+ self.web.eval(f"_showQuestion({json.dumps(q)},'{bodyclass}');")
self._drawFlag()
self._drawMark()
self._showAnswerButton()
@@ -220,10 +216,10 @@ class Reviewer:
return card.autoplay()
def _drawFlag(self) -> None:
- self.web.eval("_drawFlag(%s);" % self.card.userFlag())
+ self.web.eval(f"_drawFlag({self.card.userFlag()});")
def _drawMark(self) -> None:
- self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked")))
+ self.web.eval(f"_drawMark({json.dumps(self.card.note().hasTag('marked'))});")
# Showing the answer
##########################################################################
@@ -248,7 +244,7 @@ class Reviewer:
a = self._mungeQA(a)
a = gui_hooks.card_will_show(a, c, "reviewAnswer")
# render and update bottom
- self.web.eval("_showAnswer(%s);" % json.dumps(a))
+ self.web.eval(f"_showAnswer({json.dumps(a)});")
self._showEaseButtons()
self.mw.web.setFocus()
# user hook
@@ -399,13 +395,12 @@ class Reviewer:
return re.sub(self.typeAnsPat, "", buf)
return re.sub(
self.typeAnsPat,
- """
+ f"""
+ style="font-family: '{self.typeFont}'; font-size: {self.typeSize}px;">
-"""
- % (self.typeFont, self.typeSize),
+""",
buf,
)
@@ -440,7 +435,7 @@ class Reviewer:
if hadHR:
# a hack to ensure the q/a separator falls before the answer
# comparison when user is using {{FrontSide}}
- s = "
" + s
+ s = f"
{s}"
return s
return re.sub(self.typeAnsPat, repl, buf)
@@ -506,13 +501,13 @@ class Reviewer:
givenElems, correctElems = self.tokenizeComparison(given, correct)
def good(s: str) -> str:
- return "" + html.escape(s) + ""
+ return f"{html.escape(s)}"
def bad(s: str) -> str:
- return "" + html.escape(s) + ""
+ return f"{html.escape(s)}"
def missed(s: str) -> str:
- return "" + html.escape(s) + ""
+ return f"{html.escape(s)}"
if given == correct:
res = good(given)
@@ -531,14 +526,14 @@ class Reviewer:
res += good(txt)
else:
res += missed(txt)
- res = "" + res + "
"
+ res = f"{res}
"
return res
def _noLoneMarks(self, s: str) -> str:
# ensure a combining character at the start does not join to
# previous text
if s and ucd.category(s[0]).startswith("M"):
- return "\xa0" + s
+ return f"\xa0{s}"
return s
def _getTypedAnswer(self) -> None:
@@ -602,7 +597,7 @@ time = %(time)d;
def _showEaseButtons(self) -> None:
middle = self._answerButtons()
- self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle))
+ self.bottom.web.eval(f"showAnswer({json.dumps(middle)});")
def _remaining(self) -> str:
if not self.mw.col.conf["dueCounts"]:
@@ -613,11 +608,11 @@ time = %(time)d;
else:
counts = list(self.mw.col.sched.counts(self.card))
idx = self.mw.col.sched.countIdx(self.card)
- counts[idx] = "%s" % (counts[idx])
+ counts[idx] = f"{counts[idx]}"
space = " + "
- ctxt = "%s" % counts[0]
- ctxt += space + "%s" % counts[1]
- ctxt += space + "%s" % counts[2]
+ ctxt = f"{counts[0]}"
+ ctxt += f"{space}{counts[1]}"
+ ctxt += f"{space}{counts[2]}"
return ctxt
def _defaultEase(self) -> int:
@@ -683,7 +678,7 @@ time = %(time)d;
if not self.mw.col.conf["estTimes"]:
return ""
txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " "
- return "%s
" % txt
+ return f"{txt}
"
# Leeches
##########################################################################
@@ -692,7 +687,7 @@ time = %(time)d;
# for now
s = tr(TR.STUDYING_CARD_WAS_A_LEECH)
if card.queue < 0:
- s += " " + tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED)
+ s += f" {tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED)}"
tooltip(s)
# Context menu
diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py
index da4fed791..e3ddb63e1 100644
--- a/qt/aqt/sidebar.py
+++ b/qt/aqt/sidebar.py
@@ -805,7 +805,7 @@ class SidebarTreeView(QTreeView):
full_name=head + node.name,
)
root.add_child(item)
- newhead = head + node.name + "::"
+ newhead = f"{head + node.name}::"
render(item, node.children, newhead)
tree = self.col.tags.tree()
@@ -854,7 +854,7 @@ class SidebarTreeView(QTreeView):
full_name=head + node.name,
)
root.add_child(item)
- newhead = head + node.name + "::"
+ newhead = f"{head + node.name}::"
render(item, node.children, newhead)
tree = self.col.decks.deck_tree()
@@ -906,7 +906,7 @@ class SidebarTreeView(QTreeView):
SearchTerm(note=nt["name"]), SearchTerm(template=c)
),
item_type=SidebarItemType.NOTETYPE_TEMPLATE,
- full_name=nt["name"] + "::" + tmpl["name"],
+ full_name=f"{nt['name']}::{tmpl['name']}",
)
item.add_child(child)
diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py
index 6fc1281a5..c4532754a 100644
--- a/qt/aqt/sound.py
+++ b/qt/aqt/sound.py
@@ -240,7 +240,7 @@ def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]:
del env["LD_LIBRARY_PATH"]
if isMac:
dir = os.path.dirname(os.path.abspath(__file__))
- exeDir = os.path.abspath(dir + "/../../Resources/audio")
+ exeDir = os.path.abspath(f"{dir}/../../Resources/audio")
else:
exeDir = os.path.dirname(os.path.abspath(sys.argv[0]))
if isWin and not cmd[0].endswith(".exe"):
@@ -353,7 +353,7 @@ class SimpleMpvPlayer(SimpleProcessPlayer, VideoPlayer):
def __init__(self, taskman: TaskManager, base_folder: str) -> None:
super().__init__(taskman)
- self.args += ["--config-dir=" + base_folder]
+ self.args += [f"--config-dir={base_folder}"]
class SimpleMplayerPlayer(SimpleProcessPlayer, SoundOrVideoPlayer):
@@ -377,7 +377,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
mpvPath, self.popenEnv = _packagedCmd(["mpv"])
self.executable = mpvPath[0]
self._on_done: Optional[OnDoneCallback] = None
- self.default_argv += ["--config-dir=" + base_path]
+ self.default_argv += [f"--config-dir={base_path}"]
super().__init__(window_id=None, debug=False)
def on_init(self) -> None:
@@ -764,7 +764,7 @@ class RecordDialog(QDialog):
def _on_timer(self) -> None:
self._recorder.on_timer()
duration = self._recorder.duration()
- self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs="%0.1f" % duration))
+ self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs=f"{duration:0.1f}"))
def accept(self) -> None:
self._timer.stop()
diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py
index 094f293fd..04158ae6c 100644
--- a/qt/aqt/stats.py
+++ b/qt/aqt/stats.py
@@ -66,7 +66,7 @@ class NewDeckStats(QDialog):
def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
- name = "anki-" + tr(TR.STATISTICS_STATS) + name
+ name = f"anki-{tr(TR.STATISTICS_STATS)}{name}"
file = getSaveFile(
self,
title=tr(TR.STATISTICS_SAVE_PDF),
@@ -156,7 +156,7 @@ class DeckStats(QDialog):
def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
- name = "anki-" + tr(TR.STATISTICS_STATS) + name
+ name = f"anki-{tr(TR.STATISTICS_STATS)}{name}"
file = getSaveFile(
self,
title=tr(TR.STATISTICS_SAVE_PDF),
@@ -189,7 +189,7 @@ class DeckStats(QDialog):
self.report = stats.report(type=self.period)
self.form.web.title = "deck stats"
self.form.web.stdHtml(
- "" + self.report + "",
+ f"{self.report}",
js=["js/vendor/jquery.min.js", "js/vendor/plot.js"],
context=self,
)
diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py
index 4d3327768..d44ff1d26 100644
--- a/qt/aqt/studydeck.py
+++ b/qt/aqt/studydeck.py
@@ -45,7 +45,7 @@ class StudyDeck(QDialog):
self.form.filter.installEventFilter(self)
self.cancel = cancel
gui_hooks.state_did_reset.append(self.onReset)
- self.geomKey = "studyDeck-" + geomKey
+ self.geomKey = f"studyDeck-{geomKey}"
restoreGeom(self, self.geomKey)
disable_help_button(self)
if not cancel:
diff --git a/qt/aqt/tagedit.py b/qt/aqt/tagedit.py
index cf2302784..10ac52027 100644
--- a/qt/aqt/tagedit.py
+++ b/qt/aqt/tagedit.py
@@ -137,4 +137,4 @@ class TagCompleter(QCompleter):
self.tags.remove("")
except ValueError:
pass
- return " ".join(self.tags) + " "
+ return f"{' '.join(self.tags)} "
diff --git a/qt/aqt/taglimit.py b/qt/aqt/taglimit.py
index e9e182ebf..15c2e9bab 100644
--- a/qt/aqt/taglimit.py
+++ b/qt/aqt/taglimit.py
@@ -98,12 +98,12 @@ class TagLimit(QDialog):
if yes:
arr = []
for req in yes:
- arr.append('tag:"%s"' % req)
- self.tags += "(" + " or ".join(arr) + ")"
+ arr.append(f'tag:"{req}"')
+ self.tags += f"({' or '.join(arr)})"
if no:
arr = []
for req in no:
- arr.append('-tag:"%s"' % req)
- self.tags += " " + " ".join(arr)
+ arr.append(f'-tag:"{req}"')
+ self.tags += f" {' '.join(arr)}"
saveGeom(self, "tagLimit")
QDialog.accept(self)
diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py
index 5f5afe74f..2b71a79f5 100644
--- a/qt/aqt/tts.py
+++ b/qt/aqt/tts.py
@@ -141,7 +141,7 @@ def on_tts_voices(
f"{{{{tts {v.lang} voices={v.name}}}}}" # pylint: disable=no-member
for v in voices
)
- return buf + ""
+ return f"{buf}"
hooks.field_filter.append(on_tts_voices)
@@ -199,7 +199,7 @@ class MacTTSPlayer(TTSProcessPlayer):
return None
original_name = m.group(1).strip()
- tidy_name = "Apple_" + original_name.replace(" ", "_")
+ tidy_name = f"Apple_{original_name.replace(' ', '_')}"
return MacVoice(name=tidy_name, original_name=original_name, lang=m.group(2))
diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py
index 7eba24109..8d94aa5c7 100644
--- a/qt/aqt/utils.py
+++ b/qt/aqt/utils.py
@@ -479,7 +479,7 @@ def getFile(
"Ask the user for a file."
assert not dir or not key
if not dir:
- dirkey = key + "Directory"
+ dirkey = f"{key}Directory"
dir = aqt.mw.pm.profile.get(dirkey, "")
else:
dirkey = None
@@ -521,7 +521,7 @@ def getSaveFile(
) -> str:
"""Ask the user for a file to save. Use DIR_DESCRIPTION as config
variable. The file dialog will default to open with FNAME."""
- config_key = dir_description + "Directory"
+ config_key = f"{dir_description}Directory"
defaultPath = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
base = aqt.mw.pm.profile.get(config_key, defaultPath)
@@ -644,8 +644,8 @@ def restore_is_checked(widget: QWidget, key: str) -> None:
def save_combo_index_for_session(widget: QComboBox, key: str) -> None:
- textKey = key + "ComboActiveText"
- indexKey = key + "ComboActiveIndex"
+ textKey = f"{key}ComboActiveText"
+ indexKey = f"{key}ComboActiveIndex"
aqt.mw.pm.session[textKey] = widget.currentText()
aqt.mw.pm.session[indexKey] = widget.currentIndex()
@@ -653,8 +653,8 @@ def save_combo_index_for_session(widget: QComboBox, key: str) -> None:
def restore_combo_index_for_session(
widget: QComboBox, history: List[str], key: str
) -> None:
- textKey = key + "ComboActiveText"
- indexKey = key + "ComboActiveIndex"
+ textKey = f"{key}ComboActiveText"
+ indexKey = f"{key}ComboActiveIndex"
text = aqt.mw.pm.session.get(textKey)
index = aqt.mw.pm.session.get(indexKey)
if text is not None and index is not None:
@@ -696,10 +696,10 @@ def mungeQA(col: Collection, txt: str) -> str:
def openFolder(path: str) -> None:
if isWin:
- subprocess.Popen(["explorer", "file://" + path])
+ subprocess.Popen(["explorer", f"file://{path}"])
else:
with noBundledLibs():
- QDesktopServices.openUrl(QUrl("file://" + path))
+ QDesktopServices.openUrl(QUrl(f"file://{path}"))
def shortcut(key: str) -> str:
@@ -755,13 +755,11 @@ def tooltip(
closeTooltip()
aw = parent or aqt.mw.app.activeWindow() or aqt.mw
lab = CustomLabel(
- """\
-""",
aw,
)
lab.setFrameStyle(QFrame.Panel)
@@ -886,9 +884,9 @@ def supportText() -> str:
from aqt import mw
if isWin:
- platname = "Windows " + platform.win32_ver()[0]
+ platname = f"Windows {platform.win32_ver()[0]}"
elif isMac:
- platname = "Mac " + platform.mac_ver()[0]
+ platname = f"Mac {platform.mac_ver()[0]}"
else:
platname = "Linux"
diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py
index e905e10a1..f5543eaeb 100644
--- a/qt/aqt/webview.py
+++ b/qt/aqt/webview.py
@@ -97,7 +97,7 @@ class AnkiWebPage(QWebEnginePage):
else:
level_str = str(level)
buf = "JS %(t)s %(f)s:%(a)d %(b)s" % dict(
- t=level_str, a=line, f=srcID, b=msg + "\n"
+ t=level_str, a=line, f=srcID, b=f"{msg}\n"
)
if "MathJax localStorage" in buf:
# silence localStorage noise
@@ -392,10 +392,10 @@ class AnkiWebView(QWebEngineView):
family = tr(TR.QT_MISC_SEGOE_UI)
button_style = "button { font-family:%s; }" % family
button_style += "\n:focus { outline: 1px solid %s; }" % color_hl
- font = "font-size:12px;font-family:%s;" % family
+ font = f"font-size:12px;font-family:{family};"
elif isMac:
family = "Helvetica"
- font = 'font-size:15px;font-family:"%s";' % family
+ font = f'font-size:15px;font-family:"{family}";'
button_style = """
button { -webkit-appearance: none; background: #fff; border: 1px solid #ccc;
border-radius:5px; font-family: Helvetica }"""
@@ -403,7 +403,7 @@ border-radius:5px; font-family: Helvetica }"""
family = self.font().family()
color_hl_txt = palette.color(QPalette.HighlightedText).name()
color_btn = palette.color(QPalette.Button).name()
- font = 'font-size:14px;font-family:"%s";' % family
+ font = f'font-size:14px;font-family:"{family}";'
button_style = """
/* Buttons */
button{
@@ -503,7 +503,7 @@ body {{ zoom: {zoom}; background: {background}; direction: {lang_dir}; {font} }}
return f"http://127.0.0.1:{mw.mediaServer.getPort()}{subpath}{path}"
def bundledScript(self, fname: str) -> str:
- return '' % self.webBundlePath(fname)
+ return f''
def bundledCSS(self, fname: str) -> str:
return '' % self.webBundlePath(
@@ -641,5 +641,5 @@ document.head.appendChild(style);
else:
extra = ""
self.hide_while_preserving_layout()
- self.load(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html" + extra))
+ self.load(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{extra}"))
self.inject_dynamic_style_and_show()
diff --git a/qt/aqt/winpaths.py b/qt/aqt/winpaths.py
index 6542a6c32..ce5530a35 100644
--- a/qt/aqt/winpaths.py
+++ b/qt/aqt/winpaths.py
@@ -86,7 +86,7 @@ def _err_unless_zero(result):
if result == 0:
return result
else:
- raise WinPathsException("Failed to retrieve windows path: %s" % result)
+ raise WinPathsException(f"Failed to retrieve windows path: {result}")
_SHGetFolderPath = windll.shell32.SHGetFolderPathW