Skip to content

Conversation

knguyen1
Copy link
Contributor

@knguyen1 knguyen1 commented Aug 21, 2025

Summary

  • This is a…
    • Bug fix
    • Feature addition
    • Refactoring
    • Minor / simple change (like a typo)
    • Other
  • Describe this change in 1-2 sentences:
    Adds a UI to manage custom columns from the header menu: create, edit, duplicate, delete, place, and apply to File/Album views.

Problem

Users had no built-in UI to add/manage custom columns; changes required backend code or were not discoverable from the views.

Solution

  • Header context menu: new “Manage Custom Columns…” entry (unlock columns if needed).
  • Manager dialog:
    • List of custom columns with Type, Expression, Align, Width.
    • Actions: Add…, Edit…, Duplicate, Delete, “Make It So!” (apply).
  • Expression dialog (per column):
    • Field Name, Type (Field or Script), Expression, Width, Align.
    • Add to views: File view / Album view.
    • Insert after key: position by existing column key (e.g. title, artist).
  • UX details:
    • Immediate registration into live views on apply; closing applies pending changes.
    • Idempotent updates when editing or duplicating; safe deletion with confirmation.
    • Columns can also be toggled from the header menu like built-ins.

Demo

image

Action

Additional actions required:

  • Update Picard documentation (please include a reference to this PR)
  • Other (please specify below)

Note: API PR #2709 must be merged first; this PR only covers the UI/UX management layer.

@zas zas requested review from phw, rdswift and zas August 23, 2025 07:09
@rdswift
Copy link
Collaborator

rdswift commented Aug 23, 2025

I haven't looked at this in detail yet, but have some initial thoughts:

  1. It may be better to just implement the api hooks, and leave the rest to plugins. [I just noticed that the API is in a different PR (PICARD-2103: Add API for custom columns (field ref, scripts, transforms, etc.) #2709).] If we are to include the "Custom Columns" dialog, it might be better to have this as a new option settings page under the UI section, rather than as a context menu. Custom column specs could be managed similarly to tagging scripts for definition, ordering and enable/disable selection.

  2. Any custom column definitions (and activations) that have been stored by the user should be managed through the option profiles.

  3. I'm not sure about the README files in the code tree (which seems more appropriate to a plugin). They do however provide information that should be in the Picard documentation (with some editing).

@rdswift
Copy link
Collaborator

rdswift commented Aug 23, 2025

I see that many of the files in this PR are also in the API PR #2709 (which you indicated must be merged first). This makes the review of this PR much more difficult because we're either reviewing the same changes twice (in this PR and PR #2709) or it isn't clear what changes in this PR will be applied to modify the changes in PR #2709.

My suggestion is that we wait until PR #2709 is merged, and this PR rebased before we review this PR in detail.

Copy link
Collaborator

@zas zas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall that's a good move, but that's also a lot of changes at once.

I didn't test (just reviewed code), I'll do later.
Also I wonder about how it fits with plugin V3 (we tried to regroup "API" access in picard/extension_points)

@knguyen1
Copy link
Contributor Author

knguyen1 commented Aug 26, 2025

Overall that's a good move, but that's also a lot of changes at once.

I recommend reviewing and merging #2709 first. It'll reduce the number of files changed in #2714.

Plus, changes requested there would likely result in changes here.

@knguyen1
Copy link
Contributor Author

@rdswift

Any custom column definitions (and activations) that have been stored by the user should be managed through the option profiles.

storage.py handles persistence: https://github.com/metabrainz/picard/blob/a2cde3daeb3accd48e9f44ab682ee06ad739ae08/picard/ui/itemviews/custom_columns/storage.py#L317C1-L322C19

And recall: https://github.com/metabrainz/picard/blob/a2cde3daeb3accd48e9f44ab682ee06ad739ae08/picard/ui/itemviews/custom_columns/storage.py#L405C1-L422C24

Persistence is saved to the global config object.

@knguyen1
Copy link
Contributor Author

@rdswift

If we are to include the "Custom Columns" dialog, it might be better to have this as a new option settings page under the UI section, rather than as a context menu.

I disagree, as adding these menu/dialogs there to an already crowded Options/Settings page. The Manage Custom Columns... context menu feels cleaner. But feel free to put this to a vote and I'll be happy to move it to Options/Settings -> UI section.

@rdswift
Copy link
Collaborator

rdswift commented Aug 26, 2025

Persistence is saved to the global config object.

However, as currently handled it is not being managed through option profiles.

@rdswift
Copy link
Collaborator

rdswift commented Aug 26, 2025

If we are to include the "Custom Columns" dialog, it might be better to have this as a new option settings page under the UI section, rather than as a context menu.

I disagree, as adding these menu/dialogs there to an already crowded Options/Settings page. The Manage Custom Columns... context menu feels cleaner. But feel free to put this to a vote and I'll be happy to move it to Options/Settings -> UI section.

@zas / @phw what are your thoughts on this? My thinking is that by including these under Option Settings we can have a standardized approach (based on something like the tagger scripts page), the UI settings will be accessed in one place, and it will allow the Custom Columns settings to be managed via option profiles.

@knguyen1
Copy link
Contributor Author

Persistence is saved to the global config object.

However, as currently handled it is not being managed through option profiles.

As-is, it's pretty seamless to the user (it's opaque to them where these settings get saved). What's the advantage of moving it to profiles? To the user? To the dev?

@knguyen1 knguyen1 force-pushed the feat/PICARD-2103/add-ui-to-manage-custom-columns branch 2 times, most recently from c8e47a9 to 63ed30b Compare August 27, 2025 09:39
@rdswift
Copy link
Collaborator

rdswift commented Aug 28, 2025

Last night, instead of sleeping, I spent a few hours thinking about this UI and how I believe it should be set up. I offer the following for consideration:

  1. The custom column definitions should be in a separate page (e.g. "Custom Columns for Lists") under the "User Interface" section of the option settings. This would be more consistent with the way other items are managed, and could be in a format similar to the "Scripting" options page. This should support adding, removing and editing custom column definitions, and setting their default display order.

  2. The custom column definitions, including default sort order, should be stored in a config.setting[] item that is not managed by option profiles. My thinking is that all custom columns should be available for use in all profiles, in the same way that all file naming scripts are available to all profiles.

  3. The custom column definitions page should use the ScriptTextEdit widget for script entry. This will allow for autocompletion, function and variable tooltip information, and the ability to confirm that the script is valid before saving.

  4. The new "Custom Columns for Lists" options page should include the ability to select which custom columns are included for each of the "files" and "albums" lists, and the column display order for each list. This could be via a separate dialog for each of "files" and "albums", which displays a sortable list of all available columns with a checkbox on each to enable or disable the display of that column. My initial thoughts are that the selection and order settings should be stored in a separate config.setting[] item for each list, perhaps containing a list of the enabled columns in the selected display order.

  5. The "files" and "albums" selected columns and column display order settings should be managed by option profiles. My thinking behind this is that a user may have a workflow that depends on a different set of settings in addition to different columns displayed in a different order, and switching to or from this collection of settings is quickly and easily done by enabling or disabling the appropriate option profile. That's one of the reasons that option profiles were created.

  6. Similar to Item 4, the ability to select which custom columns are included for each of the "files" and "albums" lists, and the column display order for each list should be available via the context menu from each of the lists. Right clicking on the header of each list already displays a list of the available columns with the ability to display or hide each item. All of the defined custom columns should be included in this list. The columns display order can currently be modified by dragging a column header to the appropriate location (left or right), and this functionality should be retained but should also include the enabled custom columns. I don't believe that the current column selection and display order is managed through option profiles, however I believe that it should be.

I believe that this would provide the most flexibility while remaining consistent and intuitive in accessing the configuration. The learning curve and impact to the users should be minimal.

@knguyen1
Copy link
Contributor Author

  1. IMO: I think we should focus on MVP (minimum viable product) and save these for future enhancements. Happy to discuss/follow along with group consensus on this though.

  2. I think it's helpful if you draw up some wireframes for these.

Also, imo column definitions should only be defined once per column/per use-case, in the spirit of DRY. Then they can/should be mapped to different profiles/views.

Copy link
Member

@phw phw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Just a few initial comments after looking at the UI. I'll do a code review once #2709 has been merged.

  • It's uncommon to have a "apply" and a "close" button in Picard. We should be consistent and have a button that saves the changes and closes the dialog and a cancel button that closes the dialog without saving changes.
  • As a user I couldn't figure out how the "field" column type works. From reading the code I should be able to set one of those field names we use for the standard columns (which gets resolved by the column function of the underlying object), but this didn't give me results.
    In any case this is a bit difficult option, even if it seems simpler then the script option on first sight. The "column" names currently are an implementation detail. They can be exactly metadata names, but we also have special ones. Maybe if we offer such option we should provide the user with a select box for choosing a value. The values available probably would be a mix of default columns and known metadata tags.
  • The alignment values in the UI should be lowercase "left" and "right" and be marked as translatable.
  • Saving and applying the changes seems a bit wonky. Actual making new columns visible seems to require a restart. And I also managed it somehow to get all custom columns hidden again after editing the custom columns.
  • I don't think we need the insert after input. The UI is confusing, and it exposes some internal implementation that does not necessarily reflect actual visual columns position. See my comment at #2709 (comment)

@zas / @phw what are your thoughts on this? My thinking is that by including these under Option Settings we can have a standardized approach (based on something like the tagger scripts page), the UI settings will be accessed in one place, and it will allow the Custom Columns settings to be managed via option profiles.

I actually like the approach here of adding it to the columns context menu. This is already used to customize columns displayed, together with other actions like resizing and reordering which is also done on the header.

I think we'll need to refactor the context menu a bit as one of the next steps, though. It has become quite long. We could add submenus and group the columns a bit. But that's something for a PR afterwards.

@rdswift
Copy link
Collaborator

rdswift commented Aug 28, 2025

Regardless of the UI, I still think it would be good to have the selected columns and position settings managed through the option profiles.

@knguyen1
Copy link
Contributor Author

knguyen1 commented Aug 28, 2025

  • Saving and applying the changes seems a bit wonky. Actual making new columns visible seems to require a restart.

@phw I struggled with this too. Attempts to make newly created columns in a "live" manner required touching registry.py which I did not want to do in this PR. I'll make more attempts once we merge #2709

@zas
Copy link
Collaborator

zas commented Sep 1, 2025

@knguyen1 now that #2709 was merged, please rebase this one

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

Try this: Default col name and focus on add; fix bug invalid spec closing window

Look for this function. It's part of the Add flow if you'd like to make some suggestions.

def clear_for_new(self, default_width: int) -> None:
        """Clear form for new column creation.

        Parameters
        ----------
        default_width : int
            Default width value to set for the new column.
        """
        self.set_enabled(True)
        self._title_input.clear()
        self._title_input.setText(DEFAULT_NEW_COLUMN_NAME)
        self._title_input.setFocus()
        self._expression_input.setPlainText("")
        self._width_input.setValue(default_width)
        idx = self._align_input.findData(normalize_align_name(ALIGN_LEFT_NAME))
        if idx >= 0:
            self._align_input.setCurrentIndex(idx)

        # Reset sorting adapter to default (first item)
        self._sorting_adapter_input.setCurrentIndex(0)

        # Select all views by default
        with suppress(AttributeError):
            self._view_selector.select_all()

@zas
Copy link
Collaborator

zas commented Sep 5, 2025

I prefer it like this (without the Save button).

I noticed few issues:

  • middle pane should be refreshed after expressions change (there's no mean to refresh it)
image
  • just after adding a new column, it appears in context menu as not checked, though it is visible:
image (see col4, shown in table, but not checked in the menu)

# Always sync _current_row with actual selection before commit
actual_selected_row = self._selected_row()
if self._current_row != actual_selected_row:
self._current_row = actual_selected_row
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe simplify here as the test isn't really useful and actual_selected_row isn't used later :

        self._current_row = self._selected_row()

Could we avoid this totally, just by using self._selected_row() (with local variable caching if needed)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if self._current_row != actual_selected_row:
self._current_row = actual_selected_row

if self._populating or self._deleting or self._current_row < 0 or self._current_row >= self._model.rowCount():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would split this in two:

if self._populating or self._deleting:
    return

if self._current_row < 0 or self._current_row >= self._model.rowCount():
    return

Also shouldn't we set self._current_row only if not populating or deleting?

def _commit_form_to_model(self) -> None:
    if self._populating or self._deleting:
        return

    self._current_row = self._selected_row()
    if self._current_row < 0 or self._current_row >= self._model.rowCount():
        return

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if name == TransformName.BRACKETS:
return lambda s: f"[{s}]" if s else ""
# Fallback no-op
return lambda s: s or ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just use a defaultdict for O(1) access:

from collections import defaultdict

_transforms = defaultdict(lambda: lambda s: s or "")
_transforms[TransformName.UPPER] = lambda s: (s or "").upper()
...
_transforms[TransformName.BRACKETS] = lambda s: f"[{s}]" if s else ""

def _make_transform_callable(name: TransformName) -> Callable[[str], str]:
    return _transforms[name]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expr_val = entry.get('expression')
if not isinstance(key_val, str) or not key_val.strip():
continue
if not isinstance(expr_val, str) or not expr_val.strip():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move expr_val declaration just before this line:

                key_val = entry.get('key')
                if not isinstance(key_val, str) or not key_val.strip():
                    continue
                expr_val = entry.get('expression')
                if not isinstance(expr_val, str) or not expr_val.strip():

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

"""Validation for CustomColumnSpec objects."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, definitively.


# Display an error message to the user
self._error_message_display = QtWidgets.QLabel(self._editor_panel)
self._error_message_display.setStyleSheet("QLabel { color : red; }")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it should be stored in a variable somehow, like in

self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR)

Having STYLESHEET_ERROR will help identifying places were we have those hard-coded colors.

Also we could use a color defined in

picard/picard/ui/colors.py

Lines 91 to 126 in cc0b900

register_color(_ALL, 'entity_error', '#C80000')
register_color(_ALL, 'entity_pending', '#808080')
register_color(_ALL, 'entity_saved', '#00AA00')
register_color(_LIGHT, 'log_debug', 'purple')
register_color(_DARK, 'log_debug', 'plum')
register_color(_ALL, 'log_error', 'red')
register_color(_LIGHT, 'log_info', 'black')
register_color(_DARK, 'log_info', 'white')
register_color(_ALL, 'log_warning', 'darkorange')
register_color(_ALL, 'tagstatus_added', 'green')
register_color(_ALL, 'tagstatus_changed', 'darkgoldenrod')
register_color(_ALL, 'tagstatus_removed', 'red')
register_color(_DARK, 'profile_hl_fg', '#FFFFFF')
register_color(_LIGHT, 'profile_hl_fg', '#000000')
register_color(_DARK, 'profile_hl_bg', '#000080')
register_color(_LIGHT, 'profile_hl_bg', '#F9F906')
register_color(_LIGHT, 'row_highlight', '#FFFFE0')
register_color(_DARK, 'row_highlight', '#90907E')
register_color(_LIGHT, 'first_cover_hl', 'darkgoldenrod')
register_color(_DARK, 'first_cover_hl', 'orange')
# syntax highlighting colors
register_color(_LIGHT, 'syntax_hl_error', 'red')
register_color(_LIGHT, 'syntax_hl_escape', 'darkRed')
register_color(_LIGHT, 'syntax_hl_func', 'blue')
register_color(_LIGHT, 'syntax_hl_noop', 'darkGray')
register_color(_LIGHT, 'syntax_hl_special', 'blue')
register_color(_LIGHT, 'syntax_hl_unicode', 'darkRed')
register_color(_LIGHT, 'syntax_hl_var', 'darkCyan')
register_color(_DARK, 'syntax_hl_error', '#F16161')
register_color(_DARK, 'syntax_hl_escape', '#4BEF1F')
register_color(_DARK, 'syntax_hl_func', '#FF57A0')
register_color(_DARK, 'syntax_hl_noop', '#04E7D5')
register_color(_DARK, 'syntax_hl_special', '#FF57A0')
register_color(_DARK, 'syntax_hl_unicode', '#4BEF1F')
register_color(_DARK, 'syntax_hl_var', '#FCBB51')
for such stylesheets (but that's another PR).

Ideally we should get them from themes somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

I prefer it like this (without the Save button).

I prefer the manual Save button actually. Without it, the workflow becomes confusing: users add a column, modify it, but have no clear signal that it’s safe to proceed or that changes are staged. There’s no obvious way to cancel unwanted changes either.

The manual staging button gives users control and clear feedback about their actions. This mirrors familiar patterns like repeatedly hitting Ctrl+S to save documents, or gamers using save points despite auto-save features. Sometimes the illusion of control improves user experience, even if it adds an extra step.

Thoughts?

@rdswift
Copy link
Collaborator

rdswift commented Sep 5, 2025

Is there a reason that we can't show the newly created column definition in the list on the left and select it for editing when we click the "Add" button?

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

That is the current behaviour. Click Add then "untitled column" appears and available for edit right away.

Problem is it isn't clear to user if it's safe to click Add for another. "I'm done. Do I click Make it So? Can I click add again? Did my changes get saved (staged)? If I click Add ColB in the middle of modifying ColA, will my changes get wiped?"

We need some indicator that it's Staged. Maybe a debounce event that puts a star next to a column indicator it's not yet staged.

My Cool Col* (unsaved)
My Cool Col (saved)

@rdswift
Copy link
Collaborator

rdswift commented Sep 5, 2025

Problem is it isn't clear to user if it's safe to click Add for another.

Couldn't this be done by simply trying to stage the current edits when "Add" (or "Duplicate") button is clicked before adding the new item? If there are errors preventing the current edits from being staged, then the new item would not be created and the editor would remain on the current item (with the error message displayed). The "Delete" should always be honoured, and remove the current item (after the confirmation).

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

Couldn't this be done by simply trying to stage the current edits when "Add" (or "Duplicate") button is clicked before adding the new item?

trying to stage the current edits

Well, things are already being silently staged.

Nowhere on the UI right now is it clear that it's safe to click Add while in the middle of an edit though.

@rdswift
Copy link
Collaborator

rdswift commented Sep 5, 2025

Aren't the current edits staged (or applied or saved or whetever the right term is) automatically when you select a different item in the column definitions list? Couldn't the same be done when the user clicks "Add" or "Duplicate"? It should be clear that leaving the current edit is an indicator that the user is done making changes (at least for the time being) and edits should be saved.

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

I think we're talking past each other. If you feel that it's clear to user that they feel it's safe to click Add, we'll move on.

I'll address the change requests and close out this PR. Discussions getting long.

Couldn't the same be done when the user clicks "Add" or "Duplicate"?

This is already happening.

@rdswift
Copy link
Collaborator

rdswift commented Sep 5, 2025

I think we're talking past each other.

In that case let me make it clear (I hope)... What I would like to see is that when the user clicks "Add" or "Duplicate", the new item appears immediately in the list on the left and is selected for editing.

This would then be consistent with the behavior of other sections of Picard, such as the Options... -> Scripting page. I believe that we should be consistent as much as possible in the way the UI works.

@rdswift
Copy link
Collaborator

rdswift commented Sep 5, 2025

I think we're talking past each other.

In that case let me make it clear (I hope)... What I would like to see is that when the user clicks "Add" or "Duplicate", the new item appears immediately in the list on the left and is selected for editing.

This would then be consistent with the behavior of other sections of Picard, such as the Options... -> Scripting page. I believe that we should be consistent as much as possible in the way the UI works.

@zas / @phw, am I out of line here? I don't want to drive @knguyen1 crazy coding something that I would like to see, only to find out that it's something that you don't agree with. If it's just me, I'll hold my nose and back off.

I think we all agree that we're to the point where we should merge this so we can move on to the next step of revamping the columns selection context menu and managing the column selections through the option profiles system.

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 5, 2025

What I would like to see is that when the user clicks "Add" or "Duplicate", the new item appears immediately in the list on the left and is selected for editing.

Try this one Add should create a placeholder spec

@zas
Copy link
Collaborator

zas commented Sep 5, 2025

This would then be consistent with the behavior of other sections of Picard, such as the Options... -> Scripting page. I believe that we should be consistent as much as possible in the way the UI works.

@rdswift I agree we should be consistent.
Perhaps @Aerozol could jump in regarding UI questions, not really my thing, so...

@rdswift
Copy link
Collaborator

rdswift commented Sep 6, 2025

Try this one Add should create a placeholder spec

I just ran the latest commit and it gets two thumbs up from me. 👍 👍 This is exactly what I had in mind. Thanks for all the extra effort you put in to make it happen.

@rdswift
Copy link
Collaborator

rdswift commented Sep 6, 2025

Just found one other issue, but I suspect it's easy to fix. I was testing the latest commit and had two custom columns already defined, and when I tried editing one of them I was presented with a "Key must be a valid UUID" error. This is a problem because there is absolutely nothing that the user can do to fix this. Perhaps we only need to check that the key is not empty, and not that it is a valid UUID. I understand that when this is placed in production it is unlikely that this error will occur, but I am really reluctant to block the user with an error that they have no means of correcting.

image

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 6, 2025

Just found one other issue, but I suspect it's easy to fix. I was testing the latest commit and had two custom columns already defined, and when I tried editing one of them I was presented with a "Key must be a valid UUID" error.

@rdswift Can you delete all and recreate? I think it's because you had created these columns before they were key'ed to UUID's. At this point, I can't reproduce. I relaxed the rule: Relax key rule; mark and translate errors

@rdswift
Copy link
Collaborator

rdswift commented Sep 6, 2025

I think it's because you had created these columns before they were key'ed to UUID's. At this point, I can't reproduce. I relaxed the rule: Relax key rule; mark and translate errors

That is exactly the case. The IDs for the two existing items were "1" and "3". I just tried the latest commit and it works fine. Thanks.

Now I did notice something in the debug log. A lot of lines like:

D: 20:57:40,737 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'title'
D: 20:57:40,738 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~length'
D: 20:57:40,738 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'artist'
D: 20:57:40,739 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'albumartist'
D: 20:57:40,739 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'composer'
D: 20:57:40,740 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'album'
D: 20:57:40,740 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'discsubtitle'
D: 20:57:40,740 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'tracknumber'
D: 20:57:40,741 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'discnumber'
D: 20:57:40,741 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'catalognumber'
D: 20:57:40,742 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'barcode'
D: 20:57:40,742 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'media'
D: 20:57:40,742 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~filesize'
D: 20:57:40,743 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~format'
D: 20:57:40,743 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~bitrate'
D: 20:57:40,744 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'genre'
D: 20:57:40,744 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'date'
D: 20:57:40,745 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'originaldate'
D: 20:57:40,745 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'releasedate'
D: 20:57:40,746 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'covercount'
D: 20:57:40,746 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'coverdimensions'
D: 20:58:56,225 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'title'
D: 20:58:56,225 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~length'
D: 20:58:56,226 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'artist'
D: 20:58:56,226 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'albumartist'
D: 20:58:56,226 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'composer'
D: 20:58:56,227 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'album'
D: 20:58:56,227 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'discsubtitle'
D: 20:58:56,228 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'tracknumber'
D: 20:58:56,228 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'discnumber'
D: 20:58:56,228 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'catalognumber'
D: 20:58:56,229 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'barcode'
D: 20:58:56,229 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'media'
D: 20:58:56,229 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~filesize'
D: 20:58:56,230 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~format'
D: 20:58:56,230 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key '~bitrate'
D: 20:58:56,230 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'genre'
D: 20:58:56,231 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'date'
D: 20:58:56,231 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'originaldate'
D: 20:58:56,232 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'releasedate'
D: 20:58:56,232 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'covercount'
D: 20:58:56,232 ui/itemviews.update_colums_text:428: Object 'ClusterList' does not have column method for key 'coverdimensions'

It appears that there is an entry for each of the standard columns available. This doesn't seem to impact the way that the column selection and display works, but it kind of junks up the log. Not sure whether or not that's enough of a concern to worry about.

@knguyen1
Copy link
Contributor Author

knguyen1 commented Sep 6, 2025

It's from this change: e3c2c0c reported by #2714 (comment)

Without the change, you have to manually Refresh each album item for the col value to appear.

That try-catch and log is needed for automatically recomputing data on custom column apply. Without that try-except, the programme crashes. We could remove the log.debug? Suppress mising attr ClusterList.column log

@rdswift
Copy link
Collaborator

rdswift commented Sep 6, 2025

Without that try-except, the programme crashes. We could remove the log.debug?

I'd rather not because it does provide good information in the case of custom columns. Perhaps we could suppress the logging if the key is in ALL_TAGS?

EDIT: Actually, I don't have a strong opinion either way. I can live with whatever you decide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants