|
8 | 8 |
|
9 | 9 | import structlog |
10 | 10 | from PySide6.QtCore import Qt, Signal |
| 11 | +from PySide6.QtGui import QColor |
11 | 12 | from PySide6.QtWidgets import ( |
12 | 13 | QApplication, |
| 14 | + QButtonGroup, |
13 | 15 | QCheckBox, |
14 | 16 | QFrame, |
15 | 17 | QHBoxLayout, |
16 | 18 | QLabel, |
17 | 19 | QLineEdit, |
18 | 20 | QPushButton, |
| 21 | + QRadioButton, |
19 | 22 | QScrollArea, |
20 | 23 | QTableWidget, |
21 | 24 | QVBoxLayout, |
|
28 | 31 | from src.qt.modals.tag_search import TagSearchPanel |
29 | 32 | from src.qt.translations import Translations |
30 | 33 | from src.qt.widgets.panel import PanelModal, PanelWidget |
31 | | -from src.qt.widgets.tag import TagWidget |
| 34 | +from src.qt.widgets.tag import ( |
| 35 | + TagWidget, |
| 36 | + get_border_color, |
| 37 | + get_highlight_color, |
| 38 | + get_primary_color, |
| 39 | + get_text_color, |
| 40 | +) |
32 | 41 | from src.qt.widgets.tag_color_preview import TagColorPreview |
33 | 42 |
|
34 | 43 | logger = structlog.get_logger(__name__) |
@@ -62,6 +71,7 @@ def __init__(self, library: Library, tag: Tag | None = None): |
62 | 71 | self.tag: Tag # NOTE: This gets set at the end of the init. |
63 | 72 | self.tag_color_namespace: str | None |
64 | 73 | self.tag_color_slug: str | None |
| 74 | + self.disambiguation_id: int | None |
65 | 75 |
|
66 | 76 | self.setMinimumSize(300, 400) |
67 | 77 | self.root_layout = QVBoxLayout(self) |
@@ -129,6 +139,8 @@ def __init__(self, library: Library, tag: Tag | None = None): |
129 | 139 | self.parent_tags_layout.setContentsMargins(0, 0, 0, 0) |
130 | 140 | self.parent_tags_layout.setSpacing(0) |
131 | 141 | self.parent_tags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) |
| 142 | + self.disam_button_group = QButtonGroup(self) |
| 143 | + self.disam_button_group.setExclusive(False) |
132 | 144 |
|
133 | 145 | self.parent_tags_title = QLabel() |
134 | 146 | Translations.translate_qobject(self.parent_tags_title, "tag.parent_tags") |
@@ -314,22 +326,111 @@ def set_parent_tags(self): |
314 | 326 | while self.parent_tags_scroll_layout.itemAt(0): |
315 | 327 | self.parent_tags_scroll_layout.takeAt(0).widget().deleteLater() |
316 | 328 |
|
317 | | - last: QWidget = self.aliases_table.cellWidget(self.aliases_table.rowCount() - 1, 1) |
318 | 329 | c = QWidget() |
319 | 330 | layout = QVBoxLayout(c) |
320 | 331 | layout.setContentsMargins(0, 0, 0, 0) |
321 | 332 | layout.setSpacing(3) |
322 | | - for tag_id in self.parent_ids: |
323 | | - tag = self.lib.get_tag(tag_id) |
324 | | - tw = TagWidget(tag, has_edit=False, has_remove=True) |
325 | | - tw.on_remove.connect(lambda t=tag_id: self.remove_parent_tag_callback(t)) |
326 | | - layout.addWidget(tw) |
327 | | - self.setTabOrder(last, tw.bg_button) |
328 | | - last = tw.bg_button |
329 | | - self.setTabOrder(last, self.name_field) |
330 | | - |
| 333 | + last_tab: QWidget = self.aliases_table.cellWidget(self.aliases_table.rowCount() - 1, 1) |
| 334 | + next_tab: QWidget = last_tab |
| 335 | + |
| 336 | + for parent_id in self.parent_ids: |
| 337 | + tag = self.lib.get_tag(parent_id) |
| 338 | + if not tag: |
| 339 | + continue |
| 340 | + is_disam = parent_id == self.disambiguation_id |
| 341 | + last_tab, next_tab, container = self.__build_row_item_widget(tag, parent_id, is_disam) |
| 342 | + layout.addWidget(container) |
| 343 | + # TODO: Disam buttons after the first currently can't be added due to this error: |
| 344 | + # QWidget::setTabOrder: 'first' and 'second' must be in the same window |
| 345 | + self.setTabOrder(last_tab, next_tab) |
| 346 | + |
| 347 | + self.setTabOrder(next_tab, self.name_field) |
331 | 348 | self.parent_tags_scroll_layout.addWidget(c) |
332 | 349 |
|
| 350 | + def __build_row_item_widget(self, tag: Tag, parent_id: int, is_disambiguation: bool): |
| 351 | + container = QWidget() |
| 352 | + row = QHBoxLayout(container) |
| 353 | + row.setContentsMargins(0, 0, 0, 0) |
| 354 | + row.setSpacing(3) |
| 355 | + |
| 356 | + # Init Colors |
| 357 | + primary_color = get_primary_color(tag) |
| 358 | + border_color = ( |
| 359 | + get_border_color(primary_color) |
| 360 | + if not (tag.color and tag.color.secondary) |
| 361 | + else (QColor(tag.color.secondary)) |
| 362 | + ) |
| 363 | + highlight_color = get_highlight_color( |
| 364 | + primary_color |
| 365 | + if not (tag.color and tag.color.secondary) |
| 366 | + else QColor(tag.color.secondary) |
| 367 | + ) |
| 368 | + text_color: QColor |
| 369 | + if tag.color and tag.color.secondary: |
| 370 | + text_color = QColor(tag.color.secondary) |
| 371 | + else: |
| 372 | + text_color = get_text_color(primary_color, highlight_color) |
| 373 | + |
| 374 | + # Add Disambiguation Tag Button |
| 375 | + disam_button = QRadioButton() |
| 376 | + disam_button.setObjectName(f"disambiguationButton.{parent_id}") |
| 377 | + disam_button.setFixedSize(22, 22) |
| 378 | + disam_button.setToolTip(Translations.translate_formatted("tag.disambiguation.tooltip")) |
| 379 | + disam_button.setStyleSheet( |
| 380 | + f"QRadioButton{{" |
| 381 | + f"background: rgba{primary_color.toTuple()};" |
| 382 | + f"color: rgba{text_color.toTuple()};" |
| 383 | + f"border-color: rgba{border_color.toTuple()};" |
| 384 | + f"border-radius: 6px;" |
| 385 | + f"border-style:solid;" |
| 386 | + f"border-width: 2px;" |
| 387 | + f"}}" |
| 388 | + f"QRadioButton::indicator{{" |
| 389 | + f"width: 10px;" |
| 390 | + f"height: 10px;" |
| 391 | + f"border-radius: 2px;" |
| 392 | + f"margin: 4px;" |
| 393 | + f"}}" |
| 394 | + f"QRadioButton::indicator:checked{{" |
| 395 | + f"background: rgba{text_color.toTuple()};" |
| 396 | + f"}}" |
| 397 | + f"QRadioButton::hover{{" |
| 398 | + f"border-color: rgba{highlight_color.toTuple()};" |
| 399 | + f"}}" |
| 400 | + ) |
| 401 | + |
| 402 | + self.disam_button_group.addButton(disam_button) |
| 403 | + if is_disambiguation: |
| 404 | + disam_button.setChecked(True) |
| 405 | + |
| 406 | + disam_button.clicked.connect(lambda checked=False: self.toggle_disam_id(parent_id)) |
| 407 | + row.addWidget(disam_button) |
| 408 | + |
| 409 | + # Add Tag Widget |
| 410 | + tag_widget = TagWidget( |
| 411 | + tag, |
| 412 | + library=self.lib, |
| 413 | + has_edit=False, |
| 414 | + has_remove=True, |
| 415 | + ) |
| 416 | + |
| 417 | + tag_widget.on_remove.connect(lambda t=parent_id: self.remove_parent_tag_callback(t)) |
| 418 | + row.addWidget(tag_widget) |
| 419 | + |
| 420 | + return disam_button, tag_widget.bg_button, container |
| 421 | + |
| 422 | + def toggle_disam_id(self, disambiguation_id: int | None): |
| 423 | + if self.disambiguation_id == disambiguation_id: |
| 424 | + self.disambiguation_id = None |
| 425 | + else: |
| 426 | + self.disambiguation_id = disambiguation_id |
| 427 | + |
| 428 | + for button in self.disam_button_group.buttons(): |
| 429 | + if button.objectName() == f"disambiguationButton.{self.disambiguation_id}": |
| 430 | + button.setChecked(True) |
| 431 | + else: |
| 432 | + button.setChecked(False) |
| 433 | + |
333 | 434 | def add_aliases(self): |
334 | 435 | names: set[str] = set() |
335 | 436 | for i in range(0, self.aliases_table.rowCount()): |
@@ -406,6 +507,7 @@ def set_tag(self, tag: Tag): |
406 | 507 | self.alias_ids.append(alias_id) |
407 | 508 | self._set_aliases() |
408 | 509 |
|
| 510 | + self.disambiguation_id = tag.disambiguation_id |
409 | 511 | for parent_id in tag.parent_ids: |
410 | 512 | self.parent_ids.add(parent_id) |
411 | 513 | self.set_parent_tags() |
@@ -440,10 +542,10 @@ def build_tag(self) -> Tag: |
440 | 542 |
|
441 | 543 | tag.name = self.name_field.text() |
442 | 544 | tag.shorthand = self.shorthand_field.text() |
443 | | - tag.is_category = self.cat_checkbox.isChecked() |
444 | | - |
| 545 | + tag.disambiguation_id = self.disambiguation_id |
445 | 546 | tag.color_namespace = self.tag_color_namespace |
446 | 547 | tag.color_slug = self.tag_color_slug |
| 548 | + tag.is_category = self.cat_checkbox.isChecked() |
447 | 549 |
|
448 | 550 | logger.info("built tag", tag=tag) |
449 | 551 | return tag |
|
0 commit comments