diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index b5c43c313..ff72c7ad1 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -185,7 +185,7 @@ def set_subtags(self): l.setContentsMargins(0,0,0,0) l.setSpacing(3) for tag_id in self.tag.subtag_ids: - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, True) + tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, True, False) tw.on_remove.connect(lambda checked=False, t=tag_id: self.remove_subtag_callback(t)) l.addWidget(tw) self.scroll_layout.addWidget(c) diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index 7cb2c9593..5c8ea338f 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -4,7 +4,7 @@ from PySide6.QtCore import Signal, Qt, QSize -from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QScrollArea, QFrame +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QScrollArea, QFrame, QMessageBox, QApplication from src.core.library import Library from src.qt.widgets import PanelWidget, PanelModal, TagWidget @@ -92,8 +92,10 @@ def update_tags(self, query:str): l = QHBoxLayout(c) l.setContentsMargins(0,0,0,0) l.setSpacing(3) - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False) + tag_deletable = tag_id not in [0, 1] # [0, 1] should probably be extracted as a constant during refactor + tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False, tag_deletable) tw.on_edit.connect(lambda checked=False, t=self.lib.get_tag(tag_id): (self.edit_tag(t.id))) + tw.on_delete.connect(lambda checked=False, t=self.lib.get_tag(tag_id): (self.delete_tag(t.id))) l.addWidget(tw) self.scroll_layout.addWidget(c) else: @@ -106,8 +108,10 @@ def update_tags(self, query:str): l = QHBoxLayout(c) l.setContentsMargins(0,0,0,0) l.setSpacing(3) - tw = TagWidget(self.lib, tag, True, False) + tag_deletable = tag_id not in [0, 1] # [0, 1] should probably be extracted as a constant during refactor + tw = TagWidget(self.lib, tag, True, False, tag_deletable) tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t.id))) + tw.on_delete.connect(lambda checked=False, t=tag: (self.delete_tag(t.id))) l.addWidget(tw) self.scroll_layout.addWidget(c) @@ -128,6 +132,23 @@ def edit_tag(self, tag_id:int): # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.edit_modal.show() + def delete_tag(self, tag_id:int): + def show_delete_prompt() -> bool: + result = QMessageBox.question(self, "Delete Tag", f"Are you sure you want to delete Tag {tag.name}?", + QMessageBox.Yes | QMessageBox.No) + + return result == QMessageBox.Yes + + tag = self.lib.get_tag(tag_id) + shift_held = Qt.KeyboardModifier.ShiftModifier in QApplication.keyboardModifiers() + + if shift_held or show_delete_prompt(): + self.lib.remove_tag(tag_id) + self.update_tags(self.search_field.text()) + + if not shift_held: + QMessageBox.information(self, "Delete Tag", "Tag deleted.") + def edit_tag_callback(self, btp:BuildTagPanel): self.lib.update_tag(btp.build_tag()) self.update_tags(self.search_field.text()) diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index 7f79f8f29..b445882fb 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -100,7 +100,7 @@ def update_tags(self, query:str): l = QHBoxLayout(c) l.setContentsMargins(0, 0, 0, 0) l.setSpacing(3) - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False) + tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False, False) ab = QPushButton() ab.setMinimumSize(23, 23) ab.setMaximumSize(23, 23) diff --git a/tagstudio/src/qt/widgets/tag.py b/tagstudio/src/qt/widgets/tag.py index 4232fb3c6..65007f52a 100644 --- a/tagstudio/src/qt/widgets/tag.py +++ b/tagstudio/src/qt/widgets/tag.py @@ -29,8 +29,9 @@ class TagWidget(QWidget): on_remove = Signal() on_click = Signal() on_edit = Signal() + on_delete = Signal() - def __init__(self, library:Library, tag:Tag, has_edit:bool, has_remove:bool, on_remove_callback:FunctionType=None, on_click_callback:FunctionType=None, on_edit_callback:FunctionType=None) -> None: + def __init__(self, library:Library, tag:Tag, has_edit:bool, has_remove:bool, has_delete:bool, on_remove_callback:FunctionType=None, on_click_callback:FunctionType=None, on_edit_callback:FunctionType=None) -> None: super().__init__() self.lib = library self.tag = tag @@ -66,6 +67,11 @@ def __init__(self, library:Library, tag:Tag, has_edit:bool, has_remove:bool, on_ self.bg_button.addAction(search_for_tag_action) add_to_search_action = QAction('Add to Search', self) self.bg_button.addAction(add_to_search_action) + + if has_delete: + delete_action = QAction('Delete Tag', self) + delete_action.triggered.connect(self.on_delete.emit) + self.bg_button.addAction(delete_action) self.inner_layout = QHBoxLayout() self.inner_layout.setObjectName('innerLayout') diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 52ee76d7c..4a458a25d 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -8,7 +8,7 @@ import typing from PySide6.QtCore import Signal, Qt -from PySide6.QtWidgets import QPushButton +from PySide6.QtWidgets import QPushButton, QMessageBox, QApplication from src.core.library import Library, Tag from src.qt.flowlayout import FlowLayout @@ -71,36 +71,43 @@ def __init__(self, item, title, field_index, library:Library, tags:list[int], dr def set_item(self, item): self.item = item - - def set_tags(self, tags:list[int]): - logging.info(f'[TAG BOX WIDGET] SET TAGS: T:{tags} for E:{self.item.id}') + + def update_tags(self): is_recycled = False + if self.base_layout.itemAt(0): # logging.info(type(self.base_layout.itemAt(0).widget())) + is_recycled = True while self.base_layout.itemAt(0) and self.base_layout.itemAt(1): # logging.info(f"I'm deleting { self.base_layout.itemAt(0).widget()}") self.base_layout.takeAt(0).widget().deleteLater() - is_recycled = True - for tag in tags: + + for tag_id in self.tags: # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. # tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True, # on_remove_callback=lambda checked=False, t=tag: (self.lib.get_entry(self.item.id).remove_tag(self.lib, t, self.field_index), self.updated.emit()), # on_click_callback=lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q)), # on_edit_callback=lambda checked=False, t=tag: (self.edit_tag(t)) # ) - tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True) - tw.on_click.connect(lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q))) - tw.on_remove.connect(lambda checked=False, t=tag: (self.remove_tag(t))) - tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t))) + tag_deletable = tag_id not in [0, 1] # [0, 1] should probably be extracted as a constant during refactor + tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, True, tag_deletable) + tw.on_click.connect(lambda checked=False, q=f'tag_id: {tag_id}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q))) + tw.on_remove.connect(lambda checked=False, t=tag_id: (self.remove_tag(t))) + tw.on_edit.connect(lambda checked=False, t=tag_id: (self.edit_tag(t))) + tw.on_delete.connect(lambda checked=False, t=tag_id: (self.delete_tag(t))) self.base_layout.addWidget(tw) - self.tags = tags - + # Move or add the '+' button. if is_recycled: self.base_layout.addWidget(self.base_layout.takeAt(0).widget()) else: self.base_layout.addWidget(self.add_button) + def set_tags(self, tags:list[int]): + logging.info(f'[TAG BOX WIDGET] SET TAGS: T:{tags} for E:{self.item.id}') + self.tags = tags + self.update_tags() + # Handles an edge case where there are no more tags and the '+' button # doesn't move all the way to the left. if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1): @@ -120,6 +127,24 @@ def edit_tag(self, tag_id:int): self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag())) # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.edit_modal.show() + + + def delete_tag(self, tag_id:int): + def show_delete_prompt() -> bool: + result = QMessageBox.question(self, "Delete Tag", f"Are you sure you want to delete Tag {tag.name}?", + QMessageBox.Yes | QMessageBox.No) + + return result == QMessageBox.Yes + + tag = self.lib.get_tag(tag_id) + shift_held = Qt.KeyboardModifier.ShiftModifier in QApplication.keyboardModifiers() + + if shift_held or show_delete_prompt(): + self.lib.remove_tag(tag_id) + self.update_tags() + + if not shift_held: + QMessageBox.information(self, "Delete Tag", "Tag deleted.") def add_tag_callback(self, tag_id):