From 95853bb603efacc9890f2b86bd28b0377e448ee5 Mon Sep 17 00:00:00 2001
From: ThanosApollo
Date: Thu, 1 Sep 2022 07:46:56 +0300
Subject: Re-add anki addons
---
.../share/Anki2/addons21/Anki_connect/__init__.py | 1688 +++++++++++++
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 41249 bytes
.../__pycache__/edit.cpython-39.opt-2.pyc | Bin 0 -> 16181 bytes
.../__pycache__/util.cpython-39.opt-2.pyc | Bin 0 -> 2261 bytes
.../__pycache__/web.cpython-39.opt-2.pyc | Bin 0 -> 6969 bytes
.../share/Anki2/addons21/Anki_connect/config.json | 8 +
.local/share/Anki2/addons21/Anki_connect/config.md | 1 +
.local/share/Anki2/addons21/Anki_connect/edit.py | 458 ++++
.local/share/Anki2/addons21/Anki_connect/meta.json | 1 +
.local/share/Anki2/addons21/Anki_connect/util.py | 93 +
.local/share/Anki2/addons21/Anki_connect/web.py | 301 +++
.../Anki2/addons21/Background_and_gear/LICENSE | 661 +++++
.../Anki2/addons21/Background_and_gear/__init__.py | 188 ++
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 4909 bytes
.../adjust_css_files22.cpython-39.opt-2.pyc | Bin 0 -> 3564 bytes
.../__pycache__/config.cpython-39.opt-2.pyc | Bin 0 -> 1224 bytes
.../gui_updatemanager.cpython-39.opt-2.pyc | Bin 0 -> 12680 bytes
.../Background_and_gear/adjust_css_files22.py | 107 +
.../Anki2/addons21/Background_and_gear/config.json | 17 +
.../Anki2/addons21/Background_and_gear/config.md | 42 +
.../Anki2/addons21/Background_and_gear/config.py | 44 +
.../Background_and_gear/gui/forms/__init__.py | 23 +
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 316 bytes
.../Background_and_gear/gui/forms/qt5/__init__.py | 20 +
.../gui/forms/qt5/settings_dialog.py | 332 +++
.../Background_and_gear/gui/forms/qt6/__init__.py | 20 +
.../qt6/__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 241 bytes
.../settings_dialog.cpython-39.opt-2.pyc | Bin 0 -> 10288 bytes
.../gui/forms/qt6/settings_dialog.py | 330 +++
.../Background_and_gear/gui/resources/__init__.py | 22 +
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 463 bytes
.../Background_and_gear/gui_updatemanager.py | 432 ++++
.../addons21/Background_and_gear/manifest.json | 12 +
.../Anki2/addons21/Background_and_gear/meta.json | 1 +
.../sources/css/22/Other css files/editor.css | 96 +
.../sources/css/22/Other css files/webview.css | 102 +
.../sources/css/22/deckbrowser.css | 127 +
.../sources/css/22/overview.css | 67 +
.../sources/css/22/reviewer-bottom.css | 92 +
.../sources/css/22/reviewer.css | 89 +
.../sources/css/22/toolbar-bottom.css | 9 +
.../Background_and_gear/sources/css/22/toolbar.css | 74 +
.../sources/css/25/deckbrowser.css | 121 +
.../sources/css/25/overview.css | 67 +
.../sources/css/25/reviewer-bottom.css | 92 +
.../sources/css/25/reviewer.css | 90 +
.../sources/css/25/toolbar-bottom.css | 9 +
.../Background_and_gear/sources/css/25/toolbar.css | 74 +
.../sources/css/31/deckbrowser.css | 121 +
.../sources/css/31/overview.css | 67 +
.../sources/css/31/reviewer-bottom.css | 92 +
.../sources/css/31/reviewer.css | 90 +
.../sources/css/31/toolbar-bottom.css | 10 +
.../Background_and_gear/sources/css/31/toolbar.css | 89 +
.../sources/css/36/deckbrowser.css | 102 +
.../sources/css/36/overview.css | 62 +
.../sources/css/36/reviewer-bottom.css | 91 +
.../sources/css/36/reviewer.css | 87 +
.../sources/css/36/toolbar-bottom.css | 9 +
.../Background_and_gear/sources/css/36/toolbar.css | 78 +
.../user_files/background/1721729412.jpg | Bin 0 -> 1918192 bytes
.../background/samuel-inkilainen-laboratory.jpg | Bin 0 -> 1297527 bytes
...atories-original-characters-brown-wallpaper.jpg | Bin 0 -> 207363 bytes
.../user_files/background/thumb-1920-1058130.jpg | Bin 0 -> 413407 bytes
.../user_files/background/wp7186342.jpg | Bin 0 -> 880078 bytes
.../user_files/css/custom_deckbrowser.css | 0
.../user_files/css/custom_overview.css | 0
.../user_files/css/custom_reviewer-bottom.css | 0
.../user_files/css/custom_reviewer.css | 0
.../user_files/css/custom_toolbar-bottom.css | 0
.../user_files/css/custom_toolbar.css | 0
.../Background_and_gear/user_files/gear/AnKing.png | Bin 0 -> 111345 bytes
.../Background_and_gear/user_files/gear/Bam.png | Bin 0 -> 21861 bytes
.../user_files/gear/Bullseye.png | Bin 0 -> 20551 bytes
.../Background_and_gear/user_files/gear/Cowboy.png | Bin 0 -> 15581 bytes
.../user_files/gear/Diamond.png | Bin 0 -> 13720 bytes
.../Background_and_gear/user_files/gear/Dragon.png | Bin 0 -> 37362 bytes
.../Background_and_gear/user_files/gear/Fire.png | Bin 0 -> 13367 bytes
.../Background_and_gear/user_files/gear/Flower.png | Bin 0 -> 26288 bytes
.../Background_and_gear/user_files/gear/Nerd.png | Bin 0 -> 18069 bytes
.../Background_and_gear/user_files/gear/Rose.png | Bin 0 -> 14955 bytes
.../Background_and_gear/user_files/gear/Shield.png | Bin 0 -> 10331 bytes
.../Background_and_gear/user_files/gear/Skull.png | Bin 0 -> 5906 bytes
.../Background_and_gear/user_files/gear/Star.png | Bin 0 -> 15660 bytes
.../Background_and_gear/user_files/gear/Sun.png | Bin 0 -> 20171 bytes
.../Background_and_gear/user_files/gear/gears.svg | 13 +
.../Background_and_gear/web/css/deckbrowser.css | 121 +
.../Background_and_gear/web/css/overview.css | 81 +
.../web/css/reviewer-bottom.css | 110 +
.../Background_and_gear/web/css/reviewer.css | 106 +
.../Background_and_gear/web/css/toolbar-bottom.css | 28 +
.../Background_and_gear/web/css/toolbar.css | 97 +
.../Anki2/addons21/advanced_review/Bottom_Bar.py | 378 +++
.../addons21/advanced_review/Button_Colors.py | 211 ++
.../Anki2/addons21/advanced_review/CHANGELOG.md | 421 +++
.../Anki2/addons21/advanced_review/Card_Info.py | 508 ++++
.../addons21/advanced_review/Deck_Overview.py | 646 +++++
.../share/Anki2/addons21/advanced_review/LICENSE | 674 +++++
.../share/Anki2/addons21/advanced_review/README.md | 12 +
.../Anki2/addons21/advanced_review/Settings.py | 2667 ++++++++++++++++++++
.../share/Anki2/addons21/advanced_review/Skip.py | 39 +
.../Anki2/addons21/advanced_review/Tooltip.py | 613 +++++
.../Anki2/addons21/advanced_review/__init__.py | 19 +
.../__pycache__/Bottom_Bar.cpython-39.opt-2.pyc | Bin 0 -> 10614 bytes
.../__pycache__/Button_Colors.cpython-39.opt-2.pyc | Bin 0 -> 4062 bytes
.../__pycache__/Card_Info.cpython-39.opt-2.pyc | Bin 0 -> 15470 bytes
.../__pycache__/Deck_Overview.cpython-39.opt-2.pyc | Bin 0 -> 17194 bytes
.../__pycache__/Settings.cpython-39.opt-2.pyc | Bin 0 -> 85298 bytes
.../__pycache__/Skip.cpython-39.opt-2.pyc | Bin 0 -> 1445 bytes
.../__pycache__/Tooltip.cpython-39.opt-2.pyc | Bin 0 -> 11505 bytes
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 644 bytes
.../__pycache__/styles.cpython-39.opt-2.pyc | Bin 0 -> 26699 bytes
.../Anki2/addons21/advanced_review/changelog.html | 462 ++++
.../Anki2/addons21/advanced_review/config.json | 115 +
.../images/activeIndicator_border.png | Bin 0 -> 3731 bytes
.../images/activeIndicator_glow.png | Bin 0 -> 11044 bytes
.../images/activeIndicator_none.png | Bin 0 -> 3679 bytes
.../images/bottombarButtonsStyle_default.png | Bin 0 -> 5333 bytes
.../images/bottombarButtonsStyle_fill1.png | Bin 0 -> 5498 bytes
.../images/bottombarButtonsStyle_fill2.png | Bin 0 -> 12784 bytes
.../images/bottombarButtonsStyle_neon1.png | Bin 0 -> 8211 bytes
.../images/bottombarButtonsStyle_neon2.png | Bin 0 -> 12101 bytes
.../advanced_review/images/buttonColors_off.png | Bin 0 -> 3275 bytes
.../advanced_review/images/buttonColors_on.png | Bin 0 -> 3826 bytes
.../advanced_review/images/buttonSizes_off.png | Bin 0 -> 6079 bytes
.../advanced_review/images/buttonSizes_off2.png | Bin 0 -> 6120 bytes
.../advanced_review/images/buttonSizes_on.png | Bin 0 -> 6234 bytes
.../advanced_review/images/buttonSizes_on2.png | Bin 0 -> 7519 bytes
.../images/buttonStyle_defaultBackground.png | Bin 0 -> 3207 bytes
.../images/buttonStyle_defaultText.png | Bin 0 -> 3731 bytes
.../advanced_review/images/buttonStyle_fill1.png | Bin 0 -> 4019 bytes
.../advanced_review/images/buttonStyle_fill2.png | Bin 0 -> 4157 bytes
.../advanced_review/images/buttonStyle_neon1.png | Bin 0 -> 16135 bytes
.../advanced_review/images/buttonStyle_neon2.png | Bin 0 -> 14327 bytes
.../images/buttonStyle_wideBackground.png | Bin 0 -> 3955 bytes
.../images/buttonStyle_wideText.png | Bin 0 -> 4825 bytes
.../images/changeMainScreenButtons.png | Bin 0 -> 2502 bytes
.../images/changeMainScreenButtons2.png | Bin 0 -> 2088 bytes
.../images/changeMainScreenButtons3.png | Bin 0 -> 3261 bytes
.../advanced_review/images/changeStyle_text.png | Bin 0 -> 3731 bytes
.../advanced_review/images/coloredDues.png | Bin 0 -> 4895 bytes
.../images/hoverEffect_brighten.png | Bin 0 -> 3663 bytes
.../advanced_review/images/hoverEffect_glow.png | Bin 0 -> 8569 bytes
.../images/hoverEffect_glowBrighten.png | Bin 0 -> 8148 bytes
.../Anki2/addons21/advanced_review/images/icon.png | Bin 0 -> 8243 bytes
.../Anki2/addons21/advanced_review/manifest.json | 1 +
.../share/Anki2/addons21/advanced_review/meta.json | 1 +
.../share/Anki2/addons21/advanced_review/styles.py | 1169 +++++++++
.../user_files/Default Settings.json | 115 +
.../user_files/Large and Colorful Buttons.json | 118 +
.../user_files/My Settings (Large Buttons).json | 118 +
.../advanced_review/user_files/My Settings.json | 118 +
.../share/Anki2/addons21/anki_reworked/__init__.py | 705 ++++++
.../share/Anki2/addons21/anki_reworked/config.json | 8 +
.../share/Anki2/addons21/anki_reworked/config.md | 9 +
.../share/Anki2/addons21/anki_reworked/config.py | 22 +
.../addons21/anki_reworked/files/BottomBar.css | 40 +
.../addons21/anki_reworked/files/CardLayout.css | 1 +
.../addons21/anki_reworked/files/DeckBrowser.css | 48 +
.../Anki2/addons21/anki_reworked/files/Editor.css | 1 +
.../addons21/anki_reworked/files/Overview.css | 59 +
.../Anki2/addons21/anki_reworked/files/QAbout.css | 0
.../addons21/anki_reworked/files/QAddCards.css | 1 +
.../addons21/anki_reworked/files/QAddonsDialog.css | 0
.../addons21/anki_reworked/files/QBrowser.css | 0
.../addons21/anki_reworked/files/QEditCurrent.css | 0
.../files/QFilteredDeckConfigDialog.css | 0
.../addons21/anki_reworked/files/QNewDeckStats.css | 3 +
.../addons21/anki_reworked/files/QPreferences.css | 0
.../addons21/anki_reworked/files/Reviewer.css | 9 +
.../anki_reworked/files/ReviewerBottomBar.css | 60 +
.../addons21/anki_reworked/files/TopToolbar.css | 25 +
.../Anki2/addons21/anki_reworked/files/global.css | 96 +
.../Anki2/addons21/anki_reworked/files/legacy.css | 18 +
.../Anki2/addons21/anki_reworked/manifest.json | 10 +
.../share/Anki2/addons21/anki_reworked/meta.json | 1 +
.../Anki2/addons21/anki_reworked/themes/Anki.json | 230 ++
.../addons21/anki_reworked/themes/Catppuccin.json | 230 ++
.../Anki2/addons21/anki_reworked/themes/Nord.json | 230 ++
.../Anki2/addons21/anki_reworked/themes/readme.txt | 8 +
.../addons21/anki_reworked/user_files/readme.txt | 11 +
.../addons21/anki_reworked/utils/css_files.py | 37 +
.../Anki2/addons21/anki_reworked/utils/logger.py | 17 +
.../Anki2/addons21/anki_reworked/utils/modules.py | 18 +
.../Anki2/addons21/anki_reworked/utils/themes.py | 34 +
.../share/Anki2/addons21/button_colors/__init__.py | 32 +
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 843 bytes
.../share/Anki2/addons21/button_colors/config.json | 7 +
.../share/Anki2/addons21/button_colors/meta.json | 1 +
.../share/Anki2/addons21/keybindings/__init__.py | 2 +
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 231 bytes
.../__pycache__/cs_functions.cpython-39.opt-2.pyc | Bin 0 -> 8244 bytes
.../custom_shortcuts.cpython-39.opt-2.pyc | Bin 0 -> 24825 bytes
.../share/Anki2/addons21/keybindings/config.json | 152 ++
.local/share/Anki2/addons21/keybindings/config.md | 309 +++
.../Anki2/addons21/keybindings/cs_functions.py | 231 ++
.../Anki2/addons21/keybindings/custom_shortcuts.py | 882 +++++++
.local/share/Anki2/addons21/keybindings/meta.json | 1 +
.local/share/Anki2/addons21/replay/__init__.py | 148 ++
.../__pycache__/__init__.cpython-39.opt-2.pyc | Bin 0 -> 3242 bytes
.local/share/Anki2/addons21/replay/meta.json | 1 +
201 files changed, 19466 insertions(+)
create mode 100644 .local/share/Anki2/addons21/Anki_connect/__init__.py
create mode 100644 .local/share/Anki2/addons21/Anki_connect/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Anki_connect/__pycache__/edit.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Anki_connect/__pycache__/util.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Anki_connect/__pycache__/web.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Anki_connect/config.json
create mode 100644 .local/share/Anki2/addons21/Anki_connect/config.md
create mode 100644 .local/share/Anki2/addons21/Anki_connect/edit.py
create mode 100644 .local/share/Anki2/addons21/Anki_connect/meta.json
create mode 100644 .local/share/Anki2/addons21/Anki_connect/util.py
create mode 100644 .local/share/Anki2/addons21/Anki_connect/web.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/LICENSE
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/__init__.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/__pycache__/adjust_css_files22.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/__pycache__/config.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/__pycache__/gui_updatemanager.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/adjust_css_files22.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/config.json
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/config.md
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/config.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/__init__.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/__init__.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/settings_dialog.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt6/__init__.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt6/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt6/__pycache__/settings_dialog.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/forms/qt6/settings_dialog.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/resources/__init__.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui/resources/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/gui_updatemanager.py
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/manifest.json
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/meta.json
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/Other css files/editor.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/Other css files/webview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/22/toolbar.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/25/toolbar.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/31/toolbar.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/sources/css/36/toolbar.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/background/1721729412.jpg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/background/samuel-inkilainen-laboratory.jpg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/background/the-lm7-laboratories-original-characters-brown-wallpaper.jpg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/background/thumb-1920-1058130.jpg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/background/wp7186342.jpg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/css/custom_toolbar.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/AnKing.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Bam.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Bullseye.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Cowboy.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Diamond.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Dragon.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Fire.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Flower.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Nerd.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Rose.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Shield.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Skull.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Star.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/Sun.png
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/user_files/gear/gears.svg
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/deckbrowser.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/overview.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/reviewer-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/reviewer.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/toolbar-bottom.css
create mode 100644 .local/share/Anki2/addons21/Background_and_gear/web/css/toolbar.css
create mode 100644 .local/share/Anki2/addons21/advanced_review/Bottom_Bar.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/Button_Colors.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/CHANGELOG.md
create mode 100644 .local/share/Anki2/addons21/advanced_review/Card_Info.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/Deck_Overview.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/LICENSE
create mode 100644 .local/share/Anki2/addons21/advanced_review/README.md
create mode 100644 .local/share/Anki2/addons21/advanced_review/Settings.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/Skip.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/Tooltip.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/__init__.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Bottom_Bar.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Button_Colors.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Card_Info.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Deck_Overview.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Settings.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Skip.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/Tooltip.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/__pycache__/styles.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/advanced_review/changelog.html
create mode 100644 .local/share/Anki2/addons21/advanced_review/config.json
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/activeIndicator_border.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/activeIndicator_glow.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/activeIndicator_none.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/bottombarButtonsStyle_default.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/bottombarButtonsStyle_fill1.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/bottombarButtonsStyle_fill2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/bottombarButtonsStyle_neon1.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/bottombarButtonsStyle_neon2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonColors_off.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonColors_on.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonSizes_off.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonSizes_off2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonSizes_on.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonSizes_on2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_defaultBackground.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_defaultText.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_fill1.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_fill2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_neon1.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_neon2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_wideBackground.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/buttonStyle_wideText.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/changeMainScreenButtons.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/changeMainScreenButtons2.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/changeMainScreenButtons3.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/changeStyle_text.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/coloredDues.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/hoverEffect_brighten.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/hoverEffect_glow.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/hoverEffect_glowBrighten.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/images/icon.png
create mode 100644 .local/share/Anki2/addons21/advanced_review/manifest.json
create mode 100644 .local/share/Anki2/addons21/advanced_review/meta.json
create mode 100644 .local/share/Anki2/addons21/advanced_review/styles.py
create mode 100644 .local/share/Anki2/addons21/advanced_review/user_files/Default Settings.json
create mode 100644 .local/share/Anki2/addons21/advanced_review/user_files/Large and Colorful Buttons.json
create mode 100644 .local/share/Anki2/addons21/advanced_review/user_files/My Settings (Large Buttons).json
create mode 100644 .local/share/Anki2/addons21/advanced_review/user_files/My Settings.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/__init__.py
create mode 100644 .local/share/Anki2/addons21/anki_reworked/config.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/config.md
create mode 100644 .local/share/Anki2/addons21/anki_reworked/config.py
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/BottomBar.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/CardLayout.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/DeckBrowser.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/Editor.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/Overview.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QAbout.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QAddCards.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QAddonsDialog.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QBrowser.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QEditCurrent.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QFilteredDeckConfigDialog.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QNewDeckStats.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/QPreferences.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/Reviewer.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/ReviewerBottomBar.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/TopToolbar.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/global.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/files/legacy.css
create mode 100644 .local/share/Anki2/addons21/anki_reworked/manifest.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/meta.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/themes/Anki.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/themes/Catppuccin.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/themes/Nord.json
create mode 100644 .local/share/Anki2/addons21/anki_reworked/themes/readme.txt
create mode 100644 .local/share/Anki2/addons21/anki_reworked/user_files/readme.txt
create mode 100644 .local/share/Anki2/addons21/anki_reworked/utils/css_files.py
create mode 100644 .local/share/Anki2/addons21/anki_reworked/utils/logger.py
create mode 100644 .local/share/Anki2/addons21/anki_reworked/utils/modules.py
create mode 100644 .local/share/Anki2/addons21/anki_reworked/utils/themes.py
create mode 100644 .local/share/Anki2/addons21/button_colors/__init__.py
create mode 100644 .local/share/Anki2/addons21/button_colors/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/button_colors/config.json
create mode 100644 .local/share/Anki2/addons21/button_colors/meta.json
create mode 100644 .local/share/Anki2/addons21/keybindings/__init__.py
create mode 100644 .local/share/Anki2/addons21/keybindings/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/keybindings/__pycache__/cs_functions.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/keybindings/__pycache__/custom_shortcuts.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/keybindings/config.json
create mode 100644 .local/share/Anki2/addons21/keybindings/config.md
create mode 100644 .local/share/Anki2/addons21/keybindings/cs_functions.py
create mode 100644 .local/share/Anki2/addons21/keybindings/custom_shortcuts.py
create mode 100644 .local/share/Anki2/addons21/keybindings/meta.json
create mode 100644 .local/share/Anki2/addons21/replay/__init__.py
create mode 100644 .local/share/Anki2/addons21/replay/__pycache__/__init__.cpython-39.opt-2.pyc
create mode 100644 .local/share/Anki2/addons21/replay/meta.json
diff --git a/.local/share/Anki2/addons21/Anki_connect/__init__.py b/.local/share/Anki2/addons21/Anki_connect/__init__.py
new file mode 100644
index 0000000..93c92d0
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/__init__.py
@@ -0,0 +1,1688 @@
+# Copyright 2016-2021 Alex Yatskov
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import aqt
+
+anki_version = tuple(int(segment) for segment in aqt.appVersion.split("."))
+
+if anki_version < (2, 1, 45):
+ raise Exception("Minimum Anki version supported: 2.1.45")
+
+import base64
+import glob
+import hashlib
+import inspect
+import json
+import os
+import os.path
+import platform
+import re
+import time
+import unicodedata
+
+import anki
+import anki.exporting
+import anki.storage
+from anki.cards import Card
+from anki.consts import MODEL_CLOZE
+from anki.exporting import AnkiPackageExporter
+from anki.importing import AnkiPackageImporter
+from anki.notes import Note
+from anki.errors import NotFoundError
+from aqt.qt import Qt, QTimer, QMessageBox, QCheckBox
+
+from .web import format_exception_reply, format_success_reply
+from .edit import Edit
+from . import web, util
+
+
+#
+# AnkiConnect
+#
+
+class AnkiConnect:
+ def __init__(self):
+ self.log = None
+ self.timer = None
+ self.server = web.WebServer(self.handler)
+
+ def initLogging(self):
+ logPath = util.setting('apiLogPath')
+ if logPath is not None:
+ self.log = open(logPath, 'w')
+
+ def startWebServer(self):
+ try:
+ self.server.listen()
+
+ # only keep reference to prevent garbage collection
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.advance)
+ self.timer.start(util.setting('apiPollInterval'))
+ except:
+ QMessageBox.critical(
+ self.window(),
+ 'AnkiConnect',
+ 'Failed to listen on port {}.\nMake sure it is available and is not in use.'.format(util.setting('webBindPort'))
+ )
+
+ def save_model(self, models, ankiModel):
+ models.update_dict(ankiModel)
+
+ def logEvent(self, name, data):
+ if self.log is not None:
+ self.log.write('[{}]\n'.format(name))
+ json.dump(data, self.log, indent=4, sort_keys=True)
+ self.log.write('\n\n')
+ self.log.flush()
+
+
+ def advance(self):
+ self.server.advance()
+
+
+ def handler(self, request):
+ self.logEvent('request', request)
+
+ name = request.get('action', '')
+ version = request.get('version', 4)
+ params = request.get('params', {})
+ key = request.get('key')
+
+ try:
+ if key != util.setting('apiKey') and name != 'requestPermission':
+ raise Exception('valid api key must be provided')
+
+ method = None
+
+ for methodName, methodInst in inspect.getmembers(self, predicate=inspect.ismethod):
+ apiVersionLast = 0
+ apiNameLast = None
+
+ if getattr(methodInst, 'api', False):
+ for apiVersion, apiName in getattr(methodInst, 'versions', []):
+ if apiVersionLast < apiVersion <= version:
+ apiVersionLast = apiVersion
+ apiNameLast = apiName
+
+ if apiNameLast is None and apiVersionLast == 0:
+ apiNameLast = methodName
+
+ if apiNameLast is not None and apiNameLast == name:
+ method = methodInst
+ break
+
+ if method is None:
+ raise Exception('unsupported action')
+
+ api_return_value = methodInst(**params)
+ reply = format_success_reply(version, api_return_value)
+
+ except Exception as e:
+ reply = format_exception_reply(version, e)
+
+ self.logEvent('reply', reply)
+ return reply
+
+
+ def window(self):
+ return aqt.mw
+
+
+ def reviewer(self):
+ reviewer = self.window().reviewer
+ if reviewer is None:
+ raise Exception('reviewer is not available')
+
+ return reviewer
+
+
+ def collection(self):
+ collection = self.window().col
+ if collection is None:
+ raise Exception('collection is not available')
+
+ return collection
+
+
+ def decks(self):
+ decks = self.collection().decks
+ if decks is None:
+ raise Exception('decks are not available')
+
+ return decks
+
+
+ def scheduler(self):
+ scheduler = self.collection().sched
+ if scheduler is None:
+ raise Exception('scheduler is not available')
+
+ return scheduler
+
+
+ def database(self):
+ database = self.collection().db
+ if database is None:
+ raise Exception('database is not available')
+
+ return database
+
+
+ def media(self):
+ media = self.collection().media
+ if media is None:
+ raise Exception('media is not available')
+
+ return media
+
+
+ def startEditing(self):
+ self.window().requireReset()
+
+
+ def stopEditing(self):
+ if self.collection() is not None:
+ self.window().maybeReset()
+
+
+ def createNote(self, note):
+ collection = self.collection()
+
+ model = collection.models.byName(note['modelName'])
+ if model is None:
+ raise Exception('model was not found: {}'.format(note['modelName']))
+
+ deck = collection.decks.byName(note['deckName'])
+ if deck is None:
+ raise Exception('deck was not found: {}'.format(note['deckName']))
+
+ ankiNote = anki.notes.Note(collection, model)
+ ankiNote.model()['did'] = deck['id']
+ if 'tags' in note:
+ ankiNote.tags = note['tags']
+
+ for name, value in note['fields'].items():
+ for ankiName in ankiNote.keys():
+ if name.lower() == ankiName.lower():
+ ankiNote[ankiName] = value
+ break
+
+ allowDuplicate = False
+ duplicateScope = None
+ duplicateScopeDeckName = None
+ duplicateScopeCheckChildren = False
+ duplicateScopeCheckAllModels = False
+
+ if 'options' in note:
+ options = note['options']
+ if 'allowDuplicate' in options:
+ allowDuplicate = options['allowDuplicate']
+ if type(allowDuplicate) is not bool:
+ raise Exception('option parameter "allowDuplicate" must be boolean')
+ if 'duplicateScope' in options:
+ duplicateScope = options['duplicateScope']
+ if 'duplicateScopeOptions' in options:
+ duplicateScopeOptions = options['duplicateScopeOptions']
+ if 'deckName' in duplicateScopeOptions:
+ duplicateScopeDeckName = duplicateScopeOptions['deckName']
+ if 'checkChildren' in duplicateScopeOptions:
+ duplicateScopeCheckChildren = duplicateScopeOptions['checkChildren']
+ if type(duplicateScopeCheckChildren) is not bool:
+ raise Exception('option parameter "duplicateScopeOptions.checkChildren" must be boolean')
+ if 'checkAllModels' in duplicateScopeOptions:
+ duplicateScopeCheckAllModels = duplicateScopeOptions['checkAllModels']
+ if type(duplicateScopeCheckAllModels) is not bool:
+ raise Exception('option parameter "duplicateScopeOptions.checkAllModels" must be boolean')
+
+ duplicateOrEmpty = self.isNoteDuplicateOrEmptyInScope(
+ ankiNote,
+ deck,
+ collection,
+ duplicateScope,
+ duplicateScopeDeckName,
+ duplicateScopeCheckChildren,
+ duplicateScopeCheckAllModels
+ )
+
+ if duplicateOrEmpty == 1:
+ raise Exception('cannot create note because it is empty')
+ elif duplicateOrEmpty == 2:
+ if allowDuplicate:
+ return ankiNote
+ raise Exception('cannot create note because it is a duplicate')
+ elif duplicateOrEmpty == 0:
+ return ankiNote
+ else:
+ raise Exception('cannot create note for unknown reason')
+
+
+ def isNoteDuplicateOrEmptyInScope(
+ self,
+ note,
+ deck,
+ collection,
+ duplicateScope,
+ duplicateScopeDeckName,
+ duplicateScopeCheckChildren,
+ duplicateScopeCheckAllModels
+ ):
+ # Returns: 1 if first is empty, 2 if first is a duplicate, 0 otherwise.
+
+ # note.dupeOrEmpty returns if a note is a global duplicate with the specific model.
+ # This is used as the default check, and the rest of this function is manually
+ # checking if the note is a duplicate with additional options.
+ if duplicateScope != 'deck' and not duplicateScopeCheckAllModels:
+ return note.dupeOrEmpty() or 0
+
+ # Primary field for uniqueness
+ val = note.fields[0]
+ if not val.strip():
+ return 1
+ csum = anki.utils.fieldChecksum(val)
+
+ # Create dictionary of deck ids
+ dids = None
+ if duplicateScope == 'deck':
+ did = deck['id']
+ if duplicateScopeDeckName is not None:
+ deck2 = collection.decks.byName(duplicateScopeDeckName)
+ if deck2 is None:
+ # Invalid deck, so cannot be duplicate
+ return 0
+ did = deck2['id']
+
+ dids = {did: True}
+ if duplicateScopeCheckChildren:
+ for kv in collection.decks.children(did):
+ dids[kv[1]] = True
+
+ # Build query
+ query = 'select id from notes where csum=?'
+ queryArgs = [csum]
+ if note.id:
+ query += ' and id!=?'
+ queryArgs.append(note.id)
+ if not duplicateScopeCheckAllModels:
+ query += ' and mid=?'
+ queryArgs.append(note.mid)
+
+ # Search
+ for noteId in note.col.db.list(query, *queryArgs):
+ if dids is None:
+ # Duplicate note exists in the collection
+ return 2
+ # Validate that a card exists in one of the specified decks
+ for cardDeckId in note.col.db.list('select did from cards where nid=?', noteId):
+ if cardDeckId in dids:
+ return 2
+
+ # Not a duplicate
+ return 0
+
+ def getCard(self, card_id: int) -> Card:
+ try:
+ return self.collection().getCard(card_id)
+ except NotFoundError:
+ raise NotFoundError('Card was not found: {}'.format(card_id))
+
+ def getNote(self, note_id: int) -> Note:
+ try:
+ return self.collection().getNote(note_id)
+ except NotFoundError:
+ raise NotFoundError('Note was not found: {}'.format(note_id))
+
+ def deckStatsToJson(self, due_tree):
+ deckStats = {'deck_id': due_tree.deck_id,
+ 'name': due_tree.name,
+ 'new_count': due_tree.new_count,
+ 'learn_count': due_tree.learn_count,
+ 'review_count': due_tree.review_count}
+ if anki_version > (2, 1, 46):
+ # total_in_deck is not supported on lower Anki versions
+ deckStats['total_in_deck'] = due_tree.total_in_deck
+ return deckStats
+
+ def collectDeckTreeChildren(self, parent_node):
+ allNodes = {parent_node.deck_id: parent_node}
+ for child in parent_node.children:
+ for deckId, childNode in self.collectDeckTreeChildren(child).items():
+ allNodes[deckId] = childNode
+ return allNodes
+
+ #
+ # Miscellaneous
+ #
+
+ @util.api()
+ def version(self):
+ return util.setting('apiVersion')
+
+
+ @util.api()
+ def requestPermission(self, origin, allowed):
+ results = {
+ "permission": "denied",
+ }
+
+ if allowed:
+ results = {
+ "permission": "granted",
+ "requireApikey": bool(util.setting('apiKey')),
+ "version": util.setting('apiVersion')
+ }
+
+ elif origin in util.setting('ignoreOriginList'):
+ pass # defaults to denied
+
+ else: # prompt the user
+ msg = QMessageBox(None)
+ msg.setWindowTitle("A website wants to access to Anki")
+ msg.setText('"{}" requests permission to use Anki through AnkiConnect. Do you want to give it access?'.format(origin))
+ msg.setInformativeText("By granting permission, you'll allow the website to modify your collection on your behalf, including the execution of destructive actions such as deck deletion.")
+ msg.setWindowIcon(self.window().windowIcon())
+ msg.setIcon(QMessageBox.Question)
+ msg.setStandardButtons(QMessageBox.Yes|QMessageBox.No)
+ msg.setDefaultButton(QMessageBox.No)
+ msg.setCheckBox(QCheckBox(text='Ignore further requests from "{}"'.format(origin), parent=msg))
+ msg.setWindowFlags(Qt.WindowStaysOnTopHint)
+ pressedButton = msg.exec_()
+
+ if pressedButton == QMessageBox.Yes:
+ config = aqt.mw.addonManager.getConfig(__name__)
+ config["webCorsOriginList"] = util.setting('webCorsOriginList')
+ config["webCorsOriginList"].append(origin)
+ aqt.mw.addonManager.writeConfig(__name__, config)
+ results = {
+ "permission": "granted",
+ "requireApikey": bool(util.setting('apiKey')),
+ "version": util.setting('apiVersion')
+ }
+
+ # if the origin isn't an empty string, the user clicks "No", and the ignore box is checked
+ elif origin and pressedButton == QMessageBox.No and msg.checkBox().isChecked():
+ config = aqt.mw.addonManager.getConfig(__name__)
+ config["ignoreOriginList"] = util.setting('ignoreOriginList')
+ config["ignoreOriginList"].append(origin)
+ aqt.mw.addonManager.writeConfig(__name__, config)
+
+ # else defaults to denied
+
+ return results
+
+
+ @util.api()
+ def getProfiles(self):
+ return self.window().pm.profiles()
+
+
+ @util.api()
+ def loadProfile(self, name):
+ if name not in self.window().pm.profiles():
+ return False
+
+ if self.window().isVisible():
+ cur_profile = self.window().pm.name
+ if cur_profile != name:
+ self.window().unloadProfileAndShowProfileManager()
+
+ def waiter():
+ # This function waits until main window is closed
+ # It's needed cause sync can take quite some time
+ # And if we call loadProfile until sync is ended things will go wrong
+ if self.window().isVisible():
+ QTimer.singleShot(1000, waiter)
+ else:
+ self.loadProfile(name)
+
+ waiter()
+ else:
+ self.window().pm.load(name)
+ self.window().loadProfile()
+ self.window().profileDiag.closeWithoutQuitting()
+
+ return True
+
+
+ @util.api()
+ def sync(self):
+ self.window().onSync()
+
+
+ @util.api()
+ def multi(self, actions):
+ return list(map(self.handler, actions))
+
+
+ @util.api()
+ def getNumCardsReviewedToday(self):
+ return self.database().scalar('select count() from revlog where id > ?', (self.scheduler().dayCutoff - 86400) * 1000)
+
+ @util.api()
+ def getNumCardsReviewedByDay(self):
+ return self.database().all('select date(id/1000 - ?, "unixepoch", "localtime") as day, count() from revlog group by day order by day desc',
+ int(time.strftime("%H", time.localtime(self.scheduler().dayCutoff))) * 3600)
+
+
+ @util.api()
+ def getCollectionStatsHTML(self, wholeCollection=True):
+ stats = self.collection().stats()
+ stats.wholeCollection = wholeCollection
+ return stats.report()
+
+
+ #
+ # Decks
+ #
+
+ @util.api()
+ def deckNames(self):
+ return self.decks().allNames()
+
+
+ @util.api()
+ def deckNamesAndIds(self):
+ decks = {}
+ for deck in self.deckNames():
+ decks[deck] = self.decks().id(deck)
+
+ return decks
+
+
+ @util.api()
+ def getDecks(self, cards):
+ decks = {}
+ for card in cards:
+ did = self.database().scalar('select did from cards where id=?', card)
+ deck = self.decks().get(did)['name']
+ if deck in decks:
+ decks[deck].append(card)
+ else:
+ decks[deck] = [card]
+
+ return decks
+
+
+ @util.api()
+ def createDeck(self, deck):
+ try:
+ self.startEditing()
+ did = self.decks().id(deck)
+ finally:
+ self.stopEditing()
+
+ return did
+
+
+ @util.api()
+ def changeDeck(self, cards, deck):
+ self.startEditing()
+
+ did = self.collection().decks.id(deck)
+ mod = anki.utils.intTime()
+ usn = self.collection().usn()
+
+ # normal cards
+ scids = anki.utils.ids2str(cards)
+ # remove any cards from filtered deck first
+ self.collection().sched.remFromDyn(cards)
+
+ # then move into new deck
+ self.collection().db.execute('update cards set usn=?, mod=?, did=? where id in ' + scids, usn, mod, did)
+ self.stopEditing()
+
+
+ @util.api()
+ def deleteDecks(self, decks, cardsToo=False):
+ if not cardsToo:
+ # since f592672fa952260655881a75a2e3c921b2e23857 (2.1.28)
+ # (see anki$ git log "-Gassert cardsToo")
+ # you can't delete decks without deleting cards as well.
+ # however, since 62c23c6816adf912776b9378c008a52bb50b2e8d (2.1.45)
+ # passing cardsToo to `rem` (long deprecated) won't raise an error!
+ # this is dangerous, so let's raise our own exception
+ raise Exception("Since Anki 2.1.28 it's not possible "
+ "to delete decks without deleting cards as well")
+ try:
+ self.startEditing()
+ decks = filter(lambda d: d in self.deckNames(), decks)
+ for deck in decks:
+ did = self.decks().id(deck)
+ self.decks().rem(did, cardsToo=cardsToo)
+ finally:
+ self.stopEditing()
+
+
+ @util.api()
+ def getDeckConfig(self, deck):
+ if deck not in self.deckNames():
+ return False
+
+ collection = self.collection()
+ did = collection.decks.id(deck)
+ return collection.decks.confForDid(did)
+
+
+ @util.api()
+ def saveDeckConfig(self, config):
+ collection = self.collection()
+
+ config['id'] = str(config['id'])
+ config['mod'] = anki.utils.intTime()
+ config['usn'] = collection.usn()
+ if int(config['id']) not in [c['id'] for c in collection.decks.all_config()]:
+ return False
+ try:
+ collection.decks.save(config)
+ collection.decks.updateConf(config)
+ except:
+ return False
+ return True
+
+
+ @util.api()
+ def setDeckConfigId(self, decks, configId):
+ configId = int(configId)
+ for deck in decks:
+ if not deck in self.deckNames():
+ return False
+
+ collection = self.collection()
+
+ for deck in decks:
+ try:
+ did = str(collection.decks.id(deck))
+ deck_dict = aqt.mw.col.decks.decks[did]
+ deck_dict['conf'] = configId
+ collection.decks.save(deck_dict)
+ except:
+ return False
+
+ return True
+
+
+ @util.api()
+ def cloneDeckConfigId(self, name, cloneFrom='1'):
+ configId = int(cloneFrom)
+ collection = self.collection()
+ if configId not in [c['id'] for c in collection.decks.all_config()]:
+ return False
+
+ config = collection.decks.getConf(configId)
+ return collection.decks.confId(name, config)
+
+
+ @util.api()
+ def removeDeckConfigId(self, configId):
+ collection = self.collection()
+ if int(configId) not in [c['id'] for c in collection.decks.all_config()]:
+ return False
+
+ collection.decks.remConf(configId)
+ return True
+
+ @util.api()
+ def getDeckStats(self, decks):
+ collection = self.collection()
+ scheduler = self.scheduler()
+ responseDict = {}
+ deckIds = list(map(lambda d: collection.decks.id(d), decks))
+
+ allDeckNodes = self.collectDeckTreeChildren(scheduler.deck_due_tree())
+ for deckId, deckNode in allDeckNodes.items():
+ if deckId in deckIds:
+ responseDict[deckId] = self.deckStatsToJson(deckNode)
+ return responseDict
+
+ @util.api()
+ def storeMediaFile(self, filename, data=None, path=None, url=None, skipHash=None, deleteExisting=True):
+ if not (data or path or url):
+ raise Exception('You must provide a "data", "path", or "url" field.')
+ if data:
+ mediaData = base64.b64decode(data)
+ elif path:
+ with open(path, 'rb') as f:
+ mediaData = f.read()
+ elif url:
+ mediaData = util.download(url)
+
+ if skipHash is None:
+ skip = False
+ else:
+ m = hashlib.md5()
+ m.update(mediaData)
+ skip = skipHash == m.hexdigest()
+
+ if skip:
+ return None
+ if deleteExisting:
+ self.deleteMediaFile(filename)
+ return self.media().writeData(filename, mediaData)
+
+
+ @util.api()
+ def retrieveMediaFile(self, filename):
+ filename = os.path.basename(filename)
+ filename = unicodedata.normalize('NFC', filename)
+ filename = self.media().stripIllegal(filename)
+
+ path = os.path.join(self.media().dir(), filename)
+ if os.path.exists(path):
+ with open(path, 'rb') as file:
+ return base64.b64encode(file.read()).decode('ascii')
+
+ return False
+
+
+ @util.api()
+ def getMediaFilesNames(self, pattern='*'):
+ path = os.path.join(self.media().dir(), pattern)
+ return [os.path.basename(p) for p in glob.glob(path)]
+
+
+ @util.api()
+ def deleteMediaFile(self, filename):
+ try:
+ self.media().syncDelete(filename)
+ except AttributeError:
+ self.media().trash_files([filename])
+
+
+ @util.api()
+ def addNote(self, note):
+ ankiNote = self.createNote(note)
+
+ self.addMediaFromNote(ankiNote, note)
+
+ collection = self.collection()
+ self.startEditing()
+ nCardsAdded = collection.addNote(ankiNote)
+ if nCardsAdded < 1:
+ raise Exception('The field values you have provided would make an empty question on all cards.')
+ collection.autosave()
+ self.stopEditing()
+
+ return ankiNote.id
+
+
+ def addMediaFromNote(self, ankiNote, note):
+ audioObjectOrList = note.get('audio')
+ self.addMedia(ankiNote, audioObjectOrList, util.MediaType.Audio)
+
+ videoObjectOrList = note.get('video')
+ self.addMedia(ankiNote, videoObjectOrList, util.MediaType.Video)
+
+ pictureObjectOrList = note.get('picture')
+ self.addMedia(ankiNote, pictureObjectOrList, util.MediaType.Picture)
+
+
+
+ def addMedia(self, ankiNote, mediaObjectOrList, mediaType):
+ if mediaObjectOrList is None:
+ return
+
+ if isinstance(mediaObjectOrList, list):
+ mediaList = mediaObjectOrList
+ else:
+ mediaList = [mediaObjectOrList]
+
+ for media in mediaList:
+ if media is not None and len(media['fields']) > 0:
+ try:
+ mediaFilename = self.storeMediaFile(media['filename'],
+ data=media.get('data'),
+ path=media.get('path'),
+ url=media.get('url'),
+ skipHash=media.get('skipHash'),
+ deleteExisting=media.get('deleteExisting'))
+
+ if mediaFilename is not None:
+ for field in media['fields']:
+ if field in ankiNote:
+ if mediaType is util.MediaType.Picture:
+ ankiNote[field] += u''.format(mediaFilename)
+ elif mediaType is util.MediaType.Audio or mediaType is util.MediaType.Video:
+ ankiNote[field] += u'[sound:{}]'.format(mediaFilename)
+
+ except Exception as e:
+ errorMessage = str(e).replace('&', '&').replace('<', '<').replace('>', '>')
+ for field in media['fields']:
+ if field in ankiNote:
+ ankiNote[field] += errorMessage
+
+
+ @util.api()
+ def canAddNote(self, note):
+ try:
+ return bool(self.createNote(note))
+ except:
+ return False
+
+
+ @util.api()
+ def updateNoteFields(self, note):
+ ankiNote = self.getNote(note['id'])
+
+ self.startEditing()
+ for name, value in note['fields'].items():
+ if name in ankiNote:
+ ankiNote[name] = value
+
+ audioObjectOrList = note.get('audio')
+ self.addMedia(ankiNote, audioObjectOrList, util.MediaType.Audio)
+
+ videoObjectOrList = note.get('video')
+ self.addMedia(ankiNote, videoObjectOrList, util.MediaType.Video)
+
+ pictureObjectOrList = note.get('picture')
+ self.addMedia(ankiNote, pictureObjectOrList, util.MediaType.Picture)
+
+ ankiNote.flush()
+
+ self.collection().autosave()
+ self.stopEditing()
+
+
+ @util.api()
+ def addTags(self, notes, tags, add=True):
+ self.startEditing()
+ self.collection().tags.bulkAdd(notes, tags, add)
+ self.stopEditing()
+
+
+ @util.api()
+ def removeTags(self, notes, tags):
+ return self.addTags(notes, tags, False)
+
+
+ @util.api()
+ def getTags(self):
+ return self.collection().tags.all()
+
+
+ @util.api()
+ def clearUnusedTags(self):
+ self.collection().tags.registerNotes()
+
+
+ @util.api()
+ def replaceTags(self, notes, tag_to_replace, replace_with_tag):
+ self.window().progress.start()
+
+ for nid in notes:
+ try:
+ note = self.getNote(nid)
+ except NotFoundError:
+ continue
+
+ if note.hasTag(tag_to_replace):
+ note.delTag(tag_to_replace)
+ note.addTag(replace_with_tag)
+ note.flush()
+
+ self.window().requireReset()
+ self.window().progress.finish()
+ self.window().reset()
+
+
+ @util.api()
+ def replaceTagsInAllNotes(self, tag_to_replace, replace_with_tag):
+ self.window().progress.start()
+
+ collection = self.collection()
+ for nid in collection.db.list('select id from notes'):
+ note = self.getNote(nid)
+ if note.hasTag(tag_to_replace):
+ note.delTag(tag_to_replace)
+ note.addTag(replace_with_tag)
+ note.flush()
+
+ self.window().requireReset()
+ self.window().progress.finish()
+ self.window().reset()
+
+
+ @util.api()
+ def setEaseFactors(self, cards, easeFactors):
+ couldSetEaseFactors = []
+ for i, card in enumerate(cards):
+ try:
+ ankiCard = self.getCard(card)
+ except NotFoundError:
+ couldSetEaseFactors.append(False)
+ continue
+
+ couldSetEaseFactors.append(True)
+ ankiCard.factor = easeFactors[i]
+ ankiCard.flush()
+
+ return couldSetEaseFactors
+
+ @util.api()
+ def setSpecificValueOfCard(self, card, keys,
+ newValues, warning_check=False):
+ if isinstance(card, list):
+ print("card has to be int, not list")
+ return False
+
+ if not isinstance(keys, list) or not isinstance(newValues, list):
+ print("keys and newValues have to be lists.")
+ return False
+
+ if len(newValues) != len(keys):
+ print("Invalid list lengths.")
+ return False
+
+ for key in keys:
+ if key in ["did", "id", "ivl", "lapses", "left", "mod", "nid",
+ "odid", "odue", "ord", "queue", "reps", "type", "usn"]:
+ if warning_check is False:
+ return False
+
+ result = []
+ try:
+ ankiCard = self.getCard(card)
+ for i, key in enumerate(keys):
+ setattr(ankiCard, key, newValues[i])
+ ankiCard.flush()
+ result.append(True)
+ except Exception as e:
+ result.append([False, str(e)])
+ return result
+
+
+ @util.api()
+ def getEaseFactors(self, cards):
+ easeFactors = []
+ for card in cards:
+ try:
+ ankiCard = self.getCard(card)
+ except NotFoundError:
+ easeFactors.append(None)
+ continue
+
+ easeFactors.append(ankiCard.factor)
+
+ return easeFactors
+
+
+ @util.api()
+ def suspend(self, cards, suspend=True):
+ for card in cards:
+ if self.suspended(card) == suspend:
+ cards.remove(card)
+
+ if len(cards) == 0:
+ return False
+
+ scheduler = self.scheduler()
+ self.startEditing()
+ if suspend:
+ scheduler.suspendCards(cards)
+ else:
+ scheduler.unsuspendCards(cards)
+ self.stopEditing()
+
+ return True
+
+
+ @util.api()
+ def unsuspend(self, cards):
+ self.suspend(cards, False)
+
+
+ @util.api()
+ def suspended(self, card):
+ card = self.getCard(card)
+ return card.queue == -1
+
+
+ @util.api()
+ def areSuspended(self, cards):
+ suspended = []
+ for card in cards:
+ try:
+ suspended.append(self.suspended(card))
+ except NotFoundError:
+ suspended.append(None)
+
+ return suspended
+
+
+ @util.api()
+ def areDue(self, cards):
+ due = []
+ for card in cards:
+ if self.findCards('cid:{} is:new'.format(card)):
+ due.append(True)
+ else:
+ date, ivl = self.collection().db.all('select id/1000.0, ivl from revlog where cid = ?', card)[-1]
+ if ivl >= -1200:
+ due.append(bool(self.findCards('cid:{} is:due'.format(card))))
+ else:
+ due.append(date - ivl <= time.time())
+
+ return due
+
+
+ @util.api()
+ def getIntervals(self, cards, complete=False):
+ intervals = []
+ for card in cards:
+ if self.findCards('cid:{} is:new'.format(card)):
+ intervals.append(0)
+ else:
+ interval = self.collection().db.list('select ivl from revlog where cid = ?', card)
+ if not complete:
+ interval = interval[-1]
+ intervals.append(interval)
+
+ return intervals
+
+
+
+ @util.api()
+ def modelNames(self):
+ return self.collection().models.allNames()
+
+
+ @util.api()
+ def createModel(self, modelName, inOrderFields, cardTemplates, css = None, isCloze = False):
+ # https://github.com/dae/anki/blob/b06b70f7214fb1f2ce33ba06d2b095384b81f874/anki/stdmodels.py
+ if len(inOrderFields) == 0:
+ raise Exception('Must provide at least one field for inOrderFields')
+ if len(cardTemplates) == 0:
+ raise Exception('Must provide at least one card for cardTemplates')
+ if modelName in self.collection().models.allNames():
+ raise Exception('Model name already exists')
+
+ collection = self.collection()
+ mm = collection.models
+
+ # Generate new Note
+ m = mm.new(modelName)
+ if isCloze:
+ m['type'] = MODEL_CLOZE
+
+ # Create fields and add them to Note
+ for field in inOrderFields:
+ fm = mm.newField(field)
+ mm.addField(m, fm)
+
+ # Add shared css to model if exists. Use default otherwise
+ if (css is not None):
+ m['css'] = css
+
+ # Generate new card template(s)
+ cardCount = 1
+ for card in cardTemplates:
+ cardName = 'Card ' + str(cardCount)
+ if 'Name' in card:
+ cardName = card['Name']
+
+ t = mm.newTemplate(cardName)
+ cardCount += 1
+ t['qfmt'] = card['Front']
+ t['afmt'] = card['Back']
+ mm.addTemplate(m, t)
+
+ mm.add(m)
+ return m
+
+
+ @util.api()
+ def modelNamesAndIds(self):
+ models = {}
+ for model in self.modelNames():
+ models[model] = int(self.collection().models.byName(model)['id'])
+
+ return models
+
+
+ @util.api()
+ def modelNameFromId(self, modelId):
+ model = self.collection().models.get(modelId)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelId))
+ else:
+ return model['name']
+
+
+ @util.api()
+ def modelFieldNames(self, modelName):
+ model = self.collection().models.byName(modelName)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelName))
+ else:
+ return [field['name'] for field in model['flds']]
+
+
+ @util.api()
+ def modelFieldsOnTemplates(self, modelName):
+ model = self.collection().models.byName(modelName)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelName))
+
+ templates = {}
+ for template in model['tmpls']:
+ fields = []
+ for side in ['qfmt', 'afmt']:
+ fieldsForSide = []
+
+ # based on _fieldsOnTemplate from aqt/clayout.py
+ matches = re.findall('{{[^#/}]+?}}', template[side])
+ for match in matches:
+ # remove braces and modifiers
+ match = re.sub(r'[{}]', '', match)
+ match = match.split(':')[-1]
+
+ # for the answer side, ignore fields present on the question side + the FrontSide field
+ if match == 'FrontSide' or side == 'afmt' and match in fields[0]:
+ continue
+ fieldsForSide.append(match)
+
+ fields.append(fieldsForSide)
+
+ templates[template['name']] = fields
+
+ return templates
+
+
+ @util.api()
+ def modelTemplates(self, modelName):
+ model = self.collection().models.byName(modelName)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelName))
+
+ templates = {}
+ for template in model['tmpls']:
+ templates[template['name']] = {'Front': template['qfmt'], 'Back': template['afmt']}
+
+ return templates
+
+
+ @util.api()
+ def modelStyling(self, modelName):
+ model = self.collection().models.byName(modelName)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelName))
+
+ return {'css': model['css']}
+
+
+ @util.api()
+ def updateModelTemplates(self, model):
+ models = self.collection().models
+ ankiModel = models.byName(model['name'])
+ if ankiModel is None:
+ raise Exception('model was not found: {}'.format(model['name']))
+
+ templates = model['templates']
+ for ankiTemplate in ankiModel['tmpls']:
+ template = templates.get(ankiTemplate['name'])
+ if template:
+ qfmt = template.get('Front')
+ if qfmt:
+ ankiTemplate['qfmt'] = qfmt
+
+ afmt = template.get('Back')
+ if afmt:
+ ankiTemplate['afmt'] = afmt
+
+ self.save_model(models, ankiModel)
+
+
+ @util.api()
+ def updateModelStyling(self, model):
+ models = self.collection().models
+ ankiModel = models.byName(model['name'])
+ if ankiModel is None:
+ raise Exception('model was not found: {}'.format(model['name']))
+
+ ankiModel['css'] = model['css']
+
+ self.save_model(models, ankiModel)
+
+
+ @util.api()
+ def findAndReplaceInModels(self, modelName, findText, replaceText, front=True, back=True, css=True):
+ if not modelName:
+ ankiModel = self.collection().models.allNames()
+ else:
+ model = self.collection().models.byName(modelName)
+ if model is None:
+ raise Exception('model was not found: {}'.format(modelName))
+ ankiModel = [modelName]
+ updatedModels = 0
+ for model in ankiModel:
+ model = self.collection().models.byName(model)
+ checkForText = False
+ if css and findText in model['css']:
+ checkForText = True
+ model['css'] = model['css'].replace(findText, replaceText)
+ for tmpls in model.get('tmpls'):
+ if front and findText in tmpls['qfmt']:
+ checkForText = True
+ tmpls['qfmt'] = tmpls['qfmt'].replace(findText, replaceText)
+ if back and findText in tmpls['afmt']:
+ checkForText = True
+ tmpls['afmt'] = tmpls['afmt'].replace(findText, replaceText)
+ self.save_model(self.collection().models, model)
+ if checkForText:
+ updatedModels += 1
+ return updatedModels
+
+
+ @util.api()
+ def deckNameFromId(self, deckId):
+ deck = self.collection().decks.get(deckId)
+ if deck is None:
+ raise Exception('deck was not found: {}'.format(deckId))
+
+ return deck['name']
+
+
+ @util.api()
+ def findNotes(self, query=None):
+ if query is None:
+ return []
+
+ return list(map(int, self.collection().findNotes(query)))
+
+
+ @util.api()
+ def findCards(self, query=None):
+ if query is None:
+ return []
+
+ return list(map(int, self.collection().findCards(query)))
+
+
+ @util.api()
+ def cardsInfo(self, cards):
+ result = []
+ for cid in cards:
+ try:
+ card = self.getCard(cid)
+ model = card.model()
+ note = card.note()
+ fields = {}
+ for info in model['flds']:
+ order = info['ord']
+ name = info['name']
+ fields[name] = {'value': note.fields[order], 'order': order}
+
+ result.append({
+ 'cardId': card.id,
+ 'fields': fields,
+ 'fieldOrder': card.ord,
+ 'question': util.cardQuestion(card),
+ 'answer': util.cardAnswer(card),
+ 'modelName': model['name'],
+ 'ord': card.ord,
+ 'deckName': self.deckNameFromId(card.did),
+ 'css': model['css'],
+ 'factor': card.factor,
+ #This factor is 10 times the ease percentage,
+ # so an ease of 310% would be reported as 3100
+ 'interval': card.ivl,
+ 'note': card.nid,
+ 'type': card.type,
+ 'queue': card.queue,
+ 'due': card.due,
+ 'reps': card.reps,
+ 'lapses': card.lapses,
+ 'left': card.left,
+ 'mod': card.mod,
+ })
+ except NotFoundError:
+ # Anki will give a NotFoundError if the card ID does not exist.
+ # Best behavior is probably to add an 'empty card' to the
+ # returned result, so that the items of the input and return
+ # lists correspond.
+ result.append({})
+
+ return result
+
+ @util.api()
+ def cardsModTime(self, cards):
+ result = []
+ for cid in cards:
+ try:
+ card = self.getCard(cid)
+ result.append({
+ 'cardId': card.id,
+ 'mod': card.mod,
+ })
+ except NotFoundError:
+ # Anki will give a NotFoundError if the card ID does not exist.
+ # Best behavior is probably to add an 'empty card' to the
+ # returned result, so that the items of the input and return
+ # lists correspond.
+ result.append({})
+ return result
+
+
+ @util.api()
+ def forgetCards(self, cards):
+ self.startEditing()
+ scids = anki.utils.ids2str(cards)
+ self.collection().db.execute('update cards set type=0, queue=0, left=0, ivl=0, due=0, odue=0, factor=0 where id in ' + scids)
+ self.stopEditing()
+
+
+ @util.api()
+ def relearnCards(self, cards):
+ self.startEditing()
+ scids = anki.utils.ids2str(cards)
+ self.collection().db.execute('update cards set type=3, queue=1 where id in ' + scids)
+ self.stopEditing()
+
+
+ @util.api()
+ def cardReviews(self, deck, startID):
+ return self.database().all(
+ 'select id, cid, usn, ease, ivl, lastIvl, factor, time, type from revlog ''where id>? and cid in (select id from cards where did=?)',
+ startID,
+ self.decks().id(deck)
+ )
+
+
+ @util.api()
+ def reloadCollection(self):
+ self.collection().reset()
+
+
+ @util.api()
+ def getLatestReviewID(self, deck):
+ return self.database().scalar(
+ 'select max(id) from revlog where cid in (select id from cards where did=?)',
+ self.decks().id(deck)
+ ) or 0
+
+
+ @util.api()
+ def insertReviews(self, reviews):
+ if len(reviews) > 0:
+ sql = 'insert into revlog(id,cid,usn,ease,ivl,lastIvl,factor,time,type) values '
+ for row in reviews:
+ sql += '(%s),' % ','.join(map(str, row))
+ sql = sql[:-1]
+ self.database().execute(sql)
+
+
+ @util.api()
+ def notesInfo(self, notes):
+ result = []
+ for nid in notes:
+ try:
+ note = self.getNote(nid)
+ model = note.model()
+
+ fields = {}
+ for info in model['flds']:
+ order = info['ord']
+ name = info['name']
+ fields[name] = {'value': note.fields[order], 'order': order}
+
+ result.append({
+ 'noteId': note.id,
+ 'tags' : note.tags,
+ 'fields': fields,
+ 'modelName': model['name'],
+ 'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
+ })
+ except NotFoundError:
+ # Anki will give a NotFoundError if the note ID does not exist.
+ # Best behavior is probably to add an 'empty card' to the
+ # returned result, so that the items of the input and return
+ # lists correspond.
+ result.append({})
+
+ return result
+
+
+ @util.api()
+ def deleteNotes(self, notes):
+ try:
+ self.collection().remNotes(notes)
+ finally:
+ self.stopEditing()
+
+
+ @util.api()
+ def removeEmptyNotes(self):
+ for model in self.collection().models.all():
+ if self.collection().models.useCount(model) == 0:
+ self.collection().models.rem(model)
+ self.window().requireReset()
+
+
+ @util.api()
+ def cardsToNotes(self, cards):
+ return self.collection().db.list('select distinct nid from cards where id in ' + anki.utils.ids2str(cards))
+
+
+ @util.api()
+ def guiBrowse(self, query=None):
+ browser = aqt.dialogs.open('Browser', self.window())
+ browser.activateWindow()
+
+ if query is not None:
+ browser.form.searchEdit.lineEdit().setText(query)
+ if hasattr(browser, 'onSearch'):
+ browser.onSearch()
+ else:
+ browser.onSearchActivated()
+
+ return self.findCards(query)
+
+
+ @util.api()
+ def guiEditNote(self, note):
+ Edit.open_dialog_and_show_note_with_id(note)
+
+
+ @util.api()
+ def guiSelectedNotes(self):
+ (creator, instance) = aqt.dialogs._dialogs['Browser']
+ if instance is None:
+ return []
+ return instance.selectedNotes()
+
+ @util.api()
+ def guiAddCards(self, note=None):
+ if note is not None:
+ collection = self.collection()
+
+ deck = collection.decks.byName(note['deckName'])
+ if deck is None:
+ raise Exception('deck was not found: {}'.format(note['deckName']))
+
+ collection.decks.select(deck['id'])
+ savedMid = deck.pop('mid', None)
+
+ model = collection.models.byName(note['modelName'])
+ if model is None:
+ raise Exception('model was not found: {}'.format(note['modelName']))
+
+ collection.models.setCurrent(model)
+ collection.models.update(model)
+
+ ankiNote = anki.notes.Note(collection, model)
+
+ # fill out card beforehand, so we can be sure of the note id
+ if 'fields' in note:
+ for name, value in note['fields'].items():
+ if name in ankiNote:
+ ankiNote[name] = value
+
+ self.addMediaFromNote(ankiNote, note)
+
+ if 'tags' in note:
+ ankiNote.tags = note['tags']
+
+ def openNewWindow():
+ nonlocal ankiNote
+
+ addCards = aqt.dialogs.open('AddCards', self.window())
+
+ if savedMid:
+ deck['mid'] = savedMid
+
+ addCards.editor.set_note(ankiNote)
+
+ addCards.activateWindow()
+
+ aqt.dialogs.open('AddCards', self.window())
+ addCards.setAndFocusNote(addCards.editor.note)
+
+ currentWindow = aqt.dialogs._dialogs['AddCards'][1]
+
+ if currentWindow is not None:
+ currentWindow.closeWithCallback(openNewWindow)
+ else:
+ openNewWindow()
+
+ return ankiNote.id
+
+ else:
+ addCards = aqt.dialogs.open('AddCards', self.window())
+ addCards.activateWindow()
+
+ return addCards.editor.note.id
+
+
+ @util.api()
+ def guiReviewActive(self):
+ return self.reviewer().card is not None and self.window().state == 'review'
+
+
+ @util.api()
+ def guiCurrentCard(self):
+ if not self.guiReviewActive():
+ raise Exception('Gui review is not currently active.')
+
+ reviewer = self.reviewer()
+ card = reviewer.card
+ model = card.model()
+ note = card.note()
+
+ fields = {}
+ for info in model['flds']:
+ order = info['ord']
+ name = info['name']
+ fields[name] = {'value': note.fields[order], 'order': order}
+
+ buttonList = reviewer._answerButtonList()
+ return {
+ 'cardId': card.id,
+ 'fields': fields,
+ 'fieldOrder': card.ord,
+ 'question': util.cardQuestion(card),
+ 'answer': util.cardAnswer(card),
+ 'buttons': [b[0] for b in buttonList],
+ 'nextReviews': [reviewer.mw.col.sched.nextIvlStr(reviewer.card, b[0], True) for b in buttonList],
+ 'modelName': model['name'],
+ 'deckName': self.deckNameFromId(card.did),
+ 'css': model['css'],
+ 'template': card.template()['name']
+ }
+
+
+ @util.api()
+ def guiStartCardTimer(self):
+ if not self.guiReviewActive():
+ return False
+
+ card = self.reviewer().card
+ if card is not None:
+ card.startTimer()
+ return True
+
+ return False
+
+
+ @util.api()
+ def guiShowQuestion(self):
+ if self.guiReviewActive():
+ self.reviewer()._showQuestion()
+ return True
+
+ return False
+
+
+ @util.api()
+ def guiShowAnswer(self):
+ if self.guiReviewActive():
+ self.window().reviewer._showAnswer()
+ return True
+
+ return False
+
+
+ @util.api()
+ def guiAnswerCard(self, ease):
+ if not self.guiReviewActive():
+ return False
+
+ reviewer = self.reviewer()
+ if reviewer.state != 'answer':
+ return False
+ if ease <= 0 or ease > self.scheduler().answerButtons(reviewer.card):
+ return False
+
+ reviewer._answerCard(ease)
+ return True
+
+
+ @util.api()
+ def guiDeckOverview(self, name):
+ collection = self.collection()
+ if collection is not None:
+ deck = collection.decks.byName(name)
+ if deck is not None:
+ collection.decks.select(deck['id'])
+ self.window().onOverview()
+ return True
+
+ return False
+
+
+ @util.api()
+ def guiDeckBrowser(self):
+ self.window().moveToState('deckBrowser')
+
+
+ @util.api()
+ def guiDeckReview(self, name):
+ if self.guiDeckOverview(name):
+ self.window().moveToState('review')
+ return True
+
+ return False
+
+
+ @util.api()
+ def guiExitAnki(self):
+ timer = QTimer()
+ timer.timeout.connect(self.window().close)
+ timer.start(1000) # 1s should be enough to allow the response to be sent.
+
+
+ @util.api()
+ def guiCheckDatabase(self):
+ self.window().onCheckDB()
+ return True
+
+
+ @util.api()
+ def addNotes(self, notes):
+ results = []
+ for note in notes:
+ try:
+ results.append(self.addNote(note))
+ except:
+ results.append(None)
+
+ return results
+
+
+ @util.api()
+ def canAddNotes(self, notes):
+ results = []
+ for note in notes:
+ results.append(self.canAddNote(note))
+
+ return results
+
+
+ @util.api()
+ def exportPackage(self, deck, path, includeSched=False):
+ collection = self.collection()
+ if collection is not None:
+ deck = collection.decks.byName(deck)
+ if deck is not None:
+ exporter = AnkiPackageExporter(collection)
+ exporter.did = deck['id']
+ exporter.includeSched = includeSched
+ exporter.exportInto(path)
+ return True
+
+ return False
+
+
+ @util.api()
+ def importPackage(self, path):
+ collection = self.collection()
+ if collection is not None:
+ try:
+ self.startEditing()
+ importer = AnkiPackageImporter(collection, path)
+ importer.run()
+ except:
+ self.stopEditing()
+ raise
+ else:
+ self.stopEditing()
+ return True
+
+ return False
+
+
+ @util.api()
+ def apiReflect(self, scopes=None, actions=None):
+ if not isinstance(scopes, list):
+ raise Exception('scopes has invalid value')
+ if not (actions is None or isinstance(actions, list)):
+ raise Exception('actions has invalid value')
+
+ cls = type(self)
+ scopes2 = []
+ result = {'scopes': scopes2}
+
+ if 'actions' in scopes:
+ if actions is None:
+ actions = dir(cls)
+
+ methodNames = []
+ for methodName in actions:
+ if not isinstance(methodName, str):
+ pass
+ method = getattr(cls, methodName, None)
+ if method is not None and getattr(method, 'api', False):
+ methodNames.append(methodName)
+
+ scopes2.append('actions')
+ result['actions'] = methodNames
+
+ return result
+
+
+#
+# Entry
+#
+
+# when run inside Anki, `__name__` would be either numeric,
+# or, if installed via `link.sh`, `AnkiConnectDev`
+if __name__ != "plugin":
+ if platform.system() == "Windows" and anki_version == (2, 1, 50):
+ util.patch_anki_2_1_50_having_null_stdout_on_windows()
+
+ Edit.register_with_anki()
+
+ ac = AnkiConnect()
+ ac.initLogging()
+ ac.startWebServer()
diff --git a/.local/share/Anki2/addons21/Anki_connect/__pycache__/__init__.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/Anki_connect/__pycache__/__init__.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..cd46468
Binary files /dev/null and b/.local/share/Anki2/addons21/Anki_connect/__pycache__/__init__.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/Anki_connect/__pycache__/edit.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/Anki_connect/__pycache__/edit.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..8290761
Binary files /dev/null and b/.local/share/Anki2/addons21/Anki_connect/__pycache__/edit.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/Anki_connect/__pycache__/util.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/Anki_connect/__pycache__/util.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..d9529a9
Binary files /dev/null and b/.local/share/Anki2/addons21/Anki_connect/__pycache__/util.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/Anki_connect/__pycache__/web.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/Anki_connect/__pycache__/web.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..44f3e41
Binary files /dev/null and b/.local/share/Anki2/addons21/Anki_connect/__pycache__/web.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/Anki_connect/config.json b/.local/share/Anki2/addons21/Anki_connect/config.json
new file mode 100644
index 0000000..d3b51ea
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/config.json
@@ -0,0 +1,8 @@
+{
+ "apiKey": null,
+ "apiLogPath": null,
+ "webBindAddress": "127.0.0.1",
+ "webBindPort": 8765,
+ "webCorsOriginList": ["http://localhost"],
+ "ignoreOriginList": []
+}
diff --git a/.local/share/Anki2/addons21/Anki_connect/config.md b/.local/share/Anki2/addons21/Anki_connect/config.md
new file mode 100644
index 0000000..6bacba2
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/config.md
@@ -0,0 +1 @@
+Read the documentation on the [AnkiConnect](https://foosoft.net/projects/anki-connect/) project page for details.
diff --git a/.local/share/Anki2/addons21/Anki_connect/edit.py b/.local/share/Anki2/addons21/Anki_connect/edit.py
new file mode 100644
index 0000000..d414575
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/edit.py
@@ -0,0 +1,458 @@
+import aqt
+import aqt.editor
+import aqt.browser.previewer
+from aqt import gui_hooks
+from aqt.qt import QDialog, Qt, QKeySequence, QShortcut
+from aqt.utils import disable_help_button, restoreGeom, saveGeom, tooltip
+from anki.errors import NotFoundError
+from anki.consts import QUEUE_TYPE_SUSPENDED
+from anki.utils import ids2str
+
+from . import anki_version
+
+
+# Edit dialog. Like Edit Current, but:
+# * has a Preview button to preview the cards for the note
+# * has Previous/Back buttons to navigate the history of the dialog
+# * has a Browse button to open the history in the Browser
+# * has no bar with the Close button
+#
+# To register in Anki's dialog system:
+# > from .edit import Edit
+# > Edit.register_with_anki()
+#
+# To (re)open (note_id is an integer):
+# > Edit.open_dialog_and_show_note_with_id(note_id)
+
+
+DOMAIN_PREFIX = "foosoft.ankiconnect."
+
+
+def get_note_by_note_id(note_id):
+ return aqt.mw.col.get_note(note_id)
+
+def is_card_suspended(card):
+ return card.queue == QUEUE_TYPE_SUSPENDED
+
+def filter_valid_note_ids(note_ids):
+ return aqt.mw.col.db.list(
+ "select id from notes where id in " + ids2str(note_ids)
+ )
+
+
+##############################################################################
+
+
+class DecentPreviewer(aqt.browser.previewer.MultiCardPreviewer):
+ class Adapter:
+ def get_current_card(self): raise NotImplementedError
+ def can_select_previous_card(self): raise NotImplementedError
+ def can_select_next_card(self): raise NotImplementedError
+ def select_previous_card(self): raise NotImplementedError
+ def select_next_card(self): raise NotImplementedError
+
+ def __init__(self, adapter: Adapter):
+ super().__init__(parent=None, mw=aqt.mw, on_close=lambda: None) # noqa
+ self.adapter = adapter
+ self.last_card_id = 0
+
+ def card(self):
+ return self.adapter.get_current_card()
+
+ def card_changed(self):
+ current_card_id = self.adapter.get_current_card().id
+ changed = self.last_card_id != current_card_id
+ self.last_card_id = current_card_id
+ return changed
+
+ # the check if we can select next/previous card is needed because
+ # the buttons sometimes get disabled a tad too late
+ # and can still be pressed by user.
+ # this is likely due to Anki sometimes delaying rendering of cards
+ # in order to avoid rendering them too fast?
+ def _on_prev_card(self):
+ if self.adapter.can_select_previous_card():
+ self.adapter.select_previous_card()
+ self.render_card()
+
+ def _on_next_card(self):
+ if self.adapter.can_select_next_card():
+ self.adapter.select_next_card()
+ self.render_card()
+
+ def _should_enable_prev(self):
+ return self.showing_answer_and_can_show_question() or \
+ self.adapter.can_select_previous_card()
+
+ def _should_enable_next(self):
+ return self.showing_question_and_can_show_answer() or \
+ self.adapter.can_select_next_card()
+
+ def _render_scheduled(self):
+ super()._render_scheduled() # noqa
+ self._updateButtons()
+
+ def showing_answer_and_can_show_question(self):
+ return self._state == "answer" and not self._show_both_sides
+
+ def showing_question_and_can_show_answer(self):
+ return self._state == "question"
+
+
+class ReadyCardsAdapter(DecentPreviewer.Adapter):
+ def __init__(self, cards):
+ self.cards = cards
+ self.current = 0
+
+ def get_current_card(self):
+ return self.cards[self.current]
+
+ def can_select_previous_card(self):
+ return self.current > 0
+
+ def can_select_next_card(self):
+ return self.current < len(self.cards) - 1
+
+ def select_previous_card(self):
+ self.current -= 1
+
+ def select_next_card(self):
+ self.current += 1
+
+
+##############################################################################
+
+
+# store note ids instead of notes, as note objects don't implement __eq__ etc
+class History:
+ number_of_notes_to_keep_in_history = 25
+
+ def __init__(self):
+ self.note_ids = []
+
+ def append(self, note):
+ if note.id in self.note_ids:
+ self.note_ids.remove(note.id)
+ self.note_ids.append(note.id)
+ self.note_ids = self.note_ids[-self.number_of_notes_to_keep_in_history:]
+
+ def has_note_to_left_of(self, note):
+ return note.id in self.note_ids and note.id != self.note_ids[0]
+
+ def has_note_to_right_of(self, note):
+ return note.id in self.note_ids and note.id != self.note_ids[-1]
+
+ def get_note_to_left_of(self, note):
+ note_id = self.note_ids[self.note_ids.index(note.id) - 1]
+ return get_note_by_note_id(note_id)
+
+ def get_note_to_right_of(self, note):
+ note_id = self.note_ids[self.note_ids.index(note.id) + 1]
+ return get_note_by_note_id(note_id)
+
+ def get_last_note(self): # throws IndexError if history empty
+ return get_note_by_note_id(self.note_ids[-1])
+
+ def remove_invalid_notes(self):
+ self.note_ids = filter_valid_note_ids(self.note_ids)
+
+history = History()
+
+
+# see method `find_cards` of `collection.py`
+def trigger_search_for_dialog_history_notes(search_context, use_history_order):
+ search_context.search = " or ".join(
+ f"nid:{note_id}" for note_id in history.note_ids
+ )
+
+ if use_history_order:
+ search_context.order = f"""case c.nid {
+ " ".join(
+ f"when {note_id} then {n}"
+ for (n, note_id) in enumerate(reversed(history.note_ids))
+ )
+ } end asc"""
+
+
+##############################################################################
+
+
+# noinspection PyAttributeOutsideInit
+class Edit(aqt.editcurrent.EditCurrent):
+ dialog_geometry_tag = DOMAIN_PREFIX + "edit"
+ dialog_registry_tag = DOMAIN_PREFIX + "Edit"
+ dialog_search_tag = DOMAIN_PREFIX + "edit.history"
+
+ # depending on whether the dialog already exists,
+ # upon a request to open the dialog via `aqt.dialogs.open()`,
+ # the manager will call either the constructor or the `reopen` method
+ def __init__(self, note):
+ QDialog.__init__(self, None, Qt.WindowType.Window)
+ aqt.mw.garbage_collect_on_dialog_finish(self)
+ self.form = aqt.forms.editcurrent.Ui_Dialog()
+ self.form.setupUi(self)
+ self.setWindowTitle("Edit")
+ self.setMinimumWidth(250)
+ self.setMinimumHeight(400)
+ restoreGeom(self, self.dialog_geometry_tag)
+ disable_help_button(self)
+
+ self.form.buttonBox.setVisible(False) # hides the Close button bar
+ self.setup_editor_buttons()
+
+ self.show()
+ self.bring_to_foreground()
+
+ history.remove_invalid_notes()
+ history.append(note)
+ self.show_note(note)
+
+ gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
+ gui_hooks.editor_did_load_note.append(self.editor_did_load_note)
+
+ def reopen(self, note):
+ history.append(note)
+ self.show_note(note)
+ self.bring_to_foreground()
+
+ def cleanup_and_close(self):
+ gui_hooks.editor_did_load_note.remove(self.editor_did_load_note)
+ gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
+
+ self.editor.cleanup()
+ saveGeom(self, self.dialog_geometry_tag)
+ aqt.dialogs.markClosed(self.dialog_registry_tag)
+ QDialog.reject(self)
+
+ # This method (mostly) solves (at least on my Windows 10 machine) three issues
+ # with window activation. Without this not even too hacky a fix,
+ # * When dialog is opened from Yomichan *for the first time* since app start,
+ # the dialog opens in background (just like Browser does),
+ # but does not flash in taskbar (unlike Browser);
+ # * When dialog is opened, closed, *then main window is focused by clicking in it*,
+ # then dialog is opened from Yomichan again, same issue as above arises;
+ # * When dialog is restored from minimized state *and main window isn't minimized*,
+ # opening the dialog from Yomichan does not reliably focus it;
+ # sometimes it opens in foreground, sometimes in background.
+ # With this fix, windows nearly always appear in foreground in all three cases.
+ # In the case of the first two issues, strictly speaking, the fix is not ideal:
+ # the window appears in background first, and then quickly pops into foreground.
+ # It is not *too* unsightly, probably, no-one will notice this;
+ # still, a better solution must be possible. TODO find one!
+ #
+ # Note that operation systems, notably Windows, and desktop managers, may restrict
+ # background applications from raising windows to prevent them from interrupting
+ # what the user is currently doing. For details, see:
+ # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow#remarks
+ # https://doc.qt.io/qt-5/qwidget.html#activateWindow
+ # https://wiki.qt.io/Technical_FAQ#QWidget_::activateWindow.28.29_-_behavior_under_windows
+ def bring_to_foreground(self):
+ aqt.mw.app.processEvents()
+ self.activateWindow()
+ self.raise_()
+
+ #################################### hooks enabled during dialog lifecycle
+
+ def on_operation_did_execute(self, changes, handler):
+ if changes.note_text and handler is not self.editor:
+ self.reload_notes_after_user_action_elsewhere()
+
+ def editor_did_load_note(self, _editor):
+ self.enable_disable_next_and_previous_buttons()
+
+ ###################################################### load & reload notes
+
+ # setting editor.card is required for the "Cards…" button to work properly
+ def show_note(self, note):
+ self.note = note
+ cards = note.cards()
+
+ self.editor.set_note(note)
+ self.editor.card = cards[0] if cards else None
+
+ if any(is_card_suspended(card) for card in cards):
+ tooltip("Some of the cards associated with this note "
+ "have been suspended", parent=self)
+
+ def reload_notes_after_user_action_elsewhere(self):
+ history.remove_invalid_notes()
+
+ try:
+ self.note.load() # this also updates the fields
+ except NotFoundError:
+ try:
+ self.note = history.get_last_note()
+ except IndexError:
+ self.cleanup_and_close()
+ return
+
+ self.show_note(self.note)
+
+ ################################################################## actions
+
+ # search two times, one is to select the current note or its cards,
+ # and another to show the whole history, while keeping the above selection
+ # set sort column to our search tag, which:
+ # * prevents the column sort indicator from being shown
+ # * serves as a hint for us to show notes or cards in history order
+ # (user can then click on any of the column names
+ # to show history cards in the order of their choosing)
+ def show_browser(self, *_):
+ def search_input_select_all(hook_browser, *_):
+ hook_browser.form.searchEdit.lineEdit().selectAll()
+ gui_hooks.browser_did_change_row.remove(search_input_select_all)
+ gui_hooks.browser_did_change_row.append(search_input_select_all)
+
+ browser = aqt.dialogs.open("Browser", aqt.mw)
+ browser.table._state.sort_column = self.dialog_search_tag # noqa
+ browser.table._set_sort_indicator() # noqa
+
+ browser.search_for(f"nid:{self.note.id}")
+ browser.table.select_all()
+ browser.search_for(self.dialog_search_tag)
+
+ def show_preview(self, *_):
+ if cards := self.note.cards():
+ previewer = DecentPreviewer(ReadyCardsAdapter(cards))
+ previewer.open()
+ return previewer
+ else:
+ tooltip("No cards found", parent=self)
+ return None
+
+ def show_previous(self, *_):
+ if history.has_note_to_left_of(self.note):
+ self.show_note(history.get_note_to_left_of(self.note))
+
+ def show_next(self, *_):
+ if history.has_note_to_right_of(self.note):
+ self.show_note(history.get_note_to_right_of(self.note))
+
+ ################################################## button and hotkey setup
+
+ def setup_editor_buttons(self):
+ gui_hooks.editor_did_init.append(self.add_preview_button)
+ gui_hooks.editor_did_init_buttons.append(self.add_right_hand_side_buttons)
+
+ # on Anki 2.1.50, browser mode makes the Preview button visible
+ extra_kwargs = {} if anki_version < (2, 1, 50) else {
+ "editor_mode": aqt.editor.EditorMode.BROWSER
+ }
+
+ self.editor = aqt.editor.Editor(aqt.mw, self.form.fieldsArea, self,
+ **extra_kwargs)
+
+ gui_hooks.editor_did_init_buttons.remove(self.add_right_hand_side_buttons)
+ gui_hooks.editor_did_init.remove(self.add_preview_button)
+
+ # * on Anki < 2.1.50, make the button via js (`setupEditor` of browser.py);
+ # also, make a copy of _links so that opening Anki's browser does not
+ # screw them up as they are apparently shared between instances?!
+ # the last part seems to have been fixed in Anki 2.1.50
+ # * on Anki 2.1.50, the button is created by setting editor mode,
+ # see above; so we only need to add the link.
+ def add_preview_button(self, editor):
+ QShortcut(QKeySequence("Ctrl+Shift+P"), self, self.show_preview)
+
+ if anki_version < (2, 1, 50):
+ editor._links = editor._links.copy()
+ editor.web.eval("""
+ $editorToolbar.then(({notetypeButtons}) =>
+ notetypeButtons.appendButton(
+ {component: editorToolbar.PreviewButton, id: 'preview'}
+ )
+ );
+ """)
+
+ editor._links["preview"] = lambda _editor: self.show_preview() and None
+
+ # * on Anki < 2.1.50, button style is okay-ish from get-go,
+ # except when disabled; adding class `btn` fixes that;
+ # * on Anki 2.1.50, buttons have weird font size and are square';
+ # the style below makes them in line with left-hand side buttons
+ def add_right_hand_side_buttons(self, buttons, editor):
+ if anki_version < (2, 1, 50):
+ extra_button_class = "btn"
+ else:
+ extra_button_class = "anki-connect-button"
+ editor.web.eval("""
+ (function(){
+ const style = document.createElement("style");
+ style.innerHTML = `
+ .anki-connect-button {
+ white-space: nowrap;
+ width: auto;
+ padding: 0 2px;
+ font-size: var(--base-font-size);
+ }
+ .anki-connect-button:disabled {
+ pointer-events: none;
+ opacity: .4;
+ }
+ `;
+ document.head.appendChild(style);
+ })();
+ """)
+
+ def add(cmd, function, label, tip, keys):
+ button_html = editor.addButton(
+ icon=None,
+ cmd=DOMAIN_PREFIX + cmd,
+ id=DOMAIN_PREFIX + cmd,
+ func=function,
+ label=f" {label} ",
+ tip=f"{tip} ({keys})",
+ keys=keys,
+ )
+
+ button_html = button_html.replace('class="',
+ f'class="{extra_button_class} ')
+ buttons.append(button_html)
+
+ add("browse", self.show_browser, "Browse", "Browse", "Ctrl+F")
+ add("previous", self.show_previous, "<", "Previous", "Alt+Left")
+ add("next", self.show_next, ">", "Next", "Alt+Right")
+
+ def run_javascript_after_toolbar_ready(self, js):
+ js = f"setTimeout(function() {{ {js} }}, 1)"
+ if anki_version < (2, 1, 50):
+ js = f'$editorToolbar.then(({{ toolbar }}) => {js})'
+ else:
+ js = f'require("anki/ui").loaded.then(() => {js})'
+ self.editor.web.eval(js)
+
+ def enable_disable_next_and_previous_buttons(self):
+ def to_js(boolean):
+ return "true" if boolean else "false"
+
+ disable_previous = not(history.has_note_to_left_of(self.note))
+ disable_next = not(history.has_note_to_right_of(self.note))
+
+ self.run_javascript_after_toolbar_ready(f"""
+ document.getElementById("{DOMAIN_PREFIX}previous")
+ .disabled = {to_js(disable_previous)};
+ document.getElementById("{DOMAIN_PREFIX}next")
+ .disabled = {to_js(disable_next)};
+ """)
+
+ ##########################################################################
+
+ @classmethod
+ def browser_will_search(cls, search_context):
+ if search_context.search == cls.dialog_search_tag:
+ trigger_search_for_dialog_history_notes(
+ search_context=search_context,
+ use_history_order=cls.dialog_search_tag ==
+ search_context.browser.table._state.sort_column # noqa
+ )
+
+ @classmethod
+ def register_with_anki(cls):
+ if cls.dialog_registry_tag not in aqt.dialogs._dialogs: # noqa
+ aqt.dialogs.register_dialog(cls.dialog_registry_tag, cls)
+ gui_hooks.browser_will_search.append(cls.browser_will_search)
+
+ @classmethod
+ def open_dialog_and_show_note_with_id(cls, note_id): # raises NotFoundError
+ note = get_note_by_note_id(note_id)
+ return aqt.dialogs.open(cls.dialog_registry_tag, note)
diff --git a/.local/share/Anki2/addons21/Anki_connect/meta.json b/.local/share/Anki2/addons21/Anki_connect/meta.json
new file mode 100644
index 0000000..c27a474
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/meta.json
@@ -0,0 +1 @@
+{"name": "AnkiConnect", "mod": 1657829726, "min_point_version": 45, "max_point_version": 45, "branch_index": 1, "disabled": false, "conflicts": [], "update_enabled": true}
\ No newline at end of file
diff --git a/.local/share/Anki2/addons21/Anki_connect/util.py b/.local/share/Anki2/addons21/Anki_connect/util.py
new file mode 100644
index 0000000..3ae5eb1
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/util.py
@@ -0,0 +1,93 @@
+# Copyright 2016-2021 Alex Yatskov
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import sys
+
+import anki
+import anki.sync
+import aqt
+import enum
+
+#
+# Utilities
+#
+
+class MediaType(enum.Enum):
+ Audio = 1
+ Video = 2
+ Picture = 3
+
+
+def download(url):
+ client = anki.sync.AnkiRequestsClient()
+ client.timeout = setting('webTimeout') / 1000
+
+ resp = client.get(url)
+ if resp.status_code != 200:
+ raise Exception('{} download failed with return code {}'.format(url, resp.status_code))
+
+ return client.streamContent(resp)
+
+
+def api(*versions):
+ def decorator(func):
+ setattr(func, 'versions', versions)
+ setattr(func, 'api', True)
+ return func
+
+ return decorator
+
+
+def cardQuestion(card):
+ if getattr(card, 'question', None) is None:
+ return card._getQA()['q']
+
+ return card.question()
+
+
+def cardAnswer(card):
+ if getattr(card, 'answer', None) is None:
+ return card._getQA()['a']
+
+ return card.answer()
+
+
+DEFAULT_CONFIG = {
+ 'apiKey': None,
+ 'apiLogPath': None,
+ 'apiPollInterval': 25,
+ 'apiVersion': 6,
+ 'webBacklog': 5,
+ 'webBindAddress': os.getenv('ANKICONNECT_BIND_ADDRESS', '127.0.0.1'),
+ 'webBindPort': 8765,
+ 'webCorsOrigin': os.getenv('ANKICONNECT_CORS_ORIGIN', None),
+ 'webCorsOriginList': ['http://localhost'],
+ 'ignoreOriginList': [],
+ 'webTimeout': 10000,
+}
+
+def setting(key):
+ try:
+ return aqt.mw.addonManager.getConfig(__name__).get(key, DEFAULT_CONFIG[key])
+ except:
+ raise Exception('setting {} not found'.format(key))
+
+
+# see https://github.com/FooSoft/anki-connect/issues/308
+# fixed in https://github.com/ankitects/anki/commit/0b2a226d
+def patch_anki_2_1_50_having_null_stdout_on_windows():
+ if sys.stdout is None:
+ sys.stdout = open(os.devnull, "w", encoding="utf8")
diff --git a/.local/share/Anki2/addons21/Anki_connect/web.py b/.local/share/Anki2/addons21/Anki_connect/web.py
new file mode 100644
index 0000000..1f325b4
--- /dev/null
+++ b/.local/share/Anki2/addons21/Anki_connect/web.py
@@ -0,0 +1,301 @@
+# Copyright 2016-2021 Alex Yatskov
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import json
+import jsonschema
+import select
+import socket
+
+from . import util
+
+#
+# WebRequest
+#
+
+class WebRequest:
+ def __init__(self, method, headers, body):
+ self.method = method
+ self.headers = headers
+ self.body = body
+
+
+#
+# WebClient
+#
+
+class WebClient:
+ def __init__(self, sock, handler):
+ self.sock = sock
+ self.handler = handler
+ self.readBuff = bytes()
+ self.writeBuff = bytes()
+
+
+ def advance(self, recvSize=1024):
+ if self.sock is None:
+ return False
+
+ rlist, wlist = select.select([self.sock], [self.sock], [], 0)[:2]
+ self.sock.settimeout(5.0)
+
+ if rlist:
+ while True:
+ try:
+ msg = self.sock.recv(recvSize)
+ except (ConnectionResetError, socket.timeout):
+ self.close()
+ return False
+ if not msg:
+ self.close()
+ return False
+ self.readBuff += msg
+
+ req, length = self.parseRequest(self.readBuff)
+ if req is not None:
+ self.readBuff = self.readBuff[length:]
+ self.writeBuff += self.handler(req)
+ break
+
+
+
+ if wlist and self.writeBuff:
+ try:
+ length = self.sock.send(self.writeBuff)
+ self.writeBuff = self.writeBuff[length:]
+ if not self.writeBuff:
+ self.close()
+ return False
+ except:
+ self.close()
+ return False
+ return True
+
+
+ def close(self):
+ if self.sock is not None:
+ self.sock.close()
+ self.sock = None
+
+ self.readBuff = bytes()
+ self.writeBuff = bytes()
+
+
+ def parseRequest(self, data):
+ parts = data.split('\r\n\r\n'.encode('utf-8'), 1)
+ if len(parts) == 1:
+ return None, 0
+
+ lines = parts[0].split('\r\n'.encode('utf-8'))
+ method = None
+
+ if len(lines) > 0:
+ request_line_parts = lines[0].split(' '.encode('utf-8'))
+ method = request_line_parts[0].upper() if len(request_line_parts) > 0 else None
+
+ headers = {}
+ for line in lines[1:]:
+ pair = line.split(': '.encode('utf-8'))
+ headers[pair[0].lower()] = pair[1] if len(pair) > 1 else None
+
+ headerLength = len(parts[0]) + 4
+ bodyLength = int(headers.get('content-length'.encode('utf-8'), 0))
+ totalLength = headerLength + bodyLength
+
+ if totalLength > len(data):
+ return None, 0
+
+ body = data[headerLength : totalLength]
+ return WebRequest(method, headers, body), totalLength
+
+#
+# WebServer
+#
+
+class WebServer:
+ def __init__(self, handler):
+ self.handler = handler
+ self.clients = []
+ self.sock = None
+
+
+ def advance(self):
+ if self.sock is not None:
+ self.acceptClients()
+ self.advanceClients()
+
+
+ def acceptClients(self):
+ rlist = select.select([self.sock], [], [], 0)[0]
+ if not rlist:
+ return
+
+ clientSock = self.sock.accept()[0]
+ if clientSock is not None:
+ clientSock.setblocking(False)
+ self.clients.append(WebClient(clientSock, self.handlerWrapper))
+
+
+ def advanceClients(self):
+ self.clients = list(filter(lambda c: c.advance(), self.clients))
+
+
+ def listen(self):
+ self.close()
+
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.sock.setblocking(False)
+ self.sock.bind((util.setting('webBindAddress'), util.setting('webBindPort')))
+ self.sock.listen(util.setting('webBacklog'))
+
+
+ def handlerWrapper(self, req):
+ allowed, corsOrigin = self.allowOrigin(req)
+
+ if req.method == b'OPTIONS':
+ body = ''.encode('utf-8')
+ headers = self.buildHeaders(corsOrigin, body)
+
+ if b'access-control-request-private-network' in req.headers and (
+ req.headers[b'access-control-request-private-network'] == b'true'):
+ # include this header so that if a public origin is included in the whitelist,
+ # then browsers won't fail requests due to the private network access check
+ headers.append(['Access-Control-Allow-Private-Network', 'true'])
+
+ return self.buildResponse(headers, body)
+
+ try:
+ params = json.loads(req.body.decode('utf-8'))
+ jsonschema.validate(params, request_schema)
+ except (ValueError, jsonschema.ValidationError) as e:
+ if allowed:
+ if len(req.body) == 0:
+ body = f"AnkiConnect v.{util.setting('apiVersion')}".encode()
+ else:
+ reply = format_exception_reply(util.setting('apiVersion'), e)
+ body = json.dumps(reply).encode('utf-8')
+ headers = self.buildHeaders(corsOrigin, body)
+ return self.buildResponse(headers, body)
+ else:
+ params = {} # trigger the 403 response below
+
+ if allowed or params.get('action', '') == 'requestPermission':
+ if params.get('action', '') == 'requestPermission':
+ params['params'] = params.get('params', {})
+ params['params']['allowed'] = allowed
+ params['params']['origin'] = b'origin' in req.headers and req.headers[b'origin'].decode() or ''
+ if not allowed :
+ corsOrigin = params['params']['origin']
+
+ body = json.dumps(self.handler(params)).encode('utf-8')
+ headers = self.buildHeaders(corsOrigin, body)
+ else :
+ headers = [
+ ['HTTP/1.1 403 Forbidden', None],
+ ['Access-Control-Allow-Origin', corsOrigin],
+ ['Access-Control-Allow-Headers', '*']
+ ]
+ body = ''.encode('utf-8')
+
+ return self.buildResponse(headers, body)
+
+
+ def allowOrigin(self, req):
+ # handle multiple cors origins by checking the 'origin'-header against the allowed origin list from the config
+ webCorsOriginList = util.setting('webCorsOriginList')
+
+ # keep support for deprecated 'webCorsOrigin' field, as long it is not removed
+ webCorsOrigin = util.setting('webCorsOrigin')
+ if webCorsOrigin:
+ webCorsOriginList.append(webCorsOrigin)
+
+ allowed = False
+ corsOrigin = 'http://localhost'
+ allowAllCors = '*' in webCorsOriginList # allow CORS for all domains
+
+ if allowAllCors:
+ corsOrigin = '*'
+ allowed = True
+ elif b'origin' in req.headers:
+ originStr = req.headers[b'origin'].decode()
+ if originStr in webCorsOriginList :
+ corsOrigin = originStr
+ allowed = True
+ elif 'http://localhost' in webCorsOriginList and (
+ originStr == 'http://127.0.0.1' or originStr == 'https://127.0.0.1' or # allow 127.0.0.1 if localhost allowed
+ originStr.startswith('http://127.0.0.1:') or originStr.startswith('http://127.0.0.1:') or
+ originStr.startswith('chrome-extension://') or originStr.startswith('moz-extension://') or originStr.startswith('safari-web-extension://') ) : # allow chrome, firefox and safari extension if localhost allowed
+ corsOrigin = originStr
+ allowed = True
+ else:
+ allowed = True
+
+ return allowed, corsOrigin
+
+
+ def buildHeaders(self, corsOrigin, body):
+ return [
+ ['HTTP/1.1 200 OK', None],
+ ['Content-Type', 'text/json'],
+ ['Access-Control-Allow-Origin', corsOrigin],
+ ['Access-Control-Allow-Headers', '*'],
+ ['Content-Length', str(len(body))]
+ ]
+
+
+ def buildResponse(self, headers, body):
+ resp = bytes()
+ for key, value in headers:
+ if value is None:
+ resp += '{}\r\n'.format(key).encode('utf-8')
+ else:
+ resp += '{}: {}\r\n'.format(key, value).encode('utf-8')
+
+ resp += '\r\n'.encode('utf-8')
+ resp += body
+ return resp
+
+
+ def close(self):
+ if self.sock is not None:
+ self.sock.close()
+ self.sock = None
+
+ for client in self.clients:
+ client.close()
+
+ self.clients = []
+
+
+def format_success_reply(api_version, result):
+ if api_version <= 4:
+ return result
+ else:
+ return {"result": result, "error": None}
+
+
+def format_exception_reply(_api_version, exception):
+ return {"result": None, "error": str(exception)}
+
+
+request_schema = {
+ "type": "object",
+ "properties": {
+ "action": {"type": "string", "minLength": 1},
+ "version": {"type": "integer"},
+ "params": {"type": "object"},
+ },
+ "required": ["action"],
+}
\ No newline at end of file
diff --git a/.local/share/Anki2/addons21/Background_and_gear/LICENSE b/.local/share/Anki2/addons21/Background_and_gear/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/.local/share/Anki2/addons21/Background_and_gear/__init__.py b/.local/share/Anki2/addons21/Background_and_gear/__init__.py
new file mode 100644
index 0000000..274cddc
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/__init__.py
@@ -0,0 +1,188 @@
+# Copyright: ijgnd
+# The AnKing
+# Code License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+# Background images were obtained from Pexels.com under this license https://www.pexels.com/photo-license/
+# Gear icons were obtained from Wikimedia Commons https://commons.wikimedia.org/wiki/Category:Noto_Color_Emoji_Pie (license listed in link)
+
+import os
+import random
+from typing import List
+
+from anki import version as anki_version # type: ignore
+from anki.utils import pointVersion
+from aqt import gui_hooks, mw
+from aqt.addons import *
+from aqt.editor import pics
+# for the toolbar buttons
+from aqt.qt import *
+
+from .adjust_css_files22 import *
+from .config import addon_path, addonfoldername, gc
+from .gui.resources import initialize_qt_resources
+from .gui_updatemanager import setupMenu
+
+css_folder_for_anki_version = {
+ "22": "22",
+ "23": "22",
+ "24": "22",
+ "25": "25",
+ "26": "25",
+ "27": "25",
+ "28": "25",
+ "29": "25",
+ "30": "25",
+ "31": "31",
+ "32": "31",
+ "33": "31",
+ "34": "31",
+ "35": "31",
+ "36": "36",
+ "37": "36",
+ "38": "36",
+ "39": "36",
+ "40": "36",
+ "41": "36",
+ "42": "36",
+ "43": "36",
+ "44": "36",
+ "45": "36",
+ "46": "36",
+}
+
+version = pointVersion()
+if int(version) in css_folder_for_anki_version:
+ version_folder = css_folder_for_anki_version[str(version)]
+else: # for newer Anki versions try the latest version and hope for the best
+ version_folder = css_folder_for_anki_version[
+ max(css_folder_for_anki_version, key=int)
+ ]
+
+SOURCE_ABSOLUTE = os.path.join(addon_path, "sources", "css", version_folder)
+WEB_ABSOLUTE = os.path.join(addon_path, "web", "css")
+CSS_FILES_TO_REPLACE = [
+ os.path.basename(f) for f in os.listdir(WEB_ABSOLUTE) if f.endswith(".css")
+]
+WEB_EXPORTS_REGEX = r"(user_files.*|web.*)"
+
+
+def main():
+ initialize_qt_resources()
+ setupMenu()
+
+ mw.addonManager.setWebExports(__name__, WEB_EXPORTS_REGEX)
+ update_css_files()
+
+ gui_hooks.state_did_change.append(maybe_update_css_files)
+ gui_hooks.webview_will_set_content.append(include_css_files)
+ gui_hooks.deck_browser_will_render_content.append(replace_gears)
+
+ def on_config_update(config):
+ update_css_files()
+ mw.moveToState("deckBrowser")
+
+ mw.addonManager.setConfigUpdatedAction(__name__, on_config_update)
+
+
+def update_css_files():
+ # combine template files with config and write into webexports folder
+ change_copy = [
+ os.path.basename(f) for f in os.listdir(SOURCE_ABSOLUTE) if f.endswith(".css")
+ ]
+ for file_name in change_copy:
+ with open(os.path.join(SOURCE_ABSOLUTE, file_name)) as f:
+ content = f.read()
+
+ if version == 36:
+ if file_name == "deckbrowser.css":
+ content = adjust_deckbrowser_css22(content)
+ if file_name == "toolbar.css" and gc("Toolbar image"):
+ content = adjust_toolbar_css22(content)
+ if file_name == "overview.css":
+ content = adjust_overview_css22(content)
+ if file_name == "toolbar-bottom.css" and gc("Toolbar image"):
+ content = adjust_bottomtoolbar_css22(content)
+ if file_name == "reviewer.css" and gc("Reviewer image"):
+ content = adjust_reviewer_css22(content)
+ if (
+ file_name == "reviewer-bottom.css"
+ and gc("Reviewer image")
+ and gc("Toolbar image")
+ ):
+ content = adjust_reviewerbottom_css22(content)
+
+ # for later versions: try the latest
+ # this code will likely change when new Anki versions are released which might require
+ # updates of this add-on.
+ else:
+ if file_name == "deckbrowser.css":
+ content = adjust_deckbrowser_css22(content)
+ if file_name == "toolbar.css" and gc("Toolbar image"):
+ content = adjust_toolbar_css22(content)
+ if file_name == "overview.css":
+ content = adjust_overview_css22(content)
+ if file_name == "toolbar-bottom.css" and gc("Toolbar image"):
+ content = adjust_bottomtoolbar_css22(content)
+ if file_name == "reviewer.css" and gc("Reviewer image"):
+ content = adjust_reviewer_css22(content)
+ if file_name == "reviewer-bottom.css": # and gc("Reviewer image"):
+ content = adjust_reviewerbottom_css22(content)
+
+ with open(os.path.join(WEB_ABSOLUTE, file_name), "w") as f:
+ f.write(content)
+
+
+# reset background when refreshing page (for use with "random" setting)
+def maybe_update_css_files(new_state, old_state):
+ if new_state != "deckBrowser":
+ return
+
+ update_css_files()
+ if not tuple(int(i) for i in anki_version.split(".")) < (2, 1, 27):
+ mw.toolbar.redraw()
+
+
+def maybe_adjust_file_name(file_name):
+ if pointVersion() >= 36:
+ file_name = file_name.lstrip("css/")
+ return file_name
+
+
+def include_css_files(web_content, context):
+ new_css: List[str] = web_content.css[:]
+ for idx, file_name in enumerate(web_content.css):
+ file_name = maybe_adjust_file_name(file_name)
+ if file_name in CSS_FILES_TO_REPLACE:
+ new_css[idx] = f"/_addons/{addonfoldername}/web/css/{file_name}"
+ new_css.append(
+ f"/_addons/{addonfoldername}/user_files/css/custom_{file_name}"
+ )
+ web_content.css = new_css
+
+
+def replace_gears(deck_browser, content):
+ old = """
+
+
+
+
+
+
+
+---
+
+
If you enjoy this add-on or want individualized Anki help, please consider supporting us!
+
+
Restart is required for any change to take effect
+
+---
+
+_
You can add custom images to the folders "background" and "gears" and custom css to the "css" folder. Access this folder by Tools->Background/gear image folder. I would recommend using 20-50% opacity images for the background (this must be configured in an external editor like photoshop).
_
+
+ Background images were obtained from [Pexels.com](https://www.pexels.com/photo-license/)
+ Gear icons were obtained from [Wikimedia Commons](https://commons.wikimedia.org/wiki/Category:Noto_Color_Emoji_Pie)
+
+---
+
+##Config:
+
+* **Image name for background:** _(For no image use "". "Random" will shuffle through defaults)_ name of the background image file.
+ * _preloaded images (all .png) include: AnKing, Beach, BeachAerial, Christ&Surgeon, ColorfulLights, Fire, Island, Milkyway, MoonMountains, NightSky, Ocean, SLCtemple, Sunset, SunsetMountain_
+* **Image name for gear:** _(Anki default is "gears.svg". "Random" will shuffle through defaults)_ name of the file to replace the gear icon. (preloaded images include AnKing.png, flame.svg)
+ * _preloaded images (all .png) include: AnKing, Bam, Bullseye, Cowboy, Diamond, Dragon, Fire, Flower, Nerd, Rose, Shield, Skull, Star, Sun_
+* **Reviewer image:** _(true or false)_ show the background image in the reviewer screen
+* **Toolbar top/bottom:** _(true or false)_ Set the background position of the toolbars to top and bottom (if the main background position is set to center, this will look cleaner for most images)
+* **Toolbar image:** _(true or false)_ show the background image in the top and bottom toolbars in addition to the main screen
+
+
+_The following are css values and can be styled with appropriate css values for these css properties. See w3schools.com for more information._
+
+* **background-attachment:** _(default "fixed")_ scroll or fixed
+* **background-color:** _(default "")_ set to "" to have no background color
+* **background-position:** _(default "center")_ left top, right bottom, 25% 50%, 100px 200px, etc
+* **background scale:** _(default "1")_ set the scale of the image. 1 is the original image size. 2 is 200% original image size. You can also use 2,1 to scale x,y separately
+* **background-size:** _(default "contain", but I would recommend "cover" if you set "Toolbar image" to true)_ contain, cover, 50%, 100px, etc
+
diff --git a/.local/share/Anki2/addons21/Background_and_gear/config.py b/.local/share/Anki2/addons21/Background_and_gear/config.py
new file mode 100644
index 0000000..da450d7
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/config.py
@@ -0,0 +1,44 @@
+import os
+
+from aqt import mw
+
+
+addon_path = os.path.dirname(__file__)
+addonfoldername = os.path.basename(addon_path)
+
+
+def gc(arg="", fail=False):
+ conf = mw.addonManager.getConfig(__name__)
+ if conf:
+ if arg:
+ return conf.get(arg, fail)
+ else:
+ return conf
+ return fail
+
+
+userOption = None
+
+def _getUserOption(refresh):
+ global userOption
+ if userOption is None or refresh:
+ userOption = mw.addonManager.getConfig(__name__)
+
+
+def getUserOption(key=None, default=None, refresh=False):
+ _getUserOption(refresh)
+ if key is None:
+ return userOption
+ if key in userOption:
+ return userOption[key]
+ else:
+ return default
+
+
+def writeConfig(configToWrite=userOption):
+ mw.addonManager.writeConfig(__name__, configToWrite)
+
+
+def getDefaultConfig():
+ addon = __name__.split(".")[0]
+ return mw.addonManager.addonConfigDefaults(addon)
diff --git a/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__init__.py b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__init__.py
new file mode 100644
index 0000000..6e15507
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# Custom background and gear icon Add-on for Anki
+# Copyright (C) 2022
+#
+# This file was automatically generated by Anki Add-on Builder v1.0.0-dev.2
+# It is subject to the same licensing terms as the rest of the program
+# (see the LICENSE file which accompanies this program).
+#
+# WARNING! All changes made in this file will be lost!
+
+"""
+Shim that imports forms corresponding to runtime Qt version
+"""
+
+from typing import TYPE_CHECKING
+
+from aqt.qt import qtmajor
+
+if TYPE_CHECKING or qtmajor >= 6:
+ from .qt6 import * # noqa: F401
+else:
+ from .qt5 import * # noqa: F401
diff --git a/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__pycache__/__init__.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__pycache__/__init__.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..55ea868
Binary files /dev/null and b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/__pycache__/__init__.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/__init__.py b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/__init__.py
new file mode 100644
index 0000000..549ed56
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+# Custom background and gear icon Add-on for Anki
+# Copyright (C) 2022
+#
+# This file was automatically generated by Anki Add-on Builder v1.0.0-dev.2
+# It is subject to the same licensing terms as the rest of the program
+# (see the LICENSE file which accompanies this program).
+#
+# WARNING! All changes made in this file will be lost!
+
+"""
+Initializes generated Qt forms/resources
+"""
+
+__all__ = [
+ "settings_dialog"
+]
+
+from . import settings_dialog
diff --git a/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/settings_dialog.py b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/settings_dialog.py
new file mode 100644
index 0000000..f1cae30
--- /dev/null
+++ b/.local/share/Anki2/addons21/Background_and_gear/gui/forms/qt5/settings_dialog.py
@@ -0,0 +1,332 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'designer/settings_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.6
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(548, 601)
+ self.gridLayout_6 = QtWidgets.QGridLayout(Dialog)
+ self.gridLayout_6.setObjectName("gridLayout_6")
+ self.groupBox_4 = QtWidgets.QGroupBox(Dialog)
+ self.groupBox_4.setTitle("")
+ self.groupBox_4.setObjectName("groupBox_4")
+ self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_4)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.label_4 = QtWidgets.QLabel(self.groupBox_4)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout_2.addWidget(self.label_4, 0, 0, 1, 1)
+ self.comboBox_attachment = QtWidgets.QComboBox(self.groupBox_4)
+ self.comboBox_attachment.setObjectName("comboBox_attachment")
+ self.comboBox_attachment.addItem("")
+ self.comboBox_attachment.addItem("")
+ self.gridLayout_2.addWidget(self.comboBox_attachment, 0, 1, 1, 1)
+ self.label_8 = QtWidgets.QLabel(self.groupBox_4)
+ self.label_8.setObjectName("label_8")
+ self.gridLayout_2.addWidget(self.label_8, 0, 2, 1, 1)
+ self.comboBox_size = QtWidgets.QComboBox(self.groupBox_4)
+ self.comboBox_size.setObjectName("comboBox_size")
+ self.comboBox_size.addItem("")
+ self.comboBox_size.addItem("")
+ self.gridLayout_2.addWidget(self.comboBox_size, 0, 3, 1, 1)
+ self.label_7 = QtWidgets.QLabel(self.groupBox_4)
+ self.label_7.setObjectName("label_7")
+ self.gridLayout_2.addWidget(self.label_7, 1, 0, 1, 1)
+ self.comboBox_position = QtWidgets.QComboBox(self.groupBox_4)
+ self.comboBox_position.setObjectName("comboBox_position")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.comboBox_position.addItem("")
+ self.gridLayout_2.addWidget(self.comboBox_position, 1, 1, 1, 1)
+ self.label_9 = QtWidgets.QLabel(self.groupBox_4)
+ self.label_9.setObjectName("label_9")
+ self.gridLayout_2.addWidget(self.label_9, 1, 2, 1, 1)
+ self.scaleBox = QtWidgets.QDoubleSpinBox(self.groupBox_4)
+ self.scaleBox.setDecimals(1)
+ self.scaleBox.setMinimum(0.1)
+ self.scaleBox.setSingleStep(0.1)
+ self.scaleBox.setProperty("value", 1.0)
+ self.scaleBox.setObjectName("scaleBox")
+ self.gridLayout_2.addWidget(self.scaleBox, 1, 3, 1, 1)
+ self.gridLayout_6.addWidget(self.groupBox_4, 3, 0, 1, 12)
+ self.RestoreButton = QtWidgets.QPushButton(Dialog)
+ self.RestoreButton.setObjectName("RestoreButton")
+ self.gridLayout_6.addWidget(self.RestoreButton, 8, 8, 1, 3)
+ self.pushButton_videoTutorial = QtWidgets.QPushButton(Dialog)
+ self.pushButton_videoTutorial.setObjectName("pushButton_videoTutorial")
+ self.gridLayout_6.addWidget(self.pushButton_videoTutorial, 8, 0, 1, 8)
+ self.groupBox_3 = QtWidgets.QGroupBox(Dialog)
+ self.groupBox_3.setObjectName("groupBox_3")
+ self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_3)
+ self.gridLayout_3.setObjectName("gridLayout_3")
+ self.label_5 = QtWidgets.QLabel(self.groupBox_3)
+ self.label_5.setToolTip("")
+ self.label_5.setToolTipDuration(-1)
+ self.label_5.setStatusTip("")
+ self.label_5.setWhatsThis("")
+ self.label_5.setObjectName("label_5")
+ self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
+ self.Slider_main = QtWidgets.QSlider(self.groupBox_3)
+ self.Slider_main.setToolTip("")
+ self.Slider_main.setToolTipDuration(-1)
+ self.Slider_main.setWhatsThis("")
+ self.Slider_main.setMaximum(100)
+ self.Slider_main.setPageStep(1)
+ self.Slider_main.setProperty("value", 100)
+ self.Slider_main.setSliderPosition(100)
+ self.Slider_main.setTracking(True)
+ self.Slider_main.setOrientation(QtCore.Qt.Horizontal)
+ self.Slider_main.setTickPosition(QtWidgets.QSlider.NoTicks)
+ self.Slider_main.setTickInterval(5)
+ self.Slider_main.setObjectName("Slider_main")
+ self.gridLayout_3.addWidget(self.Slider_main, 0, 1, 1, 1)
+ self.label_6 = QtWidgets.QLabel(self.groupBox_3)
+ self.label_6.setObjectName("label_6")
+ self.gridLayout_3.addWidget(self.label_6, 1, 0, 1, 1)
+ self.Slider_review = QtWidgets.QSlider(self.groupBox_3)
+ self.Slider_review.setMaximum(100)
+ self.Slider_review.setPageStep(10)
+ self.Slider_review.setProperty("value", 1)
+ self.Slider_review.setSliderPosition(1)
+ self.Slider_review.setOrientation(QtCore.Qt.Horizontal)
+ self.Slider_review.setInvertedAppearance(False)
+ self.Slider_review.setTickPosition(QtWidgets.QSlider.NoTicks)
+ self.Slider_review.setObjectName("Slider_review")
+ self.gridLayout_3.addWidget(self.Slider_review, 1, 1, 1, 1)
+ self.gridLayout_6.addWidget(self.groupBox_3, 2, 8, 1, 4)
+ self.groupBox = QtWidgets.QGroupBox(Dialog)
+ self.groupBox.setWhatsThis("")
+ self.groupBox.setTitle("")
+ self.groupBox.setObjectName("groupBox")
+ self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox)
+ self.gridLayout_5.setObjectName("gridLayout_5")
+ self.toolButton_gear = QtWidgets.QToolButton(self.groupBox)
+ self.toolButton_gear.setObjectName("toolButton_gear")
+ self.gridLayout_5.addWidget(self.toolButton_gear, 1, 2, 1, 1)
+ self.label = QtWidgets.QLabel(self.groupBox)
+ self.label.setToolTipDuration(10000)
+ self.label.setObjectName("label")
+ self.gridLayout_5.addWidget(self.label, 0, 0, 1, 1)
+ self.pushButton_randomize = QtWidgets.QPushButton(self.groupBox)
+ self.pushButton_randomize.setObjectName("pushButton_randomize")
+ self.gridLayout_5.addWidget(self.pushButton_randomize, 2, 1, 1, 1)
+ self.pushButton_imageFolder = QtWidgets.QPushButton(self.groupBox)
+ self.pushButton_imageFolder.setObjectName("pushButton_imageFolder")
+ self.gridLayout_5.addWidget(self.pushButton_imageFolder, 2, 0, 1, 1)
+ self.lineEdit_background = QtWidgets.QLineEdit(self.groupBox)
+ self.lineEdit_background.setObjectName("lineEdit_background")
+ self.gridLayout_5.addWidget(self.lineEdit_background, 0, 1, 1, 1)
+ self.lineEdit_gear = QtWidgets.QLineEdit(self.groupBox)
+ self.lineEdit_gear.setObjectName("lineEdit_gear")
+ self.gridLayout_5.addWidget(self.lineEdit_gear, 1, 1, 1, 1)
+ self.label_2 = QtWidgets.QLabel(self.groupBox)
+ self.label_2.setToolTipDuration(10000)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout_5.addWidget(self.label_2, 1, 0, 1, 1)
+ self.toolButton_background = QtWidgets.QToolButton(self.groupBox)
+ self.toolButton_background.setObjectName("toolButton_background")
+ self.gridLayout_5.addWidget(self.toolButton_background, 0, 2, 1, 1)
+ self.gridLayout_6.addWidget(self.groupBox, 1, 0, 1, 12)
+ self.groupBox_5 = QtWidgets.QGroupBox(Dialog)
+ self.groupBox_5.setObjectName("groupBox_5")
+ self.gridLayout = QtWidgets.QGridLayout(self.groupBox_5)
+ self.gridLayout.setObjectName("gridLayout")
+ self.label_11 = QtWidgets.QLabel(self.groupBox_5)
+ self.label_11.setObjectName("label_11")
+ self.gridLayout.addWidget(self.label_11, 0, 0, 1, 1)
+ self.lineEdit_color_main = QtWidgets.QLineEdit(self.groupBox_5)
+ self.lineEdit_color_main.setObjectName("lineEdit_color_main")
+ self.gridLayout.addWidget(self.lineEdit_color_main, 0, 1, 1, 1)
+ self.toolButton_color_main = QtWidgets.QToolButton(self.groupBox_5)
+ self.toolButton_color_main.setObjectName("toolButton_color_main")
+ self.gridLayout.addWidget(self.toolButton_color_main, 0, 2, 1, 1)
+ self.label_12 = QtWidgets.QLabel(self.groupBox_5)
+ self.label_12.setObjectName("label_12")
+ self.gridLayout.addWidget(self.label_12, 0, 3, 1, 1)
+ self.lineEdit_color_top = QtWidgets.QLineEdit(self.groupBox_5)
+ self.lineEdit_color_top.setObjectName("lineEdit_color_top")
+ self.gridLayout.addWidget(self.lineEdit_color_top, 0, 4, 1, 1)
+ self.toolButton_color_top = QtWidgets.QToolButton(self.groupBox_5)
+ self.toolButton_color_top.setObjectName("toolButton_color_top")
+ self.gridLayout.addWidget(self.toolButton_color_top, 0, 5, 1, 1)
+ self.label_13 = QtWidgets.QLabel(self.groupBox_5)
+ self.label_13.setObjectName("label_13")
+ self.gridLayout.addWidget(self.label_13, 1, 3, 1, 1)
+ self.lineEdit_color_bottom = QtWidgets.QLineEdit(self.groupBox_5)
+ self.lineEdit_color_bottom.setObjectName("lineEdit_color_bottom")
+ self.gridLayout.addWidget(self.lineEdit_color_bottom, 1, 4, 1, 1)
+ self.toolButton_color_bottom = QtWidgets.QToolButton(self.groupBox_5)
+ self.toolButton_color_bottom.setObjectName("toolButton_color_bottom")
+ self.gridLayout.addWidget(self.toolButton_color_bottom, 1, 5, 1, 1)
+ self.gridLayout_6.addWidget(self.groupBox_5, 4, 0, 1, 12)
+ spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
+ self.gridLayout_6.addItem(spacerItem, 7, 0, 1, 1)
+ self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
+ self.groupBox_2.setTitle("")
+ self.groupBox_2.setObjectName("groupBox_2")
+ self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_2)
+ self.gridLayout_4.setObjectName("gridLayout_4")
+ self.checkBox_reviewer = QtWidgets.QCheckBox(self.groupBox_2)
+ self.checkBox_reviewer.setToolTipDuration(10000)
+ self.checkBox_reviewer.setChecked(True)
+ self.checkBox_reviewer.setObjectName("checkBox_reviewer")
+ self.gridLayout_4.addWidget(self.checkBox_reviewer, 0, 0, 1, 1)
+ self.checkBox_toolbar = QtWidgets.QCheckBox(self.groupBox_2)
+ self.checkBox_toolbar.setToolTipDuration(10000)
+ self.checkBox_toolbar.setChecked(True)
+ self.checkBox_toolbar.setObjectName("checkBox_toolbar")
+ self.gridLayout_4.addWidget(self.checkBox_toolbar, 1, 0, 1, 1)
+ self.checkBox_topbottom = QtWidgets.QCheckBox(self.groupBox_2)
+ self.checkBox_topbottom.setToolTipDuration(10000)
+ self.checkBox_topbottom.setChecked(True)
+ self.checkBox_topbottom.setObjectName("checkBox_topbottom")
+ self.gridLayout_4.addWidget(self.checkBox_topbottom, 2, 0, 1, 1)
+ self.gridLayout_6.addWidget(self.groupBox_2, 2, 0, 1, 8)
+ self.OkButton = QtWidgets.QPushButton(Dialog)
+ self.OkButton.setObjectName("OkButton")
+ self.gridLayout_6.addWidget(self.OkButton, 8, 11, 1, 1)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
+ self.gridLayout_6.addItem(spacerItem1, 5, 0, 1, 1)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.toolButton_palace = QtWidgets.QToolButton(Dialog)
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap("CustomBackground:AnkiPalace_no_text.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_palace.setIcon(icon)
+ self.toolButton_palace.setIconSize(QtCore.QSize(40, 40))
+ self.toolButton_palace.setObjectName("toolButton_palace")
+ self.horizontalLayout.addWidget(self.toolButton_palace)
+ self.label_3 = QtWidgets.QLabel(Dialog)
+ self.label_3.setMinimumSize(QtCore.QSize(400, 0))
+ self.label_3.setWordWrap(True)
+ self.label_3.setObjectName("label_3")
+ self.horizontalLayout.addWidget(self.label_3)
+ self.gridLayout_6.addLayout(self.horizontalLayout, 6, 0, 1, 12)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
+ self.horizontalLayout_2.setSpacing(6)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.toolButton_website = QtWidgets.QToolButton(Dialog)
+ self.toolButton_website.setMaximumSize(QtCore.QSize(31, 31))
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap("CustomBackground:AnKingSmall.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_website.setIcon(icon1)
+ self.toolButton_website.setIconSize(QtCore.QSize(31, 31))
+ self.toolButton_website.setObjectName("toolButton_website")
+ self.horizontalLayout_2.addWidget(self.toolButton_website)
+ self.toolButton_youtube = QtWidgets.QToolButton(Dialog)
+ self.toolButton_youtube.setMaximumSize(QtCore.QSize(31, 31))
+ icon2 = QtGui.QIcon()
+ icon2.addPixmap(QtGui.QPixmap("CustomBackground:YouTube.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_youtube.setIcon(icon2)
+ self.toolButton_youtube.setIconSize(QtCore.QSize(31, 31))
+ self.toolButton_youtube.setObjectName("toolButton_youtube")
+ self.horizontalLayout_2.addWidget(self.toolButton_youtube)
+ self.toolButton_patreon = QtWidgets.QToolButton(Dialog)
+ self.toolButton_patreon.setMaximumSize(QtCore.QSize(171, 26))
+ icon3 = QtGui.QIcon()
+ icon3.addPixmap(QtGui.QPixmap("CustomBackground:Patreon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_patreon.setIcon(icon3)
+ self.toolButton_patreon.setIconSize(QtCore.QSize(200, 31))
+ self.toolButton_patreon.setObjectName("toolButton_patreon")
+ self.horizontalLayout_2.addWidget(self.toolButton_patreon, 0, QtCore.Qt.AlignHCenter)
+ self.toolButton_instagram = QtWidgets.QToolButton(Dialog)
+ self.toolButton_instagram.setMaximumSize(QtCore.QSize(31, 31))
+ icon4 = QtGui.QIcon()
+ icon4.addPixmap(QtGui.QPixmap("CustomBackground:Instagram.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_instagram.setIcon(icon4)
+ self.toolButton_instagram.setIconSize(QtCore.QSize(31, 31))
+ self.toolButton_instagram.setObjectName("toolButton_instagram")
+ self.horizontalLayout_2.addWidget(self.toolButton_instagram)
+ self.toolButton_facebook = QtWidgets.QToolButton(Dialog)
+ self.toolButton_facebook.setMaximumSize(QtCore.QSize(31, 31))
+ icon5 = QtGui.QIcon()
+ icon5.addPixmap(QtGui.QPixmap("CustomBackground:Facebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_facebook.setIcon(icon5)
+ self.toolButton_facebook.setIconSize(QtCore.QSize(31, 31))
+ self.toolButton_facebook.setObjectName("toolButton_facebook")
+ self.horizontalLayout_2.addWidget(self.toolButton_facebook)
+ self.gridLayout_6.addLayout(self.horizontalLayout_2, 0, 2, 1, 9)
+ self.groupBox_5.raise_()
+ self.groupBox_4.raise_()
+ self.groupBox_3.raise_()
+ self.groupBox_2.raise_()
+ self.groupBox.raise_()
+ self.pushButton_videoTutorial.raise_()
+ self.RestoreButton.raise_()
+ self.OkButton.raise_()
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Custom Background Settings"))
+ self.label_4.setText(_translate("Dialog", "Attachment:"))
+ self.comboBox_attachment.setItemText(0, _translate("Dialog", "fixed"))
+ self.comboBox_attachment.setItemText(1, _translate("Dialog", "scroll"))
+ self.label_8.setText(_translate("Dialog", "Size:"))
+ self.comboBox_size.setItemText(0, _translate("Dialog", "cover"))
+ self.comboBox_size.setItemText(1, _translate("Dialog", "contain"))
+ self.label_7.setText(_translate("Dialog", "Position:"))
+ self.comboBox_position.setCurrentText(_translate("Dialog", "left top"))
+ self.comboBox_position.setItemText(0, _translate("Dialog", "left top"))
+ self.comboBox_position.setItemText(1, _translate("Dialog", "center top"))
+ self.comboBox_position.setItemText(2, _translate("Dialog", "right top"))
+ self.comboBox_position.setItemText(3, _translate("Dialog", "right"))
+ self.comboBox_position.setItemText(4, _translate("Dialog", "left"))
+ self.comboBox_position.setItemText(5, _translate("Dialog", "center"))
+ self.comboBox_position.setItemText(6, _translate("Dialog", "left bottom"))
+ self.comboBox_position.setItemText(7, _translate("Dialog", "center bottom"))
+ self.comboBox_position.setItemText(8, _translate("Dialog", "right bottom"))
+ self.label_9.setText(_translate("Dialog", "Scale:"))
+ self.RestoreButton.setText(_translate("Dialog", "Restore Default"))
+ self.pushButton_videoTutorial.setText(_translate("Dialog", "Video Tutorial"))
+ self.groupBox_3.setTitle(_translate("Dialog", "Background opacity"))
+ self.label_5.setText(_translate("Dialog", "Main"))
+ self.label_6.setText(_translate("Dialog", "Review"))
+ self.toolButton_gear.setText(_translate("Dialog", "..."))
+ self.label.setToolTip(_translate("Dialog", "
Name of the background image file.
"Random" will shuffle through defaults
"))
+ self.label.setText(_translate("Dialog", "Image name for background:"))
+ self.pushButton_randomize.setText(_translate("Dialog", "Random Images"))
+ self.pushButton_imageFolder.setText(_translate("Dialog", "Open Image Folders"))
+ self.label_2.setToolTip(_translate("Dialog", "
"))
+ self.checkBox_reviewer.setText(_translate("Dialog", "Show in reviewer"))
+ self.checkBox_toolbar.setToolTip(_translate("Dialog", "
Show the background image in the top and bottom toolbars in addition to the main screen
"))
+ self.checkBox_toolbar.setText(_translate("Dialog", "Show in toolbar"))
+ self.checkBox_topbottom.setToolTip(_translate("Dialog", "
Set the background position of the toolbars to top and bottom (if the main background position is set to center, this will look cleaner for most images)
"))
+ self.checkBox_reviewer.setText(_translate("Dialog", "Show in reviewer"))
+ self.checkBox_toolbar.setToolTip(_translate("Dialog", "
Show the background image in the top and bottom toolbars in addition to the main screen
"))
+ self.checkBox_toolbar.setText(_translate("Dialog", "Show in toolbar"))
+ self.checkBox_topbottom.setToolTip(_translate("Dialog", "
Set the background position of the toolbars to top and bottom (if the main background position is set to center, this will look cleaner for most images)
Added an option to change Card Info Sidebar default position
+
Fixed description button not showing on deck overview screen
+
Fixed a sidebar error caused by rescheduled cards
+
+
+
+
2022/1/30
+
+
Changed load settings prompt message (forgot to do it yesterday)
+
+
+
+
2022/1/29
+
+
Added a feature to change button text size [go to "Button Sizes" and change button text size]
+
Added another style for button intervals (now you can move button intervals inside the buttons) [to change button interval style go to "Styles" tab and change button interval style]
+
Added a feature to enable direct config edit (serves no purpose for now, don't enable it unless you're told to | The idea is to quickly be able to add new features without having to add options in settings menu)
+
Added an option to backup your settings (just press Backup Settings button and it will create a backup file of your settings - you can also share your settings and button stylings with other people by sharing the settings file)
+
Added an option to load settings file (you can load settings and not go through settings and changing different settings and styles)
+
Fixed button tooltip bug in python 3.10 (Thanks to @sdvcrx)
+
Removed "Restore Defaults" button (with the new "Load Settings" function, having this extra button doesn't make sense)
+
+
+
+
2021/9/22
+
+
Minor macOS bug fix (Hopefully -_-)
+
+
+
+
2021/9/15
+
+
Minor bug fix
+
+
+
+
2021/8/23
+
+
Detailed Deck Overview bug fix
+
+
+
+
2021/8/22
+
+
Added a new method for skipping cards.
+
+
This method is partially manual. The skipped cards won't show automatically unless you finish reviewing normal cards.
+
This method uses Anki's "Bury" function and buries skipped cards. The skipped cards will get unburied once you exit review screen or press press Button.
+
If you want for the skipped cards to show mid-review, you'll have to press Button or press the assigned shortcut (default shortcut is Alt + C).
+
If you use V3 sheduler, this is the only method that'll work for you and will be chosen by default.
+
If you use V2 scheduler you can use this method or the old method. You can choose the skip method in Settings Menu -> Misc -> Skip Method
+
The old method is Next Card and the new method is Bury.
+
The new "Bury" method might be a bit slower, especially when you use the button. If you choose to use this method, I suggets using shortcuts for skipping cards.
+
+
Adjusted Settings Menu height for better viewing on screens with lower resulotions
+
Moved changelog from main Settings Menu window to a separate window
+
+
+
+
2021/8/4
+
+
Bug fix (now ARBb is compatible with Anki 2.1.45)
+
From now on, No update will be released for Anki versions older than 2.1.45
+
+
+
+
2021/7/31
+
+
Bug Fix
+
+
+
+
2021/7/30
+
+
Bug Fix
+
+
+
+
2021/7/30
+
+
Added an option to set your custom text as button labels.
+ replace again, hard, good, easy, etc. text with your custom text or emoji.
+ To change button labels and use your own custom text, go to "Button label" tab in the settings.
+ To the person asking me how to change button labels -_- you can use this from now on. No need to change the code.
+
Added an option to hide hard, good, easy buttons. (Requested)
+ (no I haven't forgotten to put again in the list -_- you can't hide again button).
+ To use this option, go to "Bottombar Buttons" and look for "Hide Buttons" part there.
+
Added an option to change the roundness of the buttons.
+ To use this option, go to "Styles" tab and look for "Button Border Radius" there.
+
Removed pressed button stats from the add-on.
+ For those who used it, I'll be publishing it as a separate add on named "Pressed Button Stats"
+
+
+
+
2020/6/9
+
+
Added an option to turn off more overview stats.
+ pressed button count STILL at 90%
+
+
+
+
2020/12/6
+
+
Added another mode to overview stats (taken from "More Overview Stats 2.1")
+
Fixed conflict with speedfocus add-on (If you use speedfocus you need to enable "Speed focus" option in ARBb settings -> Misc)
+
+
+
2020/6/9
+
+
Added an option to turn off more overview stats.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/30
+
+
Changed tooltip behavior.
+ Now it's size won't be as size of the buttons when it's position is fixed.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/18
+
+
Minor code changes/improvements.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/15
+
+
Now it designs review buttons that other add-ons add (like rememorize).
+ it treats them like other bottombar button so their color and style
+ will be like other bottombar buttons
+
you can style other bottombar buttons that are added by other add-on (like deferer button).
+ you'll need to change their code a bit. if you want to style them leave a comment here or on github page.
+ (the last picture is how the extra buttons the those add-on add look after styling them using this add-on)
Changed color of timer text in bottombar.
+ now it uses the same color you have set for other bottombar buttons text color. (not a big deal though, right?)
+ pressed button count STILL at 90%
+
+
+
+
2020/5/9
+
+
Made neon and fill designs customizable. now you can change their colors using "Colors" tab.
+ Enable custom colors by checking "Custom Review Button Colors" checkbox and
+ changing again, hard, good and easy colors.
+ as these designs don't have a separate hover color, changing hover colors won't
+ change anything about these buttons
+
Made review bottombar buttons, deck overview buttons and main screen bottombar buttons customizable.
+ you can change their border and text colors in "Colors" tab by changing "General Button" text and border colors.
+ you can't chage text or background color for general buttons if their button style is set on default.
+ to change general buttons style go to "Styles" tab and change "General Buttons Style".
+
Added an option to change show answer button border color based on card ease.
+ you can enable than option in "Style" tab by changing "Show Answer Border Color Style"
+ from "Fixed" to "Based on Card Ease". you cand change color for each ease range in "Colors" tab.
+ - (honestly i think it's gonna be usless for most of you :/ it was just something that i needed).
+
+ Other settings menu and bottombar buttons changes and improvements.
+ pressed button count STILL at 90%
+
+
+
+
2020/4/28
+
+
Added an option to choose card type [learn, review, re-learn, cram] for button count stats
+
Added an option to manually change decks in button count stats
+ at 90%
+
+
+
+
2020/5/01
+
+
Added total time and time per card to information shown in pressed button stats
+ at 85%
+
+
+
+
2020/4/28
+
+
Added an option to choose card type [learn, review, re-learn, cram] for button count stats
+
Added an option to manually change decks in button count stats
+ at 80%
+
+
+
+
2020/4/27
+
+
Added an option to choose time period for button count stats
+
Added an option to change button count stats scope
+
Button count stats window improvements
+ at 50%
+
+
+
+
2020/4/26
+
+
NEW FEATURE: pressed button count + Percent
+ NOTE: it's work in progress and very basic
+ the only reason i'm publishing it is that i want to hear you opinions on it and see what you need
+ I want to hear your ideas about it, tell me what i can do to make it better
+ you can Email me your ideas
+ however, i think some of you may want to change the time period for this option
+ to do that go to config -> Advanced review bottombar -> open add-on folder ->
+ open Button_Count.py -> go to line 47 you'll see what you need there
+ when you're on a deck, it shows pressed button stats for that deck,
+ when you're in main window, it'll show overall stats
+ at 15%
+
+
+
+
2020/4/22
+
+
Made styling main screen and deck overview compatible with anki versions older than 2.1.19
+
+
+
+
2020/4/21
+
+
Added an option to change main screen and deck overview buttons style
+ (Their style will be as other bottombar buttons style)
+
+
+
+
2020/4/20
+
+
Fixed tooltip bug (where it would show hard on tooltip when you
+ pressed good if you were in a cutom study session )
+
Added card info sidebar auto open (opens sidebar automatically when you review a card)
+
+
+
+
2020/4/18
+
+
Minor settings menu improvements
+
+
+
+
2020/4/17
+
+
Fixed Neon 1 style bug
+
Addded correct percentage, fastest reveiw, slowest review, note ID and card ID options to card info sidebar
+
+
+
+
2020/4/16
+
+
Added change button transition time option (for fill and neon designs only)
+
+
+
+
2020/4/15
+
+
Added an option to change cursor type when you hover over bottombar buttons
+
+
+
+
2020/4/14
+
+
Added answer tooltips
+
Adjusted tooltips for neon and fill designs
+
Adjusted tooltips for custom button sizes
+
+
+
+
2020/4/13
+
+
Added a function to get shortcuts (Don't have to test keys that you want to set as shortcuts anymore, if it's Anki's default shortcut for something, the add-on wont accept it)
+
Moved button design tooltip to another tab (noticed it was WAY too big for lower resulotions to be useful)
+
# NOTE: if you're updating from any version other than 2020/4/12 you might run into some problems trying to
+ open settings menu if you can't open settings menu after update open add-on folder and delete meta.json file if
+ that didn't help go to settings.py and put a # in front of the last line then go to tools -> add-ons and press restore defaults on this addon's config page
+
+
+
+
2020/4/12
+
+
Changed settings menu so it's easier to work with on lower resolutions (had to code it all over again)
+
Made picking colors completely automatic (no color code copy/paste, choose the color and it's set)
+
Added an option for you to choose settings menu's position
+
Made wide buttons compatible with no distractions add-on
+
# NOTE: After update you need to restore config to defaults in tools -> addons
+
+
+
+
2020/4/8
+
+
settings menu bugs fixes
+
settings menu minor adjustments for smaller screens
+
+
+
+
2020/4/7
+
+
settings menu improvements
+
added an option to color intervals
+
added an option to style other bottombar buttons
+
added 4 new button designs
+
+
+
+
2020/4/6
+
+
minor settings menu improvements
+
card info sidebar improvements for old scheduler
+
+
+
+
2020/4/5
+
+
minor settings menu improvements
+
added tooltips with pictures for different settings
+
fixed card info sidebar crash bug
+
+
+
+
2020/4/4
+
+
added settings menu
+
minor settings menu adjustments
+
+
+
+
2020/4/2
+
+
fix for wide buttons
+
fixed card info sidebar problem with beta versions of anki (2.1.23 and 2.1.24)
+
+
+
+
2020/4/1
+
+
fixed issue with limiting card reviews in card info sidebar
+
added an option to change active button indicator from border to glow and change it's color
+
+
+
+
2020/3/30
+
+
adjusted colors and gradients for background color change for light mode
+
added background shadow for review buttons (enable in config)
+
+
+
+
2020/3/29
+
+
added undo button (enable in config)
+
fixed button color for old scheduler
+
removed conflict with customize keyboard shortcuts add-on
+
removed conflict with speed focus add-on (needs to be enabled in config)
+
removed conflict with slackers add-on
+
added an option to choose text color in review button background color change
+
+
+
+
2020/3/26
+
+
added change button size option
+
+
+
+
2020/3/25
+
+
added change skip and info button position option
+
+
+
+
2020/3/20
+
+
fixed conflict with "replay button on card" add-on
+
+
+
+
2020/3/7
+
+
adjusted the color for review buttons
+
added an option to choose the font for the text in card info side bar in config
+
added an option so you could limit the maximum number of previous reviews that are shown on sidebar for a card
+
+
+
+
2020/3/6
+
+
made the info sidebar customizable, you can choose what you want to see on card info sidebar in config
+
+
+
+
2020/3/4
+
+
fixed not showing review button colors on new in-app night mode
+
adjusted review button text colors for new in-app night mode
+
adjusted wide button widths
+
+
+
+
2020/2/8
+
+
added an option for you to choose the shortcut key for skip and info buttons (in add-on config)
+
added an option to choose the sidebar theme (in add-on config)
+ '''.format(label=labels, cards=cards, percent=cards_percent, percent2=cards_percent_without_suspended)
+
+ output = ''
+
+ if deck_is_finished:
+ if (config == None or not 'options' in config) or (config['options'].get('Show table for finished decks', True)):
+ output += output_table
+ output += u'''
+
+
+ '''
+ output += u'''
+
{:s}
+ '''.format(self.mw.col.sched.finishedMsg())
+ else:
+ if style_mainScreenButtons:
+ #// style='height: px' -> to prevent changing main screen buttons heights
+ # based on height defined in #main {}
+ mainScreen_style = """id=main style='height: px' """
+ else:
+ mainScreen_style = ""
+ if style_mainScreenButtons:
+ studyButton_id = "main"
+ else:
+ studyButton_id = "study"
+
+ output += output_table
+ output += bottomHTML_style
+ output += u'''
+
+
{button:s}
+
+
+ '''.format(button=button('study', tr.studying_study_now(), id='{}'.format(studyButton_id), extra="autofocus"))
+
+ return output
+
+else:
+ def _table(self):
+ counts = list(self.mw.col.sched.counts())
+ finished = not sum(counts)
+ if self.mw.col.sched_ver() == 1:
+ for n in range(len(counts)):
+ if counts[n] >= 1000:
+ counts[n] = "1000+"
+ but = self.mw.button
+ if finished:
+ return '
%s
' % (
+ self.mw.col.sched.finishedMsg()
+ )
+ else:
+ if style_mainScreenButtons:
+ #// style='height: px' -> to prevent changing main screen buttons heights
+ # based on height defined in #main {}
+ mainScreen_style = """id=main style='height: px' """
+ else:
+ mainScreen_style = ""
+ if style_mainScreenButtons:
+ studyButton_id = "main"
+ else:
+ studyButton_id = "study"
+ return """%s
+
+
+
+
%s:
%s
+
%s:
%s
+
%s:
%s
+
+
+ %s
""" % (
+ bottomHTML_style,
+ tr.actions_new(),
+ counts[0],
+ tr.scheduling_learning(),
+ counts[1],
+ tr.studying_to_review(),
+ counts[2],
+ but("study", tr.studying_study_now(), id="{}".format(studyButton_id), extra="autofocus"),
+ )
+
+def _limit(counts):
+ for i, count in enumerate(counts):
+ if count >= 1000:
+ counts[i] = "1000+"
+ return counts
+
+
+Overview._renderBottom = _renderBottom
+DeckBrowser._drawButtons = _drawButtons
+Overview._table = _table
diff --git a/.local/share/Anki2/addons21/advanced_review/LICENSE b/.local/share/Anki2/addons21/advanced_review/LICENSE
new file mode 100644
index 0000000..3877ae0
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/.local/share/Anki2/addons21/advanced_review/README.md b/.local/share/Anki2/addons21/advanced_review/README.md
new file mode 100644
index 0000000..b20ba61
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/README.md
@@ -0,0 +1,12 @@
+# Anki-Advanced-Review-Bottombar
+An Anki add-on that makes you able to change Anki button styles to your liking.
+
+You can see these images to see the settings menu and the things you can do with this add-on:
+
+
+
+
+
+
+
+If you find the settings too complicated, just hover over each title in the settings, a little help box will appear in which I have explained what the option does (used images to show you what it does in most cases).
diff --git a/.local/share/Anki2/addons21/advanced_review/Settings.py b/.local/share/Anki2/addons21/advanced_review/Settings.py
new file mode 100644
index 0000000..4c279c3
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/Settings.py
@@ -0,0 +1,2667 @@
+#// auth_ Mohamad Janati
+#// Copyright (c) 2019-2022 Mohamad Janati (freaking stupid, right? :|)
+
+from os.path import join, dirname
+from datetime import datetime
+from aqt import mw
+from aqt.qt import *
+from aqt.utils import tooltip, showInfo, askUser, getText
+import random
+import os
+import json
+import subprocess
+
+
+def refreshConfig():
+ #// Makes the information that it gets fron "config" global so I can use them for loading the current settings in "loadCurrent(self)" function
+ global C_style_mainScreenButtons, C_button_style, C_hover_effect, C_active_indicator, C_bottombarButtons_style, C_cursor_style, C_interval_style, C_showAnswerBorderColor_style, C_buttonTransition_time, C_buttonBorderRadius, C_reviewTooltip, C_reviewTooltip_timer, C_reviewTooltipText_color, C_reviewTooltip_style, C_reviewTooltip_position, C_info, C_skip, C_showSkipped, C_undo, C_hideHard, C_hideGood, C_hideEasy, C_right_info, C_middleRight_info, C_middleLeft_info, C_left_info, C_right_skip, C_middleRight_skip, C_middleLeft_skip, C_left_skip, C_right_showSkipped, C_middleRight_showSkipped, C_middleLeft_showSkipped, C_left_showSkipped, C_right_undo, C_middleRight_undo, C_middleLeft_undo, C_left_undo, C_skip_shortcut, C_showSkipped_shortcut, C_info_shortcut, C_undo_shortcut, C_custom_sizes, C_text_size, C_buttons_height, C_reviewButtons_width, C_edit_width, C_answer_width, C_more_width, C_info_width, C_skip_width, C_showSkipped_width, C_undo_width, C_buttonLabel_studyNow, C_buttonLabel_edit, C_buttonLabel_showAnswer, C_buttonLabel_more, C_buttonLabel_info, C_buttonLabel_skip, C_buttonLabel_showSkipped, C_buttonLabel_undo, C_buttonLabel_again, C_buttonLabel_hard, C_buttonLabel_good, C_buttonLabel_easy, C_sidebar_position, C_sidebar_theme, C_sidebar_font, C_sidebar_PreviousCards, C_sidebar_reviewsToShow, C_sidebar_currentReviewCount, C_sidebar_reviewsToShow, C_sidebar_dateCreated, C_sidebar_dateEdited, C_sidebar_firstReview, C_sidebar_latestReview, C_sidebar_due, C_sidebar_interval, C_sidebar_ease, C_sidebar_numberOfReviews, C_sidebar_lapses, C_infobar_correctPercent, C_infobar_fastestReview, C_infobar_slowestReview, C_sidebar_averageTime, C_sidebar_totalTime, C_sidebar_cardType, C_sidebar_noteType, C_sidebar_deck, C_sidebar_tags, C_infobar_noteID, C_infobar_cardID, C_sidebar_sortField, C_sidebar_autoOpen, C_sidebar_warningNote, C_custom_reviewButtonColors, C_custom_reviewButtonTextColor, C_custom_activeIndicatorColor, C_custom_bottombarButtonTextColor, C_custom_bottombarButtonBorderColor, C_reviewButtonText_color, C_activeIndicator_color, C_bottombarButtonText_color, C_bottombarButtonBorder_color, C_again_color, C_againHover_color, C_hard_color, C_hardHover_color, C_good_color, C_goodHover_color, C_easy_color, C_easyHover_color, C_button_colors, C_showAnswerEase1, C_showAnswerEase2, C_showAnswerEase3, C_showAnswerEase4, C_showAnswerEase1_color, C_showAnswerEase2_color, C_showAnswerEase3_color, C_showAnswerEase4_color, C_speedFocus, C_configEdit, C_overViewStats, C_settingsMenu_palce, C_skipMethod
+
+ config = mw.addonManager.getConfig(__name__)
+
+ #// Gets the information from the config and assigns them to the "C_" variables so I can make them global | "C_" is added to the name of the parts of the settings variables to avoid confusion :D
+ #// Just delete the "C_" from the name to find related parts of the settings (C_style_mainScreenButtons -> style_mainScreenButtons)
+ C_style_mainScreenButtons = config[' Style Main Screen Buttons']
+
+ C_button_style = config[' Review_ Buttons Style']
+ C_hover_effect = config[' Review_ Hover Effect']
+ C_active_indicator = config[' Review_ Active Button Indicator']
+ C_bottombarButtons_style = config[' Review_ Bottombar Buttons Style']
+ C_cursor_style = config[' Review_ Cursor Style']
+ C_interval_style = config[' Review_ Interval Style']
+ C_buttonTransition_time = config[' Review_ Button Transition Time']
+ # Button Border Radius is used for all buttons, not just the review buttons
+ C_buttonBorderRadius = config[' Review_ Button Border Radius']
+
+ C_reviewTooltip = config['Tooltip']
+ C_reviewTooltip_timer = config['Tooltip Timer']
+ C_reviewTooltipText_color = config['Tooltip Text Color']
+ C_reviewTooltip_style = config['Tooltip Style']
+ C_reviewTooltip_position = config['Tooltip Position']
+
+ C_info = config['Button_ Info Button']
+ C_skip = config['Button_ Skip Button']
+ C_showSkipped = config['Button_ Show Skipped Button']
+ C_undo = config['Button_ Undo Button']
+ C_hideHard = config['Button_ Hide Hard']
+ C_hideGood = config['Button_ Hide Good']
+ C_hideEasy = config['Button_ Hide Easy']
+ C_info_position = config['Button_ Position_ Info Button']
+ C_skip_position = config['Button_ Position_ Skip Button']
+ C_showSkipped_position = config['Button_ Position_ Show Skipped Button']
+ C_undo_position = config['Button_ Position_ Undo Button']
+ C_skip_shortcut = config ['Button_ Shortcut_ Skip Button']
+ C_showSkipped_shortcut = config ['Button_ Shortcut_ Show Skipped Button']
+ C_info_shortcut = config['Button_ Shortcut_ Info Button']
+ C_undo_shortcut = config['Button_ Shortcut_ Undo Button']
+
+ C_custom_sizes = config ['Button_ Custom Button Sizes']
+ C_text_size = config['Button_ Text Size']
+ C_buttons_height = config['Button_ Height_ All Bottombar Buttons']
+ C_reviewButtons_width = config['Button_ Width_ Review Buttons']
+ C_edit_width = config['Button_ Width_ Edit Button']
+ C_answer_width = config['Button_ Width_ Show Answer Button']
+ C_more_width = config['Button_ Width_ More Button']
+ C_info_width = config['Button_ Width_ Info Button']
+ C_skip_width = config['Button_ Width_ Skip Button']
+ C_showSkipped_width = config['Button_ Width_ Show Skipped Button']
+ C_undo_width = config['Button_ Width_ Undo Button']
+
+ C_buttonLabel_studyNow = config['Button Label_ Study Now']
+ C_buttonLabel_edit = config['Button Label_ Edit']
+ C_buttonLabel_showAnswer = config['Button Label_ Show Answer']
+ C_buttonLabel_more = config['Button Label_ More']
+ C_buttonLabel_info = config['Button Label_ Info']
+ C_buttonLabel_skip = config['Button Label_ Skip']
+ C_buttonLabel_showSkipped = config['Button Label_ Show Skipped']
+ C_buttonLabel_undo = config['Button Label_ Undo']
+ C_buttonLabel_again = config['Button Label_ Again']
+ C_buttonLabel_hard = config['Button Label_ Hard']
+ C_buttonLabel_good = config['Button Label_ Good']
+ C_buttonLabel_easy = config['Button Label_ Easy']
+
+ C_sidebar_position = config['Card Info sidebar_ Default Position']
+ C_sidebar_theme = config['Card Info sidebar_ theme']
+ C_sidebar_font = config['Card Info sidebar_ Font']
+ C_sidebar_PreviousCards = config['Card Info sidebar_ Number of previous cards to show']
+ C_sidebar_reviewsToShow = config['Card Info sidebar_ number of reviews to show for a card']
+ C_sidebar_currentReviewCount = config['Card Info sidebar_ Current Review Count']
+ C_sidebar_dateCreated = config['Card Info sidebar_ Created']
+ C_sidebar_dateEdited = config['Card Info sidebar_ Edited']
+ C_sidebar_firstReview = config['Card Info sidebar_ First Review']
+ C_sidebar_latestReview = config['Card Info sidebar_ Latest Review']
+ C_sidebar_due = config['Card Info sidebar_ Due']
+ C_sidebar_interval = config['Card Info sidebar_ Interval']
+ C_sidebar_ease = config['Card Info sidebar_ Ease']
+ C_sidebar_numberOfReviews = config['Card Info sidebar_ Reviews']
+ C_sidebar_lapses = config['Card Info sidebar_ Lapses']
+ C_infobar_correctPercent = config['Card Info Sidebar_ Correct Percent']
+ C_infobar_fastestReview = config['Card Info Sidebar_ Fastest Review']
+ C_infobar_slowestReview = config['Card Info Sidebar_ Slowest Review']
+ C_sidebar_averageTime = config['Card Info sidebar_ Average Time']
+ C_sidebar_totalTime = config['Card Info sidebar_ Total Time']
+ C_sidebar_cardType = config['Card Info sidebar_ Card Type']
+ C_sidebar_noteType = config['Card Info sidebar_ Note Type']
+ C_sidebar_deck = config['Card Info sidebar_ Deck']
+ C_sidebar_tags = config['Card Info sidebar_ Tags']
+ C_infobar_noteID = config['Card Info Sidebar_ Note ID']
+ C_infobar_cardID = config['Card Info Sidebar_ Card ID']
+ C_sidebar_sortField = config['Card Info sidebar_ Sort Field']
+ C_sidebar_autoOpen = config['Card Info sidebar_ Auto Open']
+ C_sidebar_warningNote = config['Card Info sidebar_ warning note']
+
+ C_custom_reviewButtonColors = config[' Review_ Custom Colors']
+ C_custom_reviewButtonTextColor = config[' Review_ Custom Review Button Text Color']
+ C_custom_activeIndicatorColor = config[' Review_ Custom Active Indicator Color']
+ C_custom_bottombarButtonTextColor = config['Color_ Custom Bottombar Button Text Color']
+ C_custom_bottombarButtonBorderColor = config['Color_ Custom Bottombar Button Border Color']
+ C_reviewButtonText_color = config['Color_ General Text Color']
+ C_activeIndicator_color = config['Color_ Active Button Indicator']
+ C_bottombarButtonText_color = config['Color_ Bottombar Button Text Color']
+ C_bottombarButtonBorder_color = config['Color_ Bottombar Button Border Color']
+ C_again_color = config['Color_ Again']
+ C_againHover_color = config['Color_ Again on hover']
+ C_hard_color = config['Color_ Hard']
+ C_hardHover_color = config['Color_ Hard on hover']
+ C_good_color = config['Color_ Good']
+ C_goodHover_color = config['Color_ Good on hover']
+ C_easy_color = config['Color_ Easy']
+ C_easyHover_color = config['Color_ Easy on hover']
+
+ C_showAnswerBorderColor_style = config['ShowAnswer_ Border Color Style']
+ C_showAnswerEase1 = config['ShowAnswer_ Ease1']
+ C_showAnswerEase2 = config['ShowAnswer_ Ease2']
+ C_showAnswerEase3 = config['ShowAnswer_ Ease3']
+ C_showAnswerEase4 = config['ShowAnswer_ Ease4']
+ C_showAnswerEase1_color = config['ShowAnswer_ Ease1 Color']
+ C_showAnswerEase2_color = config['ShowAnswer_ Ease2 Color']
+ C_showAnswerEase3_color = config['ShowAnswer_ Ease3 Color']
+ C_showAnswerEase4_color = config['ShowAnswer_ Ease4 Color']
+
+ C_button_colors = config[' Button Colors']
+ C_speedFocus = config[' Speed Focus Add-on']
+ C_configEdit = config[' Direct Config Edit']
+ C_overViewStats = config[' More Overview Stats']
+ C_settingsMenu_palce = config[' Settings Menu Place']
+ C_skipMethod = config[' Skip Method']
+
+ #// it's easier to store extra button positions as text in config | but here in the settings, I hate to turn it into true/false as each checkbox is diabled/enabled like that :|
+ #// Every checkbox is disabled by default
+ C_right_info = False
+ C_middleRight_info = False
+ C_middleLeft_info = False
+ C_left_info = False
+ C_right_skip = False
+ C_middleRight_skip = False
+ C_middleLeft_skip = False
+ C_left_showSkipped = False
+ C_right_showSkipped = False
+ C_middleRight_showSkipped = False
+ C_middleLeft_showSkipped = False
+ C_left_showSkipped = False
+ C_right_undo = False
+ C_middleRight_undo = False
+ C_middleLeft_undo = False
+ C_left_undo = False
+
+ #// here we enable (make it "True") the correct checkbox based on the config value
+ #// All of this is for loading the current settings in "loadCurrent(self)" function
+ if C_info_position == "right":
+ C_right_info = True
+ elif C_info_position == "middle right":
+ C_middleRight_info = True
+ elif C_info_position == "middle left":
+ C_middleLeft_info = True
+ else:
+ C_left_info = True
+ if C_skip_position == "right":
+ C_right_skip = True
+ elif C_skip_position == "middle right":
+ C_middleRight_skip = True
+ elif C_skip_position == "middle left":
+ C_middleLeft_skip = True
+ else:
+ C_left_skip = True
+ if C_showSkipped_position == "right":
+ C_right_showSkipped = True
+ elif C_showSkipped_position == "middle right":
+ C_middleRight_showSkipped = True
+ elif C_showSkipped_position == "middle left":
+ C_middleLeft_showSkipped = True
+ else:
+ C_left_showSkipped = True
+ if C_undo_position == "right":
+ C_right_undo = True
+ elif C_undo_position == "middle right":
+ C_middleRight_undo = True
+ elif C_undo_position == "middle left":
+ C_middleLeft_undo = True
+ else:
+ C_left_undo = True
+
+class GetShortcut(QDialog):
+ def __init__(self, parent, button_variable):
+ QDialog.__init__(self, parent=parent)
+ self.parent = parent
+ self.button_variable = button_variable
+ #// when recording a shortcut, there is 0 active (pushed) key at first | by pressing each key, this increases by +1
+ self.active = 0
+ #// and the state of all the accepted keys on the keyboard is "False" | by pressing each key, the state for that button changes to "True"
+ self.ctrl = False
+ self.alt = False
+ self.shift = False
+ self.f1 = False
+ self.f2 = False
+ self.f3 = False
+ self.f4 = False
+ self.f5 = False
+ self.f3 = False
+ self.f6 = False
+ self.f7 = False
+ self.f8 = False
+ self.f9 = False
+ self.f10 = False
+ self.f11 = False
+ self.f12 = False
+ self.extra = None
+ self.getShortcutWindow()
+
+ def getShortcutWindow(self):
+ #// Sets up the screen that asks you to press the shortcut you want to assign
+ text = QLabel('
Press the new shortcut key...
')
+ mainLayout = QVBoxLayout()
+ mainLayout.addWidget(text)
+ self.setLayout(mainLayout)
+ self.setWindowTitle('Set Shortcut')
+
+ def keyPressEvent(self, evt):
+ #// increases the active keys count upon pressing each key
+ self.active += 1
+ #// limits the allowed keys to keyboard keys
+ if evt.key() > 30 and evt.key() < 127:
+ self.extra = chr(evt.key())
+ #// stores the pressed key in a variable so we could later add it in a list and use it as a key combination
+ elif evt.key() == Qt.Key_Control:
+ self.ctrl = True
+ elif evt.key() == Qt.Key_Alt:
+ self.alt = True
+ elif evt.key() == Qt.Key_Shift:
+ self.shift = True
+ elif evt.key() == Qt.Key_F1:
+ self.f1 = True
+ elif evt.key() == Qt.Key_F2:
+ self.f2 = True
+ elif evt.key() == Qt.Key_F3:
+ self.f3 = True
+ elif evt.key() == Qt.Key_F4:
+ self.f4 = True
+ elif evt.key() == Qt.Key_F5:
+ self.f5 = True
+ elif evt.key() == Qt.Key_F6:
+ self.f6 = True
+ elif evt.key() == Qt.Key_F7:
+ self.f7 = True
+ elif evt.key() == Qt.Key_F8:
+ self.f8 = True
+ elif evt.key() == Qt.Key_F9:
+ self.f9 = True
+ elif evt.key() == Qt.Key_F10:
+ self.f10 = True
+ elif evt.key() == Qt.Key_F11:
+ self.f11 = True
+ elif evt.key() == Qt.Key_F12:
+ self.f12 = True
+
+ def keyReleaseEvent(self, evt):
+ #// reduces the number of held keys upon releasing each key
+ self.active -= 1
+ #// I was having fun -_- don't blame me
+ shiftList = ["Who uses \"Shift\" alone as a shortcut key?", "You wanna use \"Shift\" without any other key as your shortcut??\n\n SERIOUSLY?!?!", "You must have forgotten to press another key...\n\n I don't wanna believe that there is someone who uses \"Shift\" without another key as a shortcut", "Dude, you can't just use \"Shift\"\n\n You should combine it with another key", "Are you really trying to use \"Shift\" as your shortcut?!!\n\n C'mon...", "What's so special about \"Shift\" that you wanna use it alone as a shortcut??", "\"Shift\" is scared of being used alone, you should use it with another key :)"]
+ if os.name == "nt":
+ altList = ["Who uses \"Alt\" alone as a shortcut key?", "You wanna use \"Alt\" without any other key as your shortcut??\n\n SERIOUSLY?!?!", "You must have forgotten to press another key...\n\n I don't wanna believe that there is someone who uses \"Alt\" without another key as a shortcut", "Dude, you can't just use \"Alt\"\n\n You should combine it with another key", "Are you really trying to use \"Alt\" as your shortcut?!!\n\n C'mon...", "What's so special about \"Alt\" that you wanna use it alone as a shortcut??", "\"Alt\" is scared of being used alone, you should use it with another key :)"]
+ ctrlList = ["Who uses \"Ctrl\" alone as a shortcut key?", "You wanna use \"Ctrl\" without any other key as your shortcut??\n\n SERIOUSLY?!?!", "You must have forgotten to press another key...\n\n I don't wanna believe that there is someone who uses \"Ctrl\" without another key as a shortcut", "Dude, you can't just use \"Ctrl\"\n\n You should combine it with another key", "Are you really trying to use \"Ctrl\" as your shortcut?!!\n\n C'mon...", "What's so special about \"Ctrl\" that you wanna use it alone as a shortcut??", "\"Ctrl\" is scared of being used alone, you should use it with another key :)"]
+ else:
+ altList = ["Who uses \"Option\" alone as a shortcut key?", "You wanna use \"Option\" without any other key as your shortcut??\n\n SERIOUSLY?!?!", "You must have forgotten to press another key...\n\n I don't wanna believe that there is someone who uses \"Option\" without another key as a shortcut", "Dude, you can't just use \"Option\"\n\n You should combine it with another key", "Are you really trying to use \"Option\" as your shortcut?!!\n\n C'mon...", "What's so special about \"Option\" that you wanna use it alone as a shortcut??", "\"Option\" is scared of being used alone, you should use it with another key :)"]
+ ctrlList = ["Who uses \"Command\" alone as a shortcut key?", "You wanna use \"Command\" without any other key as your shortcut??\n\n SERIOUSLY?!?!", "You must have forgotten to press another key...\n\n I don't wanna believe that there is someone who uses \"Command\" without another key as a shortcut", "Dude, you can't just use \"Command\"\n\n You should combine it with another key", "Are you really trying to use \"Command\" as your shortcut?!!\n\n C'mon...", "What's so special about \"Command\" that you wanna use it alone as a shortcut??", "\"Command\" is scared of being used alone, you should use it with another key :)"]
+ shiftAlone = random.choice(shiftList)
+ altAlone = random.choice(altList)
+ ctrlAlone = random.choice(ctrlList)
+ if not (self.f1 or self.f2 or self.f3 or self.f4 or self.f5 or self.f6 or self.f7 or self.f8 or self.f9 or self.f10 or self.f11 or self.f12):
+ if not self.extra:
+ #// special treats for buttons that everyone knows can't be used as a shortcut on their own -_-
+ if self.alt:
+ showInfo("{}".format(altAlone), title="Advanced Review Bottombar")
+ elif self.shift:
+ showInfo("{}".format(shiftAlone), title="Advanced Review Bottombar")
+ elif self.ctrl:
+ showInfo("{}".format(ctrlAlone), title="Advanced Review Bottombar")
+ #// lets the users that the pressed key is not allowed to be used in a shortcut
+ elif evt.key() == Qt.Key_Escape:
+ showInfo("You can't use \"Escape\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Tab:
+ showInfo("Don't you know \"Tab\" Always does something? Why do you even try to set it as your shortcut?", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Backspace:
+ showInfo("Are you really trying to set \"Backspace\" as your shortcut? Seriously?", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Enter:
+ showInfo("Have you ever seen anyone using \"Enter\" as a shortcut??\n\nWhy are you even trying to set it as your shortcut?", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Return:
+ showInfo("Have you ever seen anyone using \"Enter\" as a shortcut??\n\nWhy are you even trying to set it as your shortcut?", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Insert:
+ showInfo("You can't use \"Insert\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Delete:
+ showInfo("Who even thinks about using \"Delete\" as shortcut?? WTF man, SERIOUSLY????", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Pause:
+ showInfo("You can't use \"Pause/Break\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Home:
+ showInfo("Do you really think \"Home Key\" is a good key a a shortcut??!", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Left:
+ showInfo("You can't use \"Left\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Up:
+ showInfo("You can't use \"Up\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Right:
+ showInfo("You can't use \"Right\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_Down:
+ showInfo("You can't use \"Down\" as shortcut, try something else", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_PageUp:
+ showInfo("You can't use \"Page Up\" as shortcut, try something else.\n\nEven if you could, would it really be a SHORTcut?? I mean look at you keyboard...", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_PageDown:
+ showInfo("You can't use \"Page Down\" as shortcut, try something else.\n\nEven if you could, would it really be a SHORTcut?? I mean look at you keyboard...", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_CapsLock:
+ showInfo("wHO THINKS IT'S A GOOD IDEA TO USE \"cAPs LoCk\" AS A SHORTCUT???", title="Advanced Review Bottombar")
+ elif evt.key() == Qt.Key_NumLock:
+ showInfo("Why \"Num Lock\"???\n\n It's THE MOST remote key on keyboard, it can't be a SHORTcut man...", title="Advanced Review Bottombar")
+ else:
+ showInfo("You can't use that as shortcut, try something else.", title="Advanced Review Bottombar")
+ self.alt = False
+ self.shift = False
+ self.ctrl = False
+ self.extra = None
+ self.active = 0
+ evt = False
+ combination = []
+ return
+
+ #// the (empty) list for storing keys and then turning them into a shortcut
+ combination = []
+ if self.ctrl:
+ combination.append("Ctrl")
+ if self.shift:
+ combination.append("Shift")
+ if self.alt:
+ combination.append("Alt")
+ if self.f1:
+ combination.append("F1")
+ if self.f2:
+ combination.append("F2")
+ if self.f3:
+ combination.append("F3")
+ if self.f4:
+ combination.append("F4")
+ if self.f5:
+ combination.append("F5")
+ if self.f6:
+ combination.append("F6")
+ if self.f7:
+ combination.append("F7")
+ if self.f8:
+ combination.append("F8")
+ if self.f9:
+ combination.append("F9")
+ if self.f10:
+ combination.append("F10")
+ if self.f11:
+ combination.append("F11")
+ if self.f12:
+ combination.append("F12")
+ if self.extra:
+ combination.append(self.extra)
+ combination = "+".join(combination)
+ #// preventing users from assigning a defauls Anki shortcut to something else | to avoid conflicts and stuff :|
+ if combination in ["E", " ", "F5", "Ctrl+1", "Ctrl+2", "Ctrl+3", "Ctrl+4", "Shift+*", "=", "-", "Shift+!", "Shift+@", "Ctrl+Delete", "V", "Shift+V", "O", "1", "2", "3", "4", "5", "6", "7", "T", "Y", "A", "S", "D", "F", "B", "I", "/", "F1", "Ctrl+Q", "Ctrl+E", "Ctrl+P", "Ctrl+Shift+I", "Ctrl+Shift+P", "Ctrl+Shift+A", "Ctrl+Shift+:", "Ctrl+Shif+N", "Ctrl+Z"]:
+ if combination == "E":
+ showInfo("\"E\" is default Anki shortcut for \"Edit Current Card\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == " ":
+ showInfo("\"Space Bar\" is default Anki shortcut for \"Show Answer\" or \"Default Review Button\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "F5":
+ showInfo("\"F5\" is default Anki shortcut for \"Replay Audio\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+1":
+ showInfo("\"Ctrl+1\" is default Anki shortcut for \"Set Red Flag\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+2":
+ showInfo("\"Ctrl+2\" is default Anki shortcut for \"Set Orange Flag\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+3":
+ showInfo("\"Ctrl+3\" is default Anki shortcut for \"Set Green Flag\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+4":
+ showInfo("\"Ctrl+4\" is default Anki shortcut for \"Set Blue Flag\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Shift+*":
+ showInfo("\"*\" is default Anki shortcut for \"Mark Current Card\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "=":
+ showInfo("\"=\" is default Anki shortcut for \"Bury Note\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "-":
+ showInfo("\"-\" is default Anki shortcut for \"Bury Current Card\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Shift+!":
+ showInfo("\"!\" is default Anki shortcut for \"Suspend Note\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Shift+@":
+ showInfo("\"@\" is default Anki shortcut for \"Suspend Current Card\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Delete":
+ showInfo("\"Ctrl+Delete\" is default Anki shortcut for \"Delete Note\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "V":
+ showInfo("\"V\" is default Anki shortcut for \"Replay Audio\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Shift+V":
+ showInfo("\"Shift+V\" is default Anki shortcut for \"Record Voice\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "O":
+ showInfo("\"O\" is default Anki shortcut for \"Options\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "1":
+ showInfo("\"1\" is default Anki shortcut for \"Answer with ease 1 (Again)\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "2":
+ showInfo("\"2\" is default Anki shortcut for \"Answer with ease 2 (usually Hard)\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "3":
+ showInfo("\"3\" is default Anki shortcut for \"Answer with ease 3 (usually Good)\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "4":
+ showInfo("\"4\" is default Anki shortcut for \"Answer with ease 4 (Easy)\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "5":
+ showInfo("\"5\" is default Anki shortcut for \"Puase Audio\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "6":
+ showInfo("\"6\" is default Anki shortcut for \"Seek Backward\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "7":
+ showInfo("\"7\" is default Anki shortcut for \"Seek Forward\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "T":
+ showInfo("\"T\" is default Anki shortcut for \"Stats\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Y":
+ showInfo("\"Y\" is default Anki shortcut for \"Sync\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "A":
+ showInfo("\"A\" is default Anki shortcut for \"Add Cards\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "S":
+ showInfo("\"S\" is default Anki shortcut for \"Toggle Study Current Deck\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "D":
+ showInfo("\"D\" is default Anki shortcut for \"Decks View\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "F":
+ showInfo("\"F\" is default Anki shortcut for \"Create Filtered Deck\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "B":
+ showInfo("\"B\" is default Anki shortcut for \"Browse\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "I":
+ showInfo("\"I\" is default Anki shortcut for \"Card Info Window\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "/":
+ showInfo("\"/\" is default Anki shortcut for \"Study Deck\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "F1":
+ showInfo("\"F1\" is default Anki shortcut for \"Open Guide (Anki Manual)\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Q":
+ showInfo("\"Ctrl+Q\" is default Anki shortcut for \"Exit\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+E":
+ showInfo("\"Ctrl+E\" is default Anki shortcut for \"Export\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+P":
+ showInfo("\"Ctrl+P\" is default Anki shortcut for \"Preferences\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+I":
+ showInfo("\"Ctrl+Shift+I\" is default Anki shortcut for \"Import\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+P":
+ showInfo("\"Ctrl+Shift+P\" is default Anki shortcut for \"Swith Profile\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+A":
+ showInfo("\"Ctrl+Shift+A\" is default Anki shortcut for \"Add-ons\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+:":
+ showInfo("\"Ctrl+Shift+:\" is default Anki shortcut for \"Debug Console\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+N":
+ showInfo("\"Ctrl+Shift+N\" is default Anki shortcut for \"Manage Note Types\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ if combination == "Ctrl+Shift+Z":
+ showInfo("\"Ctrl+Shift+Z\" is default Anki shortcut for \"Undo\" You can't use this shortcut.", type="warning", title="Advanced Review Bottombar")
+ self.ctrl = False
+ self.alt = False
+ self.shift = False
+ self.extra = None
+ self.f1 = False
+ self.f5 = False
+ self.active = 0
+ combination = []
+ return
+ self.parent.updateShortcut(self.button_variable, combination)
+ self.close()
+
+class SettingsMenu(QDialog):
+ refreshConfig()
+ addon_path = dirname(__file__)
+ images = join(addon_path, 'images')
+ begin = "
"
+ end = "
"
+ info_shortcut = C_info_shortcut
+ skip_shortcut = C_skip_shortcut
+ showSkipped_shortcut = C_showSkipped_shortcut
+ undo_shortcut = C_undo_shortcut
+ def __init__(self, parent=None):
+ super(SettingsMenu, self).__init__(parent)
+ self.mainWindow()
+ self.reviewButtonText_color = C_reviewButtonText_color
+ self.activeIndicator_color = C_activeIndicator_color
+ self.bottombarButtonText_color = C_bottombarButtonText_color
+ self.bottombarButtonBorder_color = C_bottombarButtonBorder_color
+ self.reviewTooltipText_color = C_reviewTooltipText_color
+ self.again_color = C_again_color
+ self.againHover_color = C_againHover_color
+ self.hard_color = C_hard_color
+ self.hardHover_color = C_hardHover_color
+ self.good_color = C_good_color
+ self.goodHover_color = C_goodHover_color
+ self.easy_color = C_easy_color
+ self.easyHover_color = C_easyHover_color
+ self.showAnswerEase1_color = C_showAnswerEase1_color
+ self.showAnswerEase2_color = C_showAnswerEase2_color
+ self.showAnswerEase3_color = C_showAnswerEase3_color
+ self.showAnswerEase4_color = C_showAnswerEase4_color
+ def mainWindow(self):
+ images = self.images
+ self.createFirstTab()
+ self.createSecondTab()
+ self.createThirdTab()
+ self.createFourthTab()
+ self.createFifthTab()
+ self.createSixthTab()
+ self.createSeventhTab()
+ self.createEighthTab()
+ self.createNinthTab()
+ self.loadCurrent()
+
+ #// Create the bottom row of settings menu
+ loadSettingsButton = QPushButton("&Load Settings")
+ loadSettingsButton.clicked.connect(self.onLoadSettings)
+ saveSettingsButton = QPushButton("&Backup Settings")
+ saveSettingsButton.clicked.connect(self.onSaveSettings)
+ acceptButton = QPushButton("&Apply")
+ acceptButton.clicked.connect(self.onApply)
+ rejectButton = QPushButton("&Discard")
+ rejectButton.clicked.connect(self.reject)
+ rejectButton.clicked.connect(lambda: tooltip("Changes Discarded."))
+ buttonbox = QHBoxLayout()
+ buttonbox.addWidget(loadSettingsButton)
+ buttonbox.addWidget(saveSettingsButton)
+ buttonbox.addStretch()
+ buttonbox.addWidget(acceptButton)
+ buttonbox.addWidget(rejectButton)
+
+ #// create tabs widget and adds each tab
+ tabs = QTabWidget()
+ tabs.addTab(self.tab1, "Styles")
+ tabs.addTab(self.tab2, "Answer Tooltip")
+ tabs.addTab(self.tab3, "Bottombar Buttons")
+ tabs.addTab(self.tab4, "Button Sizes")
+ tabs.addTab(self.tab5, "Button Labels")
+ tabs.addTab(self.tab6, "Sidebar")
+ tabs.addTab(self.tab7, "Colors")
+ tabs.addTab(self.tab8, "Misc")
+ tabs.addTab(self.tab9, "About")
+
+ vbox = QVBoxLayout()
+ vbox.addWidget(tabs)
+ vbox.addLayout(buttonbox)
+
+ self.setLayout(vbox)
+ self.setWindowTitle("Advanced Review Bottombar Settings Menu")
+ self.setWindowIcon(QIcon(images + "/icon.png"))
+
+ def createFirstTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ buttonStyle_label = QLabel("Button Style:")
+ buttonStyle_label.setToolTip("{0} Changes the way review buttons look.{1}".format(begin, end))
+ buttonStyle_label.setFixedWidth(180)
+ self.button_style = QComboBox()
+ self.button_style.addItems(["Default + Text Color", "Default + Background Color", "Wide + Text Color", "Wide + Background Color", "Neon 1", "Neon 2", "Fill 1", "Fill 2"])
+ self.button_style.setToolTip("{0}To see designs please go to about tab.{1}".format(begin, end))
+ self.button_style.setMinimumWidth(180)
+ reviewButtonDesigns_button = QPushButton("Show Designs")
+ reviewButtonDesigns_button.setFixedWidth(180)
+ reviewButtonDesigns_text = "{0}Default + Text Color Default\
+ + Bakground Color \
+ Wide + Text Color Wide +\
+ Background Color Neon1 (Easy is hovered over) \
+ Neon2 (Easy is hovered over) \
+ Fill1 (Easy is hovered over) \
+ Fill2 (Easy is hovered over) {1}".format(begin, end, images)
+ reviewButton_designs = QLabel()
+ reviewButton_designs.setText(reviewButtonDesigns_text)
+ reviewButtonDesigns_scroll = QScrollArea()
+ reviewButtonDesigns_scroll.setWidget(reviewButton_designs)
+ reviewButtonDesigns_layout = QVBoxLayout()
+ reviewButtonDesigns_layout.addWidget(reviewButtonDesigns_scroll)
+ reviewButtonDesigns_window = QDialog()
+ reviewButtonDesigns_window.setWindowTitle("Advanced Review Bottombar [Review Button Designs]")
+ reviewButtonDesigns_window.setWindowIcon(QIcon(images + "/icon.png"))
+ reviewButtonDesigns_window.setLayout(reviewButtonDesigns_layout)
+ reviewButtonDesigns_button.clicked.connect(lambda: reviewButtonDesigns_window.exec_())
+ buttonStyle_holder = QHBoxLayout()
+ buttonStyle_holder.addWidget(buttonStyle_label)
+ buttonStyle_holder.addWidget(self.button_style)
+ buttonStyle_holder.addWidget(reviewButtonDesigns_button)
+ buttonStyle_holder.addStretch()
+ bottombaButtonsStyle_label = QLabel("General Buttons Style:")
+ bottombaButtonsStyle_label.setToolTip("{0} Changes The way general buttons (main screen bottombar, deck overview, show answer, edit, etc.) look. {1}".format(begin, end))
+ bottombaButtonsStyle_label.setFixedWidth(180)
+ self.bottombarButtons_style = QComboBox()
+ self.bottombarButtons_style.addItems(["Default", "Neon 1", "Neon 2", "Fill1", "Fill 2"])
+ self.bottombarButtons_style.setToolTip("{0}To see what every design looks like, please go to about tab{1}".format(begin, end))
+ self.bottombarButtons_style.setMinimumWidth(180)
+ otherBottombarButtonDesigns_button = QPushButton("Show Designs")
+ otherBottombarButtonDesigns_button.setFixedWidth(180)
+ otherBottombarButtonDesigns_text = "{0} Default \
+ Neon1 (Show answer is hovered over) \
+ Neon2 (Show answer is hovered over) Fill1 (Show answer is hovered over) \
+ Fill2 (Show answer is hovered over) \
+ {1}".format(begin, end, images)
+ otherBottombarButton_designs = QLabel()
+ otherBottombarButton_designs.setText(otherBottombarButtonDesigns_text)
+ otherBottombarButtonDesigns_scroll = QScrollArea()
+ otherBottombarButtonDesigns_scroll.setWidget(otherBottombarButton_designs)
+ otherBottombarButtonDesigns_layout = QVBoxLayout()
+ otherBottombarButtonDesigns_layout.addWidget(otherBottombarButtonDesigns_scroll)
+ otherBottombarButtonDesigns_window = QDialog()
+ otherBottombarButtonDesigns_window.setWindowTitle("Advanced Review Bottombar [Other Bottombar Buttons Designs]")
+ otherBottombarButtonDesigns_window.setWindowIcon(QIcon(images + "/icon.png"))
+ otherBottombarButtonDesigns_window.setLayout(otherBottombarButtonDesigns_layout)
+ otherBottombarButtonDesigns_button.clicked.connect(lambda: otherBottombarButtonDesigns_window.exec_())
+ bottombarButtonsStyle_holder = QHBoxLayout()
+ bottombarButtonsStyle_holder.addWidget(bottombaButtonsStyle_label)
+ bottombarButtonsStyle_holder.addWidget(self.bottombarButtons_style)
+ bottombarButtonsStyle_holder.addWidget(otherBottombarButtonDesigns_button)
+ bottombarButtonsStyle_holder.addStretch()
+ hoverEffect_label = QLabel("Hover Effect:")
+ hoverEffect_label.setToolTip("{0} Changes the way review buttons look when you hover over them.\
+ This option does not change hover effect for neon buttons. If you use\
+ custom colors for review buttons, brighten and glow colors will be the color\
+ you have set for each buttons hover color.{1}".format(begin, end))
+ hoverEffect_label.setFixedWidth(180)
+ self.hover_effect = QComboBox()
+ self.hover_effect.addItems(["Disable", "Brighten", "Glow", "Brighten + Glow"])
+ self.hover_effect.setToolTip("{0} Disable -> Buttons won't change as you hover\
+ over them. Brighten -> The text or the background color will get brightened\
+ as you hover over them. Glow ->\
+ There will be a shadow around the button as you hover them. \
+ Glow + Brighten -> Combines both glow and brighten effects. {1}".format(begin, end, images))
+ self.hover_effect.setMinimumWidth(180)
+ hoverEffect_holder = QHBoxLayout()
+ hoverEffect_holder.addWidget(hoverEffect_label)
+ hoverEffect_holder.addWidget(self.hover_effect)
+ hoverEffect_holder.addStretch()
+ activeIndicator_label = QLabel("Active Indicator:")
+ activeIndicator_label.setToolTip("{0} Changes the way active review button looks. active button\
+ is the button that is clicked if you press spacebar or enter. This option can not change active\
+ indicator for neon and fill buttons as it's disabled on those designs. {1}".format(begin, end))
+ activeIndicator_label.setFixedWidth(180)
+ self.active_indicator = QComboBox()
+ self.active_indicator.addItems(["Disable", "Border", "Glow"])
+ self.active_indicator.setToolTip("{0} Indicator is turned off and all review buttons are the\
+ same. {1} {0} Indicator is\
+ set on border and there is a thin border around active button. \
+ {1} {0} Indicator is set on\
+ glow and active button is glowing. {1}\
+ ".format(begin, end, images))
+ self.active_indicator.setMinimumWidth(180)
+ activeIndicator_holder = QHBoxLayout()
+ activeIndicator_holder.addWidget(activeIndicator_label)
+ activeIndicator_holder.addWidget(self.active_indicator)
+ activeIndicator_holder.addStretch()
+ cursorStyle_label = QLabel("Cursor Style:")
+ cursorStyle_label.setToolTip("{0}Changes the cursor style when hovered over buttons.{1}".format(begin, end))
+ cursorStyle_label.setFixedWidth(180)
+ self.cursor_style = QComboBox()
+ self.cursor_style.addItems(["Normal", "Pointer"])
+ self.cursor_style.setFixedWidth(180)
+ cursorStyle_holder = QHBoxLayout()
+ cursorStyle_holder.addWidget(cursorStyle_label)
+ cursorStyle_holder.addWidget(self.cursor_style)
+ cursorStyle_holder.addStretch()
+ showAnswerBorderType_label = QLabel("Show Answer Border Color Style:")
+ showAnswerBorderType_label.setToolTip("{0}Changes how show answer border color behaves.\
+ if set on \"Fixed\" it's border color will be the same as other bottombar buttons. \
+ if set on \"Bases on Card Ease\" it's color will change based on card ease.\
+ you can change it's color for each ease range in colors tab.{1}".format(begin, end))
+ showAnswerBorderType_label.setFixedWidth(180)
+ self.showAnswerBorderColor_style = QComboBox()
+ self.showAnswerBorderColor_style.addItems(["Fixed", "Show Based on Card Ease"])
+ self.showAnswerBorderColor_style.setFixedWidth(180)
+ showAnswerBorderType_holder = QHBoxLayout()
+ showAnswerBorderType_holder.addWidget(showAnswerBorderType_label)
+ showAnswerBorderType_holder.addWidget(self.showAnswerBorderColor_style)
+ showAnswerBorderType_holder.addStretch()
+ intervalStyle_label = QLabel("Button Interval Style:")
+ intervalStyle_label.setToolTip("{0}Changes the style of button intervals.{1}".format(begin, end))
+ intervalStyle_label.setFixedWidth(180)
+ self.interval_style = QComboBox()
+ self.interval_style.addItems(["Stock", "Colored Stock", "Inside the Buttons"])
+ self.interval_style.setFixedWidth(180)
+ intervalStyle_holder = QHBoxLayout()
+ intervalStyle_holder.addWidget(intervalStyle_label)
+ intervalStyle_holder.addWidget(self.interval_style)
+ intervalStyle_holder.addStretch()
+ buttonTransitionTime_label = QLabel("Button Transition Time:")
+ buttonTransitionTime_label.setToolTip("{0}Changes button animation time for fill and neon styles.{1}".format(begin, end))
+ buttonTransitionTime_label.setFixedWidth(180)
+ self.buttonTransition_time = QSpinBox()
+ self.buttonTransition_time.setMinimum(0)
+ self.buttonTransition_time.setMaximum(10000)
+ self.buttonTransition_time.setSingleStep(20)
+ self.buttonTransition_time.setFixedWidth(180)
+ buttonTransitionTime_ms = QLabel("ms")
+ buttonTransitionTime_holder = QHBoxLayout()
+ buttonTransitionTime_holder.addWidget(buttonTransitionTime_label)
+ buttonTransitionTime_holder.addWidget(self.buttonTransition_time)
+ buttonTransitionTime_holder.addWidget(buttonTransitionTime_ms)
+ buttonTransitionTime_holder.addStretch()
+ buttonBorderRadius_label = QLabel("Button Border Radius:")
+ buttonBorderRadius_label.setToolTip("{0}Changer the roundness of the buttons.{1}".format(begin, end))
+ buttonBorderRadius_label.setFixedWidth(180)
+ self.buttonBorderRadius = QSpinBox()
+ self.buttonBorderRadius.setMinimum(0)
+ self.buttonBorderRadius.setMaximum(50)
+ self.buttonBorderRadius.setSingleStep(1)
+ self.buttonBorderRadius.setFixedWidth(180)
+ buttonBorderRadius_px = QLabel("px")
+ buttonBorderRadius_holder = QHBoxLayout()
+ buttonBorderRadius_holder.addWidget(buttonBorderRadius_label)
+ buttonBorderRadius_holder.addWidget(self.buttonBorderRadius)
+ buttonBorderRadius_holder.addWidget(buttonBorderRadius_px)
+ def buttonStyle_signal():
+ buttonStyle_index = self.button_style.currentIndex()
+ self.hover_effect.setDisabled(True)
+ if buttonStyle_index in [0, 1, 2, 3]:
+ self.hover_effect.setEnabled(True)
+ self.active_indicator.setDisabled(True)
+ if buttonStyle_index in [0, 1, 2, 3]:
+ self.active_indicator.setEnabled(True)
+ # self.cursor_style.setDisabled(True)
+ self.buttonTransition_time.setDisabled(True)
+ if buttonStyle_index in [4, 5, 6, 7]:
+ # self.cursor_style.setEnabled(True)
+ self.buttonTransition_time.setEnabled(True)
+ buttonStyle_signal()
+ self.button_style.currentIndexChanged.connect(buttonStyle_signal)
+ self.style_mainScreenButtons = QCheckBox("Style Main Screen Buttons")
+ self.style_mainScreenButtons.setToolTip("{0}Changes style of main screen and deck overview buttons if enabled.\
+ \
+ \
+ {1}".format(begin, end, images))
+ self.style_mainScreenButtons.setFixedWidth(180)
+ tab1line5 = QHBoxLayout()
+ tab1line5.addWidget(self.style_mainScreenButtons)
+ tab1line5.addStretch()
+ layout = QVBoxLayout()
+ layout.addLayout(buttonStyle_holder)
+ layout.addLayout(bottombarButtonsStyle_holder)
+ layout.addLayout(hoverEffect_holder)
+ layout.addLayout(activeIndicator_holder)
+ layout.addLayout(cursorStyle_holder)
+ layout.addLayout(showAnswerBorderType_holder)
+ layout.addLayout(intervalStyle_holder)
+ layout.addLayout(buttonTransitionTime_holder)
+ layout.addLayout(buttonBorderRadius_holder)
+ layout.addLayout(tab1line5)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab1 = QScrollArea()
+ self.tab1.setFixedWidth(640)
+ self.tab1.setAlignment(Qt.AlignHCenter)
+ self.tab1.setWidgetResizable(True)
+ self.tab1.setWidget(layout_holder)
+
+ def createSecondTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ reviewTooltip_label = QLabel("Review Confirmation Tooltip:")
+ reviewTooltip_label.setToolTip("{0}Shows a tooltip when you press any of\
+ review buttons, showing you what button you have pressed.{1}".format(begin, end))
+ reviewTooltip_label.setFixedWidth(180)
+ self.reviewTooltip_on = QRadioButton("On")
+ self.reviewTooltip_on.setFixedWidth(90)
+ self.reviewTooltip_off = QRadioButton("off")
+ self.reviewTooltip_off.setFixedWidth(90)
+ reviewTooltip_holder = QHBoxLayout()
+ reviewTooltip_holder.addWidget(reviewTooltip_label)
+ reviewTooltip_holder.addWidget(self.reviewTooltip_on)
+ reviewTooltip_holder.addWidget(self.reviewTooltip_off)
+ reviewTooltip_holder.addStretch()
+ tab2box1 = QGroupBox()
+ tab2box1.setLayout(reviewTooltip_holder)
+ reviewTooltipStyle_label = QLabel("Tooltip Position:")
+ reviewTooltipStyle_label.setToolTip("{0}Changes the position of answer tooltip.{1}".format(begin, end))
+ reviewTooltipStyle_label.setFixedWidth(180)
+ self.reviewTooltip_style = QComboBox()
+ self.reviewTooltip_style.addItems(["On Buttons", "Fixed Position"])
+ self.reviewTooltip_style.setToolTip("{0}On buttons -> Shows the tooltip on the button that you have pressed\
+ Fixed Position -> Shows all the tooltips in a position that you have chosen in review toolip position box.{1}".format(begin, end))
+ self.reviewTooltip_style.setFixedWidth(180)
+ reviewTooltipStyle_holder = QHBoxLayout()
+ reviewTooltipStyle_holder.addWidget(reviewTooltipStyle_label)
+ reviewTooltipStyle_holder.addWidget(self.reviewTooltip_style)
+ reviewTooltipStyle_holder.addStretch()
+ reviewTooltipTimer_label = QLabel("Tooltip Show Duration:")
+ reviewTooltipTimer_label.setToolTip("{0}Changes lenghth of the period that tooltip is shown.the unit is millisecond, 1000ms = 1s{1} (I know everybody knows this, put it here just in case :|)".format(begin, end))
+ reviewTooltipTimer_label.setFixedWidth(180)
+ self.reviewTooltip_timer = QSpinBox()
+ self.reviewTooltip_timer.setFixedWidth(180)
+ self.reviewTooltip_timer.setMinimum(100)
+ self.reviewTooltip_timer.setMaximum(10000)
+ reviewerTooltipTimer_ms = QLabel("ms")
+ reviewTooltipTimer_holder = QHBoxLayout()
+ reviewTooltipTimer_holder.addWidget(reviewTooltipTimer_label)
+ reviewTooltipTimer_holder.addWidget(self.reviewTooltip_timer)
+ reviewTooltipTimer_holder.addWidget(reviewerTooltipTimer_ms)
+ reviewTooltipTimer_holder.addStretch()
+ reviewTooltipTextColor_label = QLabel("Tooltip Text Color:")
+ reviewTooltipTextColor_label.setToolTip("{0}Changes color of the text inside tooltips.{1}".format(begin, end))
+ reviewTooltipTextColor_label.setFixedWidth(180)
+ self.reviewTooltipTextColor_button = QPushButton()
+ self.reviewTooltipTextColor_button.setFixedWidth(180)
+ self.reviewTooltipTextColor_button.clicked.connect(lambda: self.getNewColor("reviewTooltipText_color", self.reviewTooltipTextColor_button))
+ reviewTooltipTextColor_holder = QHBoxLayout()
+ reviewTooltipTextColor_holder.addWidget(reviewTooltipTextColor_label)
+ reviewTooltipTextColor_holder.addWidget(self.reviewTooltipTextColor_button)
+ reviewTooltipTextColor_holder.addStretch()
+ tab2line2 = QVBoxLayout()
+ tab2line2.addLayout(reviewTooltipStyle_holder)
+ tab2line2.addLayout(reviewTooltipTimer_holder)
+ tab2line2.addLayout(reviewTooltipTextColor_holder)
+ tab2box2 = QGroupBox()
+ tab2box2.setLayout(tab2line2)
+ self.reviewTooltipPositionX = QSlider(Qt.Horizontal)
+ self.reviewTooltipPositionX.setFixedWidth(200)
+ self.reviewTooltipPositionX.setMinimum(0)
+ self.reviewTooltipPositionX.setMaximum(1850)
+ self.reviewTooltipPositionX.setPageStep(100)
+ self.reviewTooltipPositionX.setSliderPosition(0)
+ reviewerTooltipPosition_holder = QHBoxLayout()
+ self.reviewTooltipPositionY = QSlider(Qt.Vertical)
+ self.reviewTooltipPositionY.setFixedHeight(200)
+ self.reviewTooltipPositionY.setMinimum(-950)
+ self.reviewTooltipPositionY.setMaximum(0)
+ self.reviewTooltipPositionY.setPageStep(100)
+ self.reviewTooltipPositionY.setSliderPosition(0)
+ reviewerTooltipPosition_holder = QHBoxLayout()
+ reviewerTooltipPosition_holder.addWidget(self.reviewTooltipPositionX)
+ reviewerTooltipPosition_holder.addWidget(self.reviewTooltipPositionY)
+ reviewerTooltipPosition_holder.addStretch()
+ tab2line3 = QVBoxLayout()
+ tab2line3.addLayout(reviewerTooltipPosition_holder)
+ tab2box3 = QGroupBox("Tooltip Position")
+ tab2box3.setToolTip("{0}Changes position of the fixed tooltip.\
+ (# NOTE: If your resulotion is not 1920 x 1080, it's not accurate, but you\
+ can find the place that you wanna put the tooltip on, by toying with the sliders\
+ and restarting anki till you get the desired result. \
+ # NOTE: If your resulotion is 1920 x 1080 the sliders are accurate for\
+ maximized anki window. # NOTE: If you set the position for a window that\
+ it's size is for example 500 x 500, the position will not be accurate when you\
+ change anki's window size to any other size. and if you decide to resize anki's\
+ window, you should set the positions again in order for the tooltip to be in the\
+ position you want.){1}".format(begin, end))
+ tab2box3.setLayout(tab2line3)
+ tab2box3.setDisabled(True)
+ if self.reviewTooltip_style.currentIndex() == 1:
+ tab2box3.setEnabled(True)
+ self.reviewTooltip_off.toggled.connect(tab2box3.setDisabled)
+ self.reviewTooltip_style.currentIndexChanged.connect(tab2box3.setEnabled)
+ tab2box2.setDisabled(True)
+ if self.reviewTooltip_on.isChecked():
+ tab2box2.setEnabled(True)
+ self.reviewTooltip_on.toggled.connect(tab2box2.setEnabled)
+ layout = QVBoxLayout()
+ layout.addWidget(tab2box1)
+ layout.addWidget(tab2box2)
+ layout.addWidget(tab2box3)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab2 = QScrollArea()
+ self.tab2.setFixedWidth(640)
+ self.tab2.setAlignment(Qt.AlignHCenter)
+ self.tab2.setWidgetResizable(True)
+ self.tab2.setWidget(layout_holder)
+
+ def createThirdTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ self.info = QCheckBox("Info")
+ self.info.setToolTip("{0} If enabled adds info button to review bottombar. {1}".format(begin, end))
+ self.skip = QCheckBox("Skip")
+ self.skip.setToolTip("{0} If enabled adds skip card button to review bottombar. {1}".format(begin, end))
+ self.showSkipped = QCheckBox("Show Skipped")
+ self.showSkipped.setToolTip("{0} If enabled adds show skipped button to review bottombar. {1}".format(begin, end))
+ self.undo = QCheckBox("Undo")
+ self.undo.setToolTip("{0} If enabled adds undo review button to review bottombar. {1}".format(begin, end))
+ extraButtonsPart = QHBoxLayout()
+ extraButtonsPart.addWidget(self.info)
+ extraButtonsPart.addWidget(self.skip)
+ extraButtonsPart.addWidget(self.showSkipped)
+ extraButtonsPart.addWidget(self.undo)
+ extraButtonsBox = QGroupBox("Extra Buttons")
+ extraButtonsBox.setLayout(extraButtonsPart)
+ self.hideHard = QCheckBox("Hide Hard")
+ self.hideHard.setToolTip("{0}Hides the Hard button.{1}".format(begin, end))
+ self.hideGood = QCheckBox("Hide Good")
+ self.hideGood.setToolTip("{0}Hides the Good button.{1}".format(begin, end))
+ self.hideEasy = QCheckBox("Hide Easy")
+ self.hideEasy.setToolTip("{0}Hides the Easy button.{1}".format(begin, end))
+ hideButtonsPart = QHBoxLayout()
+ hideButtonsPart.addWidget(self.hideHard)
+ hideButtonsPart.addWidget(self.hideGood)
+ hideButtonsPart.addWidget(self.hideEasy)
+ hideButtonsPart.addWidget(QLabel(""))
+ hideButtonsBox = QGroupBox("Hide Buttons")
+ hideButtonsBox.setLayout(hideButtonsPart)
+ infoPosition_label = QLabel("Info:")
+ infoPosition_label.setToolTip("{0} Changes info button position in bottombar. {1}".format(begin, end))
+ self.left_info = QRadioButton("Left")
+ self.middleLeft_info = QRadioButton("Middle left")
+ self.middleRight_info = QRadioButton("Middle right")
+ self.right_info = QRadioButton("Right")
+ infoPosition_holder = QHBoxLayout()
+ infoPosition_holder.addWidget(infoPosition_label)
+ infoPosition_holder.addWidget(self.left_info)
+ infoPosition_holder.addWidget(self.middleLeft_info)
+ infoPosition_holder.addWidget(self.middleRight_info)
+ infoPosition_holder.addWidget(self.right_info)
+ infoButtonPositionBox = QGroupBox()
+ infoButtonPositionBox.setDisabled(True)
+ if self.info.isChecked():
+ self.infoButtonPositionBox.setEnabled(True)
+ self.info.toggled.connect(infoButtonPositionBox.setEnabled)
+ infoButtonPositionBox.setLayout(infoPosition_holder)
+ skipPosition_label = QLabel("Skip:")
+ skipPosition_label.setToolTip("{0} Changes skip button position in bottombar. {1}".format(begin, end))
+ self.left_skip = QRadioButton("Left")
+ self.middleLeft_skip = QRadioButton("Middle left")
+ self.middleRight_skip = QRadioButton("Middle right")
+ self.right_skip = QRadioButton("Right")
+ skipPosition_holder = QHBoxLayout()
+ skipPosition_holder.addWidget(skipPosition_label)
+ skipPosition_holder.addWidget(self.left_skip)
+ skipPosition_holder.addWidget(self.middleLeft_skip)
+ skipPosition_holder.addWidget(self.middleRight_skip)
+ skipPosition_holder.addWidget(self.right_skip)
+ skipButtonPositionBox = QGroupBox()
+ skipButtonPositionBox.setDisabled(True)
+ if self.skip.isChecked():
+ skipButtonPositionBox.setEnabled(True)
+ self.skip.toggled.connect(skipButtonPositionBox.setEnabled)
+ skipButtonPositionBox.setLayout(skipPosition_holder)
+ showSkippedPosition_label = QLabel("Show Skipped:")
+ showSkippedPosition_label.setToolTip("{0} Changes show skipped button position in bottombar. {1}".format(begin, end))
+ self.left_showSkipped = QRadioButton("Left")
+ self.middleLeft_showSkipped = QRadioButton("Middle left")
+ self.middleRight_showSkipped = QRadioButton("Middle right")
+ self.right_showSkipped = QRadioButton("Right")
+ showSkippedPosition_holder = QHBoxLayout()
+ showSkippedPosition_holder.addWidget(showSkippedPosition_label)
+ showSkippedPosition_holder.addWidget(self.left_showSkipped)
+ showSkippedPosition_holder.addWidget(self.middleLeft_showSkipped)
+ showSkippedPosition_holder.addWidget(self.middleRight_showSkipped)
+ showSkippedPosition_holder.addWidget(self.right_showSkipped)
+ showSkippedButtonPositionBox = QGroupBox()
+ showSkippedButtonPositionBox.setDisabled(True)
+ if self.showSkipped.isChecked():
+ showSkippedButtonPositionBox.setEnabled(True)
+ self.showSkipped.toggled.connect(showSkippedButtonPositionBox.setEnabled)
+ showSkippedButtonPositionBox.setLayout(showSkippedPosition_holder)
+ undoPosition_label = QLabel("Undo:")
+ undoPosition_label.setToolTip("{0} Changes undo review button position in bottombar. {1}".format(begin, end))
+ self.left_undo = QRadioButton("Left")
+ self.middleLeft_undo = QRadioButton("Middle left")
+ self.middleRight_undo = QRadioButton("Middle right")
+ self.right_undo = QRadioButton("Right")
+ undoPosition_holder = QHBoxLayout()
+ undoPosition_holder.addWidget(undoPosition_label)
+ undoPosition_holder.addWidget(self.left_undo)
+ undoPosition_holder.addWidget(self.middleLeft_undo)
+ undoPosition_holder.addWidget(self.middleRight_undo)
+ undoPosition_holder.addWidget(self.right_undo)
+ undoButtonPositionBox = QGroupBox()
+ undoButtonPositionBox.setDisabled(True)
+ if self.undo.isChecked():
+ undoButtonPositionBox.setEnabled(True)
+ self.undo.toggled.connect(undoButtonPositionBox.setEnabled)
+ undoButtonPositionBox.setLayout(undoPosition_holder)
+ buttonPositionsPart = QVBoxLayout()
+ buttonPositionsPart.addWidget(infoButtonPositionBox)
+ buttonPositionsPart.addWidget(skipButtonPositionBox)
+ buttonPositionsPart.addWidget(showSkippedButtonPositionBox)
+ buttonPositionsPart.addWidget(undoButtonPositionBox)
+ buttonPositionsBox = QGroupBox("Button Positions")
+ buttonPositionsBox.setLayout(buttonPositionsPart)
+ infoShortcut_label = QLabel("Info:")
+ infoShortcut_label.setToolTip("{0} Changes show card info shortcut. Shortcut will work even if\
+ you disable the Info button. Info button can be a sngle key\
+ like \"i\" or \"f4\" or a combination of keys like \"ctrl+i\" or \"alt+i\".\
+ NOTE: Make sure the shortcut you want to set for the\
+ button isn't already in use by anki itself or another add-on. {1}".format(begin, end))
+ infoShortcut_label.setFixedWidth(125)
+ self.infoShortcut_button = QPushButton(self)
+ self.infoShortcut_button.setFixedWidth(300)
+ self.infoShortcut_button.clicked.connect(lambda: self.showGetShortcut("info_shortcut"))
+ infoShortcut_holder = QHBoxLayout()
+ infoShortcut_holder.addWidget(infoShortcut_label)
+ infoShortcut_holder.addStretch()
+ infoShortcut_holder.addWidget(self.infoShortcut_button)
+ skipShortcut_label = QLabel("Skip:")
+ skipShortcut_label.setToolTip("{0} Changes skip card shortcut. Shortcut will work even if\
+ you disable the skip button. skip button can be a sngle key\
+ like \"s\" or \"f6\" or a combination of keys like \"ctrl+s\" or \"alt+s\".\
+ NOTE: Make sure the shortcut you want to set for the\
+ button isn't already in use by anki itself or another add-on. {1}".format(begin, end))
+ skipShortcut_label.setFixedWidth(125)
+ self.skipShortcut_button = QPushButton()
+ self.skipShortcut_button.setFixedWidth(300)
+ self.skipShortcut_button.clicked.connect(lambda: self.showGetShortcut("skip_shortcut"))
+ skipShortcut_holder = QHBoxLayout()
+ skipShortcut_holder.addWidget(skipShortcut_label)
+ skipShortcut_holder.addStretch()
+ skipShortcut_holder.addWidget(self.skipShortcut_button)
+ showSkippedShortcut_label = QLabel("Show Skipped:")
+ showSkippedShortcut_label.setToolTip("{0} Changes Show Skipped cards shortcut. Shortcut will work even if\
+ you disable the Show Skipped Cards button. Show Skipped cards button shortcut can be a sngle key\
+ like \"s\" or \"f6\" or a combination of keys like \"ctrl+s\" or \"alt+s\".\
+ NOTE: Make sure the shortcut you want to set for the\
+ button isn't already in use by anki itself or another add-on. {1}".format(begin, end))
+ showSkippedShortcut_label.setFixedWidth(125)
+ self.showSkippedShortcut_button = QPushButton()
+ self.showSkippedShortcut_button.setFixedWidth(300)
+ self.showSkippedShortcut_button.clicked.connect(lambda: self.showGetShortcut("showSkipped_shortcut"))
+ showSkippedShortcut_holder = QHBoxLayout()
+ showSkippedShortcut_holder.addWidget(showSkippedShortcut_label)
+ showSkippedShortcut_holder.addStretch()
+ showSkippedShortcut_holder.addWidget(self.showSkippedShortcut_button)
+ undoShortcut_label = QLabel("Undo:")
+ undoShortcut_label.setToolTip("{0} Changes undo review shortcut. Shortcut will work even if\
+ you disable the undo button. undo button shortcut can be a sngle key\
+ like \"z\" or \"f1\" or a combination of keys like \"ctrl+z\" or \"alt+z\".\
+ NOTE: Make sure the shortcut you want to set for the button isn't already\
+ in use by anki itself or another add-on. the default shortcut for this\
+ action is (Ctrl+Z), by changing th shortcut here, the default shortcut\
+ will still wok, in other words, if you set a shortcut for undo review\
+ here, you will have two shortcuts for it, one is the default (Ctrl+Z)\
+ and the other is the shortcut that you have set. {1}".format(begin, end))
+ undoShortcut_label.setFixedWidth(125)
+ self.undoShortcut_button = QPushButton()
+ self.undoShortcut_button.setFixedWidth(300)
+ self.undoShortcut_button.clicked.connect(lambda: self.showGetShortcut("undo_shortcut"))
+ undoShortcut_holder = QHBoxLayout()
+ undoShortcut_holder.addWidget(undoShortcut_label)
+ undoShortcut_holder.addStretch()
+ undoShortcut_holder.addWidget(self.undoShortcut_button)
+ buttonShortcutsPart = QVBoxLayout()
+ buttonShortcutsPart.addLayout(infoShortcut_holder)
+ buttonShortcutsPart.addLayout(skipShortcut_holder)
+ buttonShortcutsPart.addLayout(showSkippedShortcut_holder)
+ buttonShortcutsPart.addLayout(undoShortcut_holder)
+ buttonShortcutsBox = QGroupBox("Button Shortcuts")
+ buttonShortcutsBox.setLayout(buttonShortcutsPart)
+ layout = QVBoxLayout()
+ layout.addWidget(extraButtonsBox)
+ layout.addWidget(hideButtonsBox)
+ layout.addWidget(buttonPositionsBox)
+ layout.addWidget(buttonShortcutsBox)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab3 = QScrollArea()
+ self.tab3.setFixedWidth(640)
+ self.tab3.setAlignment(Qt.AlignHCenter)
+ self.tab3.setWidgetResizable(True)
+ self.tab3.setWidget(layout_holder)
+
+ def createFourthTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ customSizes_label = QLabel('Custom Sizes:')
+ customSizes_label.setToolTip("{0} Enables and disables custom button sizes. If Enabled\
+ button sizes will change according to the sizes you set for each button.\
+ If disabled button sizes will be set on default size. NOTE: If you use\
+ wide buttons, you won't be able to change review buttons width and even when\
+ you disable custom button sizes, review buttons will be wide. {1}".format(begin, end))
+ customSizes_label.setFixedWidth(180)
+ self.customSizes_on = QRadioButton("On")
+ self.customSizes_on.setToolTip("{0} Button sizes is disabled and buttons height and width\
+ is set to height and width that we have set. {1} \
+ ".format(begin, end, images))
+ self.customSizes_on.setFixedWidth(90)
+ self.customSizes_off = QRadioButton("Off")
+ self.customSizes_off.setToolTip("{0} Button sizes is disabled and all buttons are in default size.\
+ {1} ".format(begin, end, images))
+ self.customSizes_off.setFixedWidth(90)
+ tab4line1 = QHBoxLayout()
+ tab4line1.addWidget(customSizes_label)
+ tab4line1.addWidget(self.customSizes_on)
+ tab4line1.addWidget(self.customSizes_off)
+ tab4line1.addStretch()
+ tab4box1 = QGroupBox()
+ tab4box1.setLayout(tab4line1)
+ textSize_label = QLabel("Buttons Text Size:")
+ textSize_label.setToolTip("{0}Sets the text size for all bottombar buttons.\
+ It only works on review screen buttons and it will not affect main screen\
+ and study screen buttons{1}".format(begin, end))
+ textSize_label.setFixedWidth(180)
+ self.text_size = QSpinBox()
+ self.text_size.setFixedWidth(120)
+ self.text_size.setMinimum(0)
+ self.text_size.setMaximum(200)
+ textSize_px = QLabel("px")
+ textSize_holder = QHBoxLayout()
+ textSize_holder.addWidget(textSize_label)
+ textSize_holder.addWidget(self.text_size)
+ textSize_holder.addWidget(textSize_px)
+ buttonsHeight_label = QLabel("Bottombar Buttons Height:")
+ buttonsHeight_label.setToolTip("{0} Sets height for all bottombar buttons including edit, info,\
+ ski, show answer, undo review, more and review buttons.".format(begin, end))
+ buttonsHeight_label.setFixedWidth(180)
+ self.buttons_height = QSpinBox()
+ self.buttons_height.setFixedWidth(120)
+ self.buttons_height.setMinimum(0)
+ self.buttons_height.setMaximum(200)
+ buttonsHeight_px = QLabel("px")
+ buttonsHeight_holder = QHBoxLayout()
+ buttonsHeight_holder.addWidget(buttonsHeight_label)
+ buttonsHeight_holder.addWidget(self.buttons_height)
+ buttonsHeight_holder.addWidget(buttonsHeight_px)
+ reviewButtonsWidth_label = QLabel("Review Buttons Width:")
+ reviewButtonsWidth_label.setToolTip("{0} Sets width for review buttons\
+ (again, hard, good and easy buttons).{1}".format(begin, end))
+ reviewButtonsWidth_label.setFixedWidth(180)
+ self.reviewButtons_width = QSpinBox()
+ self.reviewButtons_width.setFixedWidth(120)
+ self.reviewButtons_width.setMinimum(0)
+ self.reviewButtons_width.setMaximum(400)
+ reviewButtonsWidth_px = QLabel("px")
+ reviewButtonsWidth_holder = QHBoxLayout()
+ reviewButtonsWidth_holder.addWidget(reviewButtonsWidth_label)
+ reviewButtonsWidth_holder.addWidget(self.reviewButtons_width)
+ reviewButtonsWidth_holder.addWidget(reviewButtonsWidth_px)
+ editWidth_label = QLabel("Edit Width:")
+ editWidth_label.setToolTip("{0} Sets width for edit button.{1}".format(begin, end))
+ editWidth_label.setFixedWidth(180)
+ self.edit_width = QSpinBox()
+ self.edit_width.setFixedWidth(120)
+ self.edit_width.setMinimum(0)
+ self.edit_width.setMaximum(400)
+ editWidth_px = QLabel("px")
+ editWidth_holder = QHBoxLayout()
+ editWidth_holder.addWidget(editWidth_label)
+ editWidth_holder.addWidget(self.edit_width)
+ editWidth_holder.addWidget(editWidth_px)
+ answerWidth_label = QLabel("Show Answer Width:")
+ answerWidth_label.setToolTip("{0} Sets width for show answer button.{1}".format(begin, end))
+ answerWidth_label.setFixedWidth(180)
+ self.answer_width = QSpinBox()
+ self.answer_width.setFixedWidth(120)
+ self.answer_width.setMinimum(0)
+ self.answer_width.setMaximum(400)
+ answerWidth_px = QLabel("px")
+ answerWidth_holder = QHBoxLayout()
+ answerWidth_holder.addWidget(answerWidth_label)
+ answerWidth_holder.addWidget(self.answer_width)
+ answerWidth_holder.addWidget(answerWidth_px)
+ moreWidth_label = QLabel("More Width:")
+ moreWidth_label.setToolTip("{0} Sets width for more button.{1}".format(begin, end))
+ moreWidth_label.setFixedWidth(180)
+ self.more_width = QSpinBox()
+ self.more_width.setFixedWidth(120)
+ self.more_width.setMinimum(0)
+ self.more_width.setMaximum(400)
+ moreWidth_px = QLabel("px")
+ moreWidth_holder = QHBoxLayout()
+ moreWidth_holder.addWidget(moreWidth_label)
+ moreWidth_holder.addWidget(self.more_width)
+ moreWidth_holder.addWidget(moreWidth_px)
+ infoWidth_label = QLabel("Info Width:")
+ infoWidth_label.setToolTip("{0} Sets width for info button.{1}".format(begin, end))
+ infoWidth_label.setFixedWidth(180)
+ self.info_width = QSpinBox()
+ self.info_width.setFixedWidth(120)
+ self.info_width.setMinimum(0)
+ self.info_width.setMaximum(400)
+ infoWidth_px = QLabel("px")
+ infoWidth_holder = QHBoxLayout()
+ infoWidth_holder.addWidget(infoWidth_label)
+ infoWidth_holder.addWidget(self.info_width)
+ infoWidth_holder.addWidget(infoWidth_px)
+ skipWidth_label = QLabel("Skip Width:")
+ skipWidth_label.setToolTip("{0} Sets width for skip button.{1}".format(begin, end))
+ skipWidth_label.setFixedWidth(180)
+ self.skip_width = QSpinBox()
+ self.skip_width.setFixedWidth(120)
+ self.skip_width.setMinimum(0)
+ self.skip_width.setMaximum(400)
+ skipWidth_px = QLabel("px")
+ skipWidth_holder = QHBoxLayout()
+ skipWidth_holder.addWidget(skipWidth_label)
+ skipWidth_holder.addWidget(self.skip_width)
+ skipWidth_holder.addWidget(skipWidth_px)
+ showSkippedWidth_label = QLabel("Show Skipped Width:")
+ showSkippedWidth_label.setToolTip("{0} Sets width for Show Skipped button.{1}".format(begin, end))
+ showSkippedWidth_label.setFixedWidth(180)
+ self.showSkipped_width = QSpinBox()
+ self.showSkipped_width.setFixedWidth(120)
+ self.showSkipped_width.setMinimum(0)
+ self.showSkipped_width.setMaximum(400)
+ showSkippedWidth_px = QLabel("px")
+ showSkippedWidth_holder = QHBoxLayout()
+ showSkippedWidth_holder.addWidget(showSkippedWidth_label)
+ showSkippedWidth_holder.addWidget(self.showSkipped_width)
+ showSkippedWidth_holder.addWidget(showSkippedWidth_px)
+ undoWidth_label = QLabel("Undo Width:")
+ undoWidth_label.setToolTip("{0} Sets width for undo button.{1}".format(begin, end))
+ undoWidth_label.setFixedWidth(180)
+ self.undo_width = QSpinBox()
+ self.undo_width.setFixedWidth(120)
+ self.undo_width.setMinimum(0)
+ self.undo_width.setMaximum(400)
+ undoWidth_px = QLabel("px")
+ undoWidth_holder = QHBoxLayout()
+ undoWidth_holder.addWidget(undoWidth_label)
+ undoWidth_holder.addWidget(self.undo_width)
+ undoWidth_holder.addWidget(undoWidth_px)
+ tab4line2 = QVBoxLayout()
+ tab4line2.addLayout(textSize_holder)
+ tab4line2.addLayout(buttonsHeight_holder)
+ tab4line2.addLayout(reviewButtonsWidth_holder)
+ tab4line2.addLayout(editWidth_holder)
+ tab4line2.addLayout(answerWidth_holder)
+ tab4line2.addLayout(moreWidth_holder)
+ tab4line2.addLayout(infoWidth_holder)
+ tab4line2.addLayout(skipWidth_holder)
+ tab4line2.addLayout(showSkippedWidth_holder)
+ tab4line2.addLayout(undoWidth_holder)
+ tab4box2 = QGroupBox()
+ tab4box2.setDisabled(True)
+ if self.customSizes_on.isChecked():
+ tab4box2.setEnabled(True)
+ self.customSizes_on.toggled.connect(tab4box2.setEnabled)
+ tab4box2.setLayout(tab4line2)
+ layout = QVBoxLayout()
+ layout.addWidget(tab4box1)
+ layout.addWidget(tab4box2)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab4 = QScrollArea()
+ self.tab4.setFixedWidth(640)
+ self.tab4.setAlignment(Qt.AlignHCenter)
+ self.tab4.setWidgetResizable(True)
+ self.tab4.setWidget(layout_holder)
+
+ def createFifthTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ buttonLabel_studyNow_label = QLabel("Study Now:")
+ buttonLabel_studyNow_label.setToolTip("{0}Replaces the text for \"Study Now\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_studyNow_label.setFixedWidth(90)
+ self.buttonLabel_studyNow = QLineEdit()
+ buttonlabel_studyNow_holder = QHBoxLayout()
+ buttonlabel_studyNow_holder.addWidget(buttonLabel_studyNow_label)
+ buttonlabel_studyNow_holder.addWidget(self.buttonLabel_studyNow)
+ buttonLabel_studyNow_box = QGroupBox()
+ buttonLabel_studyNow_box.setLayout(buttonlabel_studyNow_holder)
+ buttonLabel_edit_label = QLabel("Edit:")
+ buttonLabel_edit_label.setToolTip("{0}Replaces the text for \"Edit\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_edit_label.setFixedWidth(90)
+ self.buttonLabel_edit = QLineEdit()
+ buttonlabel_edit_holder = QHBoxLayout()
+ buttonlabel_edit_holder.addWidget(buttonLabel_edit_label)
+ buttonlabel_edit_holder.addWidget(self.buttonLabel_edit)
+ buttonLabel_edit_box = QGroupBox()
+ buttonLabel_edit_box.setLayout(buttonlabel_edit_holder)
+ firstLine = QHBoxLayout()
+ firstLine.addWidget(buttonLabel_studyNow_box)
+ firstLine.addWidget(buttonLabel_edit_box)
+ buttonLabel_showAnswer_label = QLabel("Show Answer:")
+ buttonLabel_showAnswer_label.setToolTip("{0}Replaces the text for \"Show Answer\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_showAnswer_label.setFixedWidth(90)
+ self.buttonLabel_showAnswer = QLineEdit()
+ buttonlabel_showAnswer_holder = QHBoxLayout()
+ buttonlabel_showAnswer_holder.addWidget(buttonLabel_showAnswer_label)
+ buttonlabel_showAnswer_holder.addWidget(self.buttonLabel_showAnswer)
+ buttonLabel_showAnswer_box = QGroupBox()
+ buttonLabel_showAnswer_box.setLayout(buttonlabel_showAnswer_holder)
+ buttonLabel_more_label = QLabel("More:")
+ buttonLabel_more_label.setToolTip("{0}Replaces the text for \"More\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_more_label.setFixedWidth(90)
+ self.buttonLabel_more = QLineEdit()
+ buttonlabel_more_holder = QHBoxLayout()
+ buttonlabel_more_holder.addWidget(buttonLabel_more_label)
+ buttonlabel_more_holder.addWidget(self.buttonLabel_more)
+ buttonLabel_more_box = QGroupBox()
+ buttonLabel_more_box.setLayout(buttonlabel_more_holder)
+ secondLine = QHBoxLayout()
+ secondLine.addWidget(buttonLabel_showAnswer_box)
+ secondLine.addWidget(buttonLabel_more_box)
+ buttonLabel_info_label = QLabel("Info:")
+ buttonLabel_info_label.setToolTip("{0}Replaces the text for \"Info\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_info_label.setFixedWidth(90)
+ self.buttonLabel_info = QLineEdit()
+ buttonlabel_info_holder = QHBoxLayout()
+ buttonlabel_info_holder.addWidget(buttonLabel_info_label)
+ buttonlabel_info_holder.addWidget(self.buttonLabel_info)
+ buttonLabel_info_box = QGroupBox()
+ buttonLabel_info_box.setLayout(buttonlabel_info_holder)
+ buttonLabel_skip_label = QLabel("Skip:")
+ buttonLabel_skip_label.setToolTip("{0}Replaces the text for \"Skip\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_skip_label.setFixedWidth(90)
+ self.buttonLabel_skip = QLineEdit()
+ buttonlabel_skip_holder = QHBoxLayout()
+ buttonlabel_skip_holder.addWidget(buttonLabel_skip_label)
+ buttonlabel_skip_holder.addWidget(self.buttonLabel_skip)
+ buttonLabel_skip_box = QGroupBox()
+ buttonLabel_skip_box.setLayout(buttonlabel_skip_holder)
+ thirdLine = QHBoxLayout()
+ thirdLine.addWidget(buttonLabel_info_box)
+ thirdLine.addWidget(buttonLabel_skip_box)
+ buttonLabel_undo_label = QLabel("Undo:")
+ buttonLabel_undo_label.setToolTip("{0}Replaces the text for \"Undo\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_undo_label.setFixedWidth(90)
+ self.buttonLabel_undo = QLineEdit()
+ buttonlabel_undo_holder = QHBoxLayout()
+ buttonlabel_undo_holder.addWidget(buttonLabel_undo_label)
+ buttonlabel_undo_holder.addWidget(self.buttonLabel_undo)
+ buttonLabel_undo_box = QGroupBox()
+ buttonLabel_undo_box.setLayout(buttonlabel_undo_holder)
+ buttonLabel_showSkipped_label = QLabel("Show Skipped:")
+ buttonLabel_showSkipped_label.setToolTip("{0}Replaces the text for \"Show Skipped\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_showSkipped_label.setFixedWidth(90)
+ self.buttonLabel_showSkipped = QLineEdit()
+ buttonlabel_showSkipped_holder = QHBoxLayout()
+ buttonlabel_showSkipped_holder.addWidget(buttonLabel_showSkipped_label)
+ buttonlabel_showSkipped_holder.addWidget(self.buttonLabel_showSkipped)
+ buttonLabel_showSkipped_box = QGroupBox()
+ buttonLabel_showSkipped_box.setLayout(buttonlabel_showSkipped_holder)
+ fourthLine = QHBoxLayout()
+ fourthLine.addWidget(buttonLabel_undo_box)
+ fourthLine.addWidget(buttonLabel_showSkipped_box)
+ buttonLabel_again_label = QLabel("Again:")
+ buttonLabel_again_label.setToolTip("{0}Replaces the text for \"Again\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_again_label.setFixedWidth(90)
+ self.buttonLabel_again = QLineEdit()
+ buttonlabel_again_holder = QHBoxLayout()
+ buttonlabel_again_holder.addWidget(buttonLabel_again_label)
+ buttonlabel_again_holder.addWidget(self.buttonLabel_again)
+ buttonLabel_again_box = QGroupBox()
+ buttonLabel_again_box.setLayout(buttonlabel_again_holder)
+ buttonLabel_hard_label = QLabel("Hard:")
+ buttonLabel_hard_label.setToolTip("{0}Replaces the text for \"Hard\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_hard_label.setFixedWidth(90)
+ self.buttonLabel_hard = QLineEdit()
+ buttonlabel_hard_holder = QHBoxLayout()
+ buttonlabel_hard_holder.addWidget(buttonLabel_hard_label)
+ buttonlabel_hard_holder.addWidget(self.buttonLabel_hard)
+ buttonLabel_hard_box = QGroupBox()
+ buttonLabel_hard_box.setLayout(buttonlabel_hard_holder)
+ fifthLine = QHBoxLayout()
+ fifthLine.addWidget(buttonLabel_again_box)
+ fifthLine.addWidget(buttonLabel_hard_box)
+ buttonLabel_good_label = QLabel("Good:")
+ buttonLabel_good_label.setToolTip("{0}Replaces the text for \"Good\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_good_label.setFixedWidth(90)
+ self.buttonLabel_good = QLineEdit()
+ buttonlabel_good_holder = QHBoxLayout()
+ buttonlabel_good_holder.addWidget(buttonLabel_good_label)
+ buttonlabel_good_holder.addWidget(self.buttonLabel_good)
+ buttonLabel_good_box = QGroupBox()
+ buttonLabel_good_box.setLayout(buttonlabel_good_holder)
+ buttonLabel_easy_label = QLabel("Easy:")
+ buttonLabel_easy_label.setToolTip("{0}Replaces the text for \"Easy\" Button with your custom text.{1}".format(begin, end))
+ buttonLabel_easy_label.setFixedWidth(90)
+ self.buttonLabel_easy = QLineEdit()
+ buttonlabel_easy_holder = QHBoxLayout()
+ buttonlabel_easy_holder.addWidget(buttonLabel_easy_label)
+ buttonlabel_easy_holder.addWidget(self.buttonLabel_easy)
+ buttonLabel_easy_box = QGroupBox()
+ buttonLabel_easy_box.setLayout(buttonlabel_easy_holder)
+ sixthLine = QHBoxLayout()
+ sixthLine.addWidget(buttonLabel_good_box)
+ sixthLine.addWidget(buttonLabel_easy_box)
+ layout = QVBoxLayout()
+ layout.addLayout(firstLine)
+ layout.addLayout(secondLine)
+ layout.addLayout(thirdLine)
+ layout.addLayout(fourthLine)
+ layout.addLayout(fifthLine)
+ layout.addLayout(sixthLine)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab5 = QScrollArea()
+ self.tab5.setFixedWidth(640)
+ self.tab5.setAlignment(Qt.AlignHCenter)
+ self.tab5.setWidgetResizable(True)
+ self.tab5.setWidget(layout_holder)
+
+ def createSixthTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ sidebarPosition_label = QLabel("Card Info Sidebar Default Position:")
+ sidebarPosition_label.setToolTip("{0}Chanes the default position of the sidebar (duh!){1}".format(begin, end))
+ sidebarPosition_label.setFixedWidth(195)
+ self.sidebar_position = QComboBox()
+ self.sidebar_position.addItems(["Right", "Left"])
+ self.sidebar_position.setMinimumWidth(200)
+ sidebarPosition_holder = QHBoxLayout()
+ sidebarPosition_holder.addWidget(sidebarPosition_label)
+ sidebarPosition_holder.addWidget(self.sidebar_position)
+ sidebarPosition_holder.addStretch()
+ sidebarTheme_label = QLabel("Card Info Sidebar Theme:")
+ sidebarTheme_label.setToolTip("{0} Changes sidebar theme. {1}".format(begin, end))
+ sidebarTheme_label.setFixedWidth(195)
+ self.sidebar_theme = QComboBox()
+ self.sidebar_theme.addItems(["Auto", "Day/Light", "Night/Dark"])
+ self.sidebar_theme.setToolTip("{0} Auto: Chooses the sidebar theme based on your anki theme. \
+ NOTE: This option only supports anki's native night\
+ mode and does not work with night mode add-on. Day: Forces sidebar to use light theme whether your anki is\
+ in night mode or not. Night: Forces sidebar to use dark theme whether your anki is\
+ in night mode or not. {1}".format(begin, end))
+ self.sidebar_theme.setMinimumWidth(200)
+ sideBarTheme_holder = QHBoxLayout()
+ sideBarTheme_holder.addWidget(sidebarTheme_label)
+ sideBarTheme_holder.addWidget(self.sidebar_theme)
+ sideBarTheme_holder.addStretch()
+ sidebarFont_label = QLabel("Card Info Sidebar Font:")
+ sidebarFont_label.setToolTip("{0} Changes card info sidebar font. {1}".format(begin, end))
+ sidebarFont_label.setFixedWidth(195)
+ self.sidebar_font = QFontComboBox()
+ self.sidebar_font.setMinimumWidth(200)
+ sidebarFont_holder = QHBoxLayout()
+ sidebarFont_holder.addWidget(sidebarFont_label)
+ sidebarFont_holder.addWidget(self.sidebar_font)
+ sidebarFont_holder.addStretch()
+ sidebarPreviousCards_label = QLabel("Number of Previous Cards To Show:")
+ sidebarPreviousCards_label.setToolTip("{0} Changes number of previous cards that show on the card\
+ info sidebar. {1}".format(begin, end))
+ sidebarPreviousCards_label.setFixedWidth(195)
+ self.sidebar_PreviousCards = QSpinBox()
+ self.sidebar_PreviousCards.setMinimumWidth(200)
+ self.sidebar_PreviousCards.setMaximum(4)
+ sidebarPreviousCards_holder = QHBoxLayout()
+ sidebarPreviousCards_holder.addWidget(sidebarPreviousCards_label)
+ sidebarPreviousCards_holder.addWidget(self.sidebar_PreviousCards)
+ sidebarPreviousCards_holder.addStretch()
+ sidebarReviewsToShow_label = QLabel("Card Previous Reviews To Show:")
+ sidebarReviewsToShow_label.setToolTip("{0} Changes number of previous reviews for a card to show on\
+ the card info sidebar. If you want it to show all reviews for a card, set it on 0.{1}".format(begin, end))
+ sidebarReviewsToShow_label.setFixedWidth(195)
+ self.sidebar_reviewsToShow = QSpinBox()
+ self.sidebar_reviewsToShow.setMinimumWidth(200)
+ self.sidebar_reviewsToShow.setMaximum(500)
+ sidebarReviewsToShow_holder = QHBoxLayout()
+ sidebarReviewsToShow_holder.addWidget(sidebarReviewsToShow_label)
+ sidebarReviewsToShow_holder.addWidget(self.sidebar_reviewsToShow)
+ sidebarReviewsToShow_holder.addStretch()
+ tab5line1 = QVBoxLayout()
+ tab5line1.addLayout(sidebarPosition_holder)
+ tab5line1.addLayout(sideBarTheme_holder)
+ tab5line1.addLayout(sidebarFont_holder)
+ tab5line1.addLayout(sidebarPreviousCards_holder)
+ tab5line1.addLayout(sidebarReviewsToShow_holder)
+ tab5box1 = QGroupBox()
+ tab5box1.setLayout(tab5line1)
+ self.sidebar_currentReviewCount = QCheckBox("Current Review Count")
+ self.sidebar_currentReviewCount.setFixedWidth(140)
+ self.sidebar_dateCreated = QCheckBox('Date Created')
+ self.sidebar_dateCreated.setFixedWidth(140)
+ self.sidebar_dateEdited = QCheckBox('Dated Edited')
+ self.sidebar_dateEdited.setFixedWidth(140)
+ tab5subline1 = QHBoxLayout()
+ tab5subline1.addWidget(self.sidebar_currentReviewCount)
+ tab5subline1.addWidget(self.sidebar_dateCreated)
+ tab5subline1.addWidget(self.sidebar_dateEdited)
+ tab5subline1.addStretch()
+ self.sidebar_firstReview = QCheckBox('First Review')
+ self.sidebar_firstReview.setFixedWidth(140)
+ self.sidebar_latestReview = QCheckBox('Latest Review')
+ self.sidebar_latestReview.setFixedWidth(140)
+ self.sidebar_due = QCheckBox('Due')
+ self.sidebar_due.setFixedWidth(140)
+ tab5subline2 = QHBoxLayout()
+ tab5subline2.addWidget(self.sidebar_firstReview)
+ tab5subline2.addWidget(self.sidebar_latestReview)
+ tab5subline2.addWidget(self.sidebar_due)
+ tab5subline2.addStretch()
+ self.sidebar_interval = QCheckBox('Interval')
+ self.sidebar_interval.setFixedWidth(140)
+ self.sidebar_ease = QCheckBox('Ease')
+ self.sidebar_ease.setFixedWidth(140)
+ self.sidebar_numberOfReviews = QCheckBox('Number of Reviews')
+ self.sidebar_numberOfReviews.setFixedWidth(140)
+ tab5subline3 = QHBoxLayout()
+ tab5subline3.addWidget(self.sidebar_interval)
+ tab5subline3.addWidget(self.sidebar_ease)
+ tab5subline3.addWidget(self.sidebar_numberOfReviews)
+ tab5subline3.addStretch()
+ self.sidebar_lapses = QCheckBox('Lapses')
+ self.sidebar_lapses.setFixedWidth(140)
+ self.sidebar_averageTime = QCheckBox('Average Time')
+ self.sidebar_averageTime.setFixedWidth(140)
+ self.sidebar_totalTime = QCheckBox('Total Time')
+ self.sidebar_totalTime.setFixedWidth(140)
+ tab5subline4 = QHBoxLayout()
+ tab5subline4.addWidget(self.sidebar_lapses)
+ tab5subline4.addWidget(self.sidebar_averageTime)
+ tab5subline4.addWidget(self.sidebar_totalTime)
+ tab5subline4.addStretch()
+ self.sidebar_cardType = QCheckBox('Card Type')
+ self.sidebar_cardType.setFixedWidth(140)
+ self.sidebar_noteType = QCheckBox('Note Type')
+ self.sidebar_noteType.setFixedWidth(140)
+ self.sidebar_deck = QCheckBox('Deck')
+ self.sidebar_deck.setFixedWidth(140)
+ tab5subline5 = QHBoxLayout()
+ tab5subline5.addWidget(self.sidebar_cardType)
+ tab5subline5.addWidget(self.sidebar_noteType)
+ tab5subline5.addWidget(self.sidebar_deck)
+ tab5subline5.addStretch()
+ self.sidebar_tags = QCheckBox('Tags')
+ self.sidebar_tags.setFixedWidth(140)
+ self.sidebar_sortField = QCheckBox('Sort Field')
+ self.sidebar_sortField.setFixedWidth(140)
+ self.sidebar_warningNote = QCheckBox('Warning Note')
+ self.sidebar_warningNote.setFixedWidth(140)
+ tab5subline6 = QHBoxLayout()
+ tab5subline6.addWidget(self.sidebar_tags)
+ tab5subline6.addWidget(self.sidebar_sortField)
+ tab5subline6.addWidget(self.sidebar_warningNote)
+ tab5subline6.addStretch()
+ self.sidebar_correctPercent = QCheckBox('Correct Percentage')
+ self.sidebar_correctPercent.setFixedWidth(140)
+ self.sidebar_fastestReview = QCheckBox('Fastest Review')
+ self.sidebar_fastestReview.setFixedWidth(140)
+ self.sidebar_slowestReview = QCheckBox('Slowest Review')
+ self.sidebar_slowestReview.setFixedWidth(140)
+ tab5subline7 = QHBoxLayout()
+ tab5subline7.addWidget(self.sidebar_correctPercent)
+ tab5subline7.addWidget(self.sidebar_fastestReview)
+ tab5subline7.addWidget(self.sidebar_slowestReview)
+ tab5subline7.addStretch()
+ self.sidebar_noteID = QCheckBox('Note ID')
+ self.sidebar_noteID.setFixedWidth(140)
+ self.sidebar_cardID = QCheckBox('Card ID')
+ self.sidebar_cardID.setFixedWidth(140)
+ self.sidebar_autoOpen = QCheckBox('Auto Open')
+ self.sidebar_autoOpen.setToolTip("Opens sidebar automatically when you review a card")
+ self.sidebar_autoOpen.setFixedWidth(140)
+ tab5subline8 = QHBoxLayout()
+ tab5subline8.addWidget(self.sidebar_noteID)
+ tab5subline8.addWidget(self.sidebar_cardID)
+ tab5subline8.addWidget(self.sidebar_autoOpen)
+ tab5subline8.addStretch()
+ tab5line2 = QVBoxLayout()
+ tab5line2.addLayout(tab5subline1)
+ tab5line2.addLayout(tab5subline2)
+ tab5line2.addLayout(tab5subline3)
+ tab5line2.addLayout(tab5subline4)
+ tab5line2.addLayout(tab5subline5)
+ tab5line2.addLayout(tab5subline6)
+ tab5line2.addLayout(tab5subline7)
+ tab5line2.addLayout(tab5subline8)
+ tab5box2 = QGroupBox()
+ tab5box2.setLayout(tab5line2)
+ layout = QVBoxLayout()
+ layout.addWidget(tab5box1)
+ layout.addWidget(tab5box2)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab6 = QScrollArea()
+ self.tab6.setFixedWidth(640)
+ self.tab6.setAlignment(Qt.AlignHCenter)
+ self.tab6.setWidgetResizable(True)
+ self.tab6.setWidget(layout_holder)
+
+ def createSeventhTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ self.custom_reviewButtonColors = QGroupBox("Custom Review Button Colors")
+ self.custom_reviewButtonColors.setToolTip("{0}Changes text or background color and hover colors for review buttons.\
+ as the neon and fill buttons don't have a separate hover color, changing hover color will not affect those buttons.{1}".format(begin, end))
+ self.custom_reviewButtonColors.setCheckable(True)
+ self.custom_reviewButtonColors.setChecked(False)
+ againColor_label = QLabel("Again:")
+ againColor_label.setFixedWidth(140)
+ self.againColor_button = QPushButton()
+ self.againColor_button.clicked.connect(lambda: self.getNewColor("again_color", self.againColor_button))
+ againColor_holder = QHBoxLayout()
+ againColor_holder.addWidget(againColor_label)
+ againColor_holder.addWidget(self.againColor_button)
+ againColor_box = QGroupBox()
+ againColor_box.setLayout(againColor_holder)
+ againHoverColor_label = QLabel("Again on Hover:")
+ againHoverColor_label.setFixedWidth(140)
+ self.againHoverColor_button = QPushButton()
+ self.againHoverColor_button.clicked.connect(lambda: self.getNewColor("againHover_color", self.againHoverColor_button))
+ againHoverColor_holder = QHBoxLayout()
+ againHoverColor_holder.addWidget(againHoverColor_label)
+ againHoverColor_holder.addWidget(self.againHoverColor_button)
+ againHover_box = QGroupBox()
+ againHover_box.setLayout(againHoverColor_holder)
+ again_line = QHBoxLayout()
+ again_line.addWidget(againColor_box)
+ again_line.addWidget(againHover_box)
+ hardColor_label = QLabel("Hard:")
+ hardColor_label.setFixedWidth(140)
+ self.hardColor_button = QPushButton()
+ self.hardColor_button.clicked.connect(lambda: self.getNewColor("hard_color", self.hardColor_button))
+ hardColor_holder = QHBoxLayout()
+ hardColor_holder.addWidget(hardColor_label)
+ hardColor_holder.addWidget(self.hardColor_button)
+ hardColor_box = QGroupBox()
+ hardColor_box.setLayout(hardColor_holder)
+ hardHoverColor_label = QLabel("Hard on Hover:")
+ hardHoverColor_label.setFixedWidth(140)
+ self.hardHoverColor_button = QPushButton()
+ self.hardHoverColor_button.clicked.connect(lambda: self.getNewColor("hardHover_color", self.hardHoverColor_button))
+ hardHoverColor_holder = QHBoxLayout()
+ hardHoverColor_holder.addWidget(hardHoverColor_label)
+ hardHoverColor_holder.addWidget(self.hardHoverColor_button)
+ hardHover_box = QGroupBox()
+ hardHover_box.setLayout(hardHoverColor_holder)
+ hard_line = QHBoxLayout()
+ hard_line.addWidget(hardColor_box)
+ hard_line.addWidget(hardHover_box)
+ goodColor_label = QLabel("Good:")
+ goodColor_label.setFixedWidth(140)
+ self.goodColor_button = QPushButton()
+ self.goodColor_button.clicked.connect(lambda: self.getNewColor("good_color", self.goodColor_button))
+ goodColor_holder = QHBoxLayout()
+ goodColor_holder.addWidget(goodColor_label)
+ goodColor_holder.addWidget(self.goodColor_button)
+ goodColor_box = QGroupBox()
+ goodColor_box.setLayout(goodColor_holder)
+ goodHoverColor_label = QLabel("Good on Hover:")
+ goodHoverColor_label.setFixedWidth(140)
+ self.goodHoverColor_button = QPushButton()
+ self.goodHoverColor_button.clicked.connect(lambda: self.getNewColor("goodHover_color", self.goodHoverColor_button))
+ goodHoverColor_holder = QHBoxLayout()
+ goodHoverColor_holder.addWidget(goodHoverColor_label)
+ goodHoverColor_holder.addWidget(self.goodHoverColor_button)
+ goodHover_box = QGroupBox()
+ goodHover_box.setLayout(goodHoverColor_holder)
+ good_line = QHBoxLayout()
+ good_line.addWidget(goodColor_box)
+ good_line.addWidget(goodHover_box)
+ easyColor_label = QLabel("Easy:")
+ easyColor_label.setFixedWidth(140)
+ self.easyColor_button = QPushButton()
+ self.easyColor_button.clicked.connect(lambda: self.getNewColor("easy_color", self.easyColor_button))
+ easyColor_holder = QHBoxLayout()
+ easyColor_holder.addWidget(easyColor_label)
+ easyColor_holder.addWidget(self.easyColor_button)
+ easyColor_box = QGroupBox()
+ easyColor_box.setLayout(easyColor_holder)
+ easyHoverColor_label = QLabel("Easy on Hover:")
+ easyHoverColor_label.setFixedWidth(140)
+ self.easyHoverColor_button = QPushButton()
+ self.easyHoverColor_button.clicked.connect(lambda: self.getNewColor("easyHover_color", self.easyHoverColor_button))
+ easyHoverColor_holder = QHBoxLayout()
+ easyHoverColor_holder.addWidget(easyHoverColor_label)
+ easyHoverColor_holder.addWidget(self.easyHoverColor_button)
+ easyHover_box = QGroupBox()
+ easyHover_box.setLayout(easyHoverColor_holder)
+ easy_line = QHBoxLayout()
+ easy_line.addWidget(easyColor_box)
+ easy_line.addWidget(easyHover_box)
+ reviewButtonColors_layout = QVBoxLayout()
+ reviewButtonColors_layout.addLayout(again_line)
+ reviewButtonColors_layout.addLayout(hard_line)
+ reviewButtonColors_layout.addLayout(good_line)
+ reviewButtonColors_layout.addLayout(easy_line)
+ self.custom_reviewButtonColors.setLayout(reviewButtonColors_layout)
+ self.custom_reviewButtonTextColor = QCheckBox("Review Button Text:")
+ self.custom_reviewButtonTextColor.setToolTip("{0}Changes the color of general textcolor inside buttons.\
+ This option does not work on Default + Text Color and Wide + Text Color Styles {1}".format(begin, end))
+ self.custom_reviewButtonTextColor.setFixedWidth(140)
+ self.reviewButtonTextColor_button = QPushButton()
+ self.reviewButtonTextColor_button.clicked.connect(lambda: self.getNewColor("reviewButtonText_color", self.reviewButtonTextColor_button))
+ reviewButtonTextColor_holder = QHBoxLayout()
+ reviewButtonTextColor_holder.addWidget(self.custom_reviewButtonTextColor)
+ reviewButtonTextColor_holder.addWidget(self.reviewButtonTextColor_button)
+ reviewButtonTextColor_box = QGroupBox()
+ reviewButtonTextColor_box.setLayout(reviewButtonTextColor_holder)
+ self.reviewButtonTextColor_button.setDisabled(True)
+ if self.custom_reviewButtonTextColor.isChecked():
+ self.reviewButtonTextColor_button.setEnabled(True)
+ self.custom_reviewButtonTextColor.toggled.connect(self.reviewButtonTextColor_button.setEnabled)
+ self.custom_activeIndicatorColor = QCheckBox("Active Indicator:")
+ self.custom_activeIndicatorColor.setToolTip("{0}Changes the active indicator color.\
+ This option doesn not work on neon and fill buttons as they don't have active indicator for active buttons.{1}".format(begin, end))
+ self.custom_activeIndicatorColor.setFixedWidth(140)
+ self.activeIndicatorColor_button = QPushButton()
+ self.activeIndicatorColor_button.clicked.connect(lambda: self.getNewColor("activeIndicator_color", self.activeIndicatorColor_button))
+ activeIndicatorColor_holder = QHBoxLayout()
+ activeIndicatorColor_holder.addWidget(self.custom_activeIndicatorColor)
+ activeIndicatorColor_holder.addWidget(self.activeIndicatorColor_button)
+ activeIndicatorColor_box = QGroupBox()
+ activeIndicatorColor_box.setLayout(activeIndicatorColor_holder)
+ self.activeIndicatorColor_button.setDisabled(True)
+ if self.custom_activeIndicatorColor.isChecked():
+ self.activeIndicatorColor_button.setEnabled(True)
+ self.custom_activeIndicatorColor.toggled.connect(self.activeIndicatorColor_button.setEnabled)
+ reviewButtonTextColor_activeIndicatorColor_line = QHBoxLayout()
+ reviewButtonTextColor_activeIndicatorColor_line.addWidget(reviewButtonTextColor_box)
+ reviewButtonTextColor_activeIndicatorColor_line.addWidget(activeIndicatorColor_box)
+ self.custom_bottombarButtonTextColor = QCheckBox("General Button Text:")
+ self.custom_bottombarButtonTextColor.setToolTip("{0}Changes color of text inside all buttons including bottombar buttons, deck overview buttons and main screen bottombar buttons.{1}".format(begin, end))
+ self.custom_bottombarButtonTextColor.setFixedWidth(140)
+ self.bottombarButtonTextColor_button = QPushButton()
+ self.bottombarButtonTextColor_button.clicked.connect(lambda: self.getNewColor("bottombarButtonText_color", self.bottombarButtonTextColor_button))
+ bottombarButtonTextColor_holder = QHBoxLayout()
+ bottombarButtonTextColor_holder.addWidget(self.custom_bottombarButtonTextColor)
+ bottombarButtonTextColor_holder.addWidget(self.bottombarButtonTextColor_button)
+ bottombarButtonTextColor_box = QGroupBox()
+ bottombarButtonTextColor_box.setLayout(bottombarButtonTextColor_holder)
+ self.bottombarButtonTextColor_button.setDisabled(True)
+ if self.custom_bottombarButtonTextColor.isChecked():
+ self.bottombarButtonTextColor_button.setEnabled(True)
+ self.custom_bottombarButtonTextColor.toggled.connect(self.bottombarButtonTextColor_button.setEnabled)
+ self.custom_bottombarButtonBorderColor = QCheckBox("General Button Border:")
+ self.custom_bottombarButtonBorderColor.setToolTip("{0}Changes border color for all buttons including bottombar buttons, deck overview buttons and main screen bottombar buttons.{1}".format(begin, end))
+ self.custom_bottombarButtonBorderColor.setFixedWidth(140)
+ self.bottombarButtonBorderColor_button = QPushButton()
+ self.bottombarButtonBorderColor_button.clicked.connect(lambda: self.getNewColor("bottombarButtonBorder_color", self.bottombarButtonBorderColor_button))
+ bottombarButtonBorderColor_holder = QHBoxLayout()
+ bottombarButtonBorderColor_holder.addWidget(self.custom_bottombarButtonBorderColor)
+ bottombarButtonBorderColor_holder.addWidget(self.bottombarButtonBorderColor_button)
+ bottombarButtonBorderColor_box = QGroupBox()
+ bottombarButtonBorderColor_box.setLayout(bottombarButtonBorderColor_holder)
+ self.bottombarButtonBorderColor_button.setDisabled(True)
+ if self.custom_bottombarButtonBorderColor.isChecked():
+ self.bottombarButtonBorderColor_button.setEnabled(True)
+ self.custom_bottombarButtonBorderColor.toggled.connect(self.bottombarButtonBorderColor_button.setEnabled)
+ bottobarButtonTextColor_bottombarButtonBorderColor_line = QHBoxLayout()
+ bottobarButtonTextColor_bottombarButtonBorderColor_line.addWidget(bottombarButtonTextColor_box)
+ bottobarButtonTextColor_bottombarButtonBorderColor_line.addWidget(bottombarButtonBorderColor_box)
+ showAnswerEase1_label = QLabel("Ease less than")
+ showAnswerEase1_label.setFixedWidth(120)
+ self.showAnswerEase1 = QSpinBox()
+ self.showAnswerEase1.setFixedWidth(90)
+ self.showAnswerEase1.setMinimum(130)
+ self.showAnswerEase1.setMaximum(999999)
+ self.showAnswerEase1_button = QPushButton()
+ self.showAnswerEase1_button.setFixedWidth(210)
+ self.showAnswerEase1_button.clicked.connect(lambda: self.getNewColor("showAnswerEase1", self.showAnswerEase1_button))
+ showAnswerEase1_holder = QHBoxLayout()
+ showAnswerEase1_holder.addWidget(showAnswerEase1_label)
+ showAnswerEase1_holder.addWidget(self.showAnswerEase1)
+ showAnswerEase1_holder.addStretch()
+ showAnswerEase1_holder.addWidget(self.showAnswerEase1_button)
+ showAnswerEase1_box = QGroupBox()
+ showAnswerEase1_box.setLayout(showAnswerEase1_holder)
+ showAnswerEase2_label = QLabel("Ease from {} to".format(C_showAnswerEase1))
+ showAnswerEase2_label.setFixedWidth(120)
+ self.showAnswerEase2 = QSpinBox()
+ self.showAnswerEase2.setMinimum(130)
+ self.showAnswerEase2.setMaximum(999999)
+ self.showAnswerEase2.setFixedWidth(90)
+ self.showAnswerEase2_button = QPushButton()
+ self.showAnswerEase2_button.setFixedWidth(210)
+ self.showAnswerEase2_button.clicked.connect(lambda: self.getNewColor("showAnswerEase2", self.showAnswerEase2_button))
+ showAnswerEase2_holder = QHBoxLayout()
+ showAnswerEase2_holder.addWidget(showAnswerEase2_label)
+ showAnswerEase2_holder.addWidget(self.showAnswerEase2)
+ showAnswerEase2_holder.addStretch()
+ showAnswerEase2_holder.addWidget(self.showAnswerEase2_button)
+ showAnswerEase2_box = QGroupBox()
+ showAnswerEase2_box.setLayout(showAnswerEase2_holder)
+ showAnswerEase3_label = QLabel("Ease from {} to".format(C_showAnswerEase2))
+ showAnswerEase3_label.setFixedWidth(120)
+ self.showAnswerEase3 = QSpinBox()
+ self.showAnswerEase3.setMinimum(130)
+ self.showAnswerEase3.setMaximum(999999)
+ self.showAnswerEase3.setFixedWidth(90)
+ self.showAnswerEase3_button = QPushButton()
+ self.showAnswerEase3_button.setFixedWidth(210)
+ self.showAnswerEase3_button.clicked.connect(lambda: self.getNewColor("showAnswerEase3", self.showAnswerEase3_button))
+ showAnswerEase3_holder = QHBoxLayout()
+ showAnswerEase3_holder.addWidget(showAnswerEase3_label)
+ showAnswerEase3_holder.addWidget(self.showAnswerEase3)
+ showAnswerEase3_holder.addStretch()
+ showAnswerEase3_holder.addWidget(self.showAnswerEase3_button)
+ showAnswerEase3_box = QGroupBox()
+ showAnswerEase3_box.setLayout(showAnswerEase3_holder)
+ showAnswerEase4_label = QLabel("Ease from {} to".format(C_showAnswerEase3))
+ showAnswerEase4_label.setFixedWidth(120)
+ self.showAnswerEase4 = QSpinBox()
+ self.showAnswerEase4.setMinimum(130)
+ self.showAnswerEase4.setMaximum(999999)
+ self.showAnswerEase4.setFixedWidth(90)
+ self.showAnswerEase4_button = QPushButton()
+ self.showAnswerEase4_button.setFixedWidth(210)
+ self.showAnswerEase4_button.clicked.connect(lambda: self.getNewColor("showAnswerEase4", self.showAnswerEase4_button))
+ showAnswerEase4_holder = QHBoxLayout()
+ showAnswerEase4_holder.addWidget(showAnswerEase4_label)
+ showAnswerEase4_holder.addWidget(self.showAnswerEase4)
+ showAnswerEase4_holder.addStretch()
+ showAnswerEase4_holder.addWidget(self.showAnswerEase4_button)
+ showAnswerEase4_box = QGroupBox()
+ showAnswerEase4_box.setLayout(showAnswerEase4_holder)
+ showAnswerColors_layout = QVBoxLayout()
+ showAnswerColors_layout.addWidget(showAnswerEase1_box)
+ showAnswerColors_layout.addWidget(showAnswerEase2_box)
+ showAnswerColors_layout.addWidget(showAnswerEase3_box)
+ showAnswerColors_layout.addWidget(showAnswerEase4_box)
+ self.showAnswerColors_box = QGroupBox()
+ self.showAnswerColors_box.setLayout(showAnswerColors_layout)
+ def showAnswerType_signal():
+ self.showAnswerColors_box.setEnabled(True)
+ if self.showAnswerBorderColor_style.currentIndex() == 0:
+ self.showAnswerColors_box.setDisabled(True)
+ showAnswerType_signal()
+ self.showAnswerBorderColor_style.currentIndexChanged.connect(showAnswerType_signal)
+ layout = QVBoxLayout()
+ layout.addWidget(self.custom_reviewButtonColors)
+ layout.addLayout(reviewButtonTextColor_activeIndicatorColor_line)
+ layout.addLayout(bottobarButtonTextColor_bottombarButtonBorderColor_line)
+ layout.addWidget(self.showAnswerColors_box)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab7 = QScrollArea()
+ #// I use this part to control the initial settings menu width -_-
+ self.tab7.setFixedWidth(645)
+ self.tab7.setAlignment(Qt.AlignHCenter)
+ self.tab7.setWidgetResizable(True)
+ self.tab7.setWidget(layout_holder)
+
+ def createEighthTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ overViewStats_label = QLabel("More Overview Stats:")
+ overViewStats_label.setToolTip("{0}Shows number of new, learn and review cards for today and\
+ tomorrow and show total number of new, review, learn, buried and suspended cards on deck overview\
+ if enabled.{1}".format(begin, end))
+ overViewStats_label.setFixedWidth(180)
+ self.overViewStats = QComboBox()
+ self.overViewStats.addItems(["Stock", "Stock-ish", "Detailed"])
+ self.overViewStats.setFixedWidth(150)
+ overViewStats_holder = QHBoxLayout()
+ overViewStats_holder.addWidget(overViewStats_label)
+ overViewStats_holder.addWidget(self.overViewStats)
+ overViewStats_holder.addStretch()
+ settingsMenuPlace_label = QLabel("Settings Menu Placement:")
+ settingsMenuPlace_label.setToolTip("{0}Changes the position of settings menu.{1}".format(begin, end))
+ settingsMenuPlace_label.setFixedWidth(180)
+ self.settingsMenu_place = QComboBox()
+ self.settingsMenu_place.addItems(["Main Toolbar", "Tools Menu"])
+ self.settingsMenu_place.setFixedWidth(150)
+ settingsMenuPlace_holder = QHBoxLayout()
+ settingsMenuPlace_holder.addWidget(settingsMenuPlace_label)
+ settingsMenuPlace_holder.addWidget(self.settingsMenu_place)
+ settingsMenuPlace_holder.addStretch()
+ skipMethod_label = QLabel("Skip Method:")
+ skipMethod_label.setToolTip("{0}Changes Skip method.\n\"Next Card\" just skips the card and the skipped cards will be shown again randomly\
+ while \"Bury\" bureis the skipped cards and the skipped cards will get unburied when you finish reviewing normal cards. You\
+ can manually unbury skipped cards by pressing the \"Show Skipped\" button or by pressing the shortcut key that you've chosen for the button.{1}".format(begin, end))
+ skipMethod_label.setFixedWidth(180)
+ self.skipMethod = QComboBox()
+ self.skipMethod.addItems(["Next Card", "Bury"])
+ self.skipMethod.setFixedWidth(150)
+ skipMethod_holder = QHBoxLayout()
+ skipMethod_holder.addWidget(skipMethod_label)
+ skipMethod_holder.addWidget(self.skipMethod)
+ skipMethod_holder.addStretch()
+ skipMethod_box = QVBoxLayout()
+ skipMethod_box.addLayout(overViewStats_holder)
+ skipMethod_box.addLayout(settingsMenuPlace_holder)
+ skipMethod_box.addLayout(skipMethod_holder)
+ general_box = QGroupBox()
+ general_box.setLayout(skipMethod_box)
+ buttonColors_label = QLabel("Button Colors:")
+ buttonColors_label.setToolTip("{0} Enables and disables change button color. If you use\
+ any other add-on to change review button colors, turn this off and you can\
+ use other functions of this add-on without using this add-on to change\
+ review button colors. This should be enabled for options Review Button\
+ Background Shadow, Button Style, Change Style, Review Active Button Indicator\
+ and Review Buttons Width to work. {1}".format(begin, end, images))
+ buttonColors_label.setFixedWidth(180)
+ self.buttonColors_on = QRadioButton("On")
+ self.buttonColors_on.setToolTip("{0} Enabled -> Buttons are styled by this\
+ add-on. {1}".format(begin, end, images))
+ self.buttonColors_on.setFixedWidth(90)
+ self.buttonColors_off = QRadioButton("Off")
+ self.buttonColors_off.setToolTip("{0} Disabled -> Buttons are\
+ not styled by this add-on and since there is no other add-on styling them,\
+ they are in default mode.{1}".format(begin, end, images))
+ self.buttonColors_off.setFixedWidth(90)
+ buttonColors_holder = QHBoxLayout()
+ buttonColors_holder.addWidget(buttonColors_label)
+ buttonColors_holder.addWidget(self.buttonColors_on)
+ buttonColors_holder.addWidget(self.buttonColors_off)
+ buttonColors_holder.addStretch()
+ buttonColors_box = QGroupBox()
+ buttonColors_box.setLayout(buttonColors_holder)
+ speedFocus_label = QLabel("Speed Focus Add-on:")
+ speedFocus_label.setToolTip("{0} Removes the conflict with speed focus add-on so you can\
+ use this add-on on speed focus add-on at the same time without having issues.\
+ Don't forget to disable this option when you don't want to use speed focus add-on,\
+ otherwise this add-on will automatically reveal the answer after the time\
+ you set on speed focus add-on is passed. DON'T ENABLE THIS IF YOU DON'T\
+ HAVE SPEED FOCUS ADD-ON. {1}".format(begin, end))
+ speedFocus_label.setFixedWidth(180)
+ self.speedFocus_on = QRadioButton("On")
+ self.speedFocus_on.setFixedWidth(90)
+ self.speedFocus_off = QRadioButton("Off")
+ self.speedFocus_off.setFixedWidth(90)
+ speedFocus_holder = QHBoxLayout()
+ speedFocus_holder.addWidget(speedFocus_label)
+ speedFocus_holder.addWidget(self.speedFocus_on)
+ speedFocus_holder.addWidget(self.speedFocus_off)
+ speedFocus_holder.addStretch()
+ speedFocus_box = QGroupBox()
+ speedFocus_box.setLayout(speedFocus_holder)
+ configEdit_label = QLabel("Direct Config Edit:")
+ configEdit_label.setToolTip("{0} Enables direct config editor.\
+ If you enable this option, clicking on \"Config\" in add-ons window\
+ won't open ARBb settings window and will open the config editor instead.{1}".format(begin, end))
+ configEdit_label.setFixedWidth(180)
+ self.configEdit_on = QRadioButton("On")
+ self.configEdit_on.setFixedWidth(90)
+ self.configEdit_off = QRadioButton("Off")
+ self.configEdit_off.setFixedWidth(90)
+ configEdit_holder = QHBoxLayout()
+ configEdit_holder.addWidget(configEdit_label)
+ configEdit_holder.addWidget(self.configEdit_on)
+ configEdit_holder.addWidget(self.configEdit_off)
+ configEdit_holder.addStretch()
+ configEdit_box = QGroupBox()
+ configEdit_box.setLayout(configEdit_holder)
+ layout = QVBoxLayout()
+ layout.addWidget(general_box)
+ layout.addWidget(buttonColors_box)
+ layout.addWidget(speedFocus_box)
+ layout.addWidget(configEdit_box)
+ layout.addStretch()
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab8 = QScrollArea()
+ self.tab8.setFixedWidth(640)
+ self.tab8.setAlignment(Qt.AlignHCenter)
+ self.tab8.setWidgetResizable(True)
+ self.tab8.setWidget(layout_holder)
+ self.tab1.setDisabled(True)
+ if self.buttonColors_on.isChecked():
+ self.tab1.setEnabled(True)
+ self.buttonColors_on.toggled.connect(self.tab1.setEnabled)
+
+ def createNinthTab(self):
+ begin = self.begin
+ end = self.end
+ images = self.images
+ about_text = """
+
+ Don't know what each option does?
+ hover over the title and you'll see a brief description about that option
+
+ Wanna see what each design is like withot having to restart anki?
+ by hovering over options in front of titles, if the option is related to styling buttons
+ or changing how something looks like, you'll see pictures showing you what each option looks like
+ Hovering over them won't show you the animations, if the buttons are animated
+
+ Have an idea for a new feature?
+ Feel free to tell me in comment section on Add-on's Page or Email me
+
+ Saw a cool button design somewhere?
+ senf me a link to where you saw that design, i'll try to replicate that design
+ and put it on the add-on as and option for you to choose
+ Add-on's Page or my Email
+
+ Encountered a bug or some part is not acting how it's supposed to?
+ Tell me what your settings were on add-on, what's your anki version or if anki showed you an error log,
+ copy the error log and comment it on Add-on's Page or or Email me
+ (the more information you give me,
+ the sooner i find out what's causing the problem and i fix the bug)
+
+ Like the add-on?
+ Give it a like on Add-on's Page
+
+ """
+ about = QLabel()
+ about.setText(about_text)
+ about.setOpenExternalLinks(True)
+ about_scroll = QScrollArea()
+ about_scroll.setWidget(about)
+ changeLog_window = QDialog()
+ changeLog_window.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint)
+ changeLog_window.setWindowTitle("Changelog")
+ changeLog_window.setWindowIcon(QIcon(images + "\icon.png"))
+ changeLog_button = QPushButton("Show Changelog")
+ self.changeLog_webView = QWebEngineView()
+ self.loadChaneLog()
+ changeLog_layout = QVBoxLayout()
+ changeLog_layout.addWidget(self.changeLog_webView)
+ changeLog_window.setLayout(changeLog_layout)
+ changeLog_button.clicked.connect(lambda: changeLog_window.exec_())
+ layout = QVBoxLayout()
+ layout.addWidget(about_scroll)
+ layout.addWidget(changeLog_button)
+ layout_holder = QWidget()
+ layout_holder.setLayout(layout)
+ self.tab9 = QScrollArea()
+ self.tab9.setAlignment(Qt.AlignHCenter)
+ self.tab9.setWidgetResizable(True)
+ self.tab9.setWidget(layout_holder)
+
+ def loadChaneLog(self):
+ #// For some weird reason, using dirname(__file__) inside the .format() thingy doesn't seem to be working on macOS
+ #// Can't confirm tho -_- since I can't test my add-on on mac
+ addon_path = dirname(__file__)
+ file = "{}/changelog.html".format(addon_path)
+ with open(file, 'r') as f:
+ html = f.read()
+ self.changeLog_webView.setHtml(html)
+
+ def loadCurrent(self):
+ self.button_style.setCurrentIndex(C_button_style)
+ self.bottombarButtons_style.setCurrentIndex(C_bottombarButtons_style)
+ self.hover_effect.setCurrentIndex(C_hover_effect)
+ self.active_indicator.setCurrentIndex(C_active_indicator)
+ self.cursor_style.setCurrentIndex(C_cursor_style)
+ self.interval_style.setCurrentIndex(C_interval_style)
+ self.showAnswerBorderColor_style.setCurrentIndex(C_showAnswerBorderColor_style)
+ self.buttonTransition_time.setValue(int(C_buttonTransition_time))
+ self.buttonBorderRadius.setValue(int(C_buttonBorderRadius))
+ if C_style_mainScreenButtons:
+ self.style_mainScreenButtons.setChecked(True)
+ if C_reviewTooltip:
+ self.reviewTooltip_on.setChecked(True)
+ else:
+ self.reviewTooltip_off.setChecked(True)
+ self.reviewTooltip_style.setCurrentIndex(C_reviewTooltip_style)
+ self.reviewTooltip_timer.setValue(int(C_reviewTooltip_timer))
+ self.changeButtonColor(self.reviewTooltipTextColor_button, C_reviewTooltipText_color)
+ self.reviewTooltipPositionX.setValue(int(C_reviewTooltip_position[0]))
+ self.reviewTooltipPositionY.setValue(int(C_reviewTooltip_position[1]))
+ if C_info:
+ self.info.setChecked(True)
+ if C_skip:
+ self.skip.setChecked(True)
+ if C_showSkipped:
+ self.showSkipped.setChecked(True)
+ if C_undo:
+ self.undo.setChecked(True)
+ if C_hideHard:
+ self.hideHard.setChecked(True)
+ if C_hideGood:
+ self.hideGood.setChecked(True)
+ if C_hideEasy:
+ self.hideEasy.setChecked(True)
+ if C_right_info:
+ self.right_info.setChecked(True)
+ elif C_middleRight_info:
+ self.middleRight_info.setChecked(True)
+ elif C_middleLeft_info:
+ self.middleLeft_info.setChecked(True)
+ else:
+ self.left_info.setChecked(True)
+ if C_right_skip:
+ self.right_skip.setChecked(True)
+ elif C_middleRight_skip:
+ self.middleRight_skip.setChecked(True)
+ elif C_middleLeft_skip:
+ self.middleLeft_skip.setChecked(True)
+ else:
+ self.left_skip.setChecked(True)
+ if C_right_showSkipped:
+ self.right_showSkipped.setChecked(True)
+ elif C_middleRight_showSkipped:
+ self.middleRight_showSkipped.setChecked(True)
+ elif C_middleLeft_showSkipped:
+ self.middleLeft_showSkipped.setChecked(True)
+ else:
+ self.left_showSkipped.setChecked(True)
+ if C_right_undo:
+ self.right_undo.setChecked(True)
+ elif C_middleRight_undo:
+ self.middleRight_undo.setChecked(True)
+ elif C_middleLeft_undo:
+ self.middleLeft_undo.setChecked(True)
+ else:
+ self.left_undo.setChecked(True)
+ self.infoShortcut_button.setText("Change Shortcut (Current: {})".format(C_info_shortcut))
+ self.skipShortcut_button.setText("Change Shortcut (Current: {})".format(C_skip_shortcut))
+ self.showSkippedShortcut_button.setText("Change Shortcut (Current: {})".format(C_showSkipped_shortcut))
+ self.undoShortcut_button.setText("Change Shortcut (Current: {})".format(C_undo_shortcut))
+ if C_custom_sizes:
+ self.customSizes_on.setChecked(True)
+ else:
+ self.customSizes_off.setChecked(True)
+ self.text_size.setValue(int(C_text_size))
+ self.buttons_height.setValue(int(C_buttons_height))
+ self.reviewButtons_width.setValue(int(C_reviewButtons_width))
+ self.edit_width.setValue(int(C_edit_width))
+ self.answer_width.setValue(int(C_answer_width))
+ self.more_width.setValue(int(C_more_width))
+ self.info_width.setValue(int(C_info_width))
+ self.skip_width.setValue(int(C_skip_width))
+ self.showSkipped_width.setValue(int(C_showSkipped_width))
+ self.undo_width.setValue(int(C_undo_width))
+ self.buttonLabel_studyNow.setText(C_buttonLabel_studyNow)
+ self.buttonLabel_edit.setText(C_buttonLabel_edit)
+ self.buttonLabel_showAnswer.setText(C_buttonLabel_showAnswer)
+ self.buttonLabel_more.setText(C_buttonLabel_more)
+ self.buttonLabel_info.setText(C_buttonLabel_info)
+ self.buttonLabel_skip.setText(C_buttonLabel_skip)
+ self.buttonLabel_showSkipped.setText(C_buttonLabel_showSkipped)
+ self.buttonLabel_undo.setText(C_buttonLabel_undo)
+ self.buttonLabel_again.setText(C_buttonLabel_again)
+ self.buttonLabel_hard.setText(C_buttonLabel_hard)
+ self.buttonLabel_good.setText(C_buttonLabel_good)
+ self.buttonLabel_easy.setText(C_buttonLabel_easy)
+ self.sidebar_position.setCurrentIndex(C_sidebar_position)
+ self.sidebar_theme.setCurrentIndex(C_sidebar_theme)
+ self.sidebar_font.setCurrentFont(QFont(C_sidebar_font))
+ self.sidebar_PreviousCards.setValue(int(C_sidebar_PreviousCards))
+ self.sidebar_reviewsToShow.setValue(int(C_sidebar_reviewsToShow))
+ if C_sidebar_currentReviewCount:
+ self.sidebar_currentReviewCount.setChecked(True)
+ if C_sidebar_dateCreated:
+ self.sidebar_dateCreated.setChecked(True)
+ if C_sidebar_dateEdited:
+ self.sidebar_dateEdited.setChecked(True)
+ if C_sidebar_firstReview:
+ self.sidebar_firstReview.setChecked(True)
+ if C_sidebar_latestReview:
+ self.sidebar_latestReview.setChecked(True)
+ if C_sidebar_due:
+ self.sidebar_due.setChecked(True)
+ if C_sidebar_interval:
+ self.sidebar_interval.setChecked(True)
+ if C_sidebar_ease:
+ self.sidebar_ease.setChecked(True)
+ if C_sidebar_numberOfReviews:
+ self.sidebar_numberOfReviews.setChecked(True)
+ if C_sidebar_lapses:
+ self.sidebar_lapses.setChecked(True)
+ if C_sidebar_averageTime:
+ self.sidebar_averageTime.setChecked(True)
+ if C_sidebar_totalTime:
+ self.sidebar_totalTime.setChecked(True)
+ if C_sidebar_cardType:
+ self.sidebar_cardType.setChecked(True)
+ if C_sidebar_noteType:
+ self.sidebar_noteType.setChecked(True)
+ if C_sidebar_deck:
+ self.sidebar_deck.setChecked(True)
+ if C_sidebar_tags:
+ self.sidebar_tags.setChecked(True)
+ if C_sidebar_sortField:
+ self.sidebar_sortField.setChecked(True)
+ if C_sidebar_warningNote:
+ self.sidebar_warningNote.setChecked(True)
+ if C_infobar_correctPercent:
+ self.sidebar_correctPercent.setChecked(True)
+ if C_infobar_fastestReview:
+ self.sidebar_fastestReview.setChecked(True)
+ if C_infobar_slowestReview:
+ self.sidebar_slowestReview.setChecked(True)
+ if C_infobar_noteID:
+ self.sidebar_noteID.setChecked(True)
+ if C_infobar_cardID:
+ self.sidebar_cardID.setChecked(True)
+ if C_sidebar_autoOpen:
+ self.sidebar_autoOpen.setChecked(True)
+ if C_custom_reviewButtonColors:
+ self.custom_reviewButtonColors.setChecked(True)
+ if C_custom_reviewButtonTextColor:
+ self.custom_reviewButtonTextColor.setChecked(True)
+ if C_custom_activeIndicatorColor:
+ self.custom_activeIndicatorColor.setChecked(True)
+ if C_custom_bottombarButtonTextColor:
+ self.custom_bottombarButtonTextColor.setChecked(True)
+ if C_custom_bottombarButtonBorderColor:
+ self.custom_bottombarButtonBorderColor.setChecked(True)
+ self.changeButtonColor(self.reviewButtonTextColor_button, C_reviewButtonText_color)
+ self.changeButtonColor(self.activeIndicatorColor_button, C_activeIndicator_color)
+ self.changeButtonColor(self.bottombarButtonTextColor_button, C_bottombarButtonText_color)
+ self.changeButtonColor(self.bottombarButtonBorderColor_button, C_bottombarButtonBorder_color)
+ self.changeButtonColor(self.againColor_button, C_again_color)
+ self.changeButtonColor(self.againHoverColor_button, C_againHover_color)
+ self.changeButtonColor(self.hardColor_button, C_hard_color)
+ self.changeButtonColor(self.hardHoverColor_button, C_hardHover_color)
+ self.changeButtonColor(self.goodColor_button, C_good_color)
+ self.changeButtonColor(self.goodHoverColor_button, C_goodHover_color)
+ self.changeButtonColor(self.easyColor_button, C_easy_color)
+ self.changeButtonColor(self.easyHoverColor_button, C_easyHover_color)
+ self.showAnswerEase1.setValue(int(C_showAnswerEase1))
+ self.showAnswerEase2.setValue(int(C_showAnswerEase2))
+ self.showAnswerEase3.setValue(int(C_showAnswerEase3))
+ self.showAnswerEase4.setValue(int(C_showAnswerEase4))
+ self.changeButtonColor(self.showAnswerEase1_button, C_showAnswerEase1_color)
+ self.changeButtonColor(self.showAnswerEase2_button, C_showAnswerEase2_color)
+ self.changeButtonColor(self.showAnswerEase3_button, C_showAnswerEase3_color)
+ self.changeButtonColor(self.showAnswerEase4_button, C_showAnswerEase4_color)
+ if C_button_colors:
+ self.buttonColors_on.setChecked(True)
+ else:
+ self.buttonColors_off.setChecked(True)
+ if C_speedFocus:
+ self.speedFocus_on.setChecked(True)
+ else:
+ self.speedFocus_off.setChecked(True)
+ if C_configEdit:
+ self.configEdit_on.setChecked(True)
+ else:
+ self.configEdit_off.setChecked(True)
+ self.overViewStats.setCurrentIndex(C_overViewStats)
+ self.settingsMenu_place.setCurrentIndex(C_settingsMenu_palce)
+ self.skipMethod.setCurrentIndex(C_skipMethod)
+
+ def onLoadSettings(self):
+ addon_path = dirname(__file__)
+ #// Open a file browser to choose the settings file (returns a tuple) the first item in the tuple is the settings file location
+ fileName_tuple = QFileDialog.getOpenFileName(self, 'Open file', r'{}\user_files'.format(addon_path))
+ #// If user cancels the operation and no file is chosen, then return without doing anything
+ if not fileName_tuple[0]:
+ return
+ #// Select the settings file from the tuple
+ settingsFile = fileName_tuple[0]
+ #// Open and read the JSON File
+ settings = open("{}".format(settingsFile), "r")
+ conf = json.load(settings)
+ settingsFile_name = os.path.basename(settingsFile)
+ load = askUser("Replace current settings with settings file <{}>?".format(settingsFile_name), self, None, defaultno=True, title="Advanced Review Bottomabr")
+ if load:
+ mw.addonManager.writeConfig(__name__, conf)
+ showInfo("
Settings Loaded Succesfully.\
+
Changes will take \
+ effect after you restart anki.
", title="Advanced Review Bottombar Settings")
+ self.close()
+ refreshConfig()
+ else:
+ return
+ settings.close()
+
+ def onSaveSettings(self):
+ addon_path = dirname(__file__)
+ #// Choose a name for the backup file
+ file_name = "ARBb {}".format(datetime.now().strftime("%d-%b-%Y %H-%M-%S"))
+ path_to_file = "{}\\user_files\\{}.json".format(addon_path, file_name)
+ f = open(path_to_file, "w")
+
+ if self.left_skip.isChecked():
+ skip_position = "left"
+ elif self.middleRight_skip.isChecked():
+ skip_position ="middle right"
+ elif self.right_skip.isChecked():
+ skip_position ="right"
+ else:
+ skip_position = "middle left"
+ if self.left_showSkipped.isChecked():
+ showSkipped_position = "left"
+ elif self.middleRight_showSkipped.isChecked():
+ showSkipped_position ="middle right"
+ elif self.right_showSkipped.isChecked():
+ showSkipped_position ="right"
+ else:
+ showSkipped_position = "middle left"
+ if self.middleLeft_info.isChecked():
+ info_position = "middle left"
+ elif self.middleRight_info.isChecked():
+ info_position = "middle right"
+ elif self.right_info.isChecked():
+ info_position = "right"
+ else:
+ info_position = "left"
+ if self.left_undo.isChecked():
+ undo_position = "left"
+ elif self.middleLeft_undo.isChecked():
+ undo_position = "middle left"
+ elif self.right_undo.isChecked():
+ undo_position = "right"
+ else:
+ undo_position = "middle right"
+ if self.showAnswerEase1.value() > self.showAnswerEase2.value():
+ self.showAnswerEase2.setValue((self.showAnswerEase1.value() + 50))
+ if self.showAnswerEase2.value() > self.showAnswerEase3.value():
+ self.showAnswerEase3.setValue((self.showAnswerEase2.value() + 50))
+ if self.showAnswerEase3.value() > self.showAnswerEase4.value():
+ self.showAnswerEase4.setValue((self.showAnswerEase3.value() + 50))
+ conf = {
+ " Button Colors": self.buttonColors_on.isChecked(),
+ " Speed Focus Add-on": self.speedFocus_on.isChecked(),
+ " Direct Config Edit": self.configEdit_on.isChecked(),
+ " More Overview Stats": self.overViewStats.currentIndex(),
+ " Settings Menu Place": self.settingsMenu_place.currentIndex(),
+ " Skip Method": self.skipMethod.currentIndex(),
+ " Style Main Screen Buttons": self.style_mainScreenButtons.isChecked(),
+ " Review_ Active Button Indicator": self.active_indicator.currentIndex(),
+ " Review_ Buttons Style": self.button_style.currentIndex(),
+ " Review_ Hover Effect": self.hover_effect.currentIndex(),
+ " Review_ Custom Colors": self.custom_reviewButtonColors.isChecked(),
+ " Review_ Custom Review Button Text Color": self.custom_reviewButtonTextColor.isChecked(),
+ " Review_ Custom Active Indicator Color": self.custom_activeIndicatorColor.isChecked(),
+ " Review_ Bottombar Buttons Style": self.bottombarButtons_style.currentIndex(),
+ " Review_ Cursor Style": self.cursor_style.currentIndex(),
+ " Review_ Interval Style": self.interval_style.currentIndex(),
+ " Review_ Button Transition Time": self.buttonTransition_time.value(),
+ " Review_ Button Border Radius": self.buttonBorderRadius.value(),
+ "Button_ Info Button": self.info.isChecked(),
+ "Button_ Skip Button": self.skip.isChecked(),
+ "Button_ Show Skipped Button": self.showSkipped.isChecked(),
+ "Button_ Undo Button": self.undo.isChecked(),
+ "Button_ Hide Hard": self.hideHard.isChecked(),
+ "Button_ Hide Good": self.hideGood.isChecked(),
+ "Button_ Hide Easy": self.hideEasy.isChecked(),
+ "Button_ Custom Button Sizes": self.customSizes_on.isChecked(),
+ "Button_ Shortcut_ Skip Button": self.skip_shortcut,
+ "Button_ Shortcut_ Show Skipped Button": self.showSkipped_shortcut,
+ "Button_ Shortcut_ Info Button": self.info_shortcut,
+ "Button_ Shortcut_ Undo Button": self.undo_shortcut,
+ "Button_ Position_ Info Button": info_position,
+ "Button_ Position_ Skip Button": skip_position,
+ "Button_ Position_ Show Skipped Button": showSkipped_position,
+ "Button_ Position_ Undo Button": undo_position,
+ "Button_ Text Size": self.text_size.value(),
+ "Button_ Height_ All Bottombar Buttons": self.buttons_height.value(),
+ "Button_ Width_ Edit Button": self.edit_width.value(),
+ "Button_ Width_ Show Answer Button": self.answer_width.value(),
+ "Button_ Width_ Info Button": self.info_width.value(),
+ "Button_ Width_ Skip Button": self.skip_width.value(),
+ "Button_ Width_ Show Skipped Button": self.showSkipped_width.value(),
+ "Button_ Width_ More Button": self.more_width.value(),
+ "Button_ Width_ Review Buttons": self.reviewButtons_width.value(),
+ "Button_ Width_ Undo Button": self.undo_width.value(),
+ "Button Label_ Study Now": self.buttonLabel_studyNow.text(),
+ "Button Label_ Edit": self.buttonLabel_edit.text(),
+ "Button Label_ Show Answer": self.buttonLabel_showAnswer.text(),
+ "Button Label_ More": self.buttonLabel_more.text(),
+ "Button Label_ Info": self.buttonLabel_info.text(),
+ "Button Label_ Skip": self.buttonLabel_skip.text(),
+ "Button Label_ Show Skipped": self.buttonLabel_showSkipped.text(),
+ "Button Label_ Undo": self.buttonLabel_undo.text(),
+ "Button Label_ Again": self.buttonLabel_again.text(),
+ "Button Label_ Hard": self.buttonLabel_hard.text(),
+ "Button Label_ Good": self.buttonLabel_good.text(),
+ "Button Label_ Easy": self.buttonLabel_easy.text(),
+ "Card Info sidebar_ Number of previous cards to show": self.sidebar_PreviousCards.value(),
+ "Card Info sidebar_ Default Position": self.sidebar_position.currentIndex(),
+ "Card Info sidebar_ theme": self.sidebar_theme.currentIndex(),
+ "Card Info sidebar_ Created": self.sidebar_dateCreated.isChecked(),
+ "Card Info sidebar_ Edited": self.sidebar_dateEdited.isChecked(),
+ "Card Info sidebar_ First Review": self.sidebar_firstReview.isChecked(),
+ "Card Info sidebar_ Latest Review": self.sidebar_latestReview.isChecked(),
+ "Card Info sidebar_ Due": self.sidebar_due.isChecked(),
+ "Card Info sidebar_ Interval": self.sidebar_interval.isChecked(),
+ "Card Info sidebar_ Ease": self.sidebar_ease.isChecked(),
+ "Card Info sidebar_ Reviews": self.sidebar_numberOfReviews.isChecked(),
+ "Card Info sidebar_ Lapses": self.sidebar_lapses.isChecked(),
+ "Card Info Sidebar_ Correct Percent": self.sidebar_correctPercent.isChecked(),
+ "Card Info Sidebar_ Fastest Review": self.sidebar_fastestReview.isChecked(),
+ "Card Info Sidebar_ Slowest Review": self.sidebar_slowestReview.isChecked(),
+ "Card Info sidebar_ Average Time": self.sidebar_averageTime.isChecked(),
+ "Card Info sidebar_ Total Time": self.sidebar_totalTime.isChecked(),
+ "Card Info sidebar_ Card Type": self.sidebar_cardType.isChecked(),
+ "Card Info sidebar_ Note Type": self.sidebar_noteType.isChecked(),
+ "Card Info sidebar_ Deck": self.sidebar_deck.isChecked(),
+ "Card Info sidebar_ Tags": self.sidebar_tags.isChecked(),
+ "Card Info Sidebar_ Note ID": self.sidebar_noteID.isChecked(),
+ "Card Info Sidebar_ Card ID": self.sidebar_cardID.isChecked(),
+ "Card Info sidebar_ Sort Field": self.sidebar_sortField.isChecked(),
+ "Card Info sidebar_ Current Review Count": self.sidebar_currentReviewCount.isChecked(),
+ "Card Info sidebar_ Font": self.sidebar_font.currentFont().family(),
+ "Card Info sidebar_ number of reviews to show for a card": self.sidebar_reviewsToShow.value(),
+ "Card Info sidebar_ Auto Open": self.sidebar_autoOpen.isChecked(),
+ "Card Info sidebar_ warning note": self.sidebar_warningNote.isChecked(),
+ "Color_ General Text Color": self.reviewButtonText_color,
+ "Color_ Active Button Indicator": self.activeIndicator_color,
+ "Color_ Bottombar Button Text Color": self.bottombarButtonText_color,
+ "Color_ Bottombar Button Border Color": self.bottombarButtonBorder_color,
+ "Color_ Custom Bottombar Button Text Color": self.custom_bottombarButtonTextColor.isChecked(),
+ "Color_ Custom Bottombar Button Border Color": self.custom_bottombarButtonBorderColor.isChecked(),
+ "Color_ Again": self.again_color,
+ "Color_ Again on hover": self.againHover_color,
+ "Color_ Hard": self.hard_color,
+ "Color_ Hard on hover": self.hardHover_color,
+ "Color_ Good":self.good_color,
+ "Color_ Good on hover": self.goodHover_color,
+ "Color_ Easy": self.easy_color,
+ "Color_ Easy on hover": self.easyHover_color,
+ "Tooltip": self.reviewTooltip_on.isChecked(),
+ "Tooltip Timer": self.reviewTooltip_timer.value(),
+ "Tooltip Text Color": self.reviewTooltipText_color,
+ "Tooltip Style": self.reviewTooltip_style.currentIndex(),
+ "Tooltip Position": [self.reviewTooltipPositionX.value(), self.reviewTooltipPositionY.value()],
+ "ShowAnswer_ Border Color Style": self.showAnswerBorderColor_style.currentIndex(),
+ "ShowAnswer_ Ease1": self.showAnswerEase1.value(),
+ "ShowAnswer_ Ease2": self.showAnswerEase2.value(),
+ "ShowAnswer_ Ease3": self.showAnswerEase3.value(),
+ "ShowAnswer_ Ease4": self.showAnswerEase4.value(),
+ "ShowAnswer_ Ease1 Color": self.showAnswerEase1_color,
+ "ShowAnswer_ Ease2 Color": self.showAnswerEase2_color,
+ "ShowAnswer_ Ease3 Color": self.showAnswerEase3_color,
+ "ShowAnswer_ Ease4 Color": self.showAnswerEase4_color
+ }
+ #// Save settings in a JSON file
+ json.dump(conf, f, indent=4)
+ #// Open file explorer after saving so users know where the backup file is (and maybe save it somewhere else)
+ subprocess.Popen(f'explorer /select, "{path_to_file}"')
+ f.close()
+
+ def onApply(self):
+ if self.left_skip.isChecked():
+ skip_position = "left"
+ elif self.middleRight_skip.isChecked():
+ skip_position ="middle right"
+ elif self.right_skip.isChecked():
+ skip_position ="right"
+ else:
+ skip_position = "middle left"
+ if self.left_showSkipped.isChecked():
+ showSkipped_position = "left"
+ elif self.middleRight_showSkipped.isChecked():
+ showSkipped_position ="middle right"
+ elif self.right_showSkipped.isChecked():
+ showSkipped_position ="right"
+ else:
+ showSkipped_position = "middle left"
+ if self.middleLeft_info.isChecked():
+ info_position = "middle left"
+ elif self.middleRight_info.isChecked():
+ info_position = "middle right"
+ elif self.right_info.isChecked():
+ info_position = "right"
+ else:
+ info_position = "left"
+ if self.left_undo.isChecked():
+ undo_position = "left"
+ elif self.middleLeft_undo.isChecked():
+ undo_position = "middle left"
+ elif self.right_undo.isChecked():
+ undo_position = "right"
+ else:
+ undo_position = "middle right"
+ if self.showAnswerEase1.value() > self.showAnswerEase2.value():
+ self.showAnswerEase2.setValue((self.showAnswerEase1.value() + 50))
+ if self.showAnswerEase2.value() > self.showAnswerEase3.value():
+ self.showAnswerEase3.setValue((self.showAnswerEase2.value() + 50))
+ if self.showAnswerEase3.value() > self.showAnswerEase4.value():
+ self.showAnswerEase4.setValue((self.showAnswerEase3.value() + 50))
+
+ conf = {
+ " Button Colors": self.buttonColors_on.isChecked(),
+ " Speed Focus Add-on": self.speedFocus_on.isChecked(),
+ " Direct Config Edit": self.configEdit_on.isChecked(),
+ " More Overview Stats": self.overViewStats.currentIndex(),
+ " Settings Menu Place": self.settingsMenu_place.currentIndex(),
+ " Skip Method": self.skipMethod.currentIndex(),
+ " Style Main Screen Buttons": self.style_mainScreenButtons.isChecked(),
+ " Review_ Active Button Indicator": self.active_indicator.currentIndex(),
+ " Review_ Buttons Style": self.button_style.currentIndex(),
+ " Review_ Hover Effect": self.hover_effect.currentIndex(),
+ " Review_ Custom Colors": self.custom_reviewButtonColors.isChecked(),
+ " Review_ Custom Review Button Text Color": self.custom_reviewButtonTextColor.isChecked(),
+ " Review_ Custom Active Indicator Color": self.custom_activeIndicatorColor.isChecked(),
+ " Review_ Bottombar Buttons Style": self.bottombarButtons_style.currentIndex(),
+ " Review_ Cursor Style": self.cursor_style.currentIndex(),
+ " Review_ Interval Style": self.interval_style.currentIndex(),
+ " Review_ Button Transition Time": self.buttonTransition_time.value(),
+ " Review_ Button Border Radius": self.buttonBorderRadius.value(),
+ "Button_ Info Button": self.info.isChecked(),
+ "Button_ Skip Button": self.skip.isChecked(),
+ "Button_ Show Skipped Button": self.showSkipped.isChecked(),
+ "Button_ Undo Button": self.undo.isChecked(),
+ "Button_ Hide Hard": self.hideHard.isChecked(),
+ "Button_ Hide Good": self.hideGood.isChecked(),
+ "Button_ Hide Easy": self.hideEasy.isChecked(),
+ "Button_ Custom Button Sizes": self.customSizes_on.isChecked(),
+ "Button_ Shortcut_ Skip Button": self.skip_shortcut,
+ "Button_ Shortcut_ Show Skipped Button": self.showSkipped_shortcut,
+ "Button_ Shortcut_ Info Button": self.info_shortcut,
+ "Button_ Shortcut_ Undo Button": self.undo_shortcut,
+ "Button_ Position_ Info Button": info_position,
+ "Button_ Position_ Skip Button": skip_position,
+ "Button_ Position_ Show Skipped Button": showSkipped_position,
+ "Button_ Position_ Undo Button": undo_position,
+ "Button_ Text Size": self.text_size.value(),
+ "Button_ Height_ All Bottombar Buttons": self.buttons_height.value(),
+ "Button_ Width_ Edit Button": self.edit_width.value(),
+ "Button_ Width_ Show Answer Button": self.answer_width.value(),
+ "Button_ Width_ Info Button": self.info_width.value(),
+ "Button_ Width_ Skip Button": self.skip_width.value(),
+ "Button_ Width_ Show Skipped Button": self.showSkipped_width.value(),
+ "Button_ Width_ More Button": self.more_width.value(),
+ "Button_ Width_ Review Buttons": self.reviewButtons_width.value(),
+ "Button_ Width_ Undo Button": self.undo_width.value(),
+ "Button Label_ Study Now": self.buttonLabel_studyNow.text(),
+ "Button Label_ Edit": self.buttonLabel_edit.text(),
+ "Button Label_ Show Answer": self.buttonLabel_showAnswer.text(),
+ "Button Label_ More": self.buttonLabel_more.text(),
+ "Button Label_ Info": self.buttonLabel_info.text(),
+ "Button Label_ Skip": self.buttonLabel_skip.text(),
+ "Button Label_ Show Skipped": self.buttonLabel_showSkipped.text(),
+ "Button Label_ Undo": self.buttonLabel_undo.text(),
+ "Button Label_ Again": self.buttonLabel_again.text(),
+ "Button Label_ Hard": self.buttonLabel_hard.text(),
+ "Button Label_ Good": self.buttonLabel_good.text(),
+ "Button Label_ Easy": self.buttonLabel_easy.text(),
+ "Card Info sidebar_ Number of previous cards to show": self.sidebar_PreviousCards.value(),
+ "Card Info sidebar_ Default Position": self.sidebar_position.currentIndex(),
+ "Card Info sidebar_ theme": self.sidebar_theme.currentIndex(),
+ "Card Info sidebar_ Created": self.sidebar_dateCreated.isChecked(),
+ "Card Info sidebar_ Edited": self.sidebar_dateEdited.isChecked(),
+ "Card Info sidebar_ First Review": self.sidebar_firstReview.isChecked(),
+ "Card Info sidebar_ Latest Review": self.sidebar_latestReview.isChecked(),
+ "Card Info sidebar_ Due": self.sidebar_due.isChecked(),
+ "Card Info sidebar_ Interval": self.sidebar_interval.isChecked(),
+ "Card Info sidebar_ Ease": self.sidebar_ease.isChecked(),
+ "Card Info sidebar_ Reviews": self.sidebar_numberOfReviews.isChecked(),
+ "Card Info sidebar_ Lapses": self.sidebar_lapses.isChecked(),
+ "Card Info Sidebar_ Correct Percent": self.sidebar_correctPercent.isChecked(),
+ "Card Info Sidebar_ Fastest Review": self.sidebar_fastestReview.isChecked(),
+ "Card Info Sidebar_ Slowest Review": self.sidebar_slowestReview.isChecked(),
+ "Card Info sidebar_ Average Time": self.sidebar_averageTime.isChecked(),
+ "Card Info sidebar_ Total Time": self.sidebar_totalTime.isChecked(),
+ "Card Info sidebar_ Card Type": self.sidebar_cardType.isChecked(),
+ "Card Info sidebar_ Note Type": self.sidebar_noteType.isChecked(),
+ "Card Info sidebar_ Deck": self.sidebar_deck.isChecked(),
+ "Card Info sidebar_ Tags": self.sidebar_tags.isChecked(),
+ "Card Info Sidebar_ Note ID": self.sidebar_noteID.isChecked(),
+ "Card Info Sidebar_ Card ID": self.sidebar_cardID.isChecked(),
+ "Card Info sidebar_ Sort Field": self.sidebar_sortField.isChecked(),
+ "Card Info sidebar_ Current Review Count": self.sidebar_currentReviewCount.isChecked(),
+ "Card Info sidebar_ Font": self.sidebar_font.currentFont().family(),
+ "Card Info sidebar_ number of reviews to show for a card": self.sidebar_reviewsToShow.value(),
+ "Card Info sidebar_ Auto Open": self.sidebar_autoOpen.isChecked(),
+ "Card Info sidebar_ warning note": self.sidebar_warningNote.isChecked(),
+ "Color_ General Text Color": self.reviewButtonText_color,
+ "Color_ Active Button Indicator": self.activeIndicator_color,
+ "Color_ Bottombar Button Text Color": self.bottombarButtonText_color,
+ "Color_ Bottombar Button Border Color": self.bottombarButtonBorder_color,
+ "Color_ Custom Bottombar Button Text Color": self.custom_bottombarButtonTextColor.isChecked(),
+ "Color_ Custom Bottombar Button Border Color": self.custom_bottombarButtonBorderColor.isChecked(),
+ "Color_ Again": self.again_color,
+ "Color_ Again on hover": self.againHover_color,
+ "Color_ Hard": self.hard_color,
+ "Color_ Hard on hover": self.hardHover_color,
+ "Color_ Good":self.good_color,
+ "Color_ Good on hover": self.goodHover_color,
+ "Color_ Easy": self.easy_color,
+ "Color_ Easy on hover": self.easyHover_color,
+ "Tooltip": self.reviewTooltip_on.isChecked(),
+ "Tooltip Timer": self.reviewTooltip_timer.value(),
+ "Tooltip Text Color": self.reviewTooltipText_color,
+ "Tooltip Style": self.reviewTooltip_style.currentIndex(),
+ "Tooltip Position": [self.reviewTooltipPositionX.value(), self.reviewTooltipPositionY.value()],
+ "ShowAnswer_ Border Color Style": self.showAnswerBorderColor_style.currentIndex(),
+ "ShowAnswer_ Ease1": self.showAnswerEase1.value(),
+ "ShowAnswer_ Ease2": self.showAnswerEase2.value(),
+ "ShowAnswer_ Ease3": self.showAnswerEase3.value(),
+ "ShowAnswer_ Ease4": self.showAnswerEase4.value(),
+ "ShowAnswer_ Ease1 Color": self.showAnswerEase1_color,
+ "ShowAnswer_ Ease2 Color": self.showAnswerEase2_color,
+ "ShowAnswer_ Ease3 Color": self.showAnswerEase3_color,
+ "ShowAnswer_ Ease4 Color": self.showAnswerEase4_color
+ }
+ mw.addonManager.writeConfig(__name__, conf)
+ showInfo("
+ """.format(tooltip_textColor, text)
+ lab = CustomLabel(table, aw)
+ lab.setFrameStyle(QFrame.Panel)
+ lab.setLineWidth(2)
+ lab.setFixedWidth(button_width)
+ lab.setFixedHeight(button_height)
+ lab.setWindowFlags(Qt.ToolTip)
+ p = QPalette()
+ p.setColor(QPalette.Window, QColor(background_color))
+ p.setColor(QPalette.WindowText, QColor("transparent"))
+ lab.setPalette(p)
+ if tooltip_style == 0:
+ x_coordinate = int(x_offset+(aw.width()-button_width)/2)
+ y_coordinate = y_offset+aw.height()
+ lab.move(aw.mapToGlobal(QPoint(x_coordinate, y_coordinate)))
+ else:
+ x_coordinate = min(x_offset, aw.width())
+ y_coordinate = min(y_offset, aw.height())
+ lab.move(aw.mapToGlobal(QPoint(x_coordinate, y_coordinate)))
+ lab.show()
+ _tooltipTimer = aqt.mw.progress.timer(period, closeTooltip, False, requiresCollection=False)
+ _tooltipLabel = lab
+
+
+def closeTooltip():
+ global _tooltipLabel, _tooltipTimer
+ if _tooltipLabel:
+ try:
+ _tooltipLabel.deleteLater()
+ except:
+ # already deleted as parent window closed
+ pass
+ _tooltipLabel = None
+ if _tooltipTimer:
+ _tooltipTimer.stop()
+ _tooltipTimer = None
+
+Reviewer._answerCard = wrap(Reviewer._answerCard, myTooltip, 'before')
diff --git a/.local/share/Anki2/addons21/advanced_review/__init__.py b/.local/share/Anki2/addons21/advanced_review/__init__.py
new file mode 100644
index 0000000..b4f483f
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/__init__.py
@@ -0,0 +1,19 @@
+#// auth_ Mohamad Janati
+#// Copyright (c) 2019-2021 Mohamad Janati (freaking stupid, right? :|)
+
+from aqt import mw
+from anki import version
+from anki.collection import _Collection
+config = mw.addonManager.getConfig(__name__)
+button_colors = config[' Button Colors']
+reviewTooltip = config['Tooltip']
+
+anki_version = int(version.replace('.', ''))
+from . import Bottom_Bar
+from . import Settings
+from . import Deck_Overview
+if reviewTooltip:
+ from . import Tooltip
+
+if button_colors:
+ from . import Button_Colors
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Bottom_Bar.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Bottom_Bar.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..3bc79e9
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Bottom_Bar.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Button_Colors.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Button_Colors.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..dddcd88
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Button_Colors.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Card_Info.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Card_Info.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..84795e6
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Card_Info.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Deck_Overview.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Deck_Overview.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..63060e7
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Deck_Overview.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Settings.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Settings.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..7ff49ff
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Settings.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Skip.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Skip.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..f2392f6
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Skip.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/Tooltip.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/Tooltip.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..2adcc46
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/Tooltip.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/__init__.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/__init__.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..87562e0
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/__init__.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/__pycache__/styles.cpython-39.opt-2.pyc b/.local/share/Anki2/addons21/advanced_review/__pycache__/styles.cpython-39.opt-2.pyc
new file mode 100644
index 0000000..4e43aa8
Binary files /dev/null and b/.local/share/Anki2/addons21/advanced_review/__pycache__/styles.cpython-39.opt-2.pyc differ
diff --git a/.local/share/Anki2/addons21/advanced_review/changelog.html b/.local/share/Anki2/addons21/advanced_review/changelog.html
new file mode 100644
index 0000000..f364b34
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/changelog.html
@@ -0,0 +1,462 @@
+
+
+
+
+
+
+
+
2022/2/13
+
+
Added an option to change Card Info Sidebar default position
+
Fixed description button not showing on deck overview screen
+
Fixed a sidebar error caused by rescheduled cards
+
+
+
+
2022/1/30
+
+
Changed load settings prompt message (forgot to do it yesterday)
+
+
+
+
2022/1/29
+
+
Added a feature to change button text size [go to "Button Sizes" and change button text size]
+
Added another style for button intervals (now you can move button intervals inside the buttons) [to change button interval style go to "Styles" tab and change button interval style]
+
Added a feature to enable direct config edit (serves no purpose for now, don't enable it unless you're told to | The idea is to quickly be able to add new features without having to add options in settings menu)
+
Added an option to backup your settings (just press Backup Settings button and it will create a backup file of your settings - you can also share your settings and button stylings with other people by sharing the settings file)
+
Added an option to load settings file (you can load settings and not go through settings and changing different settings and styles)
+
Fixed button tooltip bug in python 3.10 (Thanks to @sdvcrx)
+
Removed "Restore Defaults" button (with the new "Load Settings" function, having this extra button doesn't make sense)
+
+
+
+
2021/9/22
+
+
Minor macOS bug fix (Hopefully -_-)
+
+
+
+
2021/9/15
+
+
Minor bug fix
+
+
+
+
2021/8/23
+
+
Detailed Deck Overview bug fix
+
+
+
+
2021/8/22
+
+
Added a new method for skipping cards.
+
+
This method is partially manual. The skipped cards won't show automatically unless you finish reviewing normal cards.
+
This method uses Anki's "Bury" function and buries skipped cards. The skipped cards will get unburied once you exit review screen or press press Button.
+
If you want for the skipped cards to show mid-review, you'll have to press Button or press the assigned shortcut (default shortcut is Alt + C).
+
If you use V3 sheduler, this is the only method that'll work for you and will be chosen by default.
+
If you use V2 scheduler you can use this method or the old method. You can choose the skip method in Settings Menu -> Misc -> Skip Method
+
The old method is Next Card and the new method is Bury.
+
The new "Bury" method might be a bit slower, especially when you use the button. If you choose to use this method, I suggets using shortcuts for skipping cards.
+
+
Adjusted Settings Menu height for better viewing on screens with lower resulotions
+
Moved changelog from main Settings Menu window to a separate window
+
+
+
+
2021/8/4
+
+
Bug fix (now ARBb is compatible with Anki 2.1.45)
+
From now on, No update will be released for Anki versions older than 2.1.45
+
+
+
+
2021/7/31
+
+
Bug Fix
+
+
+
+
2021/7/30
+
+
Bug Fix
+
+
+
+
2021/7/30
+
+
Added an option to set your custom text as button labels.
+ replace again, hard, good, easy, etc. text with your custom text or emoji.
+ To change button labels and use your own custom text, go to "Button label" tab in the settings.
+ To the person asking me how to change button labels -_- you can use this from now on. No need to change the code.
+
Added an option to hide hard, good, easy buttons. (Requested)
+ (no I haven't forgotten to put again in the list -_- you can't hide again button).
+ To use this option, go to "Bottombar Buttons" and look for "Hide Buttons" part there.
+
Added an option to change the roundness of the buttons.
+ To use this option, go to "Styles" tab and look for "Button Border Radius" there.
+
Removed pressed button stats from the add-on.
+ For those who used it, I'll be publishing it as a separate add on named "Pressed Button Stats"
+
+
+
+
2020/6/9
+
+
Added an option to turn off more overview stats.
+ pressed button count STILL at 90%
+
+
+
+
2020/12/6
+
+
Added another mode to overview stats (taken from "More Overview Stats 2.1")
+
Fixed conflict with speedfocus add-on (If you use speedfocus you need to enable "Speed focus" option in ARBb settings -> Misc)
+
+
+
2020/6/9
+
+
Added an option to turn off more overview stats.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/30
+
+
Changed tooltip behavior.
+ Now it's size won't be as size of the buttons when it's position is fixed.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/18
+
+
Minor code changes/improvements.
+ pressed button count STILL at 90%
+
+
+
+
2020/5/15
+
+
Now it designs review buttons that other add-ons add (like rememorize).
+ it treats them like other bottombar button so their color and style
+ will be like other bottombar buttons
+
you can style other bottombar buttons that are added by other add-on (like deferer button).
+ you'll need to change their code a bit. if you want to style them leave a comment here or on github page.
+ (the last picture is how the extra buttons the those add-on add look after styling them using this add-on)
Changed color of timer text in bottombar.
+ now it uses the same color you have set for other bottombar buttons text color. (not a big deal though, right?)
+ pressed button count STILL at 90%
+
+
+
+
2020/5/9
+
+
Made neon and fill designs customizable. now you can change their colors using "Colors" tab.
+ Enable custom colors by checking "Custom Review Button Colors" checkbox and
+ changing again, hard, good and easy colors.
+ as these designs don't have a separate hover color, changing hover colors won't
+ change anything about these buttons
+
Made review bottombar buttons, deck overview buttons and main screen bottombar buttons customizable.
+ you can change their border and text colors in "Colors" tab by changing "General Button" text and border colors.
+ you can't chage text or background color for general buttons if their button style is set on default.
+ to change general buttons style go to "Styles" tab and change "General Buttons Style".
+
Added an option to change show answer button border color based on card ease.
+ you can enable than option in "Style" tab by changing "Show Answer Border Color Style"
+ from "Fixed" to "Based on Card Ease". you cand change color for each ease range in "Colors" tab.
+ - (honestly i think it's gonna be usless for most of you :/ it was just something that i needed).
+
+ Other settings menu and bottombar buttons changes and improvements.
+ pressed button count STILL at 90%
+
+
+
+
2020/4/28
+
+
Added an option to choose card type [learn, review, re-learn, cram] for button count stats
+
Added an option to manually change decks in button count stats
+ at 90%
+
+
+
+
2020/5/01
+
+
Added total time and time per card to information shown in pressed button stats
+ at 85%
+
+
+
+
2020/4/28
+
+
Added an option to choose card type [learn, review, re-learn, cram] for button count stats
+
Added an option to manually change decks in button count stats
+ at 80%
+
+
+
+
2020/4/27
+
+
Added an option to choose time period for button count stats
+
Added an option to change button count stats scope
+
Button count stats window improvements
+ at 50%
+
+
+
+
2020/4/26
+
+
NEW FEATURE: pressed button count + Percent
+ NOTE: it's work in progress and very basic
+ the only reason i'm publishing it is that i want to hear you opinions on it and see what you need
+ I want to hear your ideas about it, tell me what i can do to make it better
+ you can Email me your ideas
+ however, i think some of you may want to change the time period for this option
+ to do that go to config -> Advanced review bottombar -> open add-on folder ->
+ open Button_Count.py -> go to line 47 you'll see what you need there
+ when you're on a deck, it shows pressed button stats for that deck,
+ when you're in main window, it'll show overall stats
+ at 15%
+
+
+
+
2020/4/22
+
+
Made styling main screen and deck overview compatible with anki versions older than 2.1.19
+
+
+
+
2020/4/21
+
+
Added an option to change main screen and deck overview buttons style
+ (Their style will be as other bottombar buttons style)
+
+
+
+
2020/4/20
+
+
Fixed tooltip bug (where it would show hard on tooltip when you
+ pressed good if you were in a cutom study session )
+
Added card info sidebar auto open (opens sidebar automatically when you review a card)
+
+
+
+
2020/4/18
+
+
Minor settings menu improvements
+
+
+
+
2020/4/17
+
+
Fixed Neon 1 style bug
+
Addded correct percentage, fastest reveiw, slowest review, note ID and card ID options to card info sidebar
+
+
+
+
2020/4/16
+
+
Added change button transition time option (for fill and neon designs only)
+
+
+
+
2020/4/15
+
+
Added an option to change cursor type when you hover over bottombar buttons
+
+
+
+
2020/4/14
+
+
Added answer tooltips
+
Adjusted tooltips for neon and fill designs
+
Adjusted tooltips for custom button sizes
+
+
+
+
2020/4/13
+
+
Added a function to get shortcuts (Don't have to test keys that you want to set as shortcuts anymore, if it's Anki's default shortcut for something, the add-on wont accept it)
+
Moved button design tooltip to another tab (noticed it was WAY too big for lower resulotions to be useful)
+
# NOTE: if you're updating from any version other than 2020/4/12 you might run into some problems trying to
+ open settings menu if you can't open settings menu after update open add-on folder and delete meta.json file if
+ that didn't help go to settings.py and put a # in front of the last line then go to tools -> add-ons and press restore defaults on this addon's config page
+
+
+
+
2020/4/12
+
+
Changed settings menu so it's easier to work with on lower resolutions (had to code it all over again)
+
Made picking colors completely automatic (no color code copy/paste, choose the color and it's set)
+
Added an option for you to choose settings menu's position
+
Made wide buttons compatible with no distractions add-on
+
# NOTE: After update you need to restore config to defaults in tools -> addons
+
+
+
+
2020/4/8
+
+
settings menu bugs fixes
+
settings menu minor adjustments for smaller screens
+
+
+
+
2020/4/7
+
+
settings menu improvements
+
added an option to color intervals
+
added an option to style other bottombar buttons
+
added 4 new button designs
+
+
+
+
2020/4/6
+
+
minor settings menu improvements
+
card info sidebar improvements for old scheduler
+
+
+
+
2020/4/5
+
+
minor settings menu improvements
+
added tooltips with pictures for different settings
+
fixed card info sidebar crash bug
+
+
+
+
2020/4/4
+
+
added settings menu
+
minor settings menu adjustments
+
+
+
+
2020/4/2
+
+
fix for wide buttons
+
fixed card info sidebar problem with beta versions of anki (2.1.23 and 2.1.24)
+
+
+
+
2020/4/1
+
+
fixed issue with limiting card reviews in card info sidebar
+
added an option to change active button indicator from border to glow and change it's color
+
+
+
+
2020/3/30
+
+
adjusted colors and gradients for background color change for light mode
+
added background shadow for review buttons (enable in config)
+
+
+
+
2020/3/29
+
+
added undo button (enable in config)
+
fixed button color for old scheduler
+
removed conflict with customize keyboard shortcuts add-on
+
removed conflict with speed focus add-on (needs to be enabled in config)
+
removed conflict with slackers add-on
+
added an option to choose text color in review button background color change
+
+
+
+
2020/3/26
+
+
added change button size option
+
+
+
+
2020/3/25
+
+
added change skip and info button position option
+
+
+
+
2020/3/20
+
+
fixed conflict with "replay button on card" add-on
+
+
+
+
2020/3/7
+
+
adjusted the color for review buttons
+
added an option to choose the font for the text in card info side bar in config
+
added an option so you could limit the maximum number of previous reviews that are shown on sidebar for a card
+
+
+
+
2020/3/6
+
+
made the info sidebar customizable, you can choose what you want to see on card info sidebar in config
+
+
+
+
2020/3/4
+
+
fixed not showing review button colors on new in-app night mode
+
adjusted review button text colors for new in-app night mode
+
adjusted wide button widths
+
+
+
+
2020/2/8
+
+
added an option for you to choose the shortcut key for skip and info buttons (in add-on config)
+
added an option to choose the sidebar theme (in add-on config)