mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Enable in-place editing of sidebar deck items
This commit is contained in:
parent
88c69665f3
commit
0b83828508
1 changed files with 36 additions and 11 deletions
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast
|
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast
|
||||||
|
@ -87,6 +88,7 @@ class SidebarItem:
|
||||||
item_type: SidebarItemType = SidebarItemType.CUSTOM,
|
item_type: SidebarItemType = SidebarItemType.CUSTOM,
|
||||||
id: int = 0,
|
id: int = 0,
|
||||||
full_name: str = None,
|
full_name: str = None,
|
||||||
|
editable: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
if not full_name:
|
if not full_name:
|
||||||
|
@ -97,6 +99,7 @@ class SidebarItem:
|
||||||
self.id = id
|
self.id = id
|
||||||
self.on_click = on_click
|
self.on_click = on_click
|
||||||
self.search_node = search_node
|
self.search_node = search_node
|
||||||
|
self.editable = editable
|
||||||
self.on_expanded = on_expanded
|
self.on_expanded = on_expanded
|
||||||
self.children: List["SidebarItem"] = []
|
self.children: List["SidebarItem"] = []
|
||||||
self.tooltip: Optional[str] = None
|
self.tooltip: Optional[str] = None
|
||||||
|
@ -151,8 +154,9 @@ class SidebarItem:
|
||||||
|
|
||||||
|
|
||||||
class SidebarModel(QAbstractItemModel):
|
class SidebarModel(QAbstractItemModel):
|
||||||
def __init__(self, root: SidebarItem) -> None:
|
def __init__(self, sidebar: SidebarTreeView, root: SidebarItem) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.sidebar = sidebar
|
||||||
self.root = root
|
self.root = root
|
||||||
self._cache_rows(root)
|
self._cache_rows(root)
|
||||||
|
|
||||||
|
@ -214,17 +218,19 @@ class SidebarModel(QAbstractItemModel):
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):
|
if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole, Qt.EditRole):
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
item: SidebarItem = index.internalPointer()
|
item: SidebarItem = index.internalPointer()
|
||||||
|
|
||||||
if role == Qt.DisplayRole:
|
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||||
return QVariant(item.name)
|
return QVariant(item.name)
|
||||||
elif role == Qt.ToolTipRole:
|
if role == Qt.ToolTipRole:
|
||||||
return QVariant(item.tooltip)
|
return QVariant(item.tooltip)
|
||||||
else:
|
return QVariant(theme_manager.icon_from_resources(item.icon))
|
||||||
return QVariant(theme_manager.icon_from_resources(item.icon))
|
|
||||||
|
def setData(self, index: QModelIndex, text: str, _role: int) -> bool:
|
||||||
|
return self.sidebar.rename_node(index.internalPointer(), text)
|
||||||
|
|
||||||
def supportedDropActions(self) -> Qt.DropActions:
|
def supportedDropActions(self) -> Qt.DropActions:
|
||||||
return cast(Qt.DropActions, Qt.MoveAction)
|
return cast(Qt.DropActions, Qt.MoveAction)
|
||||||
|
@ -242,6 +248,8 @@ class SidebarModel(QAbstractItemModel):
|
||||||
SidebarItemType.TAG_ROOT,
|
SidebarItemType.TAG_ROOT,
|
||||||
):
|
):
|
||||||
flags |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
|
flags |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
|
||||||
|
if item.editable:
|
||||||
|
flags |= Qt.ItemIsEditable
|
||||||
|
|
||||||
return cast(Qt.ItemFlags, flags)
|
return cast(Qt.ItemFlags, flags)
|
||||||
|
|
||||||
|
@ -385,15 +393,19 @@ class SidebarTreeView(QTreeView):
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
selection_mode = QAbstractItemView.ExtendedSelection # type: ignore
|
selection_mode = QAbstractItemView.ExtendedSelection # type: ignore
|
||||||
drag_drop_mode = QAbstractItemView.NoDragDrop
|
drag_drop_mode = QAbstractItemView.NoDragDrop
|
||||||
|
edit_triggers = QAbstractItemView.EditKeyPressed
|
||||||
elif tool == SidebarTool.SEARCH:
|
elif tool == SidebarTool.SEARCH:
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
selection_mode = QAbstractItemView.SingleSelection # type: ignore
|
selection_mode = QAbstractItemView.SingleSelection # type: ignore
|
||||||
drag_drop_mode = QAbstractItemView.NoDragDrop
|
drag_drop_mode = QAbstractItemView.NoDragDrop
|
||||||
|
edit_triggers = QAbstractItemView.EditKeyPressed
|
||||||
elif tool == SidebarTool.EDIT:
|
elif tool == SidebarTool.EDIT:
|
||||||
selection_mode = QAbstractItemView.SingleSelection # type: ignore
|
selection_mode = QAbstractItemView.SingleSelection # type: ignore
|
||||||
drag_drop_mode = QAbstractItemView.InternalMove
|
drag_drop_mode = QAbstractItemView.InternalMove
|
||||||
|
edit_triggers = QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed
|
||||||
self.setSelectionMode(selection_mode)
|
self.setSelectionMode(selection_mode)
|
||||||
self.setDragDropMode(drag_drop_mode)
|
self.setDragDropMode(drag_drop_mode)
|
||||||
|
self.setEditTriggers(edit_triggers)
|
||||||
|
|
||||||
def model(self) -> SidebarModel:
|
def model(self) -> SidebarModel:
|
||||||
return super().model()
|
return super().model()
|
||||||
|
@ -405,7 +417,7 @@ class SidebarTreeView(QTreeView):
|
||||||
|
|
||||||
def on_done(fut: Future) -> None:
|
def on_done(fut: Future) -> None:
|
||||||
root = fut.result()
|
root = fut.result()
|
||||||
model = SidebarModel(root)
|
model = SidebarModel(self, root)
|
||||||
|
|
||||||
# from PyQt5.QtTest import QAbstractItemModelTester
|
# from PyQt5.QtTest import QAbstractItemModelTester
|
||||||
# tester = QAbstractItemModelTester(model)
|
# tester = QAbstractItemModelTester(model)
|
||||||
|
@ -415,7 +427,10 @@ class SidebarTreeView(QTreeView):
|
||||||
self.search_for(self.current_search)
|
self.search_for(self.current_search)
|
||||||
else:
|
else:
|
||||||
self._expand_where_necessary(model)
|
self._expand_where_necessary(model)
|
||||||
|
self.setUpdatesEnabled(True)
|
||||||
|
|
||||||
|
# block repainting during refreshing to avoid flickering
|
||||||
|
self.setUpdatesEnabled(False)
|
||||||
self.mw.taskman.run_in_background(self._root_tree, on_done)
|
self.mw.taskman.run_in_background(self._root_tree, on_done)
|
||||||
|
|
||||||
def search_for(self, text: str) -> None:
|
def search_for(self, text: str) -> None:
|
||||||
|
@ -908,6 +923,7 @@ class SidebarTreeView(QTreeView):
|
||||||
item_type=SidebarItemType.DECK,
|
item_type=SidebarItemType.DECK,
|
||||||
id=node.deck_id,
|
id=node.deck_id,
|
||||||
full_name=head + node.name,
|
full_name=head + node.name,
|
||||||
|
editable=True,
|
||||||
)
|
)
|
||||||
root.add_child(item)
|
root.add_child(item)
|
||||||
newhead = f"{head + node.name}::"
|
newhead = f"{head + node.name}::"
|
||||||
|
@ -1044,21 +1060,22 @@ class SidebarTreeView(QTreeView):
|
||||||
lambda: set_children_collapsed(True),
|
lambda: set_children_collapsed(True),
|
||||||
)
|
)
|
||||||
|
|
||||||
def rename_deck(self, item: SidebarItem) -> None:
|
def rename_deck(self, item: SidebarItem, new_name: Optional[str] = None) -> bool:
|
||||||
deck = self.mw.col.decks.get(item.id)
|
deck = self.mw.col.decks.get(item.id)
|
||||||
old_name = deck["name"]
|
old_name = deck["name"]
|
||||||
new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name)
|
new_name = new_name or getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name)
|
||||||
new_name = new_name.replace('"', "")
|
new_name = new_name.replace('"', "")
|
||||||
if not new_name or new_name == old_name:
|
if not new_name or new_name == old_name:
|
||||||
return
|
return False
|
||||||
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK))
|
self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK))
|
||||||
try:
|
try:
|
||||||
self.mw.col.decks.rename(deck, new_name)
|
self.mw.col.decks.rename(deck, new_name)
|
||||||
except DeckRenameError as e:
|
except DeckRenameError as e:
|
||||||
showWarning(e.description)
|
showWarning(e.description)
|
||||||
return
|
return False
|
||||||
self.refresh()
|
self.refresh()
|
||||||
self.mw.deckBrowser.refresh()
|
self.mw.deckBrowser.refresh()
|
||||||
|
return True
|
||||||
|
|
||||||
def remove_tag(self, item: SidebarItem) -> None:
|
def remove_tag(self, item: SidebarItem) -> None:
|
||||||
self.browser.editor.saveNow(lambda: self._remove_tag(item))
|
self.browser.editor.saveNow(lambda: self._remove_tag(item))
|
||||||
|
@ -1129,6 +1146,14 @@ class SidebarTreeView(QTreeView):
|
||||||
self.browser.model.beginReset()
|
self.browser.model.beginReset()
|
||||||
self.mw.taskman.run_in_background(do_delete, on_done)
|
self.mw.taskman.run_in_background(do_delete, on_done)
|
||||||
|
|
||||||
|
def rename_node(self, item: SidebarItem, text: str) -> bool:
|
||||||
|
if text.replace('"', ""):
|
||||||
|
new_name = re.sub(re.escape(item.name) + '$', text, item.full_name)
|
||||||
|
if item.item_type == SidebarItemType.DECK:
|
||||||
|
return self.rename_deck(item, new_name)
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
# Saved searches
|
# Saved searches
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue