mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 15:49:09 +00:00
Merge branch 'main' into feat/translations
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -262,6 +262,7 @@ compile_commands.json
|
||||
tagstudio/tests/fixtures/library/*
|
||||
tagstudio/tests/fixtures/json_library/.TagStudio/*.sqlite
|
||||
TagStudio.ini
|
||||
*.sqlite-journal
|
||||
|
||||
.envrc
|
||||
.direnv
|
||||
|
||||
@@ -86,13 +86,13 @@ Features are broken up into the following priority levels, with nested prioritie
|
||||
- [ ] GPS Location [LOW]
|
||||
- [ ] Custom field names [HIGH] [#18](https://github.com/TagStudioDev/TagStudio/issues/18)
|
||||
- [ ] Search engine [HIGH] [#325](https://github.com/TagStudioDev/TagStudio/issues/325)
|
||||
- [ ] Boolean operators [HIGH] [#225](https://github.com/TagStudioDev/TagStudio/issues/225), [#314](https://github.com/TagStudioDev/TagStudio/issues/314)
|
||||
- [x] Boolean operators [HIGH] [#225](https://github.com/TagStudioDev/TagStudio/issues/225), [#314](https://github.com/TagStudioDev/TagStudio/issues/314)
|
||||
- [ ] Tag objects + autocomplete [HIGH] [#476 (Autocomplete)](https://github.com/TagStudioDev/TagStudio/issues/476)
|
||||
- [ ] Filename search [HIGH]
|
||||
- [ ] Filetype search [HIGH]
|
||||
- [ ] Search by extension (e.g. ".jpg", ".png") [HIGH]
|
||||
- [ ] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
|
||||
- [ ] Search by media type (e.g. "image", "video", "document") [MEDIUM]
|
||||
- [x] Filename search [HIGH]
|
||||
- [x] Filetype search [HIGH]
|
||||
- [x] Search by extension (e.g. ".jpg", ".png") [HIGH]
|
||||
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
|
||||
- [x] Search by media type (e.g. "image", "video", "document") [MEDIUM]
|
||||
- [ ] Field content search [HIGH] [#272](https://github.com/TagStudioDev/TagStudio/issues/272)
|
||||
- [ ] HAS operator for composition tags [HIGH]
|
||||
- [ ] OCR search [LOW]
|
||||
@@ -172,12 +172,12 @@ These version milestones are rough estimations for when the previous core featur
|
||||
- [ ] [Tag Categories](../library/tag_categories.md) [HIGH]
|
||||
- [ ] Property available for tags that allow the tag and any inheriting from it to be displayed separately in the preview panel under a title [HIGH]
|
||||
- [ ] Search engine [HIGH]
|
||||
- [ ] Boolean operators [HIGH]
|
||||
- [x] Boolean operators [HIGH]
|
||||
- [ ] Tag objects + autocomplete [HIGH]
|
||||
- [x] Filename search [HIGH]
|
||||
- [x] Filetype search [HIGH]
|
||||
- [x] Search by extension (e.g. ".jpg", ".png") [HIGH]
|
||||
- [ ] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
|
||||
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
|
||||
- [x] Search by media type (e.g. "image", "video", "document") [MEDIUM]
|
||||
- [ ] Field content search [HIGH]
|
||||
- [ ] Sortable results [HIGH]
|
||||
|
||||
@@ -163,7 +163,6 @@
|
||||
"tag.parent_tags": "Parent Tags",
|
||||
"tag.search_for_tag": "Search for Tag",
|
||||
"tag.shorthand": "Shorthand",
|
||||
"tag.remove_alias": "Remove selected alias",
|
||||
"tag.tag_name_required": "Tag Name (Required)",
|
||||
"view.size.0": "Mini",
|
||||
"view.size.1": "Small",
|
||||
@@ -181,5 +180,5 @@
|
||||
"drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}",
|
||||
"drop_import.progress.window_title": "Import Files",
|
||||
"drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.",
|
||||
"drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.",
|
||||
"drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library."
|
||||
}
|
||||
@@ -619,6 +619,7 @@ class Library:
|
||||
)
|
||||
|
||||
session.expunge_all()
|
||||
|
||||
return res
|
||||
|
||||
def get_all_child_tag_ids(self, tag_id: int) -> list[int]:
|
||||
@@ -901,9 +902,9 @@ class Library:
|
||||
def add_tag(
|
||||
self,
|
||||
tag: Tag,
|
||||
subtag_ids: set[int] | None = None,
|
||||
alias_names: set[str] | None = None,
|
||||
alias_ids: set[int] | None = None,
|
||||
subtag_ids: list[int] | set[int] | None = None,
|
||||
alias_names: list[str] | set[str] | None = None,
|
||||
alias_ids: list[int] | set[int] | None = None,
|
||||
) -> Tag | None:
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
try:
|
||||
@@ -1077,9 +1078,9 @@ class Library:
|
||||
def update_tag(
|
||||
self,
|
||||
tag: Tag,
|
||||
subtag_ids: set[int] | None = None,
|
||||
alias_names: set[str] | None = None,
|
||||
alias_ids: set[int] | None = None,
|
||||
subtag_ids: list[int] | set[int] | None = None,
|
||||
alias_names: list[str] | set[str] | None = None,
|
||||
alias_ids: list[int] | set[int] | None = None,
|
||||
) -> None:
|
||||
"""Edit a Tag in the Library."""
|
||||
self.add_tag(tag, subtag_ids, alias_names, alias_ids)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
||||
from sqlalchemy import and_, distinct, func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql.expression import BinaryExpression, ColumnExpressionArgument
|
||||
from src.core.media_types import MediaCategories
|
||||
from src.core.media_types import FILETYPE_EQUIVALENTS, MediaCategories
|
||||
from src.core.query_lang import BaseVisitor
|
||||
from src.core.query_lang.ast import AST, ANDList, Constraint, ConstraintType, Not, ORList, Property
|
||||
|
||||
@@ -17,6 +17,13 @@ else:
|
||||
Library = None # don't import .library because of circular imports
|
||||
|
||||
|
||||
def get_filetype_equivalency_list(item: str) -> list[str] | set[str]:
|
||||
for s in FILETYPE_EQUIVALENTS:
|
||||
if item in s:
|
||||
return s
|
||||
return [item]
|
||||
|
||||
|
||||
class SQLBoolExpressionBuilder(BaseVisitor[ColumnExpressionArgument]):
|
||||
def __init__(self, lib: Library) -> None:
|
||||
super().__init__()
|
||||
@@ -73,7 +80,9 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnExpressionArgument]):
|
||||
break
|
||||
return Entry.suffix.in_(map(lambda x: x.replace(".", ""), extensions))
|
||||
elif node.type == ConstraintType.FileType:
|
||||
return Entry.suffix.ilike(node.value)
|
||||
return or_(
|
||||
*[Entry.suffix.ilike(ft) for ft in get_filetype_equivalency_list(node.value)]
|
||||
)
|
||||
elif node.type == ConstraintType.Special: # noqa: SIM102 unnecessary once there is a second special constraint
|
||||
if node.value.lower() == "untagged":
|
||||
return ~Entry.id.in_(
|
||||
|
||||
@@ -10,6 +10,8 @@ from pathlib import Path
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
FILETYPE_EQUIVALENTS = [set(["jpg", "jpeg"])]
|
||||
|
||||
|
||||
class MediaType(str, Enum):
|
||||
"""Names of media types."""
|
||||
|
||||
@@ -3,45 +3,60 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import math
|
||||
import sys
|
||||
from typing import cast
|
||||
|
||||
import structlog
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import (
|
||||
QAction,
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QFrame,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QTableWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.library.alchemy.enums import TagColor
|
||||
from src.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import TagAliasWidget, TagWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
from ..translations import Translations
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class CustomTableItem(QLineEdit):
|
||||
def __init__(self, text, on_return, on_backspace, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setText(text)
|
||||
self.on_return = on_return
|
||||
self.on_backspace = on_backspace
|
||||
|
||||
def set_id(self, id):
|
||||
self.id = id
|
||||
|
||||
def keyPressEvent(self, event): # noqa: N802
|
||||
if event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter:
|
||||
self.on_return()
|
||||
elif event.key() == Qt.Key.Key_Backspace and self.text().strip() == "":
|
||||
self.on_backspace()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
|
||||
class BuildTagPanel(PanelWidget):
|
||||
on_edit = Signal(Tag)
|
||||
|
||||
def __init__(self, library: Library, tag: Tag | None = None):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
# self.callback = callback
|
||||
# self.tag_id = tag_id
|
||||
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -90,43 +105,16 @@ class BuildTagPanel(PanelWidget):
|
||||
Translations.translate_qobject(self.aliases_title, "tag.aliases")
|
||||
self.aliases_layout.addWidget(self.aliases_title)
|
||||
|
||||
self.aliases_flow_widget = QWidget()
|
||||
self.aliases_flow_layout = FlowLayout(self.aliases_flow_widget)
|
||||
self.aliases_flow_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.aliases_flow_layout.enable_grid_optimizations(value=False)
|
||||
self.aliases_table = QTableWidget(0, 2)
|
||||
self.aliases_table.horizontalHeader().setVisible(False)
|
||||
self.aliases_table.verticalHeader().setVisible(False)
|
||||
self.aliases_table.horizontalHeader().setStretchLastSection(True)
|
||||
self.aliases_table.setColumnWidth(0, 35)
|
||||
|
||||
self.alias_add_button = QPushButton()
|
||||
self.alias_add_button.setMinimumSize(23, 23)
|
||||
self.alias_add_button.setMaximumSize(23, 23)
|
||||
self.alias_add_button.setText("+")
|
||||
self.alias_add_button.setToolTip("CTRL + A")
|
||||
self.alias_add_button.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_A,
|
||||
)
|
||||
)
|
||||
self.alias_add_button.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
f"background: #1e1e1e;"
|
||||
f"color: #FFFFFF;"
|
||||
f"font-weight: bold;"
|
||||
f"border-color: #333333;"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width:{math.ceil(self.devicePixelRatio())}px;"
|
||||
f"padding-bottom: 5px;"
|
||||
f"font-size: 20px;"
|
||||
f"}}"
|
||||
f"QPushButton::hover"
|
||||
f"{{"
|
||||
f"border-color: #CCCCCC;"
|
||||
f"background: #555555;"
|
||||
f"}}"
|
||||
)
|
||||
|
||||
self.alias_add_button.clicked.connect(lambda: self.add_alias_callback())
|
||||
self.aliases_flow_layout.addWidget(self.alias_add_button)
|
||||
self.alias_add_button.clicked.connect(self.add_alias_callback)
|
||||
|
||||
# Subtags ------------------------------------------------------------
|
||||
|
||||
@@ -141,42 +129,25 @@ class BuildTagPanel(PanelWidget):
|
||||
Translations.translate_qobject(self.subtags_title, "tag.parent_tags")
|
||||
self.subtags_layout.addWidget(self.subtags_title)
|
||||
|
||||
self.subtag_flow_widget = QWidget()
|
||||
self.subtag_flow_layout = FlowLayout(self.subtag_flow_widget)
|
||||
self.subtag_flow_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.subtag_flow_layout.enable_grid_optimizations(value=False)
|
||||
self.scroll_contents = QWidget()
|
||||
self.subtags_scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.subtags_scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.subtags_scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
# self.scroll_area.setMinimumHeight(60)
|
||||
|
||||
self.subtags_layout.addWidget(self.scroll_area)
|
||||
|
||||
self.subtags_add_button = QPushButton()
|
||||
self.subtags_add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.subtags_add_button.setText("+")
|
||||
self.subtags_add_button.setToolTip("CTRL + P")
|
||||
self.subtags_add_button.setMinimumSize(23, 23)
|
||||
self.subtags_add_button.setMaximumSize(23, 23)
|
||||
self.subtags_add_button.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_P,
|
||||
)
|
||||
)
|
||||
self.subtags_add_button.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
f"background: #1e1e1e;"
|
||||
f"color: #FFFFFF;"
|
||||
f"font-weight: bold;"
|
||||
f"border-color: #333333;"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width:{math.ceil(self.devicePixelRatio())}px;"
|
||||
f"padding-bottom: 5px;"
|
||||
f"font-size: 20px;"
|
||||
f"}}"
|
||||
f"QPushButton::hover"
|
||||
f"{{"
|
||||
f"border-color: #CCCCCC;"
|
||||
f"background: #555555;"
|
||||
f"}}"
|
||||
)
|
||||
self.subtag_flow_layout.addWidget(self.subtags_add_button)
|
||||
self.subtags_layout.addWidget(self.subtags_add_button)
|
||||
|
||||
exclude_ids: list[int] = list()
|
||||
if tag is not None:
|
||||
@@ -188,11 +159,6 @@ class BuildTagPanel(PanelWidget):
|
||||
Translations.translate_with_setter(self.add_tag_modal.setTitle, "tag.parent_tags.add")
|
||||
Translations.translate_with_setter(self.add_tag_modal.setWindowTitle, "tag.parent_tags.add")
|
||||
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
# self.subtags_layout.addWidget(self.subtags_add_button)
|
||||
|
||||
# self.subtags_field = TagBoxWidget()
|
||||
# self.subtags_field.setMinimumHeight(60)
|
||||
# self.subtags_layout.addWidget(self.subtags_field)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
self.color_widget = QWidget()
|
||||
@@ -224,66 +190,59 @@ class BuildTagPanel(PanelWidget):
|
||||
)
|
||||
)
|
||||
self.color_layout.addWidget(self.color_field)
|
||||
remove_selected_alias_action = QAction(self)
|
||||
Translations.translate_qobject(remove_selected_alias_action, "tag.remove_alias")
|
||||
remove_selected_alias_action.triggered.connect(self.remove_selected_alias)
|
||||
remove_selected_alias_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_D,
|
||||
)
|
||||
)
|
||||
self.addAction(remove_selected_alias_action)
|
||||
|
||||
# Add Widgets to Layout ================================================
|
||||
self.root_layout.addWidget(self.name_widget)
|
||||
self.root_layout.addWidget(self.shorthand_widget)
|
||||
self.root_layout.addWidget(self.aliases_widget)
|
||||
self.root_layout.addWidget(self.aliases_flow_widget)
|
||||
self.root_layout.addWidget(self.aliases_table)
|
||||
self.root_layout.addWidget(self.alias_add_button)
|
||||
self.root_layout.addWidget(self.subtags_widget)
|
||||
self.root_layout.addWidget(self.subtag_flow_widget)
|
||||
self.root_layout.addWidget(self.color_widget)
|
||||
# self.parent().done.connect(self.update_tag)
|
||||
|
||||
self.subtag_ids: set[int] = set()
|
||||
self.alias_ids: set[int] = set()
|
||||
self.alias_names: set[str] = set()
|
||||
self.new_alias_names: dict = dict()
|
||||
self.subtag_ids: list[int] = []
|
||||
self.alias_ids: list[int] = []
|
||||
self.alias_names: list[str] = []
|
||||
self.new_alias_names: dict = {}
|
||||
self.new_item_id = sys.maxsize
|
||||
|
||||
self.set_tag(tag or Tag(name=Translations["tag.new"]))
|
||||
if tag is None:
|
||||
self.name_field.selectAll()
|
||||
|
||||
def keyPressEvent(self, event): # noqa: N802
|
||||
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: # type: ignore
|
||||
focused_widget = QApplication.focusWidget()
|
||||
if isinstance(focused_widget.parent(), TagAliasWidget):
|
||||
self.add_alias_callback()
|
||||
|
||||
def remove_selected_alias(self):
|
||||
count = self.aliases_flow_layout.count()
|
||||
if count <= 0:
|
||||
return
|
||||
|
||||
def backspace(self):
|
||||
focused_widget = QApplication.focusWidget()
|
||||
row = self.aliases_table.rowCount()
|
||||
|
||||
if focused_widget is None:
|
||||
if isinstance(focused_widget, CustomTableItem) is False:
|
||||
return
|
||||
remove_row = 0
|
||||
for i in range(0, row):
|
||||
item = self.aliases_table.cellWidget(i, 1)
|
||||
if (
|
||||
isinstance(item, CustomTableItem)
|
||||
and cast(CustomTableItem, item).id == cast(CustomTableItem, focused_widget).id
|
||||
):
|
||||
cast(QPushButton, self.aliases_table.cellWidget(i, 0)).click()
|
||||
remove_row = i
|
||||
break
|
||||
|
||||
if self.aliases_table.rowCount() <= 0:
|
||||
return
|
||||
|
||||
if isinstance(focused_widget.parent(), TagAliasWidget):
|
||||
cast(TagAliasWidget, focused_widget.parent()).on_remove.emit()
|
||||
if remove_row == 0:
|
||||
remove_row = 1
|
||||
|
||||
count = self.aliases_flow_layout.count()
|
||||
if count > 1:
|
||||
cast(
|
||||
TagAliasWidget, self.aliases_flow_layout.itemAt(count - 2).widget()
|
||||
).text_field.setFocus()
|
||||
else:
|
||||
self.alias_add_button.setFocus()
|
||||
self.aliases_table.cellWidget(remove_row - 1, 1).setFocus()
|
||||
|
||||
def enter(self):
|
||||
focused_widget = QApplication.focusWidget()
|
||||
if isinstance(focused_widget, CustomTableItem):
|
||||
self.add_alias_callback()
|
||||
|
||||
def add_subtag_callback(self, tag_id: int):
|
||||
logger.info("add_subtag_callback", tag_id=tag_id)
|
||||
self.subtag_ids.add(tag_id)
|
||||
self.subtag_ids.append(tag_id)
|
||||
self.set_subtags()
|
||||
|
||||
def remove_subtag_callback(self, tag_id: int):
|
||||
@@ -293,76 +252,71 @@ class BuildTagPanel(PanelWidget):
|
||||
|
||||
def add_alias_callback(self):
|
||||
logger.info("add_alias_callback")
|
||||
# bug passing in the text for a here means when the text changes
|
||||
# the remove callback uses what a whas initialy assigned
|
||||
new_field = TagAliasWidget()
|
||||
id = new_field.__hash__()
|
||||
new_field.id = id
|
||||
|
||||
new_field.on_remove.connect(lambda a="": self.remove_alias_callback(a, id))
|
||||
new_field.setMaximumHeight(25)
|
||||
new_field.setMinimumHeight(25)
|
||||
id = self.new_item_id
|
||||
|
||||
self.alias_ids.add(id)
|
||||
self.alias_ids.append(id)
|
||||
self.new_alias_names[id] = ""
|
||||
self.aliases_flow_layout.addWidget(new_field)
|
||||
new_field.text_field.setFocus()
|
||||
self.aliases_flow_layout.addWidget(self.alias_add_button)
|
||||
|
||||
self.new_item_id -= 1
|
||||
|
||||
self._set_aliases()
|
||||
|
||||
row = self.aliases_table.rowCount() - 1
|
||||
item = self.aliases_table.cellWidget(row, 1)
|
||||
item.setFocus()
|
||||
|
||||
def remove_alias_callback(self, alias_name: str, alias_id: int | None = None):
|
||||
logger.info("remove_alias_callback")
|
||||
|
||||
self.alias_ids.remove(alias_id)
|
||||
self._set_aliases()
|
||||
|
||||
def set_subtags(self):
|
||||
while self.subtag_flow_layout.itemAt(1):
|
||||
self.subtag_flow_layout.takeAt(0).widget().deleteLater()
|
||||
while self.subtags_scroll_layout.itemAt(0):
|
||||
self.subtags_scroll_layout.takeAt(0).widget().deleteLater()
|
||||
|
||||
c = QWidget()
|
||||
layout = QVBoxLayout(c)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(3)
|
||||
for tag_id in self.subtag_ids:
|
||||
tag = self.lib.get_tag(tag_id)
|
||||
tw = TagWidget(tag, has_edit=False, has_remove=True)
|
||||
tw.on_remove.connect(lambda t=tag_id: self.remove_subtag_callback(t))
|
||||
self.subtag_flow_layout.addWidget(tw)
|
||||
|
||||
self.subtag_flow_layout.addWidget(self.subtags_add_button)
|
||||
layout.addWidget(tw)
|
||||
self.subtags_scroll_layout.addWidget(c)
|
||||
|
||||
def add_aliases(self):
|
||||
fields: set[TagAliasWidget] = set()
|
||||
for i in range(0, self.aliases_flow_layout.count() - 1):
|
||||
widget = self.aliases_flow_layout.itemAt(i).widget()
|
||||
names: set[str] = set()
|
||||
for i in range(0, self.aliases_table.rowCount()):
|
||||
widget = self.aliases_table.cellWidget(i, 1)
|
||||
|
||||
if not isinstance(widget, TagAliasWidget):
|
||||
return
|
||||
names.add(cast(CustomTableItem, widget).text())
|
||||
|
||||
field: TagAliasWidget = cast(TagAliasWidget, widget)
|
||||
fields.add(field)
|
||||
remove: set[str] = set(self.alias_names) - names
|
||||
|
||||
remove: set[str] = self.alias_names - set([a.text_field.text() for a in fields])
|
||||
self.alias_names = list(set(self.alias_names) - remove)
|
||||
|
||||
self.alias_names = self.alias_names - remove
|
||||
|
||||
for field in fields:
|
||||
for name in names:
|
||||
# add new aliases
|
||||
if field.text_field.text() != "":
|
||||
self.alias_names.add(field.text_field.text())
|
||||
if name.strip() != "" and name not in set(self.alias_names):
|
||||
self.alias_names.append(name)
|
||||
elif name.strip() == "" and name in set(self.alias_names):
|
||||
self.alias_names.remove(name)
|
||||
|
||||
def _update_new_alias_name_dict(self):
|
||||
for i in range(0, self.aliases_flow_layout.count() - 1):
|
||||
widget = self.aliases_flow_layout.itemAt(i).widget()
|
||||
|
||||
if not isinstance(widget, TagAliasWidget):
|
||||
return
|
||||
|
||||
field: TagAliasWidget = cast(TagAliasWidget, widget)
|
||||
text_field_text = field.text_field.text()
|
||||
|
||||
self.new_alias_names[field.id] = text_field_text
|
||||
row = self.aliases_table.rowCount()
|
||||
logger.info(row)
|
||||
for i in range(0, self.aliases_table.rowCount()):
|
||||
widget = self.aliases_table.cellWidget(i, 1)
|
||||
self.new_alias_names[widget.id] = widget.text() # type: ignore
|
||||
|
||||
def _set_aliases(self):
|
||||
self._update_new_alias_name_dict()
|
||||
|
||||
while self.aliases_flow_layout.itemAt(1):
|
||||
self.aliases_flow_layout.takeAt(0).widget().deleteLater()
|
||||
while self.aliases_table.rowCount() > 0:
|
||||
self.aliases_table.removeRow(0)
|
||||
|
||||
self.alias_names.clear()
|
||||
|
||||
@@ -371,43 +325,45 @@ class BuildTagPanel(PanelWidget):
|
||||
|
||||
alias_name = alias.name if alias else self.new_alias_names[alias_id]
|
||||
|
||||
new_field = TagAliasWidget(
|
||||
alias_id,
|
||||
alias_name,
|
||||
lambda a=alias_name, id=alias_id: self.remove_alias_callback(a, id),
|
||||
)
|
||||
new_field.setMaximumHeight(25)
|
||||
new_field.setMinimumHeight(25)
|
||||
self.aliases_flow_layout.addWidget(new_field)
|
||||
self.alias_names.add(alias_name)
|
||||
# handel when an alias name changes
|
||||
if alias_id in self.new_alias_names:
|
||||
alias_name = self.new_alias_names[alias_id]
|
||||
|
||||
self.aliases_flow_layout.addWidget(self.alias_add_button)
|
||||
self.alias_names.append(alias_name)
|
||||
|
||||
remove_btn = QPushButton("-")
|
||||
remove_btn.clicked.connect(
|
||||
lambda a=alias_name, id=alias_id: self.remove_alias_callback(a, id)
|
||||
)
|
||||
|
||||
row = self.aliases_table.rowCount()
|
||||
new_item = CustomTableItem(alias_name, self.enter, self.backspace)
|
||||
new_item.set_id(alias_id)
|
||||
|
||||
new_item.editingFinished.connect(lambda item=new_item: self._alias_name_change(item))
|
||||
|
||||
self.aliases_table.insertRow(row)
|
||||
self.aliases_table.setCellWidget(row, 1, new_item)
|
||||
self.aliases_table.setCellWidget(row, 0, remove_btn)
|
||||
|
||||
def _alias_name_change(self, item: CustomTableItem):
|
||||
self.new_alias_names[item.id] = item.text()
|
||||
|
||||
def set_tag(self, tag: Tag):
|
||||
self.tag = tag
|
||||
|
||||
self.tag = tag
|
||||
|
||||
logger.info("setting tag", tag=tag)
|
||||
|
||||
self.name_field.setText(tag.name)
|
||||
self.shorthand_field.setText(tag.shorthand or "")
|
||||
|
||||
for alias_id in tag.alias_ids:
|
||||
self.alias_ids.add(alias_id)
|
||||
self.alias_ids.append(alias_id)
|
||||
|
||||
self._set_aliases()
|
||||
|
||||
for subtag in tag.subtag_ids:
|
||||
self.subtag_ids.add(subtag)
|
||||
|
||||
for alias_id in tag.alias_ids:
|
||||
self.alias_ids.add(alias_id)
|
||||
|
||||
self._set_aliases()
|
||||
|
||||
for subtag in tag.subtag_ids:
|
||||
self.subtag_ids.add(subtag)
|
||||
self.subtag_ids.append(subtag)
|
||||
|
||||
self.set_subtags()
|
||||
|
||||
|
||||
@@ -167,7 +167,9 @@ class TagDatabasePanel(PanelWidget):
|
||||
self.edit_modal.show()
|
||||
|
||||
def edit_tag_callback(self, btp: BuildTagPanel):
|
||||
self.lib.update_tag(btp.build_tag(), btp.subtag_ids, btp.alias_names, btp.alias_ids)
|
||||
self.lib.update_tag(
|
||||
btp.build_tag(), set(btp.subtag_ids), set(btp.alias_names), set(btp.alias_ids)
|
||||
)
|
||||
self.update_tags(self.search_field.text())
|
||||
|
||||
def showEvent(self, event: QShowEvent) -> None: # noqa N802
|
||||
|
||||
@@ -673,7 +673,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.modal.saved.connect(
|
||||
lambda: (
|
||||
self.lib.add_tag(
|
||||
panel.build_tag(), panel.subtag_ids, panel.alias_names, panel.alias_ids
|
||||
panel.build_tag(),
|
||||
set(panel.subtag_ids),
|
||||
set(panel.alias_names),
|
||||
set(panel.alias_ids),
|
||||
),
|
||||
self.modal.hide(),
|
||||
)
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
from typing import cast
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||
from src.core.library.alchemy.models import Tag
|
||||
from src.qt.modals.build_tag import BuildTagPanel
|
||||
from src.qt.widgets.tag import TagAliasWidget
|
||||
|
||||
|
||||
def test_build_tag_panel_add_sub_tag_callback(library, generate_tag):
|
||||
@@ -43,29 +39,6 @@ import os
|
||||
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
||||
|
||||
|
||||
def test_build_tag_panel_remove_selected_alias(library, generate_tag):
|
||||
app = QApplication.instance() or QApplication([])
|
||||
|
||||
window = QMainWindow()
|
||||
parent_tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
panel = BuildTagPanel(library, parent_tag)
|
||||
panel.setParent(window)
|
||||
|
||||
panel.add_alias_callback()
|
||||
window.show()
|
||||
|
||||
assert panel.aliases_flow_layout.count() == 2
|
||||
|
||||
alias_widget = panel.aliases_flow_layout.itemAt(0).widget()
|
||||
alias_widget.text_field.setFocus()
|
||||
|
||||
app.processEvents()
|
||||
|
||||
panel.remove_selected_alias()
|
||||
|
||||
assert panel.aliases_flow_layout.count() == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_add_alias_callback(library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
@@ -74,7 +47,7 @@ def test_build_tag_panel_add_alias_callback(library, generate_tag):
|
||||
|
||||
panel.add_alias_callback()
|
||||
|
||||
assert panel.aliases_flow_layout.count() == 2
|
||||
assert panel.aliases_table.rowCount() == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_remove_alias_callback(library, generate_tag):
|
||||
@@ -112,7 +85,7 @@ def test_build_tag_panel_set_subtags(library, generate_tag):
|
||||
panel: BuildTagPanel = BuildTagPanel(library, child)
|
||||
|
||||
assert len(panel.subtag_ids) == 1
|
||||
assert panel.subtag_flow_layout.count() == 2
|
||||
assert panel.subtags_scroll_layout.count() == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_add_aliases(library, generate_tag):
|
||||
@@ -128,19 +101,19 @@ def test_build_tag_panel_add_aliases(library, generate_tag):
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
|
||||
widget = panel.aliases_flow_layout.itemAt(0).widget()
|
||||
widget = panel.aliases_table.cellWidget(0, 1)
|
||||
|
||||
alias_names: set[str] = set()
|
||||
alias_names.add(cast(TagAliasWidget, widget).text_field.text())
|
||||
alias_names.add(widget.text())
|
||||
|
||||
widget = panel.aliases_flow_layout.itemAt(1).widget()
|
||||
alias_names.add(cast(TagAliasWidget, widget).text_field.text())
|
||||
widget = panel.aliases_table.cellWidget(1, 1)
|
||||
alias_names.add(widget.text())
|
||||
|
||||
assert "alias" in alias_names
|
||||
assert "alias_2" in alias_names
|
||||
|
||||
old_text = cast(TagAliasWidget, widget).text_field.text()
|
||||
cast(TagAliasWidget, widget).text_field.setText("alias_update")
|
||||
old_text = widget.text()
|
||||
widget.setText("alias_update")
|
||||
|
||||
panel.add_aliases()
|
||||
|
||||
@@ -161,7 +134,7 @@ def test_build_tag_panel_set_aliases(library, generate_tag):
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
|
||||
assert panel.aliases_flow_layout.count() == 2
|
||||
assert panel.aliases_table.rowCount() == 1
|
||||
assert len(panel.alias_names) == 1
|
||||
assert len(panel.alias_ids) == 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user