anki/qt/aqt/sidebar.py

867 lines
28 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from concurrent.futures import Future
from enum import Enum
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast
import aqt
from anki.collection import ConfigBoolKey, SearchTerm
from anki.decks import DeckTreeNode
from anki.errors import DeckRenameError, InvalidInput
from anki.tags import TagTreeNode
from aqt import gui_hooks
from aqt.main import ResetReason
from aqt.models import Models
from aqt.qt import *
from aqt.theme import theme_manager
from aqt.utils import (
TR,
askUser,
getOnlyText,
show_invalid_search_error,
showInfo,
showWarning,
tr,
)
class SidebarItemType(Enum):
ROOT = 0
COLLECTION = 1
CURRENT_DECK = 2
SAVED_SEARCH = 3
FILTER = 3 # legacy alias for SAVED_SEARCH
DECK = 4
NOTETYPE = 5
TAG = 6
CUSTOM = 7
TEMPLATE = 8
SAVED_SEARCH_ROOT = 9
2021-01-30 12:08:39 +01:00
DECK_ROOT = 10
2021-02-02 02:51:45 +01:00
NOTETYPE_ROOT = 11
TAG_ROOT = 12
# used by an add-on hook
class SidebarStage(Enum):
ROOT = 0
STANDARD = 1
FAVORITES = 2
DECKS = 3
MODELS = 4
TAGS = 5
class SidebarItem:
def __init__(
self,
name: str,
icon: str,
onClick: Callable[[], None] = None,
onExpanded: Callable[[bool], None] = None,
expanded: bool = False,
item_type: SidebarItemType = SidebarItemType.CUSTOM,
id: int = 0,
full_name: str = None,
) -> None:
self.name = name
if not full_name:
full_name = name
self.full_name = full_name
self.icon = icon
self.item_type = item_type
self.id = id
self.onClick = onClick
self.onExpanded = onExpanded
self.expanded = expanded
self.children: List["SidebarItem"] = []
self.parentItem: Optional["SidebarItem"] = None
self.tooltip: Optional[str] = None
self.row_in_parent: Optional[int] = None
self._search_matches_self = False
self._search_matches_child = False
def addChild(self, cb: "SidebarItem") -> None:
self.children.append(cb)
cb.parentItem = self
def rowForChild(self, child: "SidebarItem") -> Optional[int]:
try:
return self.children.index(child)
except ValueError:
return None
def is_expanded(self, searching: bool) -> bool:
if not searching:
return self.expanded
else:
if self._search_matches_child:
return True
# if search matches top level, expand children one level
return self._search_matches_self and self.item_type in (
SidebarItemType.SAVED_SEARCH_ROOT,
SidebarItemType.DECK_ROOT,
2021-02-02 02:51:45 +01:00
SidebarItemType.NOTETYPE_ROOT,
SidebarItemType.TAG_ROOT,
)
def is_highlighted(self) -> bool:
return self._search_matches_self
def search(self, lowered_text: str) -> bool:
"True if we or child matched."
self._search_matches_self = lowered_text in self.name.lower()
self._search_matches_child = any(
[child.search(lowered_text) for child in self.children]
)
return self._search_matches_self or self._search_matches_child
class SidebarModel(QAbstractItemModel):
def __init__(self, root: SidebarItem) -> None:
super().__init__()
self.root = root
self._cache_rows(root)
2021-02-01 00:51:46 +01:00
def _cache_rows(self, node: SidebarItem) -> None:
"Cache index of children in parent."
for row, item in enumerate(node.children):
item.row_in_parent = row
self._cache_rows(item)
def item_for_index(self, idx: QModelIndex) -> SidebarItem:
return idx.internalPointer()
def search(self, text: str) -> bool:
return self.root.search(text.lower())
# Qt API
######################################################################
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
if not parent.isValid():
return len(self.root.children)
else:
item: SidebarItem = parent.internalPointer()
return len(item.children)
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
return 1
def index(
self, row: int, column: int, parent: QModelIndex = QModelIndex()
) -> QModelIndex:
if not self.hasIndex(row, column, parent):
return QModelIndex()
parentItem: SidebarItem
if not parent.isValid():
parentItem = self.root
else:
parentItem = parent.internalPointer()
item = parentItem.children[row]
return self.createIndex(row, column, item)
def parent(self, child: QModelIndex) -> QModelIndex: # type: ignore
if not child.isValid():
return QModelIndex()
childItem: SidebarItem = child.internalPointer()
parentItem = childItem.parentItem
if parentItem is None or parentItem == self.root:
return QModelIndex()
row = parentItem.row_in_parent
return self.createIndex(row, 0, parentItem)
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> QVariant:
if not index.isValid():
return QVariant()
if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):
return QVariant()
item: SidebarItem = index.internalPointer()
if role == Qt.DisplayRole:
return QVariant(item.name)
elif role == Qt.ToolTipRole:
return QVariant(item.tooltip)
else:
return QVariant(theme_manager.icon_from_resources(item.icon))
2021-02-01 00:51:46 +01:00
def supportedDropActions(self) -> Qt.DropActions:
return cast(Qt.DropActions, Qt.MoveAction)
2021-01-30 12:08:39 +01:00
2021-02-01 00:51:46 +01:00
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
2021-01-30 12:08:39 +01:00
if not index.isValid():
2021-02-01 00:51:46 +01:00
return cast(Qt.ItemFlags, Qt.ItemIsEnabled)
2021-01-30 12:32:25 +01:00
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
item: SidebarItem = index.internalPointer()
if item.item_type in (
SidebarItemType.DECK,
SidebarItemType.DECK_ROOT,
2021-02-02 11:14:04 +01:00
SidebarItemType.TAG,
SidebarItemType.TAG_ROOT,
2021-01-30 12:32:25 +01:00
):
flags |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
2021-02-01 00:51:46 +01:00
return cast(Qt.ItemFlags, flags)
2021-01-30 12:08:39 +01:00
# Helpers
######################################################################
def iconFromRef(self, iconRef: str) -> QIcon:
print("iconFromRef() deprecated")
return theme_manager.icon_from_resources(iconRef)
2021-02-01 00:51:46 +01:00
def expand_where_necessary(
model: SidebarModel,
tree: QTreeView,
parent: Optional[QModelIndex] = None,
searching: bool = False,
2021-02-01 00:51:46 +01:00
) -> None:
parent = parent or QModelIndex()
for row in range(model.rowCount(parent)):
idx = model.index(row, 0, parent)
if not idx.isValid():
continue
expand_where_necessary(model, tree, idx, searching)
if item := model.item_for_index(idx):
if item.is_expanded(searching):
tree.setExpanded(idx, True)
class SidebarSearchBar(QLineEdit):
2021-02-01 00:51:46 +01:00
def __init__(self, sidebar: SidebarTreeView) -> None:
QLineEdit.__init__(self, sidebar)
self.setPlaceholderText(sidebar.col.tr(TR.BROWSING_SIDEBAR_FILTER))
self.sidebar = sidebar
self.timer = QTimer(self)
self.timer.setInterval(600)
self.timer.setSingleShot(True)
qconnect(self.timer.timeout, self.onSearch)
qconnect(self.textChanged, self.onTextChanged)
2021-02-01 00:51:46 +01:00
def onTextChanged(self, text: str) -> None:
if not self.timer.isActive():
self.timer.start()
2021-02-01 00:51:46 +01:00
def onSearch(self) -> None:
self.sidebar.search_for(self.text())
2021-02-01 00:51:46 +01:00
def keyPressEvent(self, evt: QKeyEvent) -> None:
if evt.key() in (Qt.Key_Up, Qt.Key_Down):
self.sidebar.setFocus()
elif evt.key() in (Qt.Key_Enter, Qt.Key_Return):
self.onSearch()
else:
QLineEdit.keyPressEvent(self, evt)
class SidebarTreeView(QTreeView):
def __init__(self, browser: aqt.browser.Browser) -> None:
super().__init__()
self.browser = browser
self.mw = browser.mw
self.col = self.mw.col
self.current_search: Optional[str] = None
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore
2021-01-29 13:50:29 +01:00
self.context_menus: Dict[SidebarItemType, Sequence[Tuple[str, Callable]]] = {
SidebarItemType.DECK: (
(tr(TR.ACTIONS_RENAME), self.rename_deck),
(tr(TR.ACTIONS_DELETE), self.delete_deck),
),
SidebarItemType.TAG: (
(tr(TR.ACTIONS_RENAME), self.rename_tag),
(tr(TR.ACTIONS_DELETE), self.remove_tag),
),
SidebarItemType.SAVED_SEARCH: (
(tr(TR.ACTIONS_RENAME), self.rename_saved_search),
(tr(TR.ACTIONS_DELETE), self.remove_saved_search),
),
SidebarItemType.NOTETYPE: ((tr(TR.ACTIONS_MANAGE), self.manage_notetype),),
SidebarItemType.SAVED_SEARCH_ROOT: (
(tr(TR.BROWSING_SIDEBAR_SAVE_CURRENT_SEARCH), self.save_current_search),
),
}
self.setUniformRowHeights(True)
self.setHeaderHidden(True)
self.setIndentation(15)
2021-02-02 11:14:14 +01:00
self.setAutoExpandDelay(600)
2021-01-30 12:50:53 +01:00
# this doesn't play nicely with shift+click to OR items - we may want
# to put it behind a 'multi-select' mode
# self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
2021-01-30 12:08:39 +01:00
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDragDropOverwriteMode(False)
qconnect(self.expanded, self.onExpansion)
qconnect(self.collapsed, self.onCollapse)
# match window background color
bgcolor = QPalette().window().color().name()
self.setStyleSheet("QTreeView { background: '%s'; }" % bgcolor)
def model(self) -> SidebarModel:
return super().model()
def refresh(self) -> None:
"Refresh list. No-op if sidebar is not visible."
if not self.isVisible():
return
2021-02-01 00:51:46 +01:00
def on_done(fut: Future) -> None:
root = fut.result()
model = SidebarModel(root)
# from PyQt5.QtTest import QAbstractItemModelTester
# tester = QAbstractItemModelTester(model)
self.setModel(model)
if self.current_search:
self.search_for(self.current_search)
else:
expand_where_necessary(model, self)
self.mw.taskman.run_in_background(self._root_tree, on_done)
2021-02-01 00:51:46 +01:00
def search_for(self, text: str) -> None:
self.showColumn(0)
if not text.strip():
self.current_search = None
self.refresh()
return
2021-01-28 09:58:51 +01:00
self.current_search = text
# start from a collapsed state, as it's faster
self.collapseAll()
self.setColumnHidden(0, not self.model().search(text))
expand_where_necessary(self.model(), self, searching=True)
def drawRow(
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
2021-02-01 00:51:46 +01:00
) -> None:
if self.current_search and (item := self.model().item_for_index(idx)):
if item.is_highlighted():
brush = QBrush(theme_manager.qcolor("suspended-bg"))
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()
source_items = [model.item_for_index(idx) for idx in self.selectedIndexes()]
target_item = model.item_for_index(self.indexAt(event.pos()))
if self.handle_drag_drop(source_items, target_item):
event.acceptProposedAction()
def handle_drag_drop(self, sources: List[SidebarItem], target: SidebarItem) -> bool:
if target.item_type in (SidebarItemType.DECK, SidebarItemType.DECK_ROOT):
return self._handle_drag_drop_decks(sources, target)
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-01-30 12:08:39 +01:00
return False
2021-01-30 12:32:25 +01:00
def _handle_drag_drop_decks(
self, sources: List[SidebarItem], target: SidebarItem
) -> bool:
source_ids = [
source.id for source in sources if source.item_type == SidebarItemType.DECK
]
2021-01-30 12:08:39 +01:00
if not source_ids:
return False
2021-02-01 00:51:46 +01:00
def on_done(fut: Future) -> None:
self.browser.model.endReset()
2021-01-30 12:08:39 +01:00
fut.result()
self.refresh()
self.mw.deckBrowser.refresh()
2021-01-30 12:08:39 +01:00
def on_save() -> None:
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK))
self.browser.model.beginReset()
self.mw.taskman.with_progress(
lambda: self.col.decks.drag_drop_decks(source_ids, target.id), on_done
)
self.browser.editor.saveNow(on_save)
2021-01-30 12:08:39 +01:00
return True
2021-02-02 11:14:04 +01:00
def _handle_drag_drop_tags(
self, sources: List[SidebarItem], target: SidebarItem
) -> bool:
source_ids = [
source.full_name
for source in sources
if source.item_type == SidebarItemType.TAG
]
if not source_ids:
return False
def on_done(fut: Future) -> None:
self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self)
self.browser.model.endReset()
2021-02-02 11:14:04 +01:00
fut.result()
self.refresh()
if target.item_type == SidebarItemType.TAG_ROOT:
target_name = ""
else:
target_name = target.full_name
def on_save() -> None:
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG))
self.browser.model.beginReset()
self.mw.taskman.with_progress(
lambda: self.col.tags.drag_drop(source_ids, target_name), on_done
)
self.browser.editor.saveNow(on_save)
2021-02-02 11:14:04 +01:00
return True
def onClickCurrent(self) -> None:
idx = self.currentIndex()
if item := self.model().item_for_index(idx):
if item.onClick:
item.onClick()
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton:
self.onClickCurrent()
def keyPressEvent(self, event: QKeyEvent) -> None:
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.onClickCurrent()
else:
super().keyPressEvent(event)
def onExpansion(self, idx: QModelIndex) -> None:
2021-01-28 09:58:51 +01:00
if self.current_search:
return
self._onExpansionChange(idx, True)
def onCollapse(self, idx: QModelIndex) -> None:
2021-01-28 09:58:51 +01:00
if self.current_search:
return
self._onExpansionChange(idx, False)
def _onExpansionChange(self, idx: QModelIndex, expanded: bool) -> None:
item = self.model().item_for_index(idx)
if item and item.expanded != expanded:
item.expanded = expanded
if item.onExpanded:
item.onExpanded(expanded)
# Tree building
###########################
def _root_tree(self) -> SidebarItem:
root = SidebarItem("", "", item_type=SidebarItemType.ROOT)
handled = gui_hooks.browser_will_build_tree(
False, root, SidebarStage.ROOT, self
)
if handled:
return root
for stage, builder in zip(
list(SidebarStage)[1:],
(
self._commonly_used_tree,
self._saved_searches_tree,
self._deck_tree,
self._notetype_tree,
self._tag_tree,
),
):
handled = gui_hooks.browser_will_build_tree(False, root, stage, self)
if not handled and builder:
builder(root)
return root
def _section_root(
self,
*,
root: SidebarItem,
name: TR.V,
icon: str,
collapse_key: ConfigBoolKey.V,
type: Optional[SidebarItemType] = None,
) -> SidebarItem:
2021-02-01 00:51:46 +01:00
def update(expanded: bool) -> None:
self.col.set_config_bool(collapse_key, not expanded)
top = SidebarItem(
2021-01-29 13:50:29 +01:00
tr(name),
icon,
onExpanded=update,
expanded=not self.col.get_config_bool(collapse_key),
item_type=type,
)
root.addChild(top)
return top
def _commonly_used_tree(self, root: SidebarItem) -> None:
item = SidebarItem(
tr(TR.BROWSING_WHOLE_COLLECTION),
":/icons/collection.svg",
self._filter_func(),
item_type=SidebarItemType.COLLECTION,
)
root.addChild(item)
item = SidebarItem(
tr(TR.BROWSING_CURRENT_DECK),
":/icons/deck.svg",
self._filter_func(SearchTerm(deck="current")),
item_type=SidebarItemType.CURRENT_DECK,
)
root.addChild(item)
def _saved_searches_tree(self, root: SidebarItem) -> None:
icon = ":/icons/heart.svg"
saved = self.col.get_config("savedFilters", {})
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_SAVED_SEARCHES,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_FAVORITES,
type=SidebarItemType.SAVED_SEARCH_ROOT,
)
2021-02-01 00:51:46 +01:00
def on_click() -> None:
2021-01-29 13:50:29 +01:00
self.show_context_menu(root, None)
root.onClick = on_click
for name, filt in sorted(saved.items()):
item = SidebarItem(
name,
icon,
self._filter_func(filt),
item_type=SidebarItemType.FILTER,
)
root.addChild(item)
def _tag_tree(self, root: SidebarItem) -> None:
icon = ":/icons/tag.svg"
2021-02-01 00:51:46 +01:00
def render(
root: SidebarItem, nodes: Iterable[TagTreeNode], head: str = ""
) -> None:
for node in nodes:
2021-02-01 00:51:46 +01:00
def toggle_expand() -> Callable[[bool], None]:
full_name = head + node.name # pylint: disable=cell-var-from-loop
return lambda expanded: self.mw.col.tags.set_expanded(
full_name, expanded
)
item = SidebarItem(
node.name,
icon,
self._filter_func(SearchTerm(tag=head + node.name)),
toggle_expand(),
2021-02-02 09:12:50 +01:00
node.expanded,
item_type=SidebarItemType.TAG,
full_name=head + node.name,
)
root.addChild(item)
newhead = head + node.name + "::"
render(item, node.children, newhead)
tree = self.col.tags.tree()
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_TAGS,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_TAGS,
2021-02-02 02:51:45 +01:00
type=SidebarItemType.TAG_ROOT,
)
render(root, tree.children)
def _deck_tree(self, root: SidebarItem) -> None:
icon = ":/icons/deck.svg"
2021-02-01 00:51:46 +01:00
def render(
root: SidebarItem, nodes: Iterable[DeckTreeNode], head: str = ""
) -> None:
for node in nodes:
2021-02-01 00:51:46 +01:00
def toggle_expand() -> Callable[[bool], None]:
did = node.deck_id # pylint: disable=cell-var-from-loop
return lambda _: self.mw.col.decks.collapseBrowser(did)
item = SidebarItem(
node.name,
icon,
self._filter_func(SearchTerm(deck=head + node.name)),
toggle_expand(),
not node.collapsed,
item_type=SidebarItemType.DECK,
id=node.deck_id,
full_name=head + node.name,
)
root.addChild(item)
newhead = head + node.name + "::"
render(item, node.children, newhead)
tree = self.col.decks.deck_tree()
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_DECKS,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_DECKS,
2021-01-30 12:08:39 +01:00
type=SidebarItemType.DECK_ROOT,
)
render(root, tree.children)
def _notetype_tree(self, root: SidebarItem) -> None:
icon = ":/icons/notetype.svg"
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_NOTETYPES,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_NOTETYPES,
2021-02-02 02:51:45 +01:00
type=SidebarItemType.NOTETYPE_ROOT,
)
for nt in sorted(self.col.models.all(), key=lambda nt: nt["name"].lower()):
item = SidebarItem(
nt["name"],
icon,
self._filter_func(SearchTerm(note=nt["name"])),
item_type=SidebarItemType.NOTETYPE,
id=nt["id"],
)
for c, tmpl in enumerate(nt["tmpls"]):
child = SidebarItem(
tmpl["name"],
icon,
self._filter_func(
SearchTerm(note=nt["name"]), SearchTerm(template=c)
),
item_type=SidebarItemType.TEMPLATE,
full_name=nt["name"] + "::" + tmpl["name"],
)
item.addChild(child)
root.addChild(item)
def _filter_func(self, *terms: Union[str, SearchTerm]) -> Callable:
return lambda: self.browser.update_search(self.col.build_search_string(*terms))
# Context menu actions
###########################
def onContextMenu(self, point: QPoint) -> None:
idx: QModelIndex = self.indexAt(point)
item = self.model().item_for_index(idx)
if not item:
return
2021-01-29 13:50:29 +01:00
self.show_context_menu(item, idx)
2021-02-01 00:51:46 +01:00
def show_context_menu(self, item: SidebarItem, idx: Optional[QModelIndex]) -> None:
2021-01-29 13:50:29 +01:00
m = QMenu()
if item.item_type in self.context_menus:
for action in self.context_menus[item.item_type]:
act_name = action[0]
act_func = action[1]
a = m.addAction(act_name)
qconnect(a.triggered, lambda _, func=act_func: func(item))
if idx:
self.maybe_add_tree_actions(m, item, idx)
if not m.children():
return
# until we support multiple selection, show user that only the current
# item is being operated on by clearing the selection
if idx:
sm = self.selectionModel()
sm.clear()
2021-01-30 12:50:53 +01:00
sm.select(
idx,
cast(
QItemSelectionModel.SelectionFlag,
QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows,
),
)
m.exec_(QCursor.pos())
2021-01-29 13:50:29 +01:00
def maybe_add_tree_actions(
self, menu: QMenu, item: SidebarItem, parent: QModelIndex
) -> None:
if self.current_search:
return
if not any(bool(c.children) for c in item.children):
return
def set_children_collapsed(collapsed: bool) -> None:
m = self.model()
self.setExpanded(parent, True)
for row in range(m.rowCount(parent)):
idx = m.index(row, 0, parent)
self.setExpanded(idx, not collapsed)
menu.addSeparator()
menu.addAction(
tr(TR.BROWSING_SIDEBAR_EXPAND_CHILDREN),
lambda: set_children_collapsed(False),
)
menu.addAction(
tr(TR.BROWSING_SIDEBAR_COLLAPSE_CHILDREN),
lambda: set_children_collapsed(True),
)
def rename_deck(self, item: "aqt.browser.SidebarItem") -> None:
deck = self.mw.col.decks.get(item.id)
old_name = deck["name"]
new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name)
new_name = new_name.replace('"', "")
if not new_name or new_name == old_name:
return
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK))
try:
self.mw.col.decks.rename(deck, new_name)
except DeckRenameError as e:
showWarning(e.description)
return
self.refresh()
self.mw.deckBrowser.refresh()
2021-01-06 15:04:03 +01:00
def remove_tag(self, item: "aqt.browser.SidebarItem") -> None:
self.browser.editor.saveNow(lambda: self._remove_tag(item))
def _remove_tag(self, item: "aqt.browser.SidebarItem") -> None:
old_name = item.full_name
2021-01-06 15:04:03 +01:00
2021-02-01 00:51:46 +01:00
def do_remove() -> None:
self.mw.col.tags.remove(old_name)
self.col.tags.rename(old_name, "")
2021-01-06 15:04:03 +01:00
2021-02-01 00:51:46 +01:00
def on_done(fut: Future) -> None:
2021-01-06 15:04:03 +01:00
self.mw.requireReset(reason=ResetReason.BrowserRemoveTags, context=self)
self.browser.model.endReset()
fut.result()
self.refresh()
2021-01-06 15:04:03 +01:00
self.mw.checkpoint(tr(TR.ACTIONS_REMOVE_TAG))
self.browser.model.beginReset()
self.mw.taskman.run_in_background(do_remove, on_done)
def rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
self.browser.editor.saveNow(lambda: self._rename_tag(item))
def _rename_tag(self, item: "aqt.browser.SidebarItem") -> None:
old_name = item.full_name
new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name)
if new_name == old_name or not new_name:
return
2021-02-01 00:51:46 +01:00
def do_rename() -> int:
self.mw.col.tags.remove(old_name)
return self.col.tags.rename(old_name, new_name)
2021-02-01 00:51:46 +01:00
def on_done(fut: Future) -> None:
self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self)
self.browser.model.endReset()
count = fut.result()
if not count:
showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY))
return
self.refresh()
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG))
self.browser.model.beginReset()
self.mw.taskman.run_in_background(do_rename, on_done)
def delete_deck(self, item: "aqt.browser.SidebarItem") -> None:
self.browser.editor.saveNow(lambda: self._delete_deck(item))
def _delete_deck(self, item: "aqt.browser.SidebarItem") -> None:
did = item.id
if self.mw.deckBrowser.ask_delete_deck(did):
2021-02-01 00:51:46 +01:00
def do_delete() -> None:
return self.mw.col.decks.rem(did, True)
2021-02-01 00:51:46 +01:00
def on_done(fut: Future) -> None:
2021-01-04 11:36:45 +01:00
self.mw.requireReset(reason=ResetReason.BrowserDeleteDeck, context=self)
self.browser.search()
self.browser.model.endReset()
self.refresh()
res = fut.result() # Required to check for errors
self.mw.checkpoint(tr(TR.DECKS_DELETE_DECK))
self.browser.model.beginReset()
self.mw.taskman.run_in_background(do_delete, on_done)
def remove_saved_search(self, item: "aqt.browser.SidebarItem") -> None:
name = item.name
if not askUser(tr(TR.BROWSING_REMOVE_FROM_YOUR_SAVED_SEARCHES, val=name)):
return
conf = self.col.get_config("savedFilters")
del conf[name]
self.col.set_config("savedFilters", conf)
self.refresh()
def rename_saved_search(self, item: "aqt.browser.SidebarItem") -> None:
old = item.name
conf = self.col.get_config("savedFilters")
try:
filt = conf[old]
except KeyError:
return
new = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old)
if new == old or not new:
return
conf[new] = filt
del conf[old]
self.col.set_config("savedFilters", conf)
self.refresh()
2021-02-01 00:51:46 +01:00
def save_current_search(self, _item: Any = None) -> None:
try:
filt = self.col.build_search_string(
self.browser.form.searchEdit.lineEdit().text()
)
except InvalidInput as e:
show_invalid_search_error(e)
else:
name = getOnlyText(tr(TR.BROWSING_PLEASE_GIVE_YOUR_FILTER_A_NAME))
if not name:
return
conf = self.col.get_config("savedFilters")
conf[name] = filt
self.col.set_config("savedFilters", conf)
self.refresh()
def manage_notetype(self, item: "aqt.browser.SidebarItem") -> None:
Models(
self.mw, parent=self.browser, fromMain=True, selected_notetype_id=item.id
)