Skip to content

Commit e4af550

Browse files
committed
feat(ui): replace add tag modal with autocomplete search/create bar
1 parent 6aa0cf7 commit e4af550

18 files changed

Lines changed: 1075 additions & 129 deletions

docs/usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Hover over the field and click the pencil icon. From there, add or edit text in
4040

4141
## Creating Tags
4242

43-
Create a new tag by accessing the "New Tag" option from the Edit menu or by pressing <kbd>Ctrl</kbd>+<kbd>T</kbd>. In the tag creation panel, enter a tag name, optional shorthand name, optional tag aliases, optional parent tags, and an optional color.
43+
Create a new tag by accessing the "New Tag" option from the Edit menu or by pressing <kbd>Ctrl</kbd>+<kbd>N</kbd>. In the tag creation panel, enter a tag name, optional shorthand name, optional tag aliases, optional parent tags, and an optional color.
4444

4545
- The tag **name** is the base name of the tag. **_This does NOT have to be unique!_**
4646
- The tag **shorthand** is a special type of alias that displays in situations where screen space is more valuable, notably with name disambiguation.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# SPDX-FileCopyrightText: (c) TagStudio Contributors
2+
# SPDX-License-Identifier: GPL-3.0-only
3+
4+
from typing import TYPE_CHECKING, override
5+
6+
import structlog
7+
from PySide6 import QtCore, QtGui
8+
from PySide6.QtCore import Signal
9+
from PySide6.QtWidgets import (
10+
QLineEdit,
11+
QWidget,
12+
)
13+
14+
from tagstudio.qt.views.stylesheets.stylesheets import (
15+
autofill_scroll_top_focus_style,
16+
autofill_scroll_top_style,
17+
)
18+
19+
if TYPE_CHECKING:
20+
pass
21+
22+
logger = structlog.get_logger(__name__)
23+
24+
25+
class AutofillLineEdit(QLineEdit):
26+
return_pressed = Signal()
27+
shift_return_pressed = Signal()
28+
shift_holding = Signal(bool)
29+
30+
def __init__(self, popup: QWidget) -> None:
31+
super().__init__()
32+
self._popup = popup
33+
34+
@override
35+
def focusOutEvent(self, arg__1: QtGui.QFocusEvent) -> None:
36+
self._popup.setStyleSheet(autofill_scroll_top_style("container"))
37+
return super().focusOutEvent(arg__1)
38+
39+
@override
40+
def focusInEvent(self, arg__1: QtGui.QFocusEvent) -> None:
41+
self._popup.setStyleSheet(autofill_scroll_top_focus_style("container"))
42+
return super().focusInEvent(arg__1)
43+
44+
@override
45+
def keyPressEvent(self, arg__1: QtGui.QKeyEvent) -> None:
46+
if arg__1.key() == QtCore.Qt.Key.Key_Shift:
47+
self.shift_holding.emit(True) # noqa: FBT003
48+
49+
if arg__1.key() == QtCore.Qt.Key.Key_Escape:
50+
self.setText("")
51+
self.clearFocus()
52+
elif arg__1.key() == QtCore.Qt.Key.Key_Enter or arg__1.key() == QtCore.Qt.Key.Key_Return:
53+
if arg__1.modifiers() and QtCore.Qt.KeyboardModifier.ShiftModifier:
54+
self.shift_return_pressed.emit()
55+
else:
56+
self.return_pressed.emit()
57+
58+
return super().keyPressEvent(arg__1)
59+
60+
@override
61+
def keyReleaseEvent(self, arg__1: QtGui.QKeyEvent) -> None:
62+
if arg__1.key() == QtCore.Qt.Key.Key_Shift:
63+
self.shift_holding.emit(False) # noqa: FBT003
64+
return super().keyReleaseEvent(arg__1)

src/tagstudio/qt/controllers/preview_panel_controller.py

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,99 @@
33

44

55
import typing
6+
from pathlib import Path
7+
from typing import override
68
from warnings import catch_warnings
79

10+
import structlog
11+
from PySide6 import QtCore
12+
from PySide6.QtGui import QShortcut
13+
814
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
915
from tagstudio.core.library.alchemy.library import Library
1016
from tagstudio.core.utils.ffmpeg_status import FfmpegStatus, FfprobeStatus
11-
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchModal
12-
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
13-
from tagstudio.qt.translations import Translations
17+
from tagstudio.qt.mixed.file_attributes import FileAttributeData
1418
from tagstudio.qt.views.preview_panel_view import PreviewPanelView
1519

1620
if typing.TYPE_CHECKING:
1721
from tagstudio.qt.ts_qt import QtDriver
1822

23+
logger = structlog.get_logger(__name__)
24+
1925

2026
class PreviewPanel(PreviewPanelView):
2127
def __init__(self, library: Library, driver: "QtDriver") -> None:
2228
super().__init__(library, driver)
2329

24-
self.__add_field_modal = FieldTemplateSearchModal(self.lib, is_field_template_chooser=True)
25-
self.__add_tag_modal = TagSearchModal(
26-
self.lib, title=Translations["tag.add.plural"], is_tag_chooser=True
27-
)
28-
self.__add_tag_modal.tsp.set_driver(driver)
30+
self.__current_stats: FileAttributeData | None = None
2931
self._thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning)
3032

31-
@typing.override
33+
key = QtCore.QKeyCombination(
34+
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
35+
QtCore.Qt.Key.Key_T,
36+
)
37+
self.add_tag_action = QShortcut(key, self)
38+
39+
self.__connect_callbacks()
40+
41+
def __connect_callbacks(self) -> None:
42+
self._add_field_button.clicked.connect(self._add_field_button_callback)
43+
self._add_tag_button.clicked.connect(self._add_tag_button_callback)
44+
self._thumb.stats_updated.connect(self.__thumb_stats_updated_callback)
45+
self.add_tag_action.activated.connect(self._add_tag_button.setFocus)
46+
self.add_tag_action.activated.connect(self._add_tag_button.click)
47+
48+
self.tag_search.done.connect(self.tag_added_callback)
49+
self.tag_search.tags_updated.connect(self.update_added_callback)
50+
3251
def _add_field_button_callback(self) -> None:
33-
self.__add_field_modal.show()
52+
# self.__add_field_modal.show()
53+
pass
3454

35-
@typing.override
3655
def _add_tag_button_callback(self) -> None:
37-
self.__add_tag_modal.show()
56+
self.tag_search.added = self._containers.tags
57+
self.tag_search.view.search_field.setDisabled(False)
58+
self.tag_search.setHidden(False)
59+
self._add_tag_button.setHidden(True)
60+
self._add_field_button.setHidden(True)
61+
62+
def tag_added_callback(self):
63+
self.tag_search.setHidden(True)
64+
self._add_tag_button.setHidden(False)
65+
self._add_field_button.setHidden(False)
66+
67+
self._add_tag_button.setFocus()
3868

39-
@typing.override
69+
def update_added_callback(self):
70+
self.tag_search.added = self._containers.tags
71+
72+
def __thumb_stats_updated_callback(self, filepath: Path, stats: FileAttributeData) -> None:
73+
if len(self._selected) != 1:
74+
return
75+
76+
if filepath != self._thumb.current_file:
77+
return
78+
79+
if self.__current_stats is None:
80+
self.__current_stats = FileAttributeData()
81+
82+
if stats.width is not None:
83+
self.__current_stats.width = stats.width
84+
if stats.height is not None:
85+
self.__current_stats.height = stats.height
86+
if stats.duration is not None:
87+
self.__current_stats.duration = stats.duration
88+
89+
self._file_attrs.update_stats(filepath, self.__current_stats)
90+
91+
@override
4092
def _set_selection_callback(self) -> None:
4193
with catch_warnings(record=True):
42-
self.__add_field_modal.search_panel.field_template_chosen.disconnect()
43-
self.__add_tag_modal.tsp.item_chosen.disconnect()
94+
self.field_search.field_template_chosen.disconnect()
95+
self.tag_search.item_chosen.disconnect()
4496

45-
self.__add_field_modal.search_panel.field_template_chosen.connect(
46-
self._add_field_to_selected
47-
)
48-
self.__add_tag_modal.tsp.item_chosen.connect(self._add_tag_to_selected)
97+
self.field_search.field_template_chosen.connect(self._add_field_to_selected)
98+
self.tag_search.item_chosen.connect(self._add_tag_to_selected)
4999

50100
def _add_field_to_selected(self, template: BaseFieldTemplate) -> None:
51101
self._containers.add_field_to_selected(template)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# SPDX-FileCopyrightText: (c) TagStudio Contributors
2+
# SPDX-License-Identifier: GPL-3.0-only
3+
4+
5+
import typing
6+
from typing import override
7+
8+
import structlog
9+
from PySide6 import QtCore, QtGui
10+
from PySide6.QtWidgets import QPushButton
11+
12+
if typing.TYPE_CHECKING:
13+
pass
14+
15+
logger = structlog.get_logger(__name__)
16+
17+
18+
class ReturnButton(QPushButton):
19+
def __init__(self, *args, **kwargs) -> None: # pyright: ignore
20+
super().__init__(*args, **kwargs)
21+
22+
@override
23+
def keyPressEvent(self, arg__1: QtGui.QKeyEvent) -> None:
24+
if self.hasFocus() and arg__1.key() in {QtCore.Qt.Key.Key_Enter, QtCore.Qt.Key.Key_Return}:
25+
self.click()
26+
27+
super().keyPressEvent(arg__1)

0 commit comments

Comments
 (0)