move remaining Filter button items into sidebar
- Closes #976 - Added helper to apply arbitrary colour to an icon. - Fix #979 - low res icons in night mode. - The icons and colours are not perfect - please feel free to send through a PR if you can improve them. - Convert colors dictionary into module consts, so we can use code completion. - Added "Edited Today" and "Due Tomorrow" - Rename camelCase attribute to snake_case and tweak the wording of some enum constants. We've already broken compatibility with the major sidebar add-ons, so we may as well make these changes while we can. - Removed Filter button. Currently there is no exposed way to toggle the Sidebar off - wonder if we still need it?
This commit is contained in:
parent
bd730a012e
commit
b8d67cdad5
@ -4,6 +4,7 @@ browsing-add-tags2 = Add Tags...
|
||||
browsing-added-today = Added Today
|
||||
browsing-addon = Add-on
|
||||
browsing-again-today = Again Today
|
||||
browsing-edited-today = Edited Today
|
||||
browsing-all-card-types = All Card Types
|
||||
browsing-all-fields = All Fields
|
||||
browsing-answer = Answer
|
||||
@ -34,7 +35,6 @@ browsing-ease = Ease
|
||||
browsing-end = End
|
||||
browsing-enter-tags-to-add = Enter tags to add:
|
||||
browsing-enter-tags-to-delete = Enter tags to delete:
|
||||
browsing-filter = Filter...
|
||||
browsing-filtered = (filtered)
|
||||
browsing-find = <b>Find</b>:
|
||||
browsing-find-and-replace = Find and Replace
|
||||
@ -127,3 +127,8 @@ browsing-sidebar-tags = Tags
|
||||
browsing-sidebar-notetypes = Note Types
|
||||
browsing-sidebar-saved-searches = Saved Searches
|
||||
browsing-sidebar-save-current-search = Save Current Search
|
||||
browsing-sidebar-card-state = Card State
|
||||
browsing-sidebar-flags = Flags
|
||||
browsing-sidebar-recent = Recent
|
||||
browsing-sidebar-due-today = Due Today
|
||||
browsing-sidebar-due-tomorrow = Due Tomorrow
|
||||
|
@ -1,2 +0,0 @@
|
||||
# True if a card is due/ready for review
|
||||
filtering-is-due = Due
|
@ -2,4 +2,5 @@ from typing import NoReturn
|
||||
|
||||
|
||||
def assert_exhaustive(arg: NoReturn) -> NoReturn:
|
||||
"""The type definition will cause mypy to tell us if we've missed an enum case."""
|
||||
raise Exception(f"unexpected arg received: {type(arg)} {arg}")
|
||||
|
@ -488,6 +488,7 @@ def _run(argv: Optional[List[str]] = None, exec: bool = True) -> Optional[AnkiAp
|
||||
# opt in to full hidpi support?
|
||||
if not os.environ.get("ANKI_NOHIGHDPI"):
|
||||
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
||||
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
|
||||
os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "PassThrough"
|
||||
|
||||
|
@ -33,8 +33,6 @@ from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
TR,
|
||||
HelpPage,
|
||||
MenuList,
|
||||
SubMenu,
|
||||
askUser,
|
||||
disable_help_button,
|
||||
getTag,
|
||||
@ -63,10 +61,6 @@ from aqt.utils import (
|
||||
)
|
||||
from aqt.webview import AnkiWebView
|
||||
|
||||
# legacy add-on support
|
||||
# pylint: disable=unused-import
|
||||
from aqt.sidebar import SidebarItem, SidebarStage # isort: skip
|
||||
|
||||
|
||||
@dataclass
|
||||
class FindDupesDialog:
|
||||
@ -486,7 +480,6 @@ class Browser(QMainWindow):
|
||||
# pylint: disable=unnecessary-lambda
|
||||
# actions
|
||||
f = self.form
|
||||
qconnect(f.filter.clicked, self.onFilterButton)
|
||||
# edit
|
||||
qconnect(f.actionUndo.triggered, self.mw.onUndo)
|
||||
qconnect(f.actionInvertSelection.triggered, self.invertSelection)
|
||||
@ -978,34 +971,15 @@ QTableView {{ gridline-color: {grid} }}
|
||||
self.showSidebar()
|
||||
self.sidebar.searchBar.setFocus()
|
||||
|
||||
# legacy
|
||||
def maybeRefreshSidebar(self) -> None:
|
||||
self.sidebar.refresh()
|
||||
|
||||
def toggle_sidebar(self) -> None:
|
||||
want_visible = not self.sidebarDockWidget.isVisible()
|
||||
self.sidebarDockWidget.setVisible(want_visible)
|
||||
if want_visible:
|
||||
self.sidebar.refresh()
|
||||
|
||||
# Filter button and sidebar helpers
|
||||
# Sidebar helpers
|
||||
######################################################################
|
||||
|
||||
def onFilterButton(self) -> None:
|
||||
ml = MenuList()
|
||||
|
||||
ml.addChild(self._todayFilters())
|
||||
ml.addChild(self._cardStateFilters())
|
||||
ml.addSeparator()
|
||||
|
||||
toggle_sidebar = QAction(tr(TR.BROWSING_SIDEBAR))
|
||||
qconnect(toggle_sidebar.triggered, self.toggle_sidebar)
|
||||
toggle_sidebar.setCheckable(True)
|
||||
toggle_sidebar.setChecked(self.sidebarDockWidget.isVisible())
|
||||
ml.addChild(toggle_sidebar)
|
||||
|
||||
ml.popupOver(self.form.filter)
|
||||
|
||||
def update_search(self, *terms: Union[str, SearchTerm]) -> None:
|
||||
"""Modify the current search string based on modified keys, then refresh."""
|
||||
try:
|
||||
@ -1030,84 +1004,6 @@ QTableView {{ gridline-color: {grid} }}
|
||||
def setFilter(self, *terms: str) -> None:
|
||||
self.set_filter_then_search(*terms)
|
||||
|
||||
def _simpleFilters(self, items: Sequence[Tuple[str, SearchTerm]]) -> MenuList:
|
||||
ml = MenuList()
|
||||
for row in items:
|
||||
if row is None:
|
||||
ml.addSeparator()
|
||||
else:
|
||||
label, filter_name = row
|
||||
ml.addItem(label, self.sidebar._filter_func(filter_name))
|
||||
return ml
|
||||
|
||||
def _todayFilters(self) -> SubMenu:
|
||||
subm = SubMenu(tr(TR.BROWSING_TODAY))
|
||||
subm.addChild(
|
||||
self._simpleFilters(
|
||||
(
|
||||
(tr(TR.BROWSING_ADDED_TODAY), SearchTerm(added_in_days=1)),
|
||||
(
|
||||
tr(TR.BROWSING_STUDIED_TODAY),
|
||||
SearchTerm(rated=SearchTerm.Rated(days=1)),
|
||||
),
|
||||
(
|
||||
tr(TR.BROWSING_AGAIN_TODAY),
|
||||
SearchTerm(
|
||||
rated=SearchTerm.Rated(
|
||||
days=1, rating=SearchTerm.RATING_AGAIN
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
return subm
|
||||
|
||||
def _cardStateFilters(self) -> SubMenu:
|
||||
subm = SubMenu(tr(TR.BROWSING_CARD_STATE))
|
||||
subm.addChild(
|
||||
self._simpleFilters(
|
||||
(
|
||||
(
|
||||
tr(TR.ACTIONS_NEW),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_NEW),
|
||||
),
|
||||
(
|
||||
tr(TR.SCHEDULING_LEARNING),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_LEARN),
|
||||
),
|
||||
(
|
||||
tr(TR.SCHEDULING_REVIEW),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_REVIEW),
|
||||
),
|
||||
(
|
||||
tr(TR.FILTERING_IS_DUE),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_DUE),
|
||||
),
|
||||
None,
|
||||
(
|
||||
tr(TR.BROWSING_SUSPENDED),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_SUSPENDED),
|
||||
),
|
||||
(
|
||||
tr(TR.BROWSING_BURIED),
|
||||
SearchTerm(card_state=SearchTerm.CARD_STATE_BURIED),
|
||||
),
|
||||
None,
|
||||
(tr(TR.ACTIONS_RED_FLAG), SearchTerm(flag=SearchTerm.FLAG_RED)),
|
||||
(
|
||||
tr(TR.ACTIONS_ORANGE_FLAG),
|
||||
SearchTerm(flag=SearchTerm.FLAG_ORANGE),
|
||||
),
|
||||
(tr(TR.ACTIONS_GREEN_FLAG), SearchTerm(flag=SearchTerm.FLAG_GREEN)),
|
||||
(tr(TR.ACTIONS_BLUE_FLAG), SearchTerm(flag=SearchTerm.FLAG_BLUE)),
|
||||
(tr(TR.BROWSING_NO_FLAG), SearchTerm(flag=SearchTerm.FLAG_NONE)),
|
||||
(tr(TR.BROWSING_ANY_FLAG), SearchTerm(flag=SearchTerm.FLAG_ANY)),
|
||||
)
|
||||
)
|
||||
)
|
||||
return subm
|
||||
|
||||
# Info
|
||||
######################################################################
|
||||
|
||||
|
1
qt/aqt/colors.py
Symbolic link
1
qt/aqt/colors.py
Symbolic link
@ -0,0 +1 @@
|
||||
../../bazel-bin/qt/aqt/colors.py
|
@ -91,7 +91,7 @@
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="searchEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@ -107,13 +107,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="filter">
|
||||
<property name="text">
|
||||
<string>BROWSING_FILTER</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@ -151,12 +144,12 @@
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>20</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
@ -216,7 +209,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>750</width>
|
||||
<height>21</height>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
|
@ -1,11 +1,14 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>icons/anki.png</file>
|
||||
<file>icons/tag.svg</file>
|
||||
<file>icons/deck.svg</file>
|
||||
<file>icons/notetype.svg</file>
|
||||
<file>icons/heart.svg</file>
|
||||
<file>icons/collection.svg</file>
|
||||
<file>icons/media-record.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file>icons/anki.png</file>
|
||||
<file>icons/tag.svg</file>
|
||||
<file>icons/deck.svg</file>
|
||||
<file>icons/notetype.svg</file>
|
||||
<file>icons/heart.svg</file>
|
||||
<file>icons/collection.svg</file>
|
||||
<file>icons/media-record.png</file>
|
||||
<file>icons/clock.svg</file>
|
||||
<file>icons/card-state.svg</file>
|
||||
<file>icons/flag.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
12
qt/aqt/forms/icons/card-state.svg
Normal file
12
qt/aqt/forms/icons/card-state.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 61 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0,-78)">
|
||||
<g id="card-state" transform="matrix(0.938173,0,0,0.938173,-241.492,-55.2956)">
|
||||
<rect x="257.964" y="142.08" width="63.954" height="63.954" style="fill:none;"/>
|
||||
<g transform="matrix(5.59598,0,0,5.59598,110.312,5.90138)">
|
||||
<circle cx="32" cy="30" r="4"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 832 B |
25
qt/aqt/forms/icons/clock.svg
Normal file
25
qt/aqt/forms/icons/clock.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 60 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||
<g transform="matrix(1,0,0,1,-70,-78)">
|
||||
<g id="clock" transform="matrix(0.938173,0,0,0.938173,-172.014,-55.2956)">
|
||||
<rect x="257.964" y="142.08" width="63.954" height="63.954" style="fill:none;"/>
|
||||
<g transform="matrix(0.400362,0,0,0.400362,168.807,-30.4403)">
|
||||
<g transform="matrix(1,0,0,1,-14.7441,-15.49)">
|
||||
<path d="M317.306,450.972C358.893,450.972 392.605,484.684 392.605,526.271C392.605,567.858 358.893,601.571 317.306,601.571C275.719,601.571 242.006,567.858 242.006,526.271C242.006,484.684 275.719,450.972 317.306,450.972ZM321.122,451.963L351.335,460.058L347.8,473.251L357.458,463.593L379.575,485.711L369.917,495.369L383.11,491.834L391.206,522.047L378.013,525.582L391.206,529.117L383.11,559.33L369.917,555.794L379.575,565.452L357.458,587.57L347.8,577.912L351.335,591.105L321.122,599.2L317.587,586.007L314.052,599.2L283.839,591.105L287.374,577.912L277.716,587.57L255.599,565.452L265.257,555.794L252.064,559.33L243.968,529.117L257.161,525.582L243.968,522.047L252.064,491.834L265.257,495.369L255.599,485.711L277.716,463.593L287.374,473.251L283.839,460.058L314.052,451.963L317.587,465.156L321.122,451.963Z" style="fill:rgb(115,115,115);stroke:black;stroke-width:5.55px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-0.0402259,-5.42135)">
|
||||
<path d="M302.583,519.144C302.583,503.542 301.608,474.556 301.608,458.936" style="fill:none;stroke:black;stroke-width:11.09px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-1.89244,1.87233)">
|
||||
<path d="M304.779,510.287L329.081,496.966" style="fill:none;stroke:black;stroke-width:11.09px;"/>
|
||||
</g>
|
||||
<path d="M302.707,451.966L307.7,461.953L297.714,461.953L302.707,451.966Z" style="fill:rgb(115,115,115);stroke:black;stroke-width:11.09px;"/>
|
||||
<g transform="matrix(0.487004,0.8734,-0.8734,0.487004,328.596,498.57)">
|
||||
<path d="M0,-5.016L5.016,5.016L-5.016,5.016L0,-5.016Z" style="fill:rgb(115,115,115);stroke:black;stroke-width:11.09px;"/>
|
||||
</g>
|
||||
<circle cx="303.526" cy="511.396" r="3.888" style="fill:rgb(115,115,115);stroke:black;stroke-width:11.09px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
13
qt/aqt/forms/icons/flag.svg
Normal file
13
qt/aqt/forms/icons/flag.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 60 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="flag" transform="matrix(1,0,0,1.06667,-268.333,0)">
|
||||
<rect x="268.333" y="0" width="60" height="60" style="fill:none;"/>
|
||||
<g transform="matrix(0.8,0,0,0.973558,207.333,-4.03846)">
|
||||
<rect x="85" y="8" width="5" height="52"/>
|
||||
</g>
|
||||
<g transform="matrix(0.914894,0,0,0.837054,196.993,-1.27232)">
|
||||
<path d="M137,6C137,6 130.12,4.079 122.791,4.88C114.785,5.755 112.375,9.441 103.116,9.36C98.955,9.324 90,6 90,6L90,34C90,34 98.617,37.72 103.116,37.36C112.889,36.577 119.329,32.32 124.977,31.76C129.034,31.358 137,34 137,34L137,6Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -5,7 +5,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from concurrent.futures import Future
|
||||
from enum import Enum
|
||||
from enum import Enum, auto
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
import aqt
|
||||
@ -13,11 +13,12 @@ from anki.collection import ConfigBoolKey, SearchTerm
|
||||
from anki.decks import DeckTreeNode
|
||||
from anki.errors import DeckRenameError, InvalidInput
|
||||
from anki.tags import TagTreeNode
|
||||
from aqt import gui_hooks
|
||||
from anki.types import assert_exhaustive
|
||||
from aqt import colors, gui_hooks
|
||||
from aqt.main import ResetReason
|
||||
from aqt.models import Models
|
||||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.theme import ColoredIcon, theme_manager
|
||||
from aqt.utils import (
|
||||
TR,
|
||||
askUser,
|
||||
@ -30,39 +31,51 @@ from aqt.utils import (
|
||||
|
||||
|
||||
class SidebarItemType(Enum):
|
||||
ROOT = 0
|
||||
COLLECTION = 1
|
||||
CURRENT_DECK = 2
|
||||
SAVED_SEARCH = 3
|
||||
FILTER = 3 # legacy alias for SAVED_SEARCH
|
||||
DECK = 4
|
||||
NOTETYPE = 5
|
||||
TAG = 6
|
||||
CUSTOM = 7
|
||||
TEMPLATE = 8
|
||||
SAVED_SEARCH_ROOT = 9
|
||||
DECK_ROOT = 10
|
||||
NOTETYPE_ROOT = 11
|
||||
TAG_ROOT = 12
|
||||
ROOT = auto()
|
||||
SAVED_SEARCH_ROOT = auto()
|
||||
SAVED_SEARCH = auto()
|
||||
RECENT_ROOT = auto()
|
||||
RECENT = auto()
|
||||
CARD_STATE_ROOT = auto()
|
||||
CARD_STATE = auto()
|
||||
FLAG_ROOT = auto()
|
||||
FLAG = auto()
|
||||
DECK_ROOT = auto()
|
||||
DECK = auto()
|
||||
NOTETYPE_ROOT = auto()
|
||||
NOTETYPE = auto()
|
||||
NOTETYPE_TEMPLATE = auto()
|
||||
TAG_ROOT = auto()
|
||||
TAG = auto()
|
||||
|
||||
CUSTOM = auto()
|
||||
|
||||
@staticmethod
|
||||
def section_roots() -> Iterable[SidebarItemType]:
|
||||
return (type for type in SidebarItemType if type.name.endswith("_ROOT"))
|
||||
|
||||
def is_section_root(self) -> bool:
|
||||
return self in self.section_roots()
|
||||
|
||||
|
||||
# used by an add-on hook
|
||||
class SidebarStage(Enum):
|
||||
ROOT = 0
|
||||
STANDARD = 1
|
||||
FAVORITES = 2
|
||||
DECKS = 3
|
||||
MODELS = 4
|
||||
TAGS = 5
|
||||
ROOT = auto()
|
||||
SAVED_SEARCHES = auto()
|
||||
RECENT = auto()
|
||||
SCHEDULING = auto()
|
||||
FLAGS = auto()
|
||||
DECKS = auto()
|
||||
NOTETYPES = auto()
|
||||
TAGS = auto()
|
||||
|
||||
|
||||
class SidebarItem:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
icon: str,
|
||||
onClick: Callable[[], None] = None,
|
||||
onExpanded: Callable[[bool], None] = None,
|
||||
icon: Union[str, ColoredIcon],
|
||||
on_click: Callable[[], None] = None,
|
||||
on_expanded: Callable[[bool], None] = None,
|
||||
expanded: bool = False,
|
||||
item_type: SidebarItemType = SidebarItemType.CUSTOM,
|
||||
id: int = 0,
|
||||
@ -75,39 +88,47 @@ class SidebarItem:
|
||||
self.icon = icon
|
||||
self.item_type = item_type
|
||||
self.id = id
|
||||
self.onClick = onClick
|
||||
self.onExpanded = onExpanded
|
||||
self.expanded = expanded
|
||||
self.on_click = on_click
|
||||
self.on_expanded = on_expanded
|
||||
self.children: List["SidebarItem"] = []
|
||||
self.parentItem: Optional["SidebarItem"] = None
|
||||
self.tooltip: Optional[str] = None
|
||||
self.row_in_parent: Optional[int] = None
|
||||
self._parent_item: Optional["SidebarItem"] = None
|
||||
self._is_expanded = expanded
|
||||
self._row_in_parent: Optional[int] = None
|
||||
self._search_matches_self = False
|
||||
self._search_matches_child = False
|
||||
|
||||
def addChild(self, cb: "SidebarItem") -> None:
|
||||
def add_child(self, cb: "SidebarItem") -> None:
|
||||
self.children.append(cb)
|
||||
cb.parentItem = self
|
||||
cb._parent_item = self
|
||||
|
||||
def rowForChild(self, child: "SidebarItem") -> Optional[int]:
|
||||
try:
|
||||
return self.children.index(child)
|
||||
except ValueError:
|
||||
return None
|
||||
def add_simple(
|
||||
self,
|
||||
name: Union[str, TR.V],
|
||||
icon: Union[str, ColoredIcon],
|
||||
type: SidebarItemType,
|
||||
on_click: Callable[[], None],
|
||||
) -> SidebarItem:
|
||||
"Add child sidebar item, and return it."
|
||||
if not isinstance(name, str):
|
||||
name = tr(name)
|
||||
item = SidebarItem(
|
||||
name=name,
|
||||
icon=icon,
|
||||
on_click=on_click,
|
||||
item_type=type,
|
||||
)
|
||||
self.add_child(item)
|
||||
return item
|
||||
|
||||
def is_expanded(self, searching: bool) -> bool:
|
||||
if not searching:
|
||||
return self.expanded
|
||||
return self._is_expanded
|
||||
else:
|
||||
if self._search_matches_child:
|
||||
return True
|
||||
# if search matches top level, expand children one level
|
||||
return self._search_matches_self and self.item_type in (
|
||||
SidebarItemType.SAVED_SEARCH_ROOT,
|
||||
SidebarItemType.DECK_ROOT,
|
||||
SidebarItemType.NOTETYPE_ROOT,
|
||||
SidebarItemType.TAG_ROOT,
|
||||
)
|
||||
return self._search_matches_self and self.item_type.is_section_root()
|
||||
|
||||
def is_highlighted(self) -> bool:
|
||||
return self._search_matches_self
|
||||
@ -130,7 +151,7 @@ class SidebarModel(QAbstractItemModel):
|
||||
def _cache_rows(self, node: SidebarItem) -> None:
|
||||
"Cache index of children in parent."
|
||||
for row, item in enumerate(node.children):
|
||||
item.row_in_parent = row
|
||||
item._row_in_parent = row
|
||||
self._cache_rows(item)
|
||||
|
||||
def item_for_index(self, idx: QModelIndex) -> SidebarItem:
|
||||
@ -172,12 +193,12 @@ class SidebarModel(QAbstractItemModel):
|
||||
return QModelIndex()
|
||||
|
||||
childItem: SidebarItem = child.internalPointer()
|
||||
parentItem = childItem.parentItem
|
||||
parentItem = childItem._parent_item
|
||||
|
||||
if parentItem is None or parentItem == self.root:
|
||||
return QModelIndex()
|
||||
|
||||
row = parentItem.row_in_parent
|
||||
row = parentItem._row_in_parent
|
||||
|
||||
return self.createIndex(row, 0, parentItem)
|
||||
|
||||
@ -216,30 +237,6 @@ class SidebarModel(QAbstractItemModel):
|
||||
|
||||
return cast(Qt.ItemFlags, flags)
|
||||
|
||||
# Helpers
|
||||
######################################################################
|
||||
|
||||
def iconFromRef(self, iconRef: str) -> QIcon:
|
||||
print("iconFromRef() deprecated")
|
||||
return theme_manager.icon_from_resources(iconRef)
|
||||
|
||||
|
||||
def expand_where_necessary(
|
||||
model: SidebarModel,
|
||||
tree: QTreeView,
|
||||
parent: Optional[QModelIndex] = None,
|
||||
searching: bool = False,
|
||||
) -> None:
|
||||
parent = parent or QModelIndex()
|
||||
for row in range(model.rowCount(parent)):
|
||||
idx = model.index(row, 0, parent)
|
||||
if not idx.isValid():
|
||||
continue
|
||||
expand_where_necessary(model, tree, idx, searching)
|
||||
if item := model.item_for_index(idx):
|
||||
if item.is_expanded(searching):
|
||||
tree.setExpanded(idx, True)
|
||||
|
||||
|
||||
class SidebarSearchBar(QLineEdit):
|
||||
def __init__(self, sidebar: SidebarTreeView) -> None:
|
||||
@ -308,8 +305,8 @@ class SidebarTreeView(QTreeView):
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setDragDropOverwriteMode(False)
|
||||
|
||||
qconnect(self.expanded, self.onExpansion)
|
||||
qconnect(self.collapsed, self.onCollapse)
|
||||
qconnect(self.expanded, self._on_expansion)
|
||||
qconnect(self.collapsed, self._on_collapse)
|
||||
|
||||
# match window background color
|
||||
bgcolor = QPalette().window().color().name()
|
||||
@ -334,7 +331,7 @@ class SidebarTreeView(QTreeView):
|
||||
if self.current_search:
|
||||
self.search_for(self.current_search)
|
||||
else:
|
||||
expand_where_necessary(model, self)
|
||||
self._expand_where_necessary(model)
|
||||
|
||||
self.mw.taskman.run_in_background(self._root_tree, on_done)
|
||||
|
||||
@ -349,7 +346,26 @@ class SidebarTreeView(QTreeView):
|
||||
# start from a collapsed state, as it's faster
|
||||
self.collapseAll()
|
||||
self.setColumnHidden(0, not self.model().search(text))
|
||||
expand_where_necessary(self.model(), self, searching=True)
|
||||
self._expand_where_necessary(self.model(), searching=True)
|
||||
|
||||
def _expand_where_necessary(
|
||||
self,
|
||||
model: SidebarModel,
|
||||
parent: Optional[QModelIndex] = None,
|
||||
searching: bool = False,
|
||||
) -> None:
|
||||
parent = parent or QModelIndex()
|
||||
for row in range(model.rowCount(parent)):
|
||||
idx = model.index(row, 0, parent)
|
||||
if not idx.isValid():
|
||||
continue
|
||||
self._expand_where_necessary(model, idx, searching)
|
||||
if item := model.item_for_index(idx):
|
||||
if item.is_expanded(searching):
|
||||
self.setExpanded(idx, True)
|
||||
|
||||
# Qt API
|
||||
###########
|
||||
|
||||
def drawRow(
|
||||
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
|
||||
@ -369,6 +385,19 @@ class SidebarTreeView(QTreeView):
|
||||
if self.handle_drag_drop(source_items, target_item):
|
||||
event.acceptProposedAction()
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
super().mouseReleaseEvent(event)
|
||||
if event.button() == Qt.LeftButton:
|
||||
self._on_click_current()
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||
self._on_click_current()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
###########
|
||||
|
||||
def handle_drag_drop(self, sources: List[SidebarItem], target: SidebarItem) -> bool:
|
||||
if target.item_type in (SidebarItemType.DECK, SidebarItemType.DECK_ROOT):
|
||||
return self._handle_drag_drop_decks(sources, target)
|
||||
@ -433,74 +462,70 @@ class SidebarTreeView(QTreeView):
|
||||
self.browser.editor.saveNow(on_save)
|
||||
return True
|
||||
|
||||
def onClickCurrent(self) -> None:
|
||||
def _on_click_current(self) -> None:
|
||||
idx = self.currentIndex()
|
||||
if item := self.model().item_for_index(idx):
|
||||
if item.onClick:
|
||||
item.onClick()
|
||||
if item.on_click:
|
||||
item.on_click()
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
super().mouseReleaseEvent(event)
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.onClickCurrent()
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||
self.onClickCurrent()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def onExpansion(self, idx: QModelIndex) -> None:
|
||||
def _on_expansion(self, idx: QModelIndex) -> None:
|
||||
if self.current_search:
|
||||
return
|
||||
self._onExpansionChange(idx, True)
|
||||
self._on_expand_or_collapse(idx, True)
|
||||
|
||||
def onCollapse(self, idx: QModelIndex) -> None:
|
||||
def _on_collapse(self, idx: QModelIndex) -> None:
|
||||
if self.current_search:
|
||||
return
|
||||
self._onExpansionChange(idx, False)
|
||||
self._on_expand_or_collapse(idx, False)
|
||||
|
||||
def _onExpansionChange(self, idx: QModelIndex, expanded: bool) -> None:
|
||||
def _on_expand_or_collapse(self, idx: QModelIndex, expanded: bool) -> None:
|
||||
item = self.model().item_for_index(idx)
|
||||
if item and item.expanded != expanded:
|
||||
item.expanded = expanded
|
||||
if item.onExpanded:
|
||||
item.onExpanded(expanded)
|
||||
if item and item._is_expanded != expanded:
|
||||
item._is_expanded = expanded
|
||||
if item.on_expanded:
|
||||
item.on_expanded(expanded)
|
||||
|
||||
# Tree building
|
||||
###########################
|
||||
|
||||
def _root_tree(self) -> SidebarItem:
|
||||
root = SidebarItem("", "", item_type=SidebarItemType.ROOT)
|
||||
root: Optional[SidebarItem] = None
|
||||
|
||||
handled = gui_hooks.browser_will_build_tree(
|
||||
False, root, SidebarStage.ROOT, self
|
||||
)
|
||||
if handled:
|
||||
return root
|
||||
|
||||
for stage, builder in zip(
|
||||
list(SidebarStage)[1:],
|
||||
(
|
||||
self._commonly_used_tree,
|
||||
self._saved_searches_tree,
|
||||
self._deck_tree,
|
||||
self._notetype_tree,
|
||||
self._tag_tree,
|
||||
),
|
||||
):
|
||||
for stage in SidebarStage:
|
||||
if stage == SidebarStage.ROOT:
|
||||
root = SidebarItem("", "", item_type=SidebarItemType.ROOT)
|
||||
handled = gui_hooks.browser_will_build_tree(False, root, stage, self)
|
||||
if not handled and builder:
|
||||
builder(root)
|
||||
if not handled:
|
||||
self._build_stage(root, stage)
|
||||
|
||||
return root
|
||||
|
||||
def _build_stage(self, root: SidebarItem, stage: SidebarStage) -> None:
|
||||
if stage is SidebarStage.SAVED_SEARCHES:
|
||||
self._saved_searches_tree(root)
|
||||
elif stage is SidebarStage.SCHEDULING:
|
||||
self._card_state_tree(root)
|
||||
elif stage is SidebarStage.RECENT:
|
||||
self._recent_tree(root)
|
||||
elif stage is SidebarStage.FLAGS:
|
||||
self._flags_tree(root)
|
||||
elif stage is SidebarStage.DECKS:
|
||||
self._deck_tree(root)
|
||||
elif stage is SidebarStage.NOTETYPES:
|
||||
self._notetype_tree(root)
|
||||
elif stage is SidebarStage.TAGS:
|
||||
self._tag_tree(root)
|
||||
elif stage is SidebarStage.ROOT:
|
||||
pass
|
||||
else:
|
||||
assert_exhaustive(stage)
|
||||
|
||||
def _section_root(
|
||||
self,
|
||||
*,
|
||||
root: SidebarItem,
|
||||
name: TR.V,
|
||||
icon: str,
|
||||
icon: Union[str, ColoredIcon],
|
||||
collapse_key: ConfigBoolKey.V,
|
||||
type: Optional[SidebarItemType] = None,
|
||||
) -> SidebarItem:
|
||||
@ -510,29 +535,19 @@ class SidebarTreeView(QTreeView):
|
||||
top = SidebarItem(
|
||||
tr(name),
|
||||
icon,
|
||||
onExpanded=update,
|
||||
on_expanded=update,
|
||||
expanded=not self.col.get_config_bool(collapse_key),
|
||||
item_type=type,
|
||||
)
|
||||
root.addChild(top)
|
||||
root.add_child(top)
|
||||
|
||||
return top
|
||||
|
||||
def _commonly_used_tree(self, root: SidebarItem) -> None:
|
||||
item = SidebarItem(
|
||||
tr(TR.BROWSING_WHOLE_COLLECTION),
|
||||
":/icons/collection.svg",
|
||||
self._filter_func(),
|
||||
item_type=SidebarItemType.COLLECTION,
|
||||
)
|
||||
root.addChild(item)
|
||||
item = SidebarItem(
|
||||
tr(TR.BROWSING_CURRENT_DECK),
|
||||
":/icons/deck.svg",
|
||||
self._filter_func(SearchTerm(deck="current")),
|
||||
item_type=SidebarItemType.CURRENT_DECK,
|
||||
)
|
||||
root.addChild(item)
|
||||
def _filter_func(self, *terms: Union[str, SearchTerm]) -> Callable:
|
||||
return lambda: self.browser.update_search(self.col.build_search_string(*terms))
|
||||
|
||||
# Tree: Saved Searches
|
||||
###########################
|
||||
|
||||
def _saved_searches_tree(self, root: SidebarItem) -> None:
|
||||
icon = ":/icons/heart.svg"
|
||||
@ -542,23 +557,187 @@ class SidebarTreeView(QTreeView):
|
||||
root=root,
|
||||
name=TR.BROWSING_SIDEBAR_SAVED_SEARCHES,
|
||||
icon=icon,
|
||||
collapse_key=ConfigBoolKey.COLLAPSE_FAVORITES,
|
||||
collapse_key=ConfigBoolKey.COLLAPSE_SAVED_SEARCHES,
|
||||
type=SidebarItemType.SAVED_SEARCH_ROOT,
|
||||
)
|
||||
|
||||
def on_click() -> None:
|
||||
self.show_context_menu(root, None)
|
||||
|
||||
root.onClick = on_click
|
||||
root.on_click = on_click
|
||||
|
||||
for name, filt in sorted(saved.items()):
|
||||
item = SidebarItem(
|
||||
name,
|
||||
icon,
|
||||
self._filter_func(filt),
|
||||
item_type=SidebarItemType.FILTER,
|
||||
item_type=SidebarItemType.SAVED_SEARCH,
|
||||
)
|
||||
root.addChild(item)
|
||||
root.add_child(item)
|
||||
|
||||
# Tree: Recent
|
||||
###########################
|
||||
|
||||
def _recent_tree(self, root: SidebarItem) -> None:
|
||||
icon = ":/icons/clock.svg"
|
||||
root = self._section_root(
|
||||
root=root,
|
||||
name=TR.BROWSING_SIDEBAR_RECENT,
|
||||
icon=icon,
|
||||
collapse_key=ConfigBoolKey.COLLAPSE_RECENT,
|
||||
type=SidebarItemType.FLAG_ROOT,
|
||||
)
|
||||
type = SidebarItemType.FLAG
|
||||
search = self._filter_func
|
||||
|
||||
root.add_simple(
|
||||
TR.BROWSING_CURRENT_DECK,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(deck="current")),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_SIDEBAR_DUE_TODAY,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_DUE)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_ADDED_TODAY,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(added_in_days=1)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_EDITED_TODAY,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(edited_in_days=1)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_STUDIED_TODAY,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(rated=SearchTerm.Rated(days=1))),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_AGAIN_TODAY,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(
|
||||
SearchTerm(
|
||||
rated=SearchTerm.Rated(days=1, rating=SearchTerm.RATING_AGAIN)
|
||||
)
|
||||
),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_SIDEBAR_DUE_TOMORROW,
|
||||
icon=icon,
|
||||
type=type,
|
||||
on_click=search(SearchTerm(due_in_days=1)),
|
||||
)
|
||||
|
||||
# Tree: Card State
|
||||
###########################
|
||||
|
||||
def _card_state_tree(self, root: SidebarItem) -> None:
|
||||
icon = ColoredIcon(path=":/icons/card-state.svg", color=colors.DISABLED)
|
||||
root = self._section_root(
|
||||
root=root,
|
||||
name=TR.BROWSING_SIDEBAR_CARD_STATE,
|
||||
icon=icon,
|
||||
collapse_key=ConfigBoolKey.COLLAPSE_CARD_STATE,
|
||||
type=SidebarItemType.CARD_STATE_ROOT,
|
||||
)
|
||||
type = SidebarItemType.CARD_STATE
|
||||
search = self._filter_func
|
||||
|
||||
root.add_simple(
|
||||
TR.ACTIONS_NEW,
|
||||
icon=icon.with_color(colors.NEW_COUNT),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_NEW)),
|
||||
)
|
||||
|
||||
root.add_simple(
|
||||
name=TR.SCHEDULING_LEARNING,
|
||||
icon=icon.with_color(colors.LEARN_COUNT),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_LEARN)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.SCHEDULING_REVIEW,
|
||||
icon=icon.with_color(colors.REVIEW_COUNT),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_REVIEW)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_SUSPENDED,
|
||||
icon=icon.with_color(colors.SUSPENDED_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_SUSPENDED)),
|
||||
)
|
||||
root.add_simple(
|
||||
name=TR.BROWSING_BURIED,
|
||||
icon=icon.with_color(colors.BURIED_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(card_state=SearchTerm.CARD_STATE_BURIED)),
|
||||
)
|
||||
|
||||
# Tree: Flags
|
||||
###########################
|
||||
|
||||
def _flags_tree(self, root: SidebarItem) -> None:
|
||||
icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED)
|
||||
root = self._section_root(
|
||||
root=root,
|
||||
name=TR.BROWSING_SIDEBAR_FLAGS,
|
||||
icon=icon,
|
||||
collapse_key=ConfigBoolKey.COLLAPSE_FLAGS,
|
||||
type=SidebarItemType.FLAG_ROOT,
|
||||
)
|
||||
type = SidebarItemType.FLAG
|
||||
search = self._filter_func
|
||||
|
||||
root.add_simple(
|
||||
TR.ACTIONS_RED_FLAG,
|
||||
icon=icon.with_color(colors.FLAG1_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_RED)),
|
||||
)
|
||||
root.add_simple(
|
||||
TR.ACTIONS_ORANGE_FLAG,
|
||||
icon=icon.with_color(colors.FLAG2_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_ORANGE)),
|
||||
)
|
||||
root.add_simple(
|
||||
TR.ACTIONS_GREEN_FLAG,
|
||||
icon=icon.with_color(colors.FLAG3_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_GREEN)),
|
||||
)
|
||||
root.add_simple(
|
||||
TR.ACTIONS_BLUE_FLAG,
|
||||
icon=icon.with_color(colors.FLAG4_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_BLUE)),
|
||||
)
|
||||
root.add_simple(
|
||||
TR.BROWSING_ANY_FLAG,
|
||||
icon=icon.with_color(colors.TEXT_FG),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_ANY)),
|
||||
)
|
||||
root.add_simple(
|
||||
TR.BROWSING_NO_FLAG,
|
||||
icon=icon.with_color(colors.DISABLED),
|
||||
type=type,
|
||||
on_click=search(SearchTerm(flag=SearchTerm.FLAG_NONE)),
|
||||
)
|
||||
|
||||
# Tree: Tags
|
||||
###########################
|
||||
|
||||
def _tag_tree(self, root: SidebarItem) -> None:
|
||||
icon = ":/icons/tag.svg"
|
||||
@ -583,7 +762,7 @@ class SidebarTreeView(QTreeView):
|
||||
item_type=SidebarItemType.TAG,
|
||||
full_name=head + node.name,
|
||||
)
|
||||
root.addChild(item)
|
||||
root.add_child(item)
|
||||
newhead = head + node.name + "::"
|
||||
render(item, node.children, newhead)
|
||||
|
||||
@ -597,6 +776,9 @@ class SidebarTreeView(QTreeView):
|
||||
)
|
||||
render(root, tree.children)
|
||||
|
||||
# Tree: Decks
|
||||
###########################
|
||||
|
||||
def _deck_tree(self, root: SidebarItem) -> None:
|
||||
icon = ":/icons/deck.svg"
|
||||
|
||||
@ -619,7 +801,7 @@ class SidebarTreeView(QTreeView):
|
||||
id=node.deck_id,
|
||||
full_name=head + node.name,
|
||||
)
|
||||
root.addChild(item)
|
||||
root.add_child(item)
|
||||
newhead = head + node.name + "::"
|
||||
render(item, node.children, newhead)
|
||||
|
||||
@ -633,6 +815,9 @@ class SidebarTreeView(QTreeView):
|
||||
)
|
||||
render(root, tree.children)
|
||||
|
||||
# Tree: Notetypes
|
||||
###########################
|
||||
|
||||
def _notetype_tree(self, root: SidebarItem) -> None:
|
||||
icon = ":/icons/notetype.svg"
|
||||
root = self._section_root(
|
||||
@ -659,15 +844,12 @@ class SidebarTreeView(QTreeView):
|
||||
self._filter_func(
|
||||
SearchTerm(note=nt["name"]), SearchTerm(template=c)
|
||||
),
|
||||
item_type=SidebarItemType.TEMPLATE,
|
||||
item_type=SidebarItemType.NOTETYPE_TEMPLATE,
|
||||
full_name=nt["name"] + "::" + tmpl["name"],
|
||||
)
|
||||
item.addChild(child)
|
||||
item.add_child(child)
|
||||
|
||||
root.addChild(item)
|
||||
|
||||
def _filter_func(self, *terms: Union[str, SearchTerm]) -> Callable:
|
||||
return lambda: self.browser.update_search(self.col.build_search_string(*terms))
|
||||
root.add_child(item)
|
||||
|
||||
# Context menu actions
|
||||
###########################
|
||||
@ -735,7 +917,7 @@ class SidebarTreeView(QTreeView):
|
||||
lambda: set_children_collapsed(True),
|
||||
)
|
||||
|
||||
def rename_deck(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def rename_deck(self, item: SidebarItem) -> None:
|
||||
deck = self.mw.col.decks.get(item.id)
|
||||
old_name = deck["name"]
|
||||
new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name)
|
||||
@ -751,10 +933,10 @@ class SidebarTreeView(QTreeView):
|
||||
self.refresh()
|
||||
self.mw.deckBrowser.refresh()
|
||||
|
||||
def remove_tag(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def remove_tag(self, item: SidebarItem) -> None:
|
||||
self.browser.editor.saveNow(lambda: self._remove_tag(item))
|
||||
|
||||
def _remove_tag(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def _remove_tag(self, item: SidebarItem) -> None:
|
||||
old_name = item.full_name
|
||||
|
||||
def do_remove() -> None:
|
||||
@ -771,10 +953,10 @@ class SidebarTreeView(QTreeView):
|
||||
self.browser.model.beginReset()
|
||||
self.mw.taskman.run_in_background(do_remove, on_done)
|
||||
|
||||
def rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def rename_tag(self, item: SidebarItem) -> None:
|
||||
self.browser.editor.saveNow(lambda: self._rename_tag(item))
|
||||
|
||||
def _rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def _rename_tag(self, item: SidebarItem) -> None:
|
||||
old_name = item.full_name
|
||||
new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name)
|
||||
if new_name == old_name or not new_name:
|
||||
@ -799,10 +981,10 @@ class SidebarTreeView(QTreeView):
|
||||
self.browser.model.beginReset()
|
||||
self.mw.taskman.run_in_background(do_rename, on_done)
|
||||
|
||||
def delete_deck(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def delete_deck(self, item: SidebarItem) -> None:
|
||||
self.browser.editor.saveNow(lambda: self._delete_deck(item))
|
||||
|
||||
def _delete_deck(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def _delete_deck(self, item: SidebarItem) -> None:
|
||||
did = item.id
|
||||
if self.mw.deckBrowser.ask_delete_deck(did):
|
||||
|
||||
@ -820,7 +1002,7 @@ class SidebarTreeView(QTreeView):
|
||||
self.browser.model.beginReset()
|
||||
self.mw.taskman.run_in_background(do_delete, on_done)
|
||||
|
||||
def remove_saved_search(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def remove_saved_search(self, item: SidebarItem) -> None:
|
||||
name = item.name
|
||||
if not askUser(tr(TR.BROWSING_REMOVE_FROM_YOUR_SAVED_SEARCHES, val=name)):
|
||||
return
|
||||
@ -829,7 +1011,7 @@ class SidebarTreeView(QTreeView):
|
||||
self.col.set_config("savedFilters", conf)
|
||||
self.refresh()
|
||||
|
||||
def rename_saved_search(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def rename_saved_search(self, item: SidebarItem) -> None:
|
||||
old = item.name
|
||||
conf = self.col.get_config("savedFilters")
|
||||
try:
|
||||
@ -860,7 +1042,7 @@ class SidebarTreeView(QTreeView):
|
||||
self.col.set_config("savedFilters", conf)
|
||||
self.refresh()
|
||||
|
||||
def manage_notetype(self, item: "aqt.browser.SidebarItem") -> None:
|
||||
def manage_notetype(self, item: SidebarItem) -> None:
|
||||
Models(
|
||||
self.mw, parent=self.browser, fromMain=True, selected_notetype_id=item.id
|
||||
)
|
||||
|
@ -2,14 +2,32 @@
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
from typing import Dict, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, Tuple, Union
|
||||
|
||||
from anki.utils import isMac
|
||||
from aqt import QApplication, gui_hooks, isWin
|
||||
from aqt.colors import colors
|
||||
from aqt import QApplication, colors, gui_hooks, isWin
|
||||
from aqt.platform import set_dark_mode
|
||||
from aqt.qt import QColor, QIcon, QPalette, QPixmap, QStyleFactory, Qt
|
||||
from aqt.qt import QColor, QIcon, QPainter, QPalette, QPixmap, QStyleFactory, Qt
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColoredIcon:
|
||||
path: str
|
||||
# (day, night)
|
||||
color: Tuple[str, str]
|
||||
|
||||
def current_color(self, night_mode: bool) -> str:
|
||||
if night_mode:
|
||||
return self.color[1]
|
||||
else:
|
||||
return self.color[0]
|
||||
|
||||
def with_color(self, color: Tuple[str, str]) -> ColoredIcon:
|
||||
return ColoredIcon(path=self.path, color=color)
|
||||
|
||||
|
||||
class ThemeManager:
|
||||
@ -43,22 +61,39 @@ class ThemeManager:
|
||||
|
||||
night_mode = property(get_night_mode, set_night_mode)
|
||||
|
||||
def icon_from_resources(self, path: str) -> QIcon:
|
||||
def icon_from_resources(self, path: Union[str, ColoredIcon]) -> QIcon:
|
||||
"Fetch icon from Qt resources, and invert if in night mode."
|
||||
if self.night_mode:
|
||||
cache = self._icon_cache_light
|
||||
else:
|
||||
cache = self._icon_cache_dark
|
||||
icon = cache.get(path)
|
||||
|
||||
if isinstance(path, str):
|
||||
key = path
|
||||
else:
|
||||
key = f"{path.path}-{path.color}"
|
||||
|
||||
icon = cache.get(key)
|
||||
if icon:
|
||||
return icon
|
||||
|
||||
icon = QIcon(path)
|
||||
|
||||
if self.night_mode:
|
||||
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
|
||||
img.invertPixels()
|
||||
icon = QIcon(QPixmap(img))
|
||||
if isinstance(path, str):
|
||||
# default black/white
|
||||
icon = QIcon(path)
|
||||
if self.night_mode:
|
||||
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
|
||||
img.invertPixels()
|
||||
icon = QIcon(QPixmap(img))
|
||||
else:
|
||||
# specified colours
|
||||
icon = QIcon(path.path)
|
||||
img = icon.pixmap(16)
|
||||
painter = QPainter(img)
|
||||
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
|
||||
painter.fillRect(img.rect(), QColor(path.current_color(self.night_mode)))
|
||||
painter.end()
|
||||
icon = QIcon(img)
|
||||
return icon
|
||||
|
||||
return cache.setdefault(path, icon)
|
||||
|
||||
@ -94,10 +129,10 @@ class ThemeManager:
|
||||
|
||||
Returns the color as a string hex code or color name."""
|
||||
idx = 1 if self.night_mode else 0
|
||||
c = colors.get(key)
|
||||
if c is None:
|
||||
raise Exception("no such color:", key)
|
||||
return c[idx]
|
||||
|
||||
key = key.replace("-", "_").upper()
|
||||
|
||||
return getattr(colors, key)[idx]
|
||||
|
||||
def qcolor(self, key: str) -> QColor:
|
||||
"""Get a color defined in _vars.scss as a QColor."""
|
||||
|
@ -805,12 +805,17 @@ def checkInvalidFilename(str: str, dirsep: bool = True) -> bool:
|
||||
|
||||
# Menus
|
||||
######################################################################
|
||||
# This code will be removed in the future, please don't rely on it.
|
||||
|
||||
MenuListChild = Union["SubMenu", QAction, "MenuItem", "MenuList"]
|
||||
|
||||
|
||||
class MenuList:
|
||||
def __init__(self) -> None:
|
||||
traceback.print_stack(file=sys.stdout)
|
||||
print(
|
||||
"MenuList will be removed; please copy it into your add-on's code if you need it."
|
||||
)
|
||||
self.children: List[MenuListChild] = []
|
||||
|
||||
def addItem(self, title: str, func: Callable) -> MenuItem:
|
||||
|
1
qt/icons/README.md
Normal file
1
qt/icons/README.md
Normal file
@ -0,0 +1 @@
|
||||
Source files used to produce some of the svg/png files.
|
BIN
qt/icons/sidebar.afdesign
Normal file
BIN
qt/icons/sidebar.afdesign
Normal file
Binary file not shown.
@ -32,4 +32,6 @@ for line in open(input_scss):
|
||||
|
||||
with open(output_py, "w") as buf:
|
||||
buf.write("# this file is auto-generated from _vars.scss\n")
|
||||
buf.write("colors = " + json.dumps(colors))
|
||||
for color, (day, night) in colors.items():
|
||||
color = color.replace("-", "_").upper()
|
||||
buf.write(f"{color} = (\"{day}\", \"{night}\")\n")
|
||||
|
@ -304,8 +304,8 @@ hooks = [
|
||||
name="browser_will_build_tree",
|
||||
args=[
|
||||
"handled: bool",
|
||||
"tree: aqt.browser.SidebarItem",
|
||||
"stage: aqt.browser.SidebarStage",
|
||||
"tree: aqt.sidebar.SidebarItem",
|
||||
"stage: aqt.sidebar.SidebarStage",
|
||||
"browser: aqt.browser.Browser",
|
||||
],
|
||||
return_type="bool",
|
||||
@ -316,7 +316,7 @@ hooks = [
|
||||
'stage' is an enum describing the different construction stages of
|
||||
the sidebar tree at which you can interject your changes.
|
||||
The different values can be inspected by looking at
|
||||
aqt.browser.SidebarStage.
|
||||
aqt.sidebar.SidebarStage.
|
||||
|
||||
If you want Anki to proceed with the construction of the tree stage
|
||||
in question after your have performed your changes or additions,
|
||||
|
@ -821,6 +821,7 @@ message SearchTerm {
|
||||
Flag flag = 11;
|
||||
CardState card_state = 12;
|
||||
IdList nids = 13;
|
||||
uint32 edited_in_days = 14;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1222,8 +1223,10 @@ message ConfigBool {
|
||||
COLLAPSE_TAGS = 2;
|
||||
COLLAPSE_NOTETYPES = 3;
|
||||
COLLAPSE_DECKS = 4;
|
||||
COLLAPSE_FAVORITES = 5;
|
||||
COLLAPSE_COMMON = 6;
|
||||
COLLAPSE_SAVED_SEARCHES = 5;
|
||||
COLLAPSE_RECENT = 6;
|
||||
COLLAPSE_CARD_STATE = 7;
|
||||
COLLAPSE_FLAGS = 8;
|
||||
}
|
||||
Key key = 1;
|
||||
}
|
||||
|
@ -330,6 +330,7 @@ impl From<pb::SearchTerm> for Node<'_> {
|
||||
operator: "<=".to_string(),
|
||||
kind: PropertyKind::Due(i),
|
||||
}),
|
||||
Filter::EditedInDays(u) => Node::Search(SearchNode::EditedInDays(u)),
|
||||
Filter::CardState(state) => Node::Search(SearchNode::State(
|
||||
pb::search_term::CardState::from_i32(state)
|
||||
.unwrap_or_default()
|
||||
|
@ -42,10 +42,12 @@ pub(crate) enum ConfigKey {
|
||||
BrowserSortKind,
|
||||
BrowserSortReverse,
|
||||
CardCountsSeparateInactive,
|
||||
CollapseCommon,
|
||||
CollapseCardState,
|
||||
CollapseDecks,
|
||||
CollapseFavorites,
|
||||
CollapseFlags,
|
||||
CollapseNotetypes,
|
||||
CollapseRecent,
|
||||
CollapseSavedSearches,
|
||||
CollapseTags,
|
||||
CreationOffset,
|
||||
CurrentDeckID,
|
||||
@ -79,9 +81,11 @@ impl From<ConfigKey> for &'static str {
|
||||
ConfigKey::BrowserSortKind => "sortType",
|
||||
ConfigKey::BrowserSortReverse => "sortBackwards",
|
||||
ConfigKey::CardCountsSeparateInactive => "cardCountsSeparateInactive",
|
||||
ConfigKey::CollapseCommon => "collapseCommon",
|
||||
ConfigKey::CollapseRecent => "collapseRecent",
|
||||
ConfigKey::CollapseCardState => "collapseCardState",
|
||||
ConfigKey::CollapseFlags => "collapseFlags",
|
||||
ConfigKey::CollapseDecks => "collapseDecks",
|
||||
ConfigKey::CollapseFavorites => "collapseFavorites",
|
||||
ConfigKey::CollapseSavedSearches => "collapseSavedSearches",
|
||||
ConfigKey::CollapseNotetypes => "collapseNotetypes",
|
||||
ConfigKey::CollapseTags => "collapseTags",
|
||||
ConfigKey::CreationOffset => "creationOffset",
|
||||
@ -109,12 +113,14 @@ impl From<BoolKey> for ConfigKey {
|
||||
fn from(key: BoolKey) -> Self {
|
||||
match key {
|
||||
BoolKey::BrowserSortBackwards => ConfigKey::BrowserSortReverse,
|
||||
BoolKey::PreviewBothSides => ConfigKey::PreviewBothSides,
|
||||
BoolKey::CollapseTags => ConfigKey::CollapseTags,
|
||||
BoolKey::CollapseNotetypes => ConfigKey::CollapseNotetypes,
|
||||
BoolKey::CollapseCardState => ConfigKey::CollapseCardState,
|
||||
BoolKey::CollapseDecks => ConfigKey::CollapseDecks,
|
||||
BoolKey::CollapseFavorites => ConfigKey::CollapseFavorites,
|
||||
BoolKey::CollapseCommon => ConfigKey::CollapseCommon,
|
||||
BoolKey::CollapseFlags => ConfigKey::CollapseFlags,
|
||||
BoolKey::CollapseNotetypes => ConfigKey::CollapseNotetypes,
|
||||
BoolKey::CollapseRecent => ConfigKey::CollapseRecent,
|
||||
BoolKey::CollapseSavedSearches => ConfigKey::CollapseSavedSearches,
|
||||
BoolKey::CollapseTags => ConfigKey::CollapseTags,
|
||||
BoolKey::PreviewBothSides => ConfigKey::PreviewBothSides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,16 @@
|
||||
--highlight-bg: #77ccff;
|
||||
--highlight-fg: black;
|
||||
--disabled: #777;
|
||||
--flag1-fg: #c35617;
|
||||
--flag2-fg: #ffb347;
|
||||
--flag3-fg: #0a0;
|
||||
--flag4-fg: #77ccff;
|
||||
--flag1-bg: #ffaaaa;
|
||||
--flag2-bg: #ffb347;
|
||||
--flag3-bg: #82e0aa;
|
||||
--flag4-bg: #85c1e9;
|
||||
--buried-fg: #aaaa33;
|
||||
--suspended-fg: #dd0;
|
||||
--suspended-bg: #ffffb2;
|
||||
--marked-bg: #cce;
|
||||
--tooltip-bg: #fcfcfc;
|
||||
@ -40,10 +46,16 @@
|
||||
--highlight-bg: #77ccff;
|
||||
--highlight-fg: white;
|
||||
--disabled: #777;
|
||||
--flag1-fg: #ffaaaa;
|
||||
--flag2-fg: #ffb347;
|
||||
--flag3-fg: #82e0aa;
|
||||
--flag4-fg: #85c1e9;
|
||||
--flag1-bg: #aa5555;
|
||||
--flag2-bg: #aa6337;
|
||||
--flag3-bg: #33a055;
|
||||
--flag4-bg: #3581a9;
|
||||
--buried-fg: #777733;
|
||||
--suspended-fg: #ffffb2;
|
||||
--suspended-bg: #aaaa33;
|
||||
--marked-bg: #77c;
|
||||
--tooltip-bg: #272727;
|
||||
|
Loading…
Reference in New Issue
Block a user