-
-
Notifications
You must be signed in to change notification settings - Fork 420
PICARD-2103: Add UI to manage custom columns #2714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
knguyen1
wants to merge
80
commits into
metabrainz:master
Choose a base branch
from
knguyen1:feat/PICARD-2103/add-ui-to-manage-custom-columns
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 59 commits
Commits
Show all changes
80 commits
Select commit
Hold shift + click to select a range
63e4218
Add ui to manage custom columns
knguyen1 02261a5
Fix failing tests
knguyen1 895ef3f
Use `IntEnum` and `HEADERS` dict; do not hardcode vals
knguyen1 5c56759
No need to import in try-except
knguyen1 dd09a0a
Remove readme's in code tree
knguyen1 6ffe167
Remove blind catch; log debug msg
knguyen1 11b7163
Do not hardcode ints; use screaming constants
knguyen1 79e9363
Use `add_to` instead of `add_to_file_view`, `add_to_album_view`, etc.
knguyen1 169045c
Refactor `validate` to flatten results via a comprehension
knguyen1 cb53ff4
Rename `upper` -> `align`
knguyen1 ae5dd08
Use generic `Iterable`, not `list`
knguyen1 e6b1f5f
Use `tuple` in place of list
knguyen1 264aa1d
Add dirty/cache mechanism for O(1) in `get_by_key`
knguyen1 2ba8687
Remove `insert_after_key` concept; BLE001 fix
knguyen1 f3e04b6
Apply live delete/create to columns
knguyen1 77d8e8f
Fix bug with field type custom column
knguyen1 25fb12d
Fix live column updates; remove `is_default` concept
knguyen1 1b31fa9
Provide a way to enter key; deduplicate key
knguyen1 facd6a2
Should import directly
knguyen1 88361ef
Add user-facing tooltips to `expression_dialog.py`
knguyen1 0b5da55
Use try...except..else pattern
knguyen1 60745e0
Make `left`/`right` choices translatable
knguyen1 5cf7992
Make show-in-view selection generic
knguyen1 bc31743
`FieldReferenceProvider` needs a Callable column guard
knguyen1 340d513
Rename `Field Name` -> `Column Title`
knguyen1 82b93cb
Refactor buttons for consistency
knguyen1 e8e611b
ui: single dialog custom columns manager; use `ScriptTextEdit`
knguyen1 056c111
fix: Inputs should reset on add
knguyen1 1cc7093
Apply better abstractions/decoupling for SRP
knguyen1 69b4f56
Scripts should be evaluated for errors
knguyen1 4ed5aeb
Switch over to use `ColumnSpecValidator` from `validation.py`
knguyen1 fe4753a
Massive refactor: SRP/DRY/SOC, etc; rewire flows
knguyen1 eccaf24
Add unit tests
knguyen1 68369b2
Add live spec check
knguyen1 546d933
Add help button
knguyen1 3d99541
`Manage Columns…` -> `Manage Custom Columns…` context menu
knguyen1 aafaf02
Fix failing tests; QT objects need to be mocked
knguyen1 2076a95
Fix failing tests
knguyen1 3ed794a
Update picard/ui/itemviews/custom_columns/manager_dialog.py
knguyen1 70c26f2
Fix rdswift's bug: Uncommitted changes
knguyen1 4774ba0
Add 'stage changes' clarification
knguyen1 f7ba2f4
Move `refresh_all_views` to event bus
knguyen1 6a1aed0
Make col ids guid; fix bug with delayed commit
knguyen1 f0450fd
Fix failing tests
knguyen1 cedbbb4
Minor: must click `Add` to add initially; dup/del disabled unless has…
knguyen1 eed6a77
refactor: move `SpecListModel` to own module
knguyen1 a3b51de
refactor: Make `manager_dialog.py` lighter
knguyen1 d3617e0
Add ability to pick a sorting algo
knguyen1 28fba33
Add NAT sorting; fix failing tests
knguyen1 48a2bff
Add natural sorter; fix failing tests
knguyen1 e9e0b81
Remove 'reverse' sorter from UI
knguyen1 e552137
Alias the sort_key import
knguyen1 e0e779f
Need to skip these tests on MACOS
knguyen1 4e2aa24
Natural sorting behaviour is different on Linux
knguyen1 b45413d
Formatting review by rdswift
knguyen1 74b22ca
PR review change requests from zas
knguyen1 5a6180c
Rename file refactor -> decoupling
knguyen1 f19cdfa
Remove `Save Changes` button; auto stage changes
knguyen1 549ae93
Default col name and focus on add; fix bug invalid spec closing window
knguyen1 f2752a2
Add should create a placeholder spec
knguyen1 74ede00
Newly visible columns should have a checkbox
knguyen1 e3c2c0c
Newly added custom columns should also trigger data refresh
knguyen1 6728330
Relax key rule; mark and translate errors
knguyen1 d280936
Remove `Make It So!` button from dialog
knguyen1 9c0d578
Updates from zas' code review
knguyen1 855223e
Fix failing tests
knguyen1 0bf96ec
Suppress mising attr `ClusterList.column` log
knguyen1 1bb9012
Rename `Add` -> `New`
knguyen1 12542ea
Allow highlighting multiple and deleting
knguyen1 01e3fc8
Enhance validation dialog+interaction
knguyen1 4f5c139
Make expressions optional, show warning
knguyen1 e164f54
Silently fix key errors
knguyen1 8aa756b
Unchecking views should de-register col from view
knguyen1 3f33d18
Set focus on title when `New` is clicked
knguyen1 9576f39
Use `tuple`; remove redundant code
knguyen1 4b73418
Update shared.py
knguyen1 7cdbb3a
`ValidationReport.summary` should be translated
knguyen1 e3cb29e
Fix bug with eval of regular vs. `~` tilde slugs
knguyen1 e17a2bc
Fix crash issue due to non-float-parseable values returning `QCollato…
knguyen1 4d05b75
Do not show hardcoded values for cluster/noncluster
knguyen1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
picard/ui/itemviews/custom_columns/column_controller.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Picard, the next-generation MusicBrainz tagger | ||
# | ||
# Copyright (C) 2025 The MusicBrainz Team | ||
# | ||
# This program is free software; you can redistribute it and/or | ||
# modify it under the terms of the GNU General Public License | ||
# as published by the Free Software Foundation; either version 2 | ||
# of the License, or (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program; if not, write to the Free Software | ||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
|
||
"""Column controller facade for custom column management dialogs. | ||
|
||
Thin facade layer that reduces complexity in dialog components by | ||
encapsulating validation and specification management logic. | ||
|
||
Classes | ||
------- | ||
ColumnController | ||
Facade class that simplifies custom column operations for dialogs. | ||
""" | ||
|
||
from typing import Iterable | ||
|
||
from picard.ui.itemviews.custom_columns.column_spec_service import ColumnSpecService | ||
from picard.ui.itemviews.custom_columns.spec_list_model import SpecListModel | ||
from picard.ui.itemviews.custom_columns.storage import CustomColumnSpec | ||
from picard.ui.itemviews.custom_columns.validation import ( | ||
ColumnSpecValidator, | ||
ValidationContext, | ||
ValidationReport, | ||
) | ||
|
||
|
||
class ColumnController: | ||
"""Facade for custom column operations in dialog components. | ||
|
||
Provides simplified interface for common custom column operations, | ||
delegating to specialized services. | ||
|
||
Parameters | ||
---------- | ||
spec_service : ColumnSpecService | ||
Service for managing column specifications. | ||
validator : ColumnSpecValidator | ||
Validator for checking specification validity. | ||
""" | ||
|
||
def __init__(self, spec_service: ColumnSpecService, validator: ColumnSpecValidator) -> None: | ||
"""Initialize the column controller with required services.""" | ||
self._spec_service = spec_service | ||
self._validator = validator | ||
|
||
def validate_specs(self, specs: Iterable[CustomColumnSpec]) -> dict[str, ValidationReport]: | ||
"""Validate multiple column specifications. | ||
|
||
Returns | ||
------- | ||
dict[str, ValidationReport] | ||
Dictionary mapping specification keys to validation reports. | ||
""" | ||
return self._validator.validate_multiple(specs) | ||
|
||
def first_invalid_spec(self, specs: Iterable[CustomColumnSpec]) -> CustomColumnSpec | None: | ||
"""Find the first invalid specification in a collection. | ||
|
||
Returns | ||
------- | ||
CustomColumnSpec | None | ||
First invalid specification found, or None if all valid. | ||
""" | ||
reports = self.validate_specs(specs) | ||
for spec, report in reports.items(): | ||
if not report.is_valid: | ||
return spec | ||
return None | ||
|
||
def first_invalid_spec_report(self, report: dict[str, ValidationReport]) -> tuple[str, ValidationReport] | None: | ||
"""Get first invalid specification with its validation report. | ||
|
||
Returns | ||
------- | ||
tuple[str, ValidationReport] | None | ||
(key, validation_report) for first invalid spec, or None. | ||
""" | ||
for key, validation_report in report.items(): | ||
if not validation_report.is_valid: | ||
return key, validation_report | ||
return None | ||
|
||
def validate_single(self, spec: CustomColumnSpec, existing_keys: set[str]) -> ValidationReport: | ||
"""Validate a single column specification. | ||
|
||
Returns | ||
------- | ||
ValidationReport | ||
Report detailing the validation result. | ||
""" | ||
context = ValidationContext(existing_keys=existing_keys) | ||
return self._validator.validate(spec, context) | ||
|
||
def apply_all(self, model: SpecListModel) -> None: | ||
"""Apply all specifications from a model. | ||
|
||
Deduplicates and persists specifications. | ||
""" | ||
self._spec_service.deduplicate_model_by_keys(model) | ||
self._spec_service.persist_and_register(model.specs()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may have already addressed this, but is there a reason this import isn't at the start of the module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to avoid circular import and delays the import until we actually need it. To resolve, we could move
load_persisted_columns_once
to a module higher, e.g.picard/ui/mainwindow.py
,picard/ui/itemviews/__init__.py
, but at the time, I didn't want to touch modules unrelated to this PR.The import chain creates a circular dependency:
basetreeview.py
imports frompicard.ui.itemviews.columns
(line 67:from picard.ui.itemviews.columns import ColumnAlign
)custom_columns/storage.py
imports frompicard.ui.itemviews.custom_columns
(line 42-47)custom_columns/__init__.py
imports fromcustom_columns/registry.py
(line 40)custom_columns/registry.py
imports fromcustom_columns/shared.py
(line 32-33)custom_columns/shared.py
has a local import frompicard.ui.itemviews.columns
(line 183-186)