mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-03 08:39:12 +00:00
Add the ability to create tags when adding a tag to a file (#262)
* Add the ability to create tags when adding a tag to a file. * ui: unify tag widget appearance Unify the tag widget appearance and remove unnecessary "*1" multiplication. * refactor: change some var names & add docstrings * feat: edit panel is opened before adding tag * feat(ui): focus save button on add panel Focus the save button on the Add Tag panel. This allows the user to promptly hit enter to add the tag as-is. --------- Co-authored-by: bjorn-out <b.g.out@uva.nl> Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
This commit is contained in:
@@ -5,30 +5,28 @@
|
||||
|
||||
import logging
|
||||
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QLineEdit,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
QTextEdit,
|
||||
QComboBox,
|
||||
QFrame,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QTextEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from src.core.constants import TAG_COLORS
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.core.constants import TAG_COLORS
|
||||
from src.qt.widgets.panel import PanelWidget, PanelModal
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
ERROR = "[ERROR]"
|
||||
WARNING = "[WARNING]"
|
||||
INFO = "[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
@@ -36,7 +34,7 @@ logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
class BuildTagPanel(PanelWidget):
|
||||
on_edit = Signal(Tag)
|
||||
|
||||
def __init__(self, library, tag_id: int = -1):
|
||||
def __init__(self, library, tag_id: int = -1, tag_name: str = "New Tag"):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
@@ -117,7 +115,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self.subtags_add_button = QPushButton()
|
||||
self.subtags_add_button.setText("+")
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x))
|
||||
tsp.tag_created.connect(lambda x: self.add_subtag_callback(x))
|
||||
self.add_tag_modal = PanelModal(tsp, "Add Parent Tags", "Add Parent Tags")
|
||||
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
self.subtags_layout.addWidget(self.subtags_add_button)
|
||||
@@ -163,7 +161,7 @@ class BuildTagPanel(PanelWidget):
|
||||
if tag_id >= 0:
|
||||
self.tag = self.lib.get_tag(tag_id)
|
||||
else:
|
||||
self.tag = Tag(-1, "New Tag", "", [], [], "")
|
||||
self.tag = Tag(-1, tag_name, "", [], [], "")
|
||||
self.set_tag(self.tag)
|
||||
|
||||
def add_subtag_callback(self, tag_id: int):
|
||||
|
||||
@@ -342,8 +342,8 @@ class ModifiedTagWidget(
|
||||
f"font-weight: 600;"
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:inset;"
|
||||
f"border-width: {math.ceil(1*self.devicePixelRatio())}px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width: {math.ceil(self.devicePixelRatio())}px;"
|
||||
f"padding-right: 4px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 4px;"
|
||||
|
||||
@@ -5,42 +5,38 @@
|
||||
|
||||
import logging
|
||||
import math
|
||||
|
||||
from PySide6.QtCore import Signal, Qt, QSize
|
||||
import src.qt.modals.build_tag as bt
|
||||
from PySide6.QtCore import QSize, Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
QLineEdit,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from src.core.constants import TAG_COLORS
|
||||
from src.core.library import Library
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
ERROR = "[ERROR]"
|
||||
WARNING = "[WARNING]"
|
||||
INFO = "[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class TagSearchPanel(PanelWidget):
|
||||
tag_chosen = Signal(int)
|
||||
tag_created = Signal(int)
|
||||
|
||||
def __init__(self, library):
|
||||
def __init__(self, library: "Library"):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
self.first_tag_id = None
|
||||
self.first_tag_id: int | None = None
|
||||
self.tag_limit = 100
|
||||
# self.selected_tag: int = 0
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
@@ -56,57 +52,38 @@ class TagSearchPanel(PanelWidget):
|
||||
lambda checked=False: self.on_return(self.search_field.text())
|
||||
)
|
||||
|
||||
# self.content_container = QWidget()
|
||||
# self.content_layout = QHBoxLayout(self.content_container)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setStyleSheet('background: #000000;')
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
||||
)
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
# sa.setMaximumWidth(self.preview_size[0])
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
# self.add_button = QPushButton()
|
||||
# self.root_layout.addWidget(self.add_button)
|
||||
# self.add_button.setText('Add Tag')
|
||||
# # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide()))
|
||||
# self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x))
|
||||
# # self.setLayout(self.root_layout)
|
||||
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.update_tags("")
|
||||
|
||||
# def reset(self):
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# self.search_field.setFocus()
|
||||
|
||||
def on_return(self, text: str):
|
||||
if text and self.first_tag_id is not None:
|
||||
# callback(self.first_tag_id)
|
||||
self.tag_chosen.emit(self.first_tag_id)
|
||||
self.tag_created.emit(self.first_tag_id)
|
||||
self.search_field.setText("")
|
||||
self.update_tags()
|
||||
elif text:
|
||||
self.create_and_add_tag(text)
|
||||
self.parentWidget().hide()
|
||||
else:
|
||||
self.search_field.setFocus()
|
||||
self.parentWidget().hide()
|
||||
|
||||
def update_tags(self, query: str = ""):
|
||||
# for c in self.scroll_layout.children():
|
||||
# c.widget().deleteLater()
|
||||
while self.scroll_layout.count():
|
||||
# logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}")
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
|
||||
found_tags = self.lib.search_tags(query, include_cluster=True)[: self.tag_limit]
|
||||
@@ -153,10 +130,7 @@ class TagSearchPanel(PanelWidget):
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width: {math.ceil(1*self.devicePixelRatio())}px;"
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f"padding-bottom: 5px;"
|
||||
# f'padding-left: 4px;'
|
||||
f"font-size: 20px;"
|
||||
f"}}"
|
||||
f"QPushButton::hover"
|
||||
@@ -167,15 +141,95 @@ class TagSearchPanel(PanelWidget):
|
||||
f"}}"
|
||||
)
|
||||
|
||||
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x))
|
||||
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_created.emit(x))
|
||||
|
||||
l.addWidget(tw)
|
||||
l.addWidget(ab)
|
||||
self.scroll_layout.addWidget(c)
|
||||
|
||||
# Add a create tag button if a query is entered
|
||||
if query:
|
||||
c = self.create_tag_button(query)
|
||||
self.scroll_layout.addWidget(c)
|
||||
|
||||
self.search_field.setFocus()
|
||||
|
||||
# def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# self.search_field.setFocus()
|
||||
# return super().enterEvent(event)
|
||||
# self.focusOutEvent
|
||||
def create_tag_button(self, name: str) -> QWidget:
|
||||
"""Construct a "Create Tag" button.
|
||||
|
||||
Args:
|
||||
name (str): The name of the tag to give.
|
||||
"""
|
||||
c = QWidget()
|
||||
l = QHBoxLayout(c)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
l.setSpacing(3)
|
||||
|
||||
create_and_add_button = QPushButton(self)
|
||||
create_and_add_button.setFlat(True)
|
||||
create_and_add_button.setText(
|
||||
f"Create && Add \"{name.replace("&", "&&")}\" Tag"
|
||||
)
|
||||
|
||||
inner_layout = QHBoxLayout()
|
||||
inner_layout.setObjectName("innerLayout")
|
||||
inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
create_and_add_button.setLayout(inner_layout)
|
||||
create_and_add_button.setMinimumSize(math.ceil(22 * 1.5), 22)
|
||||
|
||||
create_and_add_button.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
f"background: {get_tag_color(ColorType.PRIMARY, "dark gray")};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, "dark gray")};"
|
||||
f"font-weight: 600;"
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, "dark gray")};"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width: {math.ceil(self.devicePixelRatio())}px;"
|
||||
f"padding-right: 4px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 4px;"
|
||||
f"font-size: 13px"
|
||||
f"}}"
|
||||
f"QPushButton::hover{{"
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, "dark gray")};"
|
||||
f"}}"
|
||||
)
|
||||
|
||||
create_and_add_button.clicked.connect(
|
||||
lambda x=name: self.create_and_add_tag(name)
|
||||
)
|
||||
l.addWidget(create_and_add_button)
|
||||
|
||||
return c
|
||||
|
||||
def create_and_add_tag(self, name: str):
|
||||
"""Open "Add Tag" panel to create and add a new tag with the given name.
|
||||
|
||||
Args:
|
||||
name (str): The name of the tag to give.
|
||||
"""
|
||||
self.add_tag_modal = PanelModal(
|
||||
bt.BuildTagPanel(self.lib, tag_name=name),
|
||||
"New Tag",
|
||||
"Add Tag",
|
||||
has_save=True,
|
||||
)
|
||||
self.add_tag_modal.saved.connect(lambda n=name: self.on_tag_modal_saved(n))
|
||||
self.add_tag_modal.save_button.setFocus()
|
||||
self.add_tag_modal.show()
|
||||
|
||||
def on_tag_modal_saved(self, name):
|
||||
"""Callback for actions to perform when a new "Create & Add" tag is confirmed.
|
||||
Args:
|
||||
name (str): The name of the tag to give.
|
||||
"""
|
||||
panel: bt.BuildTagPanel = self.add_tag_modal.widget
|
||||
self.tag_created.emit(self.lib.add_tag_to_library(panel.build_tag()))
|
||||
self.add_tag_modal.hide()
|
||||
self.update_tags(name)
|
||||
|
||||
def showEvent(self, event):
|
||||
# Clear search field and focus when showing modal
|
||||
self.search_field.setText("")
|
||||
self.search_field.setFocus()
|
||||
|
||||
@@ -76,7 +76,7 @@ class TagBoxWidget(FieldWidget):
|
||||
f"}}"
|
||||
)
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
tsp.tag_created.connect(lambda x: self.add_tag_callback(x))
|
||||
self.add_modal = PanelModal(tsp, title, "Add Tags")
|
||||
self.add_button.clicked.connect(
|
||||
lambda: (tsp.update_tags(), self.add_modal.show()) # type: ignore
|
||||
|
||||
Reference in New Issue
Block a user