diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 15bdf89a..3bdd615c 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -1314,22 +1314,23 @@ class Library: return direct_tags, descendant_tags - def add_field_template(self, field_template: BaseFieldTemplate) -> bool: + def add_field_template(self, field_template: BaseFieldTemplate) -> BaseFieldTemplate | None: """Add a new field template to the library.""" if not (isinstance(field_template, (TextFieldTemplate, DatetimeFieldTemplate))): logger.error("[Library] BaseFieldTemplate attempted to be added to the library.") - return False + return None with Session(self.engine) as session: try: session.add(field_template) + session.flush() + make_transient(field_template) session.commit() + return field_template except IntegrityError as e: logger.error(e) session.rollback() - return False - - return True + return None def update_field_template(self, old_field_type: str, field_template: BaseFieldTemplate) -> bool: """Update a field template in the library. diff --git a/src/tagstudio/qt/controllers/edit_field_template_modal.py b/src/tagstudio/qt/controllers/edit_field_template_modal.py index 04edd5c2..0194b960 100644 --- a/src/tagstudio/qt/controllers/edit_field_template_modal.py +++ b/src/tagstudio/qt/controllers/edit_field_template_modal.py @@ -24,7 +24,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView): def __init__(self, field_template: BaseFieldTemplate | None = None) -> None: super().__init__() - self.__field_id: int = field_template.id if field_template else -1 + self.__field_id: int | None = field_template.id if field_template else None self.__field_name: str = "" self.__field_type: str | None = field_template.class_name if field_template else None self.__text_field_is_multiline: bool = False @@ -47,7 +47,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView): # Indicates a new template, set default values if field_template is None: self.__field_name = Translations["field_template.new"] - self.__field_type = None + self.__field_type = list(EditFieldTemplateModal.field_type_map.keys())[0] # First index return # Populate common values for any field type else: diff --git a/src/tagstudio/qt/controllers/field_template_search_panel_controller.py b/src/tagstudio/qt/controllers/field_template_search_panel_controller.py index 360ffee8..9b361118 100644 --- a/src/tagstudio/qt/controllers/field_template_search_panel_controller.py +++ b/src/tagstudio/qt/controllers/field_template_search_panel_controller.py @@ -69,9 +69,32 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]): return len(self.__lib.field_templates) @override - def on_item_create(self) -> None: - # TODO: Allow creation of field templates - pass + def on_item_create(self, add_to_entry: bool = False) -> None: + """Opens panel to create a new field template and optionally add it to an entry. + + Populates name field using current search query. + + Args: + add_to_entry (bool): Should this item be added to currently selected entries? + """ + query: str = self.get_search_query() + logger.info("[FieldTemplateSearch] Create and Add Field Template", name=query) + + panel: EditFieldTemplateModal = EditFieldTemplateModal() + modal: PanelModal = PanelModal( + panel, + Translations["field_template.new"], + Translations["field_template.add"] + if add_to_entry + else Translations["field_template.new"], + has_save=True, + ) + + if query.strip(): + panel.name_field.setText(query) + + modal.saved.connect(lambda: self.create_item(panel, choose_item=add_to_entry)) + modal.show() @override def on_item_edit(self, item: BaseFieldTemplate) -> None: @@ -89,6 +112,8 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]): @override def _on_item_remove(self, item: BaseFieldTemplate) -> None: + if self.is_chooser: + return message_box = QMessageBox( QMessageBox.Icon.Question, @@ -105,29 +130,6 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]): self.__lib.remove_field_template(item) self.update_items(self.get_search_query()) - @override - def on_item_create_and_add(self) -> None: - """Opens "Create Field Template" panel to create a new field template. - - Populates name field using current search query. - """ - query: str = self.get_search_query() - logger.info("[FieldTemplateSearch] Create and Add Field Template", name=query) - - panel: EditFieldTemplateModal = EditFieldTemplateModal() - modal: PanelModal = PanelModal( - panel, - Translations["field_template.new"], - Translations["field.add"], - has_save=True, - ) - - if query.strip(): - panel.name_field.setText(query) - - modal.saved.connect(lambda: self.create_item(panel, choose_item=True)) - modal.show() - @override def _on_item_chosen(self, item: BaseFieldTemplate) -> None: self.field_template_chosen.emit(item) @@ -146,6 +148,8 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]): if item is None: return + field_template_widget.has_remove = not self.is_chooser + # Disconnect previous callbacks with catch_warnings(record=True): field_template_widget.on_edit.disconnect() diff --git a/src/tagstudio/qt/controllers/field_template_widget_controller.py b/src/tagstudio/qt/controllers/field_template_widget_controller.py index f7c129ee..e23e9e66 100644 --- a/src/tagstudio/qt/controllers/field_template_widget_controller.py +++ b/src/tagstudio/qt/controllers/field_template_widget_controller.py @@ -16,6 +16,7 @@ class FieldTemplateWidget(FieldTemplateWidgetView): super().__init__() self.__field_template: BaseFieldTemplate | None = None + self.has_remove: bool = False # Add actions edit_action = QAction(self) @@ -36,12 +37,14 @@ class FieldTemplateWidget(FieldTemplateWidgetView): @override def enterEvent(self, event: QEnterEvent) -> None: - self._delete_button.setHidden(False) + if self.has_remove: + self._delete_button.setHidden(False) self.update() return super().enterEvent(event) @override def leaveEvent(self, event: QEvent) -> None: - self._delete_button.setHidden(True) + if self.has_remove: + self._delete_button.setHidden(True) self.update() return super().leaveEvent(event) diff --git a/src/tagstudio/qt/controllers/preview_panel_controller.py b/src/tagstudio/qt/controllers/preview_panel_controller.py index 287db4f8..d3852a80 100644 --- a/src/tagstudio/qt/controllers/preview_panel_controller.py +++ b/src/tagstudio/qt/controllers/preview_panel_controller.py @@ -42,11 +42,11 @@ class PreviewPanel(PreviewPanelView): self.__add_tag_modal.tsp.item_chosen.connect(self._add_tag_to_selected) def _add_field_to_selected(self, template: BaseFieldTemplate) -> None: - self._fields.add_field_to_selected(template) + self._containers.add_field_to_selected(template) if len(self._selected) == 1: - self._fields.update_from_entry(self._selected[0]) + self._containers.update_from_entry(self._selected[0]) def _add_tag_to_selected(self, tag_id: int) -> None: - self._fields.add_tags_to_selected(tag_id) + self._containers.add_tags_to_selected(tag_id) if len(self._selected) == 1: - self._fields.update_from_entry(self._selected[0]) + self._containers.update_from_entry(self._selected[0]) diff --git a/src/tagstudio/qt/controllers/search_panel_controller.py b/src/tagstudio/qt/controllers/search_panel_controller.py index 2b29d0e9..0b743931 100644 --- a/src/tagstudio/qt/controllers/search_panel_controller.py +++ b/src/tagstudio/qt/controllers/search_panel_controller.py @@ -136,14 +136,14 @@ class SearchPanel[T](PanelWidget): # Create and add item if no search results if len(self._search_results) <= 0: - self.on_item_create_and_add() + self.on_item_create(add_to_entry=True) elif self.is_chooser: self._on_item_chosen(self._search_results[0]) self.clear_search_query() self.update_items() - def on_item_create(self) -> None: + def on_item_create(self, add_to_entry: bool = False) -> None: # pyright: ignore[reportUnusedParameter] raise NotImplementedError() def on_item_edit(self, item: T) -> None: # pyright: ignore[reportUnusedParameter] @@ -152,9 +152,6 @@ class SearchPanel[T](PanelWidget): def _on_item_remove(self, item: T) -> None: # pyright: ignore[reportUnusedParameter] raise NotImplementedError() - def on_item_create_and_add(self) -> None: - raise NotImplementedError() - def _on_item_chosen(self, item: T) -> None: # pyright: ignore[reportUnusedParameter] raise NotImplementedError() diff --git a/src/tagstudio/qt/controllers/tag_search_panel_controller.py b/src/tagstudio/qt/controllers/tag_search_panel_controller.py index 1f0684f6..2d4aa8c0 100644 --- a/src/tagstudio/qt/controllers/tag_search_panel_controller.py +++ b/src/tagstudio/qt/controllers/tag_search_panel_controller.py @@ -76,7 +76,14 @@ class TagSearchPanel(SearchPanel[Tag]): return len(self.__lib.tags) @override - def on_item_create(self) -> None: + def on_item_create(self, add_to_entry: bool = False) -> None: + """Opens panel to create a new tag and optionally add it to an entry. + + Populates name field using current search query. + + Args: + add_to_entry (bool): Should this item be added to currently selected entries? + """ # TODO: Move this to a top-level import from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports @@ -86,13 +93,14 @@ class TagSearchPanel(SearchPanel[Tag]): modal: PanelModal = PanelModal( panel, Translations["tag.new"], + Translations["tag.add"] if add_to_entry else Translations["tag.new"], has_save=True, ) if query.strip(): panel.name_field.setText(query) - modal.saved.connect(lambda: self.create_item(panel)) + modal.saved.connect(lambda: self.create_item(panel, choose_item=add_to_entry)) modal.show() @override @@ -134,33 +142,6 @@ class TagSearchPanel(SearchPanel[Tag]): self.__lib.remove_tag(item.id) self.update_items(self.get_search_query()) - @override - def on_item_create_and_add(self) -> None: - """Opens "Create Tag" panel to create and add a new tag. - - Populates name field using current search query. - """ - # TODO: Move this to a top-level import - from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports - - query: str = self.get_search_query() - - logger.info("Create and Add Tag", name=query) - - panel: BuildTagPanel = BuildTagPanel(self.__lib) - modal: PanelModal = PanelModal( - panel, - Translations["tag.new"], - Translations["tag.add"], - has_save=True, - ) - - if query.strip(): - panel.name_field.setText(query) - - modal.saved.connect(lambda: self.create_item(panel, choose_item=True)) - modal.show() - @override def _on_item_chosen(self, item: Tag) -> None: self.item_chosen.emit(item.id) diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py index 0c2500eb..978cf38e 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/mixed/field_containers.py @@ -233,19 +233,19 @@ class FieldContainers(QWidget): ) self.lib.add_field_to_entries(entry_id, field_template.to_field()) - def add_tags_to_selected(self, tags: int | list[int]) -> None: + def add_tags_to_selected(self, tag_ids: int | list[int]) -> None: """Add list of tags to one or more selected items. Uses the current driver selection, NOT the field containers cache. """ - if isinstance(tags, int): - tags = [tags] + if isinstance(tag_ids, int): + tag_ids = [tag_ids] logger.info( "[FieldContainers][add_tags_to_selected]", selected=self.driver.selected, - tags=tags, + tag_ids=tag_ids, ) - self.driver.add_tags_to_selected_callback(tags) + self.driver.add_tags_to_selected_callback(tag_ids) def write_container(self, index: int, field: BaseField, is_mixed: bool = False) -> None: """Update/Create data for a FieldContainer. diff --git a/src/tagstudio/qt/views/preview_panel_view.py b/src/tagstudio/qt/views/preview_panel_view.py index 81ad91fb..19144a00 100644 --- a/src/tagstudio/qt/views/preview_panel_view.py +++ b/src/tagstudio/qt/views/preview_panel_view.py @@ -67,7 +67,7 @@ class PreviewPanelView(QWidget): self.__thumb = PreviewThumb(self.lib, driver) self.__file_attrs = FileAttributes(self.lib, driver) - self._fields = FieldContainers( + self._containers = FieldContainers( self.lib, driver ) # TODO: this should be name mangled, but is still needed on the controller side atm @@ -107,7 +107,7 @@ class PreviewPanelView(QWidget): preview_layout.addWidget(self.__thumb) info_layout.addWidget(self.__file_attrs) - info_layout.addWidget(self._fields) + info_layout.addWidget(self._containers) splitter.addWidget(preview_section) splitter.addWidget(info_section) @@ -148,7 +148,7 @@ class PreviewPanelView(QWidget): self.__thumb.hide_preview() self.__file_attrs.update_stats() self.__file_attrs.update_date_label() - self._fields.hide_containers() + self._containers.hide_containers() self.add_buttons_enabled = False @@ -163,7 +163,7 @@ class PreviewPanelView(QWidget): stats: FileAttributeData = self.__thumb.display_file(filepath) self.__file_attrs.update_stats(filepath, stats) self.__file_attrs.update_date_label(filepath) - self._fields.update_from_entry(entry_id) + self._containers.update_from_entry(entry_id) self._set_selection_callback() @@ -175,7 +175,7 @@ class PreviewPanelView(QWidget): self.__thumb.hide_preview() # TODO: Render mixed selection self.__file_attrs.update_multi_selection(len(selected)) self.__file_attrs.update_date_label() - self._fields.hide_containers() # TODO: Allow for mixed editing + self._containers.hide_containers() # TODO: Allow for mixed editing self._set_selection_callback() @@ -205,7 +205,7 @@ class PreviewPanelView(QWidget): @property def field_containers_widget(self) -> FieldContainers: # needed for the tests """Getter for the field containers widget.""" - return self._fields + return self._containers @property def preview_thumb(self) -> PreviewThumb: diff --git a/src/tagstudio/qt/views/search_panel_view.py b/src/tagstudio/qt/views/search_panel_view.py index 4114b513..f6c54cbe 100644 --- a/src/tagstudio/qt/views/search_panel_view.py +++ b/src/tagstudio/qt/views/search_panel_view.py @@ -139,7 +139,9 @@ class SearchPanelView(PanelWidget): ) self.create_button.clicked.connect(controller.on_item_create) - self.create_and_add_button.clicked.connect(controller.on_item_create_and_add) + self.create_and_add_button.clicked.connect( + lambda: controller.on_item_create(add_to_entry=True) + ) def set_limit_items(self, limit_items: list[tuple[str, int]]) -> None: # Remove existing limit items