2020-10-10 03:42:49 +02:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
from enum import Enum, auto
|
2021-10-03 10:59:42 +02:00
|
|
|
from typing import Iterable, cast
|
2020-10-10 03:42:49 +02:00
|
|
|
|
|
|
|
import aqt
|
2022-02-13 04:40:47 +01:00
|
|
|
import aqt.browser
|
|
|
|
import aqt.operations
|
2021-05-28 19:18:21 +02:00
|
|
|
from anki.collection import (
|
|
|
|
Config,
|
|
|
|
OpChanges,
|
|
|
|
OpChangesWithCount,
|
|
|
|
SearchJoiner,
|
|
|
|
SearchNode,
|
|
|
|
)
|
2021-05-31 08:24:51 +02:00
|
|
|
from anki.decks import DeckCollapseScope, DeckId, DeckTreeNode
|
2021-03-27 13:03:19 +01:00
|
|
|
from anki.models import NotetypeId
|
2021-03-10 10:14:06 +01:00
|
|
|
from anki.notes import Note
|
2021-01-31 06:55:08 +01:00
|
|
|
from anki.tags import TagTreeNode
|
2021-02-05 06:26:12 +01:00
|
|
|
from anki.types import assert_exhaustive
|
|
|
|
from aqt import colors, gui_hooks
|
2021-07-13 16:34:02 +02:00
|
|
|
from aqt.browser.find_and_replace import FindAndReplaceDialog
|
2021-04-13 11:05:49 +02:00
|
|
|
from aqt.browser.sidebar.item import SidebarItem, SidebarItemType
|
|
|
|
from aqt.browser.sidebar.model import SidebarModel
|
|
|
|
from aqt.browser.sidebar.searchbar import SidebarSearchBar
|
|
|
|
from aqt.browser.sidebar.toolbar import SidebarTool, SidebarToolbar
|
2021-03-10 10:14:06 +01:00
|
|
|
from aqt.clayout import CardLayout
|
2021-06-16 15:40:48 +02:00
|
|
|
from aqt.fields import FieldDialog
|
2021-01-20 00:00:53 +01:00
|
|
|
from aqt.models import Models
|
2021-05-21 09:50:41 +02:00
|
|
|
from aqt.operations import CollectionOp, QueryOp
|
2021-04-05 02:21:50 +02:00
|
|
|
from aqt.operations.deck import (
|
|
|
|
remove_decks,
|
|
|
|
rename_deck,
|
|
|
|
reparent_decks,
|
|
|
|
set_deck_collapsed,
|
|
|
|
)
|
2021-04-05 03:41:53 +02:00
|
|
|
from aqt.operations.tag import (
|
|
|
|
remove_tags_from_all_notes,
|
|
|
|
rename_tag,
|
|
|
|
reparent_tags,
|
|
|
|
set_tag_collapsed,
|
|
|
|
)
|
2020-10-10 03:42:49 +02:00
|
|
|
from aqt.qt import *
|
2021-02-05 06:26:12 +01:00
|
|
|
from aqt.theme import ColoredIcon, theme_manager
|
2021-05-28 19:18:21 +02:00
|
|
|
from aqt.utils import (
|
|
|
|
KeyboardModifiersPressed,
|
|
|
|
askUser,
|
|
|
|
getOnlyText,
|
|
|
|
showInfo,
|
|
|
|
showWarning,
|
|
|
|
tooltip,
|
|
|
|
tr,
|
|
|
|
)
|
2020-10-10 03:42:49 +02:00
|
|
|
|
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
class SidebarStage(Enum):
|
2021-02-05 06:26:12 +01:00
|
|
|
ROOT = auto()
|
|
|
|
SAVED_SEARCHES = auto()
|
2021-02-09 00:50:59 +01:00
|
|
|
TODAY = auto()
|
2021-02-05 06:26:12 +01:00
|
|
|
FLAGS = auto()
|
2021-02-09 00:54:46 +01:00
|
|
|
CARD_STATE = auto()
|
2021-02-05 06:26:12 +01:00
|
|
|
DECKS = auto()
|
|
|
|
NOTETYPES = auto()
|
|
|
|
TAGS = auto()
|
2021-01-23 10:59:12 +01:00
|
|
|
|
|
|
|
|
2021-03-17 05:51:59 +01:00
|
|
|
# fixme: we should have a top-level Sidebar class inheriting from QWidget that
|
|
|
|
# handles the treeview, search bar and so on. Currently the treeview embeds the
|
|
|
|
# search bar which is wrong, and the layout code is handled in browser.py instead
|
|
|
|
# of here
|
2021-01-23 10:59:12 +01:00
|
|
|
class SidebarTreeView(QTreeView):
|
|
|
|
def __init__(self, browser: aqt.browser.Browser) -> None:
|
2020-10-10 03:42:49 +02:00
|
|
|
super().__init__()
|
2021-01-23 10:59:12 +01:00
|
|
|
self.browser = browser
|
|
|
|
self.mw = browser.mw
|
|
|
|
self.col = self.mw.col
|
2021-10-03 10:59:42 +02:00
|
|
|
self.current_search: str | None = None
|
|
|
|
self.valid_drop_types: tuple[SidebarItemType, ...] = ()
|
2021-03-16 10:21:18 +01:00
|
|
|
self._refresh_needed = False
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-10-05 05:53:01 +02:00
|
|
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
2021-02-11 01:12:03 +01:00
|
|
|
self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore
|
2021-01-23 10:59:12 +01:00
|
|
|
self.setUniformRowHeights(True)
|
|
|
|
self.setHeaderHidden(True)
|
|
|
|
self.setIndentation(15)
|
2021-02-02 11:14:14 +01:00
|
|
|
self.setAutoExpandDelay(600)
|
2021-01-30 12:08:39 +01:00
|
|
|
self.setDragDropOverwriteMode(False)
|
2021-10-05 05:53:01 +02:00
|
|
|
self.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed)
|
2021-01-30 12:08:39 +01:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
qconnect(self.expanded, self._on_expansion)
|
|
|
|
qconnect(self.collapsed, self._on_collapse)
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-11-24 22:17:41 +01:00
|
|
|
self._setup_style()
|
|
|
|
|
|
|
|
# these do not really belong here, they should be in a higher-level class
|
|
|
|
self.toolbar = SidebarToolbar(self)
|
|
|
|
self.searchBar = SidebarSearchBar(self)
|
|
|
|
|
|
|
|
gui_hooks.flag_label_did_change.append(self.refresh)
|
|
|
|
gui_hooks.theme_did_change.append(self._setup_style)
|
|
|
|
|
|
|
|
def _setup_style(self) -> None:
|
2021-02-09 07:02:18 +01:00
|
|
|
# match window background color and tweak style
|
2021-01-23 10:59:12 +01:00
|
|
|
bgcolor = QPalette().window().color().name()
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
border = theme_manager.var(colors.BORDER)
|
2021-02-09 07:02:18 +01:00
|
|
|
styles = [
|
|
|
|
"padding: 3px",
|
|
|
|
"padding-right: 0px",
|
|
|
|
"border: 0",
|
|
|
|
f"background: {bgcolor}",
|
|
|
|
]
|
|
|
|
|
|
|
|
self.setStyleSheet("QTreeView { %s }" % ";".join(styles))
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-07-02 11:16:10 +02:00
|
|
|
def cleanup(self) -> None:
|
2021-11-24 22:17:41 +01:00
|
|
|
self.toolbar.cleanup()
|
2021-07-02 11:16:10 +02:00
|
|
|
gui_hooks.flag_label_did_change.remove(self.refresh)
|
2021-11-24 22:17:41 +01:00
|
|
|
gui_hooks.theme_did_change.remove(self._setup_style)
|
2021-07-02 11:16:10 +02:00
|
|
|
|
2021-02-25 11:06:59 +01:00
|
|
|
@property
|
|
|
|
def tool(self) -> SidebarTool:
|
|
|
|
return self._tool
|
|
|
|
|
|
|
|
@tool.setter
|
|
|
|
def tool(self, tool: SidebarTool) -> None:
|
|
|
|
self._tool = tool
|
2021-03-08 11:55:15 +01:00
|
|
|
if tool == SidebarTool.SEARCH:
|
2021-10-05 05:53:01 +02:00
|
|
|
selection_mode = QAbstractItemView.SelectionMode.SingleSelection
|
|
|
|
drag_drop_mode = QAbstractItemView.DragDropMode.NoDragDrop
|
2021-03-09 20:18:12 +01:00
|
|
|
double_click_expands = False
|
2021-03-08 11:55:15 +01:00
|
|
|
else:
|
2021-10-05 05:53:01 +02:00
|
|
|
selection_mode = QAbstractItemView.SelectionMode.ExtendedSelection
|
|
|
|
drag_drop_mode = QAbstractItemView.DragDropMode.InternalMove
|
2021-03-09 20:18:12 +01:00
|
|
|
double_click_expands = True
|
2021-02-25 13:12:51 +01:00
|
|
|
self.setSelectionMode(selection_mode)
|
|
|
|
self.setDragDropMode(drag_drop_mode)
|
2021-03-09 20:18:12 +01:00
|
|
|
self.setExpandsOnDoubleClick(double_click_expands)
|
2021-02-25 11:06:59 +01:00
|
|
|
|
2021-02-02 01:40:50 +01:00
|
|
|
def model(self) -> SidebarModel:
|
2021-03-17 05:51:59 +01:00
|
|
|
return cast(SidebarModel, super().model())
|
2021-01-28 10:10:06 +01:00
|
|
|
|
2021-03-16 10:21:18 +01:00
|
|
|
# Refreshing
|
|
|
|
###########################
|
|
|
|
|
2021-04-06 02:14:11 +02:00
|
|
|
def op_executed(
|
2021-10-03 10:59:42 +02:00
|
|
|
self, changes: OpChanges, handler: object | None, focused: bool
|
2021-04-06 02:14:11 +02:00
|
|
|
) -> None:
|
|
|
|
if changes.browser_sidebar and not handler is self:
|
2021-03-16 10:21:18 +01:00
|
|
|
self._refresh_needed = True
|
|
|
|
if focused:
|
|
|
|
self.refresh_if_needed()
|
|
|
|
|
|
|
|
def refresh_if_needed(self) -> None:
|
|
|
|
if self._refresh_needed:
|
|
|
|
self.refresh()
|
|
|
|
self._refresh_needed = False
|
|
|
|
|
2021-05-30 10:46:59 +02:00
|
|
|
def refresh(self, new_current: SidebarItem = None) -> None:
|
2021-01-23 10:59:12 +01:00
|
|
|
"Refresh list. No-op if sidebar is not visible."
|
|
|
|
if not self.isVisible():
|
|
|
|
return
|
|
|
|
|
2021-05-30 10:46:59 +02:00
|
|
|
if not new_current and self.model() and (idx := self.currentIndex()):
|
|
|
|
new_current = self.model().item_for_index(idx)
|
2021-04-06 03:18:13 +02:00
|
|
|
|
clear_unused_tags and browser redraw improvements
- clear_unused_tags() is now undoable, and returns the number of removed
notes
- add a new mw.query_op() helper for immutable queries
- decouple "freeze/unfreeze ui state" hooks from the "interface update
required" hook, so that the former is fired even on error, and can be
made re-entrant
- use a 'block_updates' flag in Python, instead of setUpdatesEnabled(),
as the latter has the side-effect of preventing child windows like
tooltips from appearing, and forces a full redrawn when updates are
enabled again. The new behaviour leads to the card list blanking out
when a long-running op is running, but in the future if we cache the
cell values we can just display them from the cache instead.
- we were indiscriminately saving the note with saveNow(), due to the
call to saveTags(). Changed so that it only saves when the tags field
is focused.
- drain the "on_done" queue on main before launching a new background
task, to lower the chances of something in on_done making a small query
to the DB and hanging until a long op finishes
- the duplicate check in the editor was executed after the webview loads,
leading to it hanging until the sidebar finishes loading. Run it at
set_note() time instead, so that the editor loads first.
- don't throw an error when a long-running op started with with_progress()
finishes after the window it was launched from has closed
- don't throw an error when the browser is closed before the sidebar
has finished loading
2021-03-17 12:27:42 +01:00
|
|
|
def on_done(root: SidebarItem) -> None:
|
|
|
|
# user may have closed browser
|
|
|
|
if sip.isdeleted(self):
|
|
|
|
return
|
2021-01-28 08:47:58 +01:00
|
|
|
|
clear_unused_tags and browser redraw improvements
- clear_unused_tags() is now undoable, and returns the number of removed
notes
- add a new mw.query_op() helper for immutable queries
- decouple "freeze/unfreeze ui state" hooks from the "interface update
required" hook, so that the former is fired even on error, and can be
made re-entrant
- use a 'block_updates' flag in Python, instead of setUpdatesEnabled(),
as the latter has the side-effect of preventing child windows like
tooltips from appearing, and forces a full redrawn when updates are
enabled again. The new behaviour leads to the card list blanking out
when a long-running op is running, but in the future if we cache the
cell values we can just display them from the cache instead.
- we were indiscriminately saving the note with saveNow(), due to the
call to saveTags(). Changed so that it only saves when the tags field
is focused.
- drain the "on_done" queue on main before launching a new background
task, to lower the chances of something in on_done making a small query
to the DB and hanging until a long op finishes
- the duplicate check in the editor was executed after the webview loads,
leading to it hanging until the sidebar finishes loading. Run it at
set_note() time instead, so that the editor loads first.
- don't throw an error when a long-running op started with with_progress()
finishes after the window it was launched from has closed
- don't throw an error when the browser is closed before the sidebar
has finished loading
2021-03-17 12:27:42 +01:00
|
|
|
# block repainting during refreshing to avoid flickering
|
|
|
|
self.setUpdatesEnabled(False)
|
2021-01-28 08:47:58 +01:00
|
|
|
|
2022-02-18 10:00:12 +01:00
|
|
|
if old_model := self.model():
|
|
|
|
old_model.deleteLater()
|
clear_unused_tags and browser redraw improvements
- clear_unused_tags() is now undoable, and returns the number of removed
notes
- add a new mw.query_op() helper for immutable queries
- decouple "freeze/unfreeze ui state" hooks from the "interface update
required" hook, so that the former is fired even on error, and can be
made re-entrant
- use a 'block_updates' flag in Python, instead of setUpdatesEnabled(),
as the latter has the side-effect of preventing child windows like
tooltips from appearing, and forces a full redrawn when updates are
enabled again. The new behaviour leads to the card list blanking out
when a long-running op is running, but in the future if we cache the
cell values we can just display them from the cache instead.
- we were indiscriminately saving the note with saveNow(), due to the
call to saveTags(). Changed so that it only saves when the tags field
is focused.
- drain the "on_done" queue on main before launching a new background
task, to lower the chances of something in on_done making a small query
to the DB and hanging until a long op finishes
- the duplicate check in the editor was executed after the webview loads,
leading to it hanging until the sidebar finishes loading. Run it at
set_note() time instead, so that the editor loads first.
- don't throw an error when a long-running op started with with_progress()
finishes after the window it was launched from has closed
- don't throw an error when the browser is closed before the sidebar
has finished loading
2021-03-17 12:27:42 +01:00
|
|
|
model = SidebarModel(self, root)
|
2021-01-28 09:51:18 +01:00
|
|
|
self.setModel(model)
|
clear_unused_tags and browser redraw improvements
- clear_unused_tags() is now undoable, and returns the number of removed
notes
- add a new mw.query_op() helper for immutable queries
- decouple "freeze/unfreeze ui state" hooks from the "interface update
required" hook, so that the former is fired even on error, and can be
made re-entrant
- use a 'block_updates' flag in Python, instead of setUpdatesEnabled(),
as the latter has the side-effect of preventing child windows like
tooltips from appearing, and forces a full redrawn when updates are
enabled again. The new behaviour leads to the card list blanking out
when a long-running op is running, but in the future if we cache the
cell values we can just display them from the cache instead.
- we were indiscriminately saving the note with saveNow(), due to the
call to saveTags(). Changed so that it only saves when the tags field
is focused.
- drain the "on_done" queue on main before launching a new background
task, to lower the chances of something in on_done making a small query
to the DB and hanging until a long op finishes
- the duplicate check in the editor was executed after the webview loads,
leading to it hanging until the sidebar finishes loading. Run it at
set_note() time instead, so that the editor loads first.
- don't throw an error when a long-running op started with with_progress()
finishes after the window it was launched from has closed
- don't throw an error when the browser is closed before the sidebar
has finished loading
2021-03-17 12:27:42 +01:00
|
|
|
|
2021-01-29 02:20:15 +01:00
|
|
|
if self.current_search:
|
|
|
|
self.search_for(self.current_search)
|
|
|
|
else:
|
2021-02-05 06:26:12 +01:00
|
|
|
self._expand_where_necessary(model)
|
2021-05-30 10:46:59 +02:00
|
|
|
if new_current:
|
|
|
|
self.restore_current(new_current)
|
2021-01-26 00:02:08 +01:00
|
|
|
|
clear_unused_tags and browser redraw improvements
- clear_unused_tags() is now undoable, and returns the number of removed
notes
- add a new mw.query_op() helper for immutable queries
- decouple "freeze/unfreeze ui state" hooks from the "interface update
required" hook, so that the former is fired even on error, and can be
made re-entrant
- use a 'block_updates' flag in Python, instead of setUpdatesEnabled(),
as the latter has the side-effect of preventing child windows like
tooltips from appearing, and forces a full redrawn when updates are
enabled again. The new behaviour leads to the card list blanking out
when a long-running op is running, but in the future if we cache the
cell values we can just display them from the cache instead.
- we were indiscriminately saving the note with saveNow(), due to the
call to saveTags(). Changed so that it only saves when the tags field
is focused.
- drain the "on_done" queue on main before launching a new background
task, to lower the chances of something in on_done making a small query
to the DB and hanging until a long op finishes
- the duplicate check in the editor was executed after the webview loads,
leading to it hanging until the sidebar finishes loading. Run it at
set_note() time instead, so that the editor loads first.
- don't throw an error when a long-running op started with with_progress()
finishes after the window it was launched from has closed
- don't throw an error when the browser is closed before the sidebar
has finished loading
2021-03-17 12:27:42 +01:00
|
|
|
self.setUpdatesEnabled(True)
|
|
|
|
|
|
|
|
# needs to be set after changing model
|
|
|
|
qconnect(self.selectionModel().selectionChanged, self._on_selection_changed)
|
|
|
|
|
2021-05-08 08:20:10 +02:00
|
|
|
QueryOp(
|
|
|
|
parent=self.browser, op=lambda _: self._root_tree(), success=on_done
|
|
|
|
).run_in_background()
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-04-06 03:18:13 +02:00
|
|
|
def restore_current(self, current: SidebarItem) -> None:
|
|
|
|
if current := self.find_item(current.has_same_id):
|
2021-03-02 23:13:34 +01:00
|
|
|
index = self.model().index_for_item(current)
|
2021-03-03 23:00:37 +01:00
|
|
|
self.selectionModel().setCurrentIndex(
|
2021-10-05 05:53:01 +02:00
|
|
|
index, QItemSelectionModel.SelectionFlag.SelectCurrent
|
2021-03-03 23:00:37 +01:00
|
|
|
)
|
2021-10-05 05:53:01 +02:00
|
|
|
self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)
|
2021-03-02 23:13:34 +01:00
|
|
|
|
|
|
|
def find_item(
|
|
|
|
self,
|
|
|
|
is_target: Callable[[SidebarItem], bool],
|
2021-10-03 10:59:42 +02:00
|
|
|
parent: SidebarItem | None = None,
|
|
|
|
) -> SidebarItem | None:
|
|
|
|
def find_item_rec(parent: SidebarItem) -> SidebarItem | None:
|
2021-03-02 23:13:34 +01:00
|
|
|
if is_target(parent):
|
|
|
|
return parent
|
|
|
|
for child in parent.children:
|
|
|
|
if item := find_item_rec(child):
|
|
|
|
return item
|
|
|
|
return None
|
|
|
|
|
|
|
|
return find_item_rec(parent or self.model().root)
|
|
|
|
|
2021-02-01 00:51:46 +01:00
|
|
|
def search_for(self, text: str) -> None:
|
2021-02-02 03:41:45 +01:00
|
|
|
self.showColumn(0)
|
2021-01-28 09:51:18 +01:00
|
|
|
if not text.strip():
|
2021-01-29 02:20:15 +01:00
|
|
|
self.current_search = None
|
2021-01-28 09:51:18 +01:00
|
|
|
self.refresh()
|
|
|
|
return
|
|
|
|
|
2021-01-28 09:58:51 +01:00
|
|
|
self.current_search = text
|
2021-02-02 01:40:50 +01:00
|
|
|
# start from a collapsed state, as it's faster
|
2021-01-28 09:51:18 +01:00
|
|
|
self.collapseAll()
|
2021-02-02 03:41:45 +01:00
|
|
|
self.setColumnHidden(0, not self.model().search(text))
|
2021-06-29 03:53:10 +02:00
|
|
|
self._expand_where_necessary(self.model(), searching=True)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
def _expand_where_necessary(
|
|
|
|
self,
|
|
|
|
model: SidebarModel,
|
2021-10-03 10:59:42 +02:00
|
|
|
parent: QModelIndex | None = None,
|
2021-02-05 06:26:12 +01:00
|
|
|
searching: bool = False,
|
|
|
|
) -> None:
|
2021-06-29 03:53:10 +02:00
|
|
|
scroll_to_first_match = searching
|
|
|
|
|
2021-06-29 03:40:59 +02:00
|
|
|
def expand_node(parent: QModelIndex) -> None:
|
|
|
|
nonlocal scroll_to_first_match
|
|
|
|
|
|
|
|
for row in range(model.rowCount(parent)):
|
|
|
|
idx = model.index(row, 0, parent)
|
|
|
|
if not idx.isValid():
|
|
|
|
continue
|
|
|
|
|
|
|
|
# descend into children first
|
|
|
|
expand_node(idx)
|
|
|
|
|
|
|
|
if item := model.item_for_index(idx):
|
|
|
|
if item.show_expanded(searching):
|
|
|
|
self.setExpanded(idx, True)
|
|
|
|
if item.is_highlighted() and scroll_to_first_match:
|
|
|
|
self.selectionModel().setCurrentIndex(
|
2021-10-05 05:53:01 +02:00
|
|
|
idx,
|
|
|
|
QItemSelectionModel.SelectionFlag.SelectCurrent,
|
|
|
|
)
|
|
|
|
self.scrollTo(
|
|
|
|
idx, QAbstractItemView.ScrollHint.PositionAtCenter
|
2021-06-29 03:40:59 +02:00
|
|
|
)
|
|
|
|
scroll_to_first_match = False
|
|
|
|
|
|
|
|
expand_node(parent or QModelIndex())
|
2021-02-05 06:26:12 +01:00
|
|
|
|
2021-02-25 21:24:11 +01:00
|
|
|
def update_search(
|
|
|
|
self,
|
|
|
|
*terms: Union[str, SearchNode],
|
|
|
|
joiner: SearchJoiner = "AND",
|
|
|
|
) -> None:
|
2021-02-11 08:11:17 +01:00
|
|
|
"""Modify the current search string based on modifier keys, then refresh."""
|
2021-03-17 05:51:59 +01:00
|
|
|
mods = KeyboardModifiersPressed()
|
2021-02-11 10:57:19 +01:00
|
|
|
previous = SearchNode(parsable_text=self.browser.current_search())
|
2021-02-25 21:24:11 +01:00
|
|
|
current = self.mw.col.group_searches(*terms, joiner=joiner)
|
2021-02-11 08:11:17 +01:00
|
|
|
|
|
|
|
# if Alt pressed, invert
|
2021-03-17 05:51:59 +01:00
|
|
|
if mods.alt:
|
2021-02-11 10:57:19 +01:00
|
|
|
current = SearchNode(negated=current)
|
2021-02-11 08:11:17 +01:00
|
|
|
|
2021-02-09 01:50:39 +01:00
|
|
|
try:
|
2021-03-17 05:51:59 +01:00
|
|
|
if mods.control and mods.shift:
|
2021-02-11 08:11:17 +01:00
|
|
|
# If Ctrl+Shift, replace searches nodes of the same type.
|
2021-02-11 10:57:19 +01:00
|
|
|
search = self.col.replace_in_search_node(previous, current)
|
2021-03-17 05:51:59 +01:00
|
|
|
elif mods.control:
|
2021-02-11 08:11:17 +01:00
|
|
|
# If Ctrl, AND with previous
|
|
|
|
search = self.col.join_searches(previous, current, "AND")
|
2021-03-17 05:51:59 +01:00
|
|
|
elif mods.shift:
|
2021-02-11 08:11:17 +01:00
|
|
|
# If Shift, OR with previous
|
|
|
|
search = self.col.join_searches(previous, current, "OR")
|
|
|
|
else:
|
|
|
|
search = self.col.build_search_string(current)
|
rework filtered deck screen & search errors
- Filtered deck creation now happens as an atomic operation, and is
undoable.
- The logic for initial search text, normalizing searches and so on
has been pushed into the backend.
- Use protobuf to pass the filtered deck to the updated dialog, so
we don't need to deal with untyped JSON.
- Change the "revise your search?" prompt to be a simple info box -
user has access to cancel and build buttons, and doesn't need a separate
prompt. Tweak the wording so the 'show excluded' button should be more
obvious.
- Filtered decks have a time appended to them instead of a number,
primarily because it's easier to implement. No objections going back to
the old behaviour if someone wants to contribute a clean patch.
The standard de-duplication will happen if two decks are created in the
same minute with the same name.
- Tweak the default sort order, and start with two searches. The UI
will still hide the second search by default, but by starting with two,
the frontend doesn't need logic for creating the starting text.
- Search errors now have their own error type, instead of using
InvalidInput, as that was intended mainly for bad API calls. The markdown
conversion is done when the error is converted from the backend, allowing
errors to printed as a string without any special handling by the calling
code.
TODO: when building a new filtered deck, update_active() is clobbering
the undo log when the overview is refreshed
2021-03-24 12:52:48 +01:00
|
|
|
except Exception as e:
|
|
|
|
showWarning(str(e))
|
2021-02-09 01:50:39 +01:00
|
|
|
else:
|
|
|
|
self.browser.search_for(search)
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
# Qt API
|
|
|
|
###########
|
2021-01-28 09:51:18 +01:00
|
|
|
|
2021-01-28 10:10:06 +01:00
|
|
|
def drawRow(
|
|
|
|
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
|
2021-02-01 00:51:46 +01:00
|
|
|
) -> None:
|
2021-02-02 01:40:50 +01:00
|
|
|
if self.current_search and (item := self.model().item_for_index(idx)):
|
|
|
|
if item.is_highlighted():
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
brush = QBrush(theme_manager.qcolor(colors.STATE_SUSPENDED))
|
2021-02-02 01:40:50 +01:00
|
|
|
painter.save()
|
|
|
|
painter.fillRect(options.rect, brush)
|
|
|
|
painter.restore()
|
2021-01-28 09:58:51 +01:00
|
|
|
return super().drawRow(painter, options, idx)
|
|
|
|
|
2021-01-30 12:08:39 +01:00
|
|
|
def dropEvent(self, event: QDropEvent) -> None:
|
|
|
|
model = self.model()
|
2021-10-05 05:53:01 +02:00
|
|
|
if qtmajor == 5:
|
|
|
|
pos = event.pos() # type: ignore
|
|
|
|
else:
|
|
|
|
pos = event.position().toPoint()
|
|
|
|
target_item = model.item_for_index(self.indexAt(pos))
|
2021-02-25 21:24:11 +01:00
|
|
|
if self.handle_drag_drop(self._selected_items(), target_item):
|
2021-01-30 12:08:39 +01:00
|
|
|
event.acceptProposedAction()
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
|
|
|
super().mouseReleaseEvent(event)
|
2021-10-05 05:53:01 +02:00
|
|
|
if (
|
|
|
|
self.tool == SidebarTool.SEARCH
|
|
|
|
and event.button() == Qt.MouseButton.LeftButton
|
|
|
|
):
|
2022-01-16 06:29:04 +01:00
|
|
|
if qtmajor == 5:
|
|
|
|
pos = event.pos() # type: ignore
|
|
|
|
else:
|
|
|
|
pos = event.position().toPoint()
|
|
|
|
if (index := self.currentIndex()) == self.indexAt(pos):
|
2021-03-09 20:36:15 +01:00
|
|
|
self._on_search(index)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
def keyPressEvent(self, event: QKeyEvent) -> None:
|
2021-03-04 18:31:35 +01:00
|
|
|
index = self.currentIndex()
|
2021-10-05 05:53:01 +02:00
|
|
|
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
2021-03-04 18:31:35 +01:00
|
|
|
if not self.isPersistentEditorOpen(index):
|
|
|
|
self._on_search(index)
|
2021-10-05 05:53:01 +02:00
|
|
|
elif event.key() == Qt.Key.Key_Delete:
|
2021-03-13 09:31:56 +01:00
|
|
|
self._on_delete_key(index)
|
2021-02-05 06:26:12 +01:00
|
|
|
else:
|
|
|
|
super().keyPressEvent(event)
|
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
# Slots
|
2021-02-05 06:26:12 +01:00
|
|
|
###########
|
|
|
|
|
2021-03-08 11:35:39 +01:00
|
|
|
def _on_selection_changed(self, _new: QItemSelection, _old: QItemSelection) -> None:
|
2021-05-30 09:30:22 +02:00
|
|
|
valid_drop_types = []
|
|
|
|
selected_items = self._selected_items()
|
|
|
|
selected_types = [item.item_type for item in selected_items]
|
|
|
|
|
|
|
|
# check if renaming is allowed
|
2021-03-08 11:35:39 +01:00
|
|
|
if all(item_type == SidebarItemType.DECK for item_type in selected_types):
|
2021-05-30 09:30:22 +02:00
|
|
|
valid_drop_types += [SidebarItemType.DECK, SidebarItemType.DECK_ROOT]
|
2021-03-08 11:35:39 +01:00
|
|
|
elif all(item_type == SidebarItemType.TAG for item_type in selected_types):
|
2021-05-30 09:30:22 +02:00
|
|
|
valid_drop_types += [SidebarItemType.TAG, SidebarItemType.TAG_ROOT]
|
|
|
|
|
|
|
|
# check if creating a saved search is allowed
|
|
|
|
if len(selected_items) == 1:
|
|
|
|
if (
|
|
|
|
selected_types[0] != SidebarItemType.SAVED_SEARCH
|
|
|
|
and selected_items[0].search_node is not None
|
|
|
|
):
|
|
|
|
valid_drop_types += [
|
|
|
|
SidebarItemType.SAVED_SEARCH_ROOT,
|
|
|
|
SidebarItemType.SAVED_SEARCH,
|
|
|
|
]
|
|
|
|
|
|
|
|
self.valid_drop_types = tuple(valid_drop_types)
|
2021-03-08 11:35:39 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def handle_drag_drop(self, sources: list[SidebarItem], target: SidebarItem) -> bool:
|
2021-01-30 12:08:39 +01:00
|
|
|
if target.item_type in (SidebarItemType.DECK, SidebarItemType.DECK_ROOT):
|
|
|
|
return self._handle_drag_drop_decks(sources, target)
|
2021-02-02 11:14:04 +01:00
|
|
|
if target.item_type in (SidebarItemType.TAG, SidebarItemType.TAG_ROOT):
|
|
|
|
return self._handle_drag_drop_tags(sources, target)
|
2021-05-30 09:30:22 +02:00
|
|
|
if target.item_type in (
|
|
|
|
SidebarItemType.SAVED_SEARCH_ROOT,
|
|
|
|
SidebarItemType.SAVED_SEARCH,
|
|
|
|
):
|
|
|
|
return self._handle_drag_drop_saved_search(sources, target)
|
2021-01-30 12:08:39 +01:00
|
|
|
return False
|
|
|
|
|
2021-01-30 12:32:25 +01:00
|
|
|
def _handle_drag_drop_decks(
|
2021-10-03 10:59:42 +02:00
|
|
|
self, sources: list[SidebarItem], target: SidebarItem
|
2021-01-30 12:32:25 +01:00
|
|
|
) -> bool:
|
2021-03-22 09:23:56 +01:00
|
|
|
deck_ids = [
|
2021-03-27 12:38:20 +01:00
|
|
|
DeckId(source.id)
|
2021-03-22 14:43:54 +01:00
|
|
|
for source in sources
|
|
|
|
if source.item_type == SidebarItemType.DECK
|
2021-01-30 12:32:25 +01:00
|
|
|
]
|
2021-03-22 09:23:56 +01:00
|
|
|
if not deck_ids:
|
2021-01-30 12:08:39 +01:00
|
|
|
return False
|
|
|
|
|
2021-03-27 12:38:20 +01:00
|
|
|
new_parent = DeckId(target.id)
|
2021-01-30 12:08:39 +01:00
|
|
|
|
2021-03-22 09:23:56 +01:00
|
|
|
reparent_decks(
|
2021-04-06 06:36:13 +02:00
|
|
|
parent=self.browser, deck_ids=deck_ids, new_parent=new_parent
|
|
|
|
).run_in_background()
|
2021-02-02 12:45:54 +01:00
|
|
|
|
2021-01-30 12:08:39 +01:00
|
|
|
return True
|
|
|
|
|
2021-02-02 11:14:04 +01:00
|
|
|
def _handle_drag_drop_tags(
|
2021-10-03 10:59:42 +02:00
|
|
|
self, sources: list[SidebarItem], target: SidebarItem
|
2021-02-02 11:14:04 +01:00
|
|
|
) -> bool:
|
2021-03-19 10:15:17 +01:00
|
|
|
tags = [
|
2021-02-02 11:14:04 +01:00
|
|
|
source.full_name
|
|
|
|
for source in sources
|
|
|
|
if source.item_type == SidebarItemType.TAG
|
|
|
|
]
|
2021-03-19 10:15:17 +01:00
|
|
|
if not tags:
|
2021-02-02 11:14:04 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
if target.item_type == SidebarItemType.TAG_ROOT:
|
2021-03-19 10:15:17 +01:00
|
|
|
new_parent = ""
|
2021-02-02 11:14:04 +01:00
|
|
|
else:
|
2021-03-19 10:15:17 +01:00
|
|
|
new_parent = target.full_name
|
2021-02-02 11:14:04 +01:00
|
|
|
|
2021-04-06 06:36:13 +02:00
|
|
|
reparent_tags(
|
|
|
|
parent=self.browser, tags=tags, new_parent=new_parent
|
|
|
|
).run_in_background()
|
2021-02-02 12:45:54 +01:00
|
|
|
|
2021-02-02 11:14:04 +01:00
|
|
|
return True
|
|
|
|
|
2021-05-30 09:30:22 +02:00
|
|
|
def _handle_drag_drop_saved_search(
|
2021-10-03 10:59:42 +02:00
|
|
|
self, sources: list[SidebarItem], _target: SidebarItem
|
2021-05-30 09:30:22 +02:00
|
|
|
) -> bool:
|
|
|
|
if len(sources) != 1 or sources[0].search_node is None:
|
|
|
|
return False
|
|
|
|
self._save_search(
|
|
|
|
sources[0].name, self.col.build_search_string(sources[0].search_node)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
2021-03-04 18:31:35 +01:00
|
|
|
def _on_search(self, index: QModelIndex) -> None:
|
2021-03-04 17:40:12 +01:00
|
|
|
if item := self.model().item_for_index(index):
|
|
|
|
if search_node := item.search_node:
|
|
|
|
self.update_search(search_node)
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
def _on_rename(self, item: SidebarItem, text: str) -> bool:
|
|
|
|
new_name = text.replace('"', "")
|
|
|
|
if new_name and new_name != item.name:
|
|
|
|
if item.item_type == SidebarItemType.DECK:
|
|
|
|
self.rename_deck(item, new_name)
|
|
|
|
elif item.item_type == SidebarItemType.SAVED_SEARCH:
|
|
|
|
self.rename_saved_search(item, new_name)
|
|
|
|
elif item.item_type == SidebarItemType.TAG:
|
|
|
|
self.rename_tag(item, new_name)
|
2021-05-18 22:08:36 +02:00
|
|
|
elif item.item_type == SidebarItemType.FLAG:
|
|
|
|
self.rename_flag(item, new_name)
|
2021-03-13 09:45:06 +01:00
|
|
|
# renaming may be asynchronous so always return False
|
|
|
|
return False
|
|
|
|
|
2021-03-13 09:31:56 +01:00
|
|
|
def _on_delete_key(self, index: QModelIndex) -> None:
|
2021-03-04 18:31:35 +01:00
|
|
|
if item := self.model().item_for_index(index):
|
2021-03-13 09:31:56 +01:00
|
|
|
if self._enable_delete(item):
|
|
|
|
self._on_delete(item)
|
|
|
|
|
|
|
|
def _enable_delete(self, item: SidebarItem) -> bool:
|
|
|
|
return item.item_type.is_deletable() and all(
|
|
|
|
s.item_type == item.item_type for s in self._selected_items()
|
|
|
|
)
|
|
|
|
|
|
|
|
def _on_delete(self, item: SidebarItem) -> None:
|
|
|
|
if item.item_type == SidebarItemType.SAVED_SEARCH:
|
|
|
|
self.remove_saved_searches(item)
|
|
|
|
elif item.item_type == SidebarItemType.DECK:
|
|
|
|
self.delete_decks(item)
|
|
|
|
elif item.item_type == SidebarItemType.TAG:
|
|
|
|
self.remove_tags(item)
|
2021-03-04 18:31:35 +01:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
def _on_expansion(self, idx: QModelIndex) -> None:
|
2021-01-28 09:58:51 +01:00
|
|
|
if self.current_search:
|
2021-01-28 09:51:18 +01:00
|
|
|
return
|
2021-03-03 11:43:31 +01:00
|
|
|
if item := self.model().item_for_index(idx):
|
|
|
|
item.expanded = True
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
def _on_collapse(self, idx: QModelIndex) -> None:
|
2021-01-28 09:58:51 +01:00
|
|
|
if self.current_search:
|
2021-01-28 09:51:18 +01:00
|
|
|
return
|
2021-03-03 11:43:31 +01:00
|
|
|
if item := self.model().item_for_index(idx):
|
|
|
|
item.expanded = False
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
# Tree building
|
|
|
|
###########################
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
def _root_tree(self) -> SidebarItem:
|
2021-10-03 10:59:42 +02:00
|
|
|
root: SidebarItem | None = None
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
for stage in SidebarStage:
|
|
|
|
if stage == SidebarStage.ROOT:
|
|
|
|
root = SidebarItem("", "", item_type=SidebarItemType.ROOT)
|
2021-03-17 05:54:06 +01:00
|
|
|
handled = gui_hooks.browser_will_build_tree(
|
|
|
|
False, root, stage, self.browser
|
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
if not handled:
|
|
|
|
self._build_stage(root, stage)
|
2021-01-23 10:59:12 +01:00
|
|
|
|
|
|
|
return root
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
def _build_stage(self, root: SidebarItem, stage: SidebarStage) -> None:
|
|
|
|
if stage is SidebarStage.SAVED_SEARCHES:
|
|
|
|
self._saved_searches_tree(root)
|
2021-02-09 00:54:46 +01:00
|
|
|
elif stage is SidebarStage.CARD_STATE:
|
2021-02-05 06:26:12 +01:00
|
|
|
self._card_state_tree(root)
|
2021-02-09 00:50:59 +01:00
|
|
|
elif stage is SidebarStage.TODAY:
|
|
|
|
self._today_tree(root)
|
2021-02-05 06:26:12 +01:00
|
|
|
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)
|
|
|
|
|
2021-01-29 13:10:31 +01:00
|
|
|
def _section_root(
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
root: SidebarItem,
|
2021-03-26 05:49:55 +01:00
|
|
|
name: str,
|
2021-02-05 06:26:12 +01:00
|
|
|
icon: Union[str, ColoredIcon],
|
2021-07-11 11:27:08 +02:00
|
|
|
collapse_key: Config.Bool.V,
|
2021-10-03 10:59:42 +02:00
|
|
|
type: SidebarItemType | None = None,
|
2021-01-29 13:10:31 +01:00
|
|
|
) -> SidebarItem:
|
2021-02-01 00:51:46 +01:00
|
|
|
def update(expanded: bool) -> None:
|
2021-05-21 09:50:41 +02:00
|
|
|
CollectionOp(
|
|
|
|
self.browser,
|
|
|
|
lambda col: col.set_config_bool(collapse_key, not expanded),
|
|
|
|
).run_in_background(initiator=self)
|
2021-01-29 13:10:31 +01:00
|
|
|
|
|
|
|
top = SidebarItem(
|
2021-03-26 05:49:55 +01:00
|
|
|
name,
|
2021-01-29 13:10:31 +01:00
|
|
|
icon,
|
2021-02-05 06:26:12 +01:00
|
|
|
on_expanded=update,
|
2021-01-29 13:10:31 +01:00
|
|
|
expanded=not self.col.get_config_bool(collapse_key),
|
|
|
|
item_type=type,
|
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_child(top)
|
2021-01-29 13:10:31 +01:00
|
|
|
|
|
|
|
return top
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
# Tree: Saved Searches
|
|
|
|
###########################
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-01-29 13:10:31 +01:00
|
|
|
def _saved_searches_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
icon = "icons:heart-outline.svg"
|
2021-02-05 13:38:44 +01:00
|
|
|
saved = self._get_saved_searches()
|
2021-01-29 13:10:31 +01:00
|
|
|
|
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_saved_searches(),
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=icon,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_SAVED_SEARCHES,
|
2021-01-29 13:10:31 +01:00
|
|
|
type=SidebarItemType.SAVED_SEARCH_ROOT,
|
|
|
|
)
|
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
for name, filt in sorted(saved.items()):
|
|
|
|
item = SidebarItem(
|
|
|
|
name,
|
2021-01-29 13:10:31 +01:00
|
|
|
icon,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(parsable_text=filt),
|
2021-02-05 06:26:12 +01:00
|
|
|
item_type=SidebarItemType.SAVED_SEARCH,
|
2021-01-23 10:59:12 +01:00
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_child(item)
|
|
|
|
|
2021-02-09 00:50:59 +01:00
|
|
|
# Tree: Today
|
2021-02-05 06:26:12 +01:00
|
|
|
###########################
|
|
|
|
|
2021-02-09 00:50:59 +01:00
|
|
|
def _today_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
icon = "icons:clock-outline.svg"
|
2021-02-05 06:26:12 +01:00
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
2021-02-09 00:50:59 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_TODAY,
|
|
|
|
type=SidebarItemType.TODAY_ROOT,
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
2021-02-09 00:50:59 +01:00
|
|
|
type = SidebarItemType.TODAY
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_due_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(due_on_day=0),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_added_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(added_in_days=1),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_edited_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(edited_in_days=1),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_studied_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(rated=SearchNode.Rated(days=1)),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
2021-04-18 13:27:10 +02:00
|
|
|
root.add_simple(
|
|
|
|
name=tr.browsing_sidebar_first_review(),
|
|
|
|
icon=icon,
|
|
|
|
type=type,
|
|
|
|
search_node=SearchNode(introduced_in_days=1),
|
|
|
|
)
|
2021-06-05 04:55:07 +02:00
|
|
|
root.add_simple(
|
|
|
|
name=tr.browsing_sidebar_rescheduled(),
|
|
|
|
icon=icon,
|
|
|
|
type=type,
|
|
|
|
search_node=SearchNode(
|
|
|
|
rated=SearchNode.Rated(days=1, rating=SearchNode.RATING_BY_RESCHEDULE)
|
|
|
|
),
|
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_again_today(),
|
2021-02-05 06:26:12 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 21:24:11 +01:00
|
|
|
search_node=SearchNode(
|
|
|
|
rated=SearchNode.Rated(days=1, rating=SearchNode.RATING_AGAIN)
|
|
|
|
),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
2021-02-11 01:49:36 +01:00
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_overdue(),
|
2021-02-11 01:49:36 +01:00
|
|
|
icon=icon,
|
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=self.col.group_searches(
|
2021-02-11 10:57:19 +01:00
|
|
|
SearchNode(card_state=SearchNode.CARD_STATE_DUE),
|
|
|
|
SearchNode(negated=SearchNode(due_on_day=0)),
|
2021-02-11 01:49:36 +01:00
|
|
|
),
|
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
# Tree: Card State
|
|
|
|
###########################
|
|
|
|
|
|
|
|
def _card_state_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
icon = "icons:circle.svg"
|
|
|
|
icon_outline = "icons:circle-outline.svg"
|
2021-06-16 16:13:55 +02:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_card_state(),
|
2021-06-16 16:13:55 +02:00
|
|
|
icon=icon_outline,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_CARD_STATE,
|
2021-02-05 06:26:12 +01:00
|
|
|
type=SidebarItemType.CARD_STATE_ROOT,
|
|
|
|
)
|
|
|
|
type = SidebarItemType.CARD_STATE
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
colored_icon = ColoredIcon(path=icon, color=colors.FG_DISABLED)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
tr.actions_new(),
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
icon=colored_icon.with_color(colors.STATE_NEW),
|
2021-02-05 06:26:12 +01:00
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(card_state=SearchNode.CARD_STATE_NEW),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.scheduling_learning(),
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
icon=colored_icon.with_color(colors.STATE_LEARN),
|
2021-02-05 06:26:12 +01:00
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(card_state=SearchNode.CARD_STATE_LEARN),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.scheduling_review(),
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
icon=colored_icon.with_color(colors.STATE_REVIEW),
|
2021-02-05 06:26:12 +01:00
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(card_state=SearchNode.CARD_STATE_REVIEW),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_suspended(),
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
icon=colored_icon.with_color(colors.STATE_SUSPENDED),
|
2021-02-05 06:26:12 +01:00
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(card_state=SearchNode.CARD_STATE_SUSPENDED),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
root.add_simple(
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_buried(),
|
Introduce new color palette using Sass maps (#2016)
* Remove --medium-border variable
* Implement color palette using Sass maps
I hand-picked the gray tones, the other colors are from the Tailwind CSS v3 palette.
Significant changes:
- light theme is brighter
- dark theme is darker
- borders are softer
I also deleted some platform- and night-mode-specific code.
* Use custom colors for note view switch
* Use same placeholder color for all inputs
* Skew color palette for more dark values
by removing gray[3], which wasn't used anywhere. Slight adjustments were made to the darker tones.
* Adjust frame- window- and border colors
* Give deck browser entries --frame-bg as background color
* Define styling for QComboBox and QLineEdit globally
* Experiment with CSS filter for inline-colors
Inside darker inputs, some colors like dark blue will be hard to read, so we could try to improve text-color contrast with global adjustments depending on the theme.
* Use different map structure for _vars.scss
after @hgiesel's idea: https://github.com/ankitects/anki/pull/2016#discussion_r947087871
* Move custom QLineEdit styles out of searchbar.py
* Merge branch 'main' into color-palette
* Revert QComboBox stylesheet override
* Align gray color palette more with macOS
* Adjust light theme
* Use --slightly-grey-text for options tab color
* Replace gray tones with more neutral values
* Improve categorization of global colors
by renaming almost all of them and sorting them into separate maps.
* Saturate highlight-bg in light theme
* Tweak gray tones
* Adjust box-shadow of EditingArea to make fields look inset
* Add Sass functions to access color palette and semantic variables
in response to https://github.com/ankitects/anki/pull/2016#issuecomment-1220571076
* Showcase use of access functions in several locations
@hgiesel in buttons.scss I access the color palette directly. Is this what you meant by "... keep it local to the component, and possibly make it global at a later time ..."?
* Fix focus box shadow transition and remove default shadow for a cleaner look
I couldn't quite get the inset look the way I wanted, because inset box-shadows do not respect the border radius, therefore causing aliasing.
* Tweak light theme border and shadow colors
* Add functions and colors to base_lib
* Add vars_lib as dependency to base_lib and button_mixins_lib
* Improve uses of default-themed variables
* Use old --frame-bg color and use darker tone for canvas-default
* Return CSS var by default and add palette-of function for raw value
* Showcase use of palette-of function
The #{...} syntax is required only because the use cases are CSS var definitions. In other cases a simple palette-of(keyword, theme) would suffice.
* Light theme: decrease brightness of canvas-default and adjust fg-default
* Use canvas-inset variable for switch knob
* Adjust light theme
* Add back box-shadow to EditingArea
* Light theme: darken background and flatten transition
also set hue and saturation of gray-8 to 0 (like all the other grays).
* Reduce flag colors to single default value
* Tweak card/note accent colors
* Experiment with inset look for fields again
Is this too dark in night mode? It's the same color used for all other text inputs.
* Dark theme: make border-default one shade darker
* Tweak inset shadow color
* Dark theme: make border-faint darker than canvas-default
meaning two shades darker than it currently was.
* Fix PlainTextInput not expanding
* Dark theme: use less saturated flag colors
* Adjust gray tones
* Fix nested variables not getting extracted correctly
* Rename canvas-outset to canvas-elevated
* Light theme: darken canvas-default
* Make canvas-elevated a bit darker
* Rename variables and use them in various components
* Refactor button mixins
* Remove fusion vars from Anki
* Adjust button gradients
* Refactor button mixins
* Fix deck browser table td background color
* Use color function in buttons.scss
* Rework QTabWidget stylesheet
* Fix crash on browser open
* Perfect QTableView header
* Fix bottom toolbar button gradient
* Fix focus outline of bottom toolbar buttons
* Fix custom webview scrollbar
* Fix uses of vars in various webviews
The command @use vars as * lead to repeated inclusion of the CSS vars.
* Enable primary button color with mixin
* Run prettier
* Fix Python code style issues
* Tweak colors
* Lighten scrollbar shades in light theme
* Fix code style issues caused by merge
* Fix harsh border color in editor
caused by leftover --medium-border variables, probably introduced with a merge commit.
* Compile Sass before extracting Python colors/props
This means the Python side doesn't need to worry about the map structure and Sass functions, just copy the output CSS values.
* Desaturate primary button colors by 10%
* Convert accidentally capitalized variable names to lowercase
* Simplify color definitions with qcolor function
* Remove default border-focus variable
* Remove redundant colon
* Apply custom scrollbar CSS only on Windows and Linux
* Make border-subtle color brighter than background in dark theme
* Make border-subtle color a shade brighter in light theme
* Use border-subtle for NoteEditor and EditorToolbar border
* Small patches
2022-09-16 06:11:18 +02:00
|
|
|
icon=colored_icon.with_color(colors.STATE_BURIED),
|
2021-02-05 06:26:12 +01:00
|
|
|
type=type,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(card_state=SearchNode.CARD_STATE_BURIED),
|
2021-02-05 06:26:12 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
# Tree: Flags
|
|
|
|
###########################
|
|
|
|
|
|
|
|
def _flags_tree(self, root: SidebarItem) -> None:
|
2022-08-01 12:15:22 +02:00
|
|
|
icon_off = "icons:flag-variant-off-outline.svg"
|
|
|
|
icon = "icons:flag-variant.svg"
|
|
|
|
icon_outline = "icons:flag-variant-outline.svg"
|
2021-07-01 01:16:50 +02:00
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_flags(),
|
2021-07-01 01:16:50 +02:00
|
|
|
icon=icon_outline,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_FLAGS,
|
2021-02-05 06:26:12 +01:00
|
|
|
type=SidebarItemType.FLAG_ROOT,
|
|
|
|
)
|
2021-02-25 19:57:12 +01:00
|
|
|
root.search_node = SearchNode(flag=SearchNode.FLAG_ANY)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
2022-01-11 07:35:46 +01:00
|
|
|
root.add_simple(
|
|
|
|
tr.browsing_no_flag(),
|
|
|
|
icon=icon_off,
|
|
|
|
type=SidebarItemType.FLAG_NONE,
|
|
|
|
search_node=SearchNode(flag=SearchNode.FLAG_NONE),
|
|
|
|
)
|
|
|
|
|
2021-07-02 11:16:10 +02:00
|
|
|
for flag in self.mw.flags.all():
|
2021-05-18 22:08:36 +02:00
|
|
|
root.add_child(
|
|
|
|
SidebarItem(
|
2021-05-19 19:17:43 +02:00
|
|
|
name=flag.label,
|
|
|
|
icon=flag.icon,
|
|
|
|
search_node=flag.search_node,
|
2021-05-18 22:08:36 +02:00
|
|
|
item_type=SidebarItemType.FLAG,
|
2021-05-19 19:17:43 +02:00
|
|
|
id=flag.index,
|
2021-05-18 22:08:36 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
# Tree: Tags
|
|
|
|
###########################
|
2021-01-23 10:59:12 +01:00
|
|
|
|
|
|
|
def _tag_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
icon = "icons:tag-outline.svg"
|
|
|
|
icon_off = "icons:tag-off-outline.svg"
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-02-01 00:51:46 +01:00
|
|
|
def render(
|
|
|
|
root: SidebarItem, nodes: Iterable[TagTreeNode], head: str = ""
|
|
|
|
) -> None:
|
2021-04-05 03:41:53 +02:00
|
|
|
def toggle_expand(node: TagTreeNode) -> Callable[[bool], None]:
|
|
|
|
full_name = head + node.name
|
|
|
|
return lambda expanded: set_tag_collapsed(
|
2021-04-06 04:47:55 +02:00
|
|
|
parent=self, tag=full_name, collapsed=not expanded
|
2021-06-24 17:27:53 +02:00
|
|
|
).run_in_background(initiator=self)
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-04-05 03:41:53 +02:00
|
|
|
for node in nodes:
|
2021-01-23 10:59:12 +01:00
|
|
|
item = SidebarItem(
|
2021-02-25 19:57:12 +01:00
|
|
|
name=node.name,
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=icon,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(tag=head + node.name),
|
2021-04-05 03:41:53 +02:00
|
|
|
on_expanded=toggle_expand(node),
|
|
|
|
expanded=not node.collapsed,
|
2021-01-23 10:59:12 +01:00
|
|
|
item_type=SidebarItemType.TAG,
|
2021-03-10 16:38:29 +01:00
|
|
|
name_prefix=head,
|
2021-01-23 10:59:12 +01:00
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_child(item)
|
2021-02-11 01:09:06 +01:00
|
|
|
newhead = f"{head + node.name}::"
|
2021-01-23 10:59:12 +01:00
|
|
|
render(item, node.children, newhead)
|
|
|
|
|
2021-01-31 06:55:08 +01:00
|
|
|
tree = self.col.tags.tree()
|
2021-01-29 13:10:31 +01:00
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_tags(),
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=icon,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_TAGS,
|
2021-02-02 02:51:45 +01:00
|
|
|
type=SidebarItemType.TAG_ROOT,
|
2021-01-29 13:10:31 +01:00
|
|
|
)
|
2021-06-15 12:11:43 +02:00
|
|
|
root.search_node = SearchNode(tag="_*")
|
2021-02-09 03:50:35 +01:00
|
|
|
root.add_simple(
|
2021-03-26 04:48:26 +01:00
|
|
|
name=tr.browsing_sidebar_untagged(),
|
2021-06-15 14:46:59 +02:00
|
|
|
icon=icon_off,
|
2021-02-09 03:50:35 +01:00
|
|
|
type=SidebarItemType.TAG_NONE,
|
2021-06-15 12:11:43 +02:00
|
|
|
search_node=SearchNode(negated=SearchNode(tag="_*")),
|
2021-02-09 03:50:35 +01:00
|
|
|
)
|
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
render(root, tree.children)
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
# Tree: Decks
|
|
|
|
###########################
|
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
def _deck_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
icon = "icons:book-outline.svg"
|
|
|
|
icon_current = "icons:book-clock-outline.svg"
|
|
|
|
icon_filtered = "icons:book-cog-outline.svg"
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-02-01 00:51:46 +01:00
|
|
|
def render(
|
|
|
|
root: SidebarItem, nodes: Iterable[DeckTreeNode], head: str = ""
|
|
|
|
) -> None:
|
2021-04-05 02:21:50 +02:00
|
|
|
def toggle_expand(node: DeckTreeNode) -> Callable[[bool], None]:
|
|
|
|
return lambda expanded: set_deck_collapsed(
|
2021-04-06 06:36:13 +02:00
|
|
|
parent=self,
|
2021-04-05 02:21:50 +02:00
|
|
|
deck_id=DeckId(node.deck_id),
|
|
|
|
collapsed=not expanded,
|
|
|
|
scope=DeckCollapseScope.BROWSER,
|
2021-04-06 06:36:13 +02:00
|
|
|
).run_in_background(
|
|
|
|
initiator=self,
|
2021-04-05 02:21:50 +02:00
|
|
|
)
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-04-05 02:21:50 +02:00
|
|
|
for node in nodes:
|
2021-01-23 10:59:12 +01:00
|
|
|
item = SidebarItem(
|
2021-02-25 19:57:12 +01:00
|
|
|
name=node.name,
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=icon_filtered if node.filtered else icon,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(deck=head + node.name),
|
2021-04-05 02:21:50 +02:00
|
|
|
on_expanded=toggle_expand(node),
|
2021-02-25 19:57:12 +01:00
|
|
|
expanded=not node.collapsed,
|
2021-01-23 10:59:12 +01:00
|
|
|
item_type=SidebarItemType.DECK,
|
|
|
|
id=node.deck_id,
|
2021-03-10 16:38:29 +01:00
|
|
|
name_prefix=head,
|
2021-01-23 10:59:12 +01:00
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_child(item)
|
2021-02-11 01:09:06 +01:00
|
|
|
newhead = f"{head + node.name}::"
|
2021-01-23 10:59:12 +01:00
|
|
|
render(item, node.children, newhead)
|
|
|
|
|
2021-01-29 13:10:31 +01:00
|
|
|
tree = self.col.decks.deck_tree()
|
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_decks(),
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=icon,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_DECKS,
|
2021-01-30 12:08:39 +01:00
|
|
|
type=SidebarItemType.DECK_ROOT,
|
2021-01-29 13:10:31 +01:00
|
|
|
)
|
2021-06-15 12:11:43 +02:00
|
|
|
root.search_node = SearchNode(deck="_*")
|
2021-02-09 00:38:37 +01:00
|
|
|
current = root.add_simple(
|
2021-03-26 04:48:26 +01:00
|
|
|
name=tr.browsing_current_deck(),
|
2021-06-16 14:53:30 +02:00
|
|
|
icon=icon_current,
|
2021-03-03 09:15:36 +01:00
|
|
|
type=SidebarItemType.DECK_CURRENT,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(deck="current"),
|
2021-02-08 13:48:45 +01:00
|
|
|
)
|
2021-02-09 00:38:37 +01:00
|
|
|
current.id = self.mw.col.decks.selected()
|
2021-02-08 13:48:45 +01:00
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
render(root, tree.children)
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
# Tree: Notetypes
|
|
|
|
###########################
|
|
|
|
|
2021-01-23 10:59:12 +01:00
|
|
|
def _notetype_tree(self, root: SidebarItem) -> None:
|
2021-10-05 06:44:07 +02:00
|
|
|
notetype_icon = "icons:newspaper.svg"
|
2022-01-11 07:35:46 +01:00
|
|
|
template_icon = "icons:application-braces-outline.svg"
|
2021-10-05 06:44:07 +02:00
|
|
|
field_icon = "icons:form-textbox.svg"
|
2021-06-16 14:26:04 +02:00
|
|
|
|
2021-01-29 13:10:31 +01:00
|
|
|
root = self._section_root(
|
|
|
|
root=root,
|
2021-03-26 05:49:55 +01:00
|
|
|
name=tr.browsing_sidebar_notetypes(),
|
2021-07-01 00:58:02 +02:00
|
|
|
icon=notetype_icon,
|
2021-02-08 05:10:05 +01:00
|
|
|
collapse_key=Config.Bool.COLLAPSE_NOTETYPES,
|
2021-02-02 02:51:45 +01:00
|
|
|
type=SidebarItemType.NOTETYPE_ROOT,
|
2021-01-29 13:10:31 +01:00
|
|
|
)
|
2021-06-15 12:11:43 +02:00
|
|
|
root.search_node = SearchNode(note="_*")
|
2021-01-23 10:59:12 +01:00
|
|
|
|
|
|
|
for nt in sorted(self.col.models.all(), key=lambda nt: nt["name"].lower()):
|
|
|
|
item = SidebarItem(
|
|
|
|
nt["name"],
|
2021-06-16 14:26:04 +02:00
|
|
|
notetype_icon,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=SearchNode(note=nt["name"]),
|
2021-01-23 10:59:12 +01:00
|
|
|
item_type=SidebarItemType.NOTETYPE,
|
|
|
|
id=nt["id"],
|
|
|
|
)
|
|
|
|
|
|
|
|
for c, tmpl in enumerate(nt["tmpls"]):
|
|
|
|
child = SidebarItem(
|
|
|
|
tmpl["name"],
|
2021-06-16 14:26:04 +02:00
|
|
|
template_icon,
|
2021-02-25 19:57:12 +01:00
|
|
|
search_node=self.col.group_searches(
|
2021-02-11 10:57:19 +01:00
|
|
|
SearchNode(note=nt["name"]), SearchNode(template=c)
|
2021-01-29 18:27:33 +01:00
|
|
|
),
|
2021-02-05 06:26:12 +01:00
|
|
|
item_type=SidebarItemType.NOTETYPE_TEMPLATE,
|
2021-03-03 18:09:53 +01:00
|
|
|
id=tmpl["ord"],
|
2021-01-23 10:59:12 +01:00
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
item.add_child(child)
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-06-16 15:40:48 +02:00
|
|
|
for c, fld in enumerate(nt["flds"]):
|
|
|
|
child = SidebarItem(
|
|
|
|
fld["name"],
|
|
|
|
field_icon,
|
|
|
|
search_node=self.col.group_searches(
|
|
|
|
SearchNode(note=nt["name"]), SearchNode(field_name=fld["name"])
|
|
|
|
),
|
|
|
|
item_type=SidebarItemType.NOTETYPE_FIELD,
|
|
|
|
id=fld["ord"],
|
|
|
|
)
|
|
|
|
item.add_child(child)
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
root.add_child(item)
|
2021-01-23 10:59:12 +01:00
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
# Context menu
|
2021-01-23 10:59:12 +01:00
|
|
|
###########################
|
2020-10-10 03:42:49 +02:00
|
|
|
|
|
|
|
def onContextMenu(self, point: QPoint) -> None:
|
2021-03-07 10:30:20 +01:00
|
|
|
index: QModelIndex = self.indexAt(point)
|
|
|
|
item = self.model().item_for_index(index)
|
|
|
|
if item and self.selectionModel().isSelected(index):
|
|
|
|
self.show_context_menu(item, index)
|
|
|
|
|
|
|
|
def show_context_menu(self, item: SidebarItem, index: QModelIndex) -> None:
|
|
|
|
menu = QMenu()
|
|
|
|
self._maybe_add_type_specific_actions(menu, item)
|
2021-09-19 18:33:36 +02:00
|
|
|
menu.addSeparator()
|
2021-03-07 10:30:20 +01:00
|
|
|
self._maybe_add_delete_action(menu, item, index)
|
2021-07-13 16:34:02 +02:00
|
|
|
self._maybe_add_rename_actions(menu, item, index)
|
|
|
|
self._maybe_add_find_and_replace_action(menu, item, index)
|
2021-09-19 18:33:36 +02:00
|
|
|
menu.addSeparator()
|
2021-03-07 10:30:20 +01:00
|
|
|
self._maybe_add_search_actions(menu)
|
2021-09-19 18:33:36 +02:00
|
|
|
menu.addSeparator()
|
2021-03-07 10:30:20 +01:00
|
|
|
self._maybe_add_tree_actions(menu)
|
2021-06-25 16:24:24 +02:00
|
|
|
gui_hooks.browser_sidebar_will_show_context_menu(self, menu, item, index)
|
2021-03-07 10:30:20 +01:00
|
|
|
if menu.children():
|
2021-10-05 02:01:45 +02:00
|
|
|
menu.exec(QCursor.pos())
|
2020-10-10 03:42:49 +02:00
|
|
|
|
2021-03-05 12:22:49 +01:00
|
|
|
def _maybe_add_type_specific_actions(self, menu: QMenu, item: SidebarItem) -> None:
|
|
|
|
if item.item_type in (SidebarItemType.NOTETYPE, SidebarItemType.NOTETYPE_ROOT):
|
2021-03-10 10:14:06 +01:00
|
|
|
menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.browsing_manage_note_types(), lambda: self.manage_notetype(item)
|
2021-03-10 10:14:06 +01:00
|
|
|
)
|
|
|
|
elif item.item_type == SidebarItemType.NOTETYPE_TEMPLATE:
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.notetypes_cards(), lambda: self.manage_template(item))
|
2021-06-16 15:40:48 +02:00
|
|
|
elif item.item_type == SidebarItemType.NOTETYPE_FIELD:
|
|
|
|
menu.addAction(tr.notetypes_fields(), lambda: self.manage_fields(item))
|
2021-03-05 12:22:49 +01:00
|
|
|
elif item.item_type == SidebarItemType.SAVED_SEARCH_ROOT:
|
|
|
|
menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.browsing_sidebar_save_current_search(), self.save_current_search
|
2021-03-05 12:22:49 +01:00
|
|
|
)
|
2021-03-23 11:20:46 +01:00
|
|
|
elif item.item_type == SidebarItemType.SAVED_SEARCH:
|
|
|
|
menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.browsing_update_saved_search(),
|
2021-03-23 11:20:46 +01:00
|
|
|
lambda: self.update_saved_search(item),
|
|
|
|
)
|
2021-09-21 11:48:43 +02:00
|
|
|
elif item.item_type == SidebarItemType.TAG:
|
|
|
|
if all(s.item_type == item.item_type for s in self._selected_items()):
|
|
|
|
menu.addAction(
|
|
|
|
tr.browsing_add_to_selected_notes(), self.add_tags_to_selected_notes
|
|
|
|
)
|
|
|
|
menu.addAction(
|
|
|
|
tr.browsing_remove_from_selected_notes(),
|
|
|
|
self.remove_tags_from_selected_notes,
|
|
|
|
)
|
2021-03-05 12:22:49 +01:00
|
|
|
|
2021-03-07 10:30:20 +01:00
|
|
|
def _maybe_add_delete_action(
|
|
|
|
self, menu: QMenu, item: SidebarItem, index: QModelIndex
|
|
|
|
) -> None:
|
2021-03-13 09:31:56 +01:00
|
|
|
if self._enable_delete(item):
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.actions_delete(), lambda: self._on_delete(item))
|
2021-03-07 10:30:20 +01:00
|
|
|
|
2021-07-13 16:34:02 +02:00
|
|
|
def _maybe_add_rename_actions(
|
2021-03-07 10:30:20 +01:00
|
|
|
self, menu: QMenu, item: SidebarItem, index: QModelIndex
|
|
|
|
) -> None:
|
|
|
|
if item.item_type.is_editable() and len(self._selected_items()) == 1:
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.actions_rename(), lambda: self.edit(index))
|
2021-08-30 11:10:14 +02:00
|
|
|
if (
|
|
|
|
item.item_type in (SidebarItemType.TAG, SidebarItemType.DECK)
|
|
|
|
and item.name_prefix
|
|
|
|
):
|
2021-07-13 16:34:02 +02:00
|
|
|
menu.addAction(
|
|
|
|
tr.actions_rename_with_parents(),
|
|
|
|
lambda: self._on_rename_with_parents(item),
|
|
|
|
)
|
|
|
|
|
|
|
|
def _maybe_add_find_and_replace_action(
|
|
|
|
self, menu: QMenu, item: SidebarItem, index: QModelIndex
|
|
|
|
) -> None:
|
|
|
|
if (
|
|
|
|
len(self._selected_items()) == 1
|
|
|
|
and item.item_type is SidebarItemType.NOTETYPE_FIELD
|
|
|
|
):
|
|
|
|
menu.addAction(
|
|
|
|
tr.browsing_find_and_replace(), lambda: self._on_find_and_replace(item)
|
|
|
|
)
|
2021-03-07 10:30:20 +01:00
|
|
|
|
2021-02-25 21:24:11 +01:00
|
|
|
def _maybe_add_search_actions(self, menu: QMenu) -> None:
|
|
|
|
nodes = [
|
|
|
|
item.search_node for item in self._selected_items() if item.search_node
|
|
|
|
]
|
|
|
|
if not nodes:
|
|
|
|
return
|
|
|
|
if len(nodes) == 1:
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.actions_search(), lambda: self.update_search(*nodes))
|
2021-02-25 21:24:11 +01:00
|
|
|
return
|
2021-03-26 04:48:26 +01:00
|
|
|
sub_menu = menu.addMenu(tr.actions_search())
|
2021-02-25 21:24:11 +01:00
|
|
|
sub_menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.actions_all_selected(), lambda: self.update_search(*nodes)
|
2021-02-25 21:24:11 +01:00
|
|
|
)
|
|
|
|
sub_menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.actions_any_selected(),
|
2021-02-25 21:24:11 +01:00
|
|
|
lambda: self.update_search(*nodes, joiner="OR"),
|
|
|
|
)
|
|
|
|
|
2021-03-03 11:44:42 +01:00
|
|
|
def _maybe_add_tree_actions(self, menu: QMenu) -> None:
|
|
|
|
def set_expanded(expanded: bool) -> None:
|
|
|
|
for index in self.selectedIndexes():
|
|
|
|
self.setExpanded(index, expanded)
|
|
|
|
|
|
|
|
def set_children_expanded(expanded: bool) -> None:
|
|
|
|
for index in self.selectedIndexes():
|
|
|
|
self.setExpanded(index, True)
|
|
|
|
for row in range(self.model().rowCount(index)):
|
|
|
|
self.setExpanded(self.model().index(row, 0, index), expanded)
|
|
|
|
|
2021-01-29 13:50:29 +01:00
|
|
|
if self.current_search:
|
|
|
|
return
|
|
|
|
|
2021-03-03 11:44:42 +01:00
|
|
|
selected_items = self._selected_items()
|
|
|
|
if not any(item.children for item in selected_items):
|
|
|
|
return
|
2021-01-29 13:50:29 +01:00
|
|
|
|
2021-03-05 10:27:44 +01:00
|
|
|
if any(not item.expanded for item in selected_items if item.children):
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.browsing_sidebar_expand(), lambda: set_expanded(True))
|
2021-03-05 10:27:44 +01:00
|
|
|
if any(item.expanded for item in selected_items if item.children):
|
2021-03-26 04:48:26 +01:00
|
|
|
menu.addAction(tr.browsing_sidebar_collapse(), lambda: set_expanded(False))
|
2021-03-03 11:44:42 +01:00
|
|
|
if any(
|
|
|
|
not c.expanded for i in selected_items for c in i.children if c.children
|
|
|
|
):
|
|
|
|
menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.browsing_sidebar_expand_children(),
|
2021-03-03 11:44:42 +01:00
|
|
|
lambda: set_children_expanded(True),
|
|
|
|
)
|
|
|
|
if any(c.expanded for i in selected_items for c in i.children if c.children):
|
|
|
|
menu.addAction(
|
2021-03-26 04:48:26 +01:00
|
|
|
tr.browsing_sidebar_collapse_children(),
|
2021-03-03 11:44:42 +01:00
|
|
|
lambda: set_children_expanded(False),
|
|
|
|
)
|
2021-01-29 13:50:29 +01:00
|
|
|
|
2021-07-13 16:34:02 +02:00
|
|
|
def _on_rename_with_parents(self, item: SidebarItem) -> None:
|
|
|
|
title = "Anki"
|
|
|
|
if item.item_type is SidebarItemType.TAG:
|
|
|
|
title = tr.actions_rename_tag()
|
|
|
|
elif item.item_type is SidebarItemType.DECK:
|
|
|
|
title = tr.actions_rename_deck()
|
|
|
|
|
|
|
|
new_name = getOnlyText(
|
|
|
|
tr.actions_new_name(), title=title, default=item.full_name
|
|
|
|
).replace('"', "")
|
|
|
|
if not new_name or new_name == item.full_name:
|
|
|
|
return
|
|
|
|
|
|
|
|
if item.item_type is SidebarItemType.TAG:
|
|
|
|
|
|
|
|
def success(out: OpChangesWithCount) -> None:
|
|
|
|
if out.count:
|
|
|
|
tooltip(tr.browsing_notes_updated(count=out.count), parent=self)
|
|
|
|
else:
|
|
|
|
showInfo(tr.browsing_tag_rename_warning_empty(), parent=self)
|
|
|
|
|
|
|
|
rename_tag(
|
|
|
|
parent=self,
|
|
|
|
current_name=item.full_name,
|
|
|
|
new_name=new_name,
|
|
|
|
).success(success).run_in_background()
|
|
|
|
|
|
|
|
elif item.item_type is SidebarItemType.DECK:
|
|
|
|
rename_deck(
|
|
|
|
parent=self,
|
|
|
|
deck_id=DeckId(item.id),
|
|
|
|
new_name=new_name,
|
|
|
|
).run_in_background()
|
|
|
|
|
|
|
|
def _on_find_and_replace(self, item: SidebarItem) -> None:
|
|
|
|
field = None
|
|
|
|
if item.item_type is SidebarItemType.NOTETYPE_FIELD:
|
|
|
|
field = item.name
|
|
|
|
FindAndReplaceDialog(
|
|
|
|
self,
|
|
|
|
mw=self.mw,
|
|
|
|
note_ids=self.browser.selected_notes(),
|
|
|
|
field=field,
|
|
|
|
)
|
|
|
|
|
2021-05-18 22:08:36 +02:00
|
|
|
# Flags
|
|
|
|
###########################
|
|
|
|
|
|
|
|
def rename_flag(self, item: SidebarItem, new_name: str) -> None:
|
|
|
|
item.name = new_name
|
2021-07-02 11:16:10 +02:00
|
|
|
self.mw.flags.rename_flag(item.id, new_name)
|
2021-05-18 22:08:36 +02:00
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
# Decks
|
|
|
|
###########################
|
|
|
|
|
2021-03-03 21:57:39 +01:00
|
|
|
def rename_deck(self, item: SidebarItem, new_name: str) -> None:
|
2021-07-15 08:56:47 +02:00
|
|
|
if not new_name or new_name == item.name:
|
2021-03-22 11:38:51 +01:00
|
|
|
return
|
2021-04-06 03:18:13 +02:00
|
|
|
|
|
|
|
# update UI immediately, to avoid redraw
|
|
|
|
item.name = new_name
|
|
|
|
|
2021-07-15 08:56:47 +02:00
|
|
|
rename_deck(
|
|
|
|
parent=self,
|
|
|
|
deck_id=DeckId(item.id),
|
|
|
|
new_name=item.name_prefix + new_name,
|
2021-05-08 08:20:10 +02:00
|
|
|
).run_in_background()
|
2021-01-03 08:36:54 +01:00
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
def delete_decks(self, _item: SidebarItem) -> None:
|
2021-04-06 06:36:13 +02:00
|
|
|
remove_decks(parent=self, deck_ids=self._selected_decks()).run_in_background()
|
2021-03-13 09:45:06 +01:00
|
|
|
|
|
|
|
# Tags
|
|
|
|
###########################
|
|
|
|
|
2021-03-18 12:35:32 +01:00
|
|
|
def remove_tags(self, item: SidebarItem) -> None:
|
|
|
|
tags = self.mw.col.tags.join(self._selected_tags())
|
|
|
|
item.name = "..."
|
2021-01-06 15:04:03 +01:00
|
|
|
|
2021-04-06 06:36:13 +02:00
|
|
|
remove_tags_from_all_notes(
|
|
|
|
parent=self.browser, space_separated_tags=tags
|
|
|
|
).run_in_background()
|
2021-01-06 15:04:03 +01:00
|
|
|
|
2021-03-03 21:57:39 +01:00
|
|
|
def rename_tag(self, item: SidebarItem, new_name: str) -> None:
|
2021-03-18 11:43:04 +01:00
|
|
|
if not new_name or new_name == item.name:
|
|
|
|
return
|
|
|
|
|
2021-05-28 19:18:21 +02:00
|
|
|
old_name = item.name
|
|
|
|
old_full_name = item.full_name
|
|
|
|
new_full_name = item.name_prefix + new_name
|
2021-01-03 08:36:54 +01:00
|
|
|
|
2021-05-28 19:18:21 +02:00
|
|
|
item.name = new_name
|
|
|
|
item.full_name = new_full_name
|
2021-01-04 05:13:20 +01:00
|
|
|
|
2021-05-28 19:18:21 +02:00
|
|
|
def success(out: OpChangesWithCount) -> None:
|
|
|
|
if out.count:
|
|
|
|
tooltip(tr.browsing_notes_updated(count=out.count), parent=self)
|
|
|
|
else:
|
|
|
|
# revert renaming of sidebar item
|
|
|
|
item.full_name = old_full_name
|
|
|
|
item.name = old_name
|
|
|
|
showInfo(tr.browsing_tag_rename_warning_empty(), parent=self)
|
2021-01-04 05:13:20 +01:00
|
|
|
|
2021-03-18 11:43:04 +01:00
|
|
|
rename_tag(
|
|
|
|
parent=self.browser,
|
2021-05-28 19:18:21 +02:00
|
|
|
current_name=old_full_name,
|
|
|
|
new_name=new_full_name,
|
|
|
|
).success(success).run_in_background()
|
2021-01-04 11:30:41 +01:00
|
|
|
|
2021-09-21 11:48:43 +02:00
|
|
|
def add_tags_to_selected_notes(self) -> None:
|
|
|
|
tags = " ".join(item.full_name for item in self._selected_items())
|
|
|
|
self.browser.add_tags_to_selected_notes(tags)
|
|
|
|
|
|
|
|
def remove_tags_from_selected_notes(self) -> None:
|
|
|
|
tags = " ".join(item.full_name for item in self._selected_items())
|
|
|
|
self.browser.remove_tags_from_selected_notes(tags)
|
|
|
|
|
2021-02-05 13:38:44 +01:00
|
|
|
# Saved searches
|
2021-03-13 09:45:06 +01:00
|
|
|
####################################
|
2021-02-05 13:38:44 +01:00
|
|
|
|
|
|
|
_saved_searches_key = "savedFilters"
|
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _get_saved_searches(self) -> dict[str, str]:
|
2021-02-05 13:38:44 +01:00
|
|
|
return self.col.get_config(self._saved_searches_key, {})
|
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _set_saved_searches(self, searches: dict[str, str]) -> None:
|
2021-02-05 13:38:44 +01:00
|
|
|
self.col.set_config(self._saved_searches_key, searches)
|
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _get_current_search(self) -> str | None:
|
2021-03-23 11:20:46 +01:00
|
|
|
try:
|
2021-04-14 10:42:26 +02:00
|
|
|
return self.col.build_search_string(self.browser.current_search())
|
rework filtered deck screen & search errors
- Filtered deck creation now happens as an atomic operation, and is
undoable.
- The logic for initial search text, normalizing searches and so on
has been pushed into the backend.
- Use protobuf to pass the filtered deck to the updated dialog, so
we don't need to deal with untyped JSON.
- Change the "revise your search?" prompt to be a simple info box -
user has access to cancel and build buttons, and doesn't need a separate
prompt. Tweak the wording so the 'show excluded' button should be more
obvious.
- Filtered decks have a time appended to them instead of a number,
primarily because it's easier to implement. No objections going back to
the old behaviour if someone wants to contribute a clean patch.
The standard de-duplication will happen if two decks are created in the
same minute with the same name.
- Tweak the default sort order, and start with two searches. The UI
will still hide the second search by default, but by starting with two,
the frontend doesn't need logic for creating the starting text.
- Search errors now have their own error type, instead of using
InvalidInput, as that was intended mainly for bad API calls. The markdown
conversion is done when the error is converted from the backend, allowing
errors to printed as a string without any special handling by the calling
code.
TODO: when building a new filtered deck, update_active() is clobbering
the undo log when the overview is refreshed
2021-03-24 12:52:48 +01:00
|
|
|
except Exception as e:
|
|
|
|
showWarning(str(e))
|
2021-03-23 11:20:46 +01:00
|
|
|
return None
|
|
|
|
|
|
|
|
def _save_search(self, name: str, search: str, update: bool = False) -> None:
|
|
|
|
conf = self._get_saved_searches()
|
2021-05-30 10:11:40 +02:00
|
|
|
if not update and name in conf:
|
|
|
|
if conf[name] == search:
|
|
|
|
# nothing to do
|
|
|
|
return
|
|
|
|
if not askUser(tr.browsing_confirm_saved_search_overwrite(name=name)):
|
|
|
|
# don't overwrite existing saved search
|
|
|
|
return
|
2021-03-23 11:20:46 +01:00
|
|
|
conf[name] = search
|
|
|
|
self._set_saved_searches(conf)
|
2021-05-30 10:46:59 +02:00
|
|
|
self.refresh(SidebarItem(name, "", item_type=SidebarItemType.SAVED_SEARCH))
|
2021-03-23 11:20:46 +01:00
|
|
|
|
2021-03-01 09:41:41 +01:00
|
|
|
def remove_saved_searches(self, _item: SidebarItem) -> None:
|
|
|
|
selected = self._selected_saved_searches()
|
2021-02-05 13:38:44 +01:00
|
|
|
conf = self._get_saved_searches()
|
2021-03-01 09:41:41 +01:00
|
|
|
for name in selected:
|
|
|
|
del conf[name]
|
2021-02-05 13:38:44 +01:00
|
|
|
self._set_saved_searches(conf)
|
2021-01-29 14:05:30 +01:00
|
|
|
self.refresh()
|
2021-01-20 01:26:53 +01:00
|
|
|
|
2021-03-03 21:57:39 +01:00
|
|
|
def rename_saved_search(self, item: SidebarItem, new_name: str) -> None:
|
|
|
|
old_name = item.name
|
2021-02-05 13:38:44 +01:00
|
|
|
conf = self._get_saved_searches()
|
2021-01-29 14:05:30 +01:00
|
|
|
try:
|
2021-03-03 21:57:39 +01:00
|
|
|
filt = conf[old_name]
|
2021-01-29 14:05:30 +01:00
|
|
|
except KeyError:
|
2021-02-28 22:36:21 +01:00
|
|
|
return
|
2021-03-04 17:22:03 +01:00
|
|
|
if new_name in conf and not askUser(
|
2021-03-26 05:21:04 +01:00
|
|
|
tr.browsing_confirm_saved_search_overwrite(name=new_name)
|
2021-03-04 17:22:03 +01:00
|
|
|
):
|
|
|
|
return
|
2021-03-03 21:57:39 +01:00
|
|
|
conf[new_name] = filt
|
|
|
|
del conf[old_name]
|
2021-02-05 13:38:44 +01:00
|
|
|
self._set_saved_searches(conf)
|
2021-04-06 03:18:13 +02:00
|
|
|
item.name = new_name
|
|
|
|
self.refresh()
|
2021-01-29 14:05:30 +01:00
|
|
|
|
2021-03-05 12:22:49 +01:00
|
|
|
def save_current_search(self) -> None:
|
2021-03-23 11:20:46 +01:00
|
|
|
if (search := self._get_current_search()) is None:
|
2021-03-04 17:22:03 +01:00
|
|
|
return
|
2021-03-26 04:48:26 +01:00
|
|
|
name = getOnlyText(tr.browsing_please_give_your_filter_a_name())
|
2021-03-04 17:22:03 +01:00
|
|
|
if not name:
|
|
|
|
return
|
2021-03-23 11:20:46 +01:00
|
|
|
self._save_search(name, search)
|
|
|
|
|
|
|
|
def update_saved_search(self, item: SidebarItem) -> None:
|
|
|
|
if (search := self._get_current_search()) is None:
|
2021-03-04 17:22:03 +01:00
|
|
|
return
|
2021-03-23 11:20:46 +01:00
|
|
|
self._save_search(item.name, search, update=True)
|
2021-01-20 00:00:53 +01:00
|
|
|
|
2021-03-13 09:45:06 +01:00
|
|
|
# Notetypes and templates
|
|
|
|
####################################
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
def manage_notetype(self, item: SidebarItem) -> None:
|
2021-01-22 03:56:39 +01:00
|
|
|
Models(
|
2021-03-23 12:41:24 +01:00
|
|
|
self.mw,
|
|
|
|
parent=self.browser,
|
|
|
|
fromMain=True,
|
2021-03-27 13:03:19 +01:00
|
|
|
selected_notetype_id=NotetypeId(item.id),
|
2021-01-22 03:56:39 +01:00
|
|
|
)
|
2021-02-25 21:24:11 +01:00
|
|
|
|
2021-03-10 10:14:06 +01:00
|
|
|
def manage_template(self, item: SidebarItem) -> None:
|
2021-03-27 13:03:19 +01:00
|
|
|
note = Note(self.col, self.col.models.get(NotetypeId(item._parent_item.id)))
|
2021-03-10 10:14:06 +01:00
|
|
|
CardLayout(self.mw, note, ord=item.id, parent=self, fill_empty=True)
|
|
|
|
|
2021-06-16 15:40:48 +02:00
|
|
|
def manage_fields(self, item: SidebarItem) -> None:
|
|
|
|
notetype = self.mw.col.models.get(NotetypeId(item._parent_item.id))
|
|
|
|
FieldDialog(self.mw, notetype, parent=self, open_at=item.id)
|
|
|
|
|
2021-02-25 21:24:11 +01:00
|
|
|
# Helpers
|
2021-03-13 09:45:06 +01:00
|
|
|
####################################
|
2021-02-25 21:24:11 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _selected_items(self) -> list[SidebarItem]:
|
2021-02-25 21:24:11 +01:00
|
|
|
return [self.model().item_for_index(idx) for idx in self.selectedIndexes()]
|
2021-02-26 19:52:34 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _selected_decks(self) -> list[DeckId]:
|
2021-02-26 19:52:34 +01:00
|
|
|
return [
|
2021-03-27 12:38:20 +01:00
|
|
|
DeckId(item.id)
|
2021-02-26 19:52:34 +01:00
|
|
|
for item in self._selected_items()
|
|
|
|
if item.item_type == SidebarItemType.DECK
|
|
|
|
]
|
2021-03-01 09:41:41 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _selected_saved_searches(self) -> list[str]:
|
2021-03-01 09:41:41 +01:00
|
|
|
return [
|
|
|
|
item.name
|
|
|
|
for item in self._selected_items()
|
|
|
|
if item.item_type == SidebarItemType.SAVED_SEARCH
|
|
|
|
]
|
2021-03-02 11:05:16 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def _selected_tags(self) -> list[str]:
|
2021-03-02 11:05:16 +01:00
|
|
|
return [
|
|
|
|
item.full_name
|
|
|
|
for item in self._selected_items()
|
|
|
|
if item.item_type == SidebarItemType.TAG
|
|
|
|
]
|