diff --git a/.idea.dist/repo.iml b/.idea.dist/repo.iml
new file mode 100644
index 000000000..a9ec5ee1a
--- /dev/null
+++ b/.idea.dist/repo.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.vscode.dist/tasks.json b/.vscode.dist/tasks.json
index 72eab9604..b89704d2e 100644
--- a/.vscode.dist/tasks.json
+++ b/.vscode.dist/tasks.json
@@ -12,8 +12,7 @@
"command": "tools/ninja.bat",
"args": [
"pylib",
- "qt",
- "extract:win_amd64_audio"
+ "qt"
]
}
}
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 4e01aa0b2..a874a313d 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -189,7 +189,7 @@ Christian Donat
Asuka Minato
Dillon Baldwin
Voczi
-Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com>
+Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com>
Themis Demetriades
Luke Bartholomew
Gregory Abrasaldo
@@ -251,6 +251,11 @@ Matbe766
Amanda Sternberg
arold0
nav1s
+Ranjit Odedra
+Eltaurus
+jariji
+Francisco Esteva
+SelfishPig
********************
diff --git a/docs/editing.md b/docs/editing.md
index ba3fd6fce..42a92c5a8 100644
--- a/docs/editing.md
+++ b/docs/editing.md
@@ -46,10 +46,14 @@ see and install a number of recommended extensions.
## PyCharm/IntelliJ
-If you decide to use PyCharm instead of VS Code, there are somethings to be
-aware of.
+### Setting up Python environment
-### Pylib References
+To make PyCharm recognize `anki` and `aqt` imports, you need to add source paths to _Settings > Project Structure_.
+You can copy the provided .idea.dist directory to set up the paths automatically:
-You'll need to use File>Project Structure to tell IntelliJ that pylib/ is a
-sources root, so it knows references to 'anki' in aqt are valid.
+```
+mkdir .idea && cd .idea
+ln -sf ../.idea.dist/* .
+```
+
+You also need to add a new Python interpreter under _Settings > Python > Interpreter_ pointing to the Python executable under `out/pyenv` (available after building Anki).
diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py
index 5dc688155..ca754e783 100644
--- a/qt/aqt/deckbrowser.py
+++ b/qt/aqt/deckbrowser.py
@@ -234,7 +234,7 @@ class DeckBrowser:
if node.collapsed:
prefix = "+"
else:
- prefix = "-"
+ prefix = "−"
def indent() -> str:
return " " * 6 * (node.level - 1)
diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py
index a6d9251e2..89e15246e 100644
--- a/qt/aqt/errors.py
+++ b/qt/aqt/errors.py
@@ -14,7 +14,7 @@ from markdown import markdown
import aqt
from anki.collection import HelpPage
-from anki.errors import BackendError, Interrupted
+from anki.errors import BackendError, CardTypeError, Interrupted
from anki.utils import is_win
from aqt.addons import AddonManager, AddonMeta
from aqt.qt import *
@@ -36,6 +36,14 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
global _mbox
error_lines = []
help_page = HelpPage.TROUBLESHOOTING
+
+ # default to PlainText
+ text_format = Qt.TextFormat.PlainText
+
+ # set CardTypeError messages as rich text to allow HTML formatting
+ if isinstance(exception, CardTypeError):
+ text_format = Qt.TextFormat.RichText
+
if isinstance(exception, BackendError):
if exception.context:
error_lines.append(exception.context)
@@ -51,7 +59,7 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
)
error_text = "\n".join(error_lines)
print(error_lines)
- _mbox = _init_message_box(str(exception), error_text, help_page)
+ _mbox = _init_message_box(str(exception), error_text, help_page, text_format)
_mbox.show()
@@ -171,7 +179,10 @@ if not os.environ.get("DEBUG"):
def _init_message_box(
- user_text: str, debug_text: str, help_page=HelpPage.TROUBLESHOOTING
+ user_text: str,
+ debug_text: str,
+ help_page=HelpPage.TROUBLESHOOTING,
+ text_format=Qt.TextFormat.PlainText,
):
global _mbox
@@ -179,7 +190,7 @@ def _init_message_box(
_mbox.setWindowTitle("Anki")
_mbox.setText(user_text)
_mbox.setIcon(QMessageBox.Icon.Warning)
- _mbox.setTextFormat(Qt.TextFormat.PlainText)
+ _mbox.setTextFormat(text_format)
def show_help():
openHelp(help_page)
diff --git a/qt/aqt/forms/filtered_deck.ui b/qt/aqt/forms/filtered_deck.ui
index 0a90c40e5..a64a3968a 100644
--- a/qt/aqt/forms/filtered_deck.ui
+++ b/qt/aqt/forms/filtered_deck.ui
@@ -85,11 +85,11 @@
-
-
-
- 60
- 16777215
-
+
+
+ 0
+ 0
+
1
@@ -168,11 +168,11 @@
-
-
-
- 60
- 16777215
-
+
+
+ 0
+ 0
+
1
diff --git a/qt/aqt/forms/finddupes.ui b/qt/aqt/forms/finddupes.ui
index 9a7c44c06..9bc8be87b 100644
--- a/qt/aqt/forms/finddupes.ui
+++ b/qt/aqt/forms/finddupes.ui
@@ -47,6 +47,9 @@
QComboBox::NoInsert
+
+ QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon
+
diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py
index afce6d489..939dd8c2c 100644
--- a/qt/aqt/preferences.py
+++ b/qt/aqt/preferences.py
@@ -260,6 +260,7 @@ class Preferences(QDialog):
self.update_login_status()
self.confirm_sync_after_login()
+ self.update_network()
sync_login(self.mw, on_success)
def sync_logout(self) -> None:
diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py
index d559fb41f..f77e5c975 100644
--- a/qt/aqt/tts.py
+++ b/qt/aqt/tts.py
@@ -94,8 +94,15 @@ class TTSPlayer:
rank -= 1
- # if no preferred voices match, we fall back on language
- # with a rank of -100
+ # if no requested voices match, use a preferred fallback voice
+ # (for example, Apple Samantha) with rank of -50
+ for avail in avail_voices:
+ if avail.lang == tag.lang:
+ if avail.lang == "en_US" and avail.name.startswith("Apple_Samantha"):
+ return TTSVoiceMatch(voice=avail, rank=-50)
+
+ # if no requested or preferred voices match, we fall back on
+ # the first available voice for the language, with a rank of -100
for avail in avail_voices:
if avail.lang == tag.lang:
return TTSVoiceMatch(voice=avail, rank=-100)
diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py
index 43efc513f..ae88dadcb 100644
--- a/qt/aqt/utils.py
+++ b/qt/aqt/utils.py
@@ -809,7 +809,7 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
wsize = widget.size()
cappedWidth = min(geom.width(), wsize.width())
cappedHeight = min(geom.height(), wsize.height())
- if cappedWidth > wsize.width() or cappedHeight > wsize.height():
+ if cappedWidth < wsize.width() or cappedHeight < wsize.height():
widget.resize(QSize(cappedWidth, cappedHeight))
# ensure widget is inside top left
diff --git a/qt/launcher/build.rs b/qt/launcher/build.rs
index 3ba75b0e1..bc30f8dff 100644
--- a/qt/launcher/build.rs
+++ b/qt/launcher/build.rs
@@ -7,4 +7,7 @@ fn main() {
.manifest_required()
.unwrap();
}
+ println!("cargo:rerun-if-changed=../../out/buildhash");
+ let buildhash = std::fs::read_to_string("../../out/buildhash").unwrap_or_default();
+ println!("cargo:rustc-env=BUILDHASH={buildhash}");
}
diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs
index 26fbe86a7..dab9435ea 100644
--- a/qt/launcher/src/main.rs
+++ b/qt/launcher/src/main.rs
@@ -152,7 +152,9 @@ fn run() -> Result<()> {
let sync_time = file_timestamp_secs(&state.sync_complete_marker);
state.pyproject_modified_by_user = pyproject_time > sync_time;
let pyproject_has_changed = state.pyproject_modified_by_user;
- if !launcher_requested && !pyproject_has_changed {
+ let different_launcher = diff_launcher_was_installed(&state)?;
+
+ if !launcher_requested && !pyproject_has_changed && !different_launcher {
// If no launcher request and venv is already up to date, launch Anki normally
let args: Vec = std::env::args().skip(1).collect();
let cmd = build_python_command(&state, &args)?;
@@ -325,7 +327,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
}
command
- .env("UV_CACHE_DIR", &state.uv_cache_dir)
.env("UV_PYTHON_INSTALL_DIR", &state.uv_python_install_dir)
.env(
"UV_HTTP_TIMEOUT",
@@ -344,10 +345,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
}
}
- if state.no_cache_marker.exists() {
- command.env("UV_NO_CACHE", "1");
- }
-
match command.ensure_success() {
Ok(_) => {
// Sync succeeded
@@ -603,18 +600,27 @@ fn get_version_kind(state: &State) -> Result