Skip to content

Commit 4fe15e0

Browse files
authored
Merge pull request #2466 from zas/itemviews_columns
Itemviews: Introduce new classes Columns & Column, simplfify code
2 parents c9174a7 + 03914b2 commit 4fe15e0

File tree

3 files changed

+427
-139
lines changed

3 files changed

+427
-139
lines changed

picard/ui/itemviews.py renamed to picard/ui/itemviews/__init__.py

Lines changed: 88 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
from picard.ui.collectionmenu import CollectionMenu
100100
from picard.ui.colors import interface_colors
101101
from picard.ui.enums import MainAction
102+
from picard.ui.itemviews.columns import (
103+
DEFAULT_COLUMNS,
104+
ITEM_ICON_COLUMN,
105+
ColumnAlign,
106+
ColumnSortType,
107+
)
102108
from picard.ui.ratingwidget import RatingWidget
103109
from picard.ui.scriptsmenu import ScriptsMenu
104110
from picard.ui.util import menu_builder
@@ -124,46 +130,6 @@ def get_match_color(similarity, basecolor):
124130

125131
class MainPanel(QtWidgets.QSplitter):
126132

127-
columns = [
128-
(N_("Title"), 'title'),
129-
(N_("Length"), '~length'),
130-
(N_("Artist"), 'artist'),
131-
(N_("Album Artist"), 'albumartist'),
132-
(N_("Composer"), 'composer'),
133-
(N_("Album"), 'album'),
134-
(N_("Disc Subtitle"), 'discsubtitle'),
135-
(N_("Track No."), 'tracknumber'),
136-
(N_("Disc No."), 'discnumber'),
137-
(N_("Catalog No."), 'catalognumber'),
138-
(N_("Barcode"), 'barcode'),
139-
(N_("Media"), 'media'),
140-
(N_("Size"), '~filesize'),
141-
(N_("Genre"), 'genre'),
142-
(N_("Fingerprint status"), '~fingerprint'),
143-
(N_("Date"), 'date'),
144-
(N_("Original Release Date"), 'originaldate'),
145-
(N_("Release Date"), 'releasedate'),
146-
(N_("Cover"), 'covercount'),
147-
]
148-
149-
_column_indexes = {column[1]: i for i, column in enumerate(columns)}
150-
151-
TITLE_COLUMN = _column_indexes['title']
152-
TRACKNUMBER_COLUMN = _column_indexes['tracknumber']
153-
DISCNUMBER_COLUMN = _column_indexes['discnumber']
154-
LENGTH_COLUMN = _column_indexes['~length']
155-
FILESIZE_COLUMN = _column_indexes['~filesize']
156-
FINGERPRINT_COLUMN = _column_indexes['~fingerprint']
157-
158-
NAT_SORT_COLUMNS = [
159-
_column_indexes['title'],
160-
_column_indexes['album'],
161-
_column_indexes['discsubtitle'],
162-
_column_indexes['tracknumber'],
163-
_column_indexes['discnumber'],
164-
_column_indexes['catalognumber'],
165-
]
166-
167133
def __init__(self, window, parent=None):
168134
super().__init__(parent)
169135
self.tagger = QtCore.QCoreApplication.instance()
@@ -300,64 +266,38 @@ def select_object(self, obj):
300266
break
301267

302268

303-
def paint_column_icon(painter, rect, icon):
304-
if not icon:
305-
return
306-
size = COLUMN_ICON_SIZE
307-
padding_h = COLUMN_ICON_BORDER
308-
padding_v = (rect.height() - size) // 2
309-
target_rect = QtCore.QRect(rect.x() + padding_h, rect.y() + padding_v, size, size)
310-
painter.drawPixmap(target_rect, icon.pixmap(size, size))
311-
312-
313269
class ConfigurableColumnsHeader(TristateSortHeaderView):
314270

315271
def __init__(self, parent=None):
316272
super().__init__(QtCore.Qt.Orientation.Horizontal, parent)
317-
self._visible_columns = set([0])
318-
319-
# The following are settings applied to default headers
320-
# of QTreeView and QTreeWidget.
321-
self.setSectionsMovable(True)
322-
self.setStretchLastSection(True)
323-
self.setDefaultAlignment(QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter)
324-
self.setSectionsClickable(False)
273+
self._visible_columns = set([ITEM_ICON_COLUMN])
274+
325275
self.sortIndicatorChanged.connect(self.on_sort_indicator_changed)
326276

327277
# enable sorting, but don't actually use it by default
328278
# XXX it would be nice to be able to go to the 'no sort' mode, but the
329279
# internal model that QTreeWidget uses doesn't support it
330280
self.setSortIndicator(-1, QtCore.Qt.SortOrder.AscendingOrder)
331-
self.setDefaultSectionSize(DEFAULT_SECTION_SIZE)
332281

333282
def show_column(self, column, show):
334-
if column == 0: # The first column is fixed
335-
return
283+
if column == ITEM_ICON_COLUMN:
284+
# The first column always visible
285+
# Still execute following to ensure it is shown
286+
show = True
336287
self.parent().setColumnHidden(column, not show)
337288
if show:
338-
if self.sectionSize(column) == 0:
339-
self.resizeSection(column, self.defaultSectionSize())
340289
self._visible_columns.add(column)
341-
if column == MainPanel.FINGERPRINT_COLUMN:
342-
self.setSectionResizeMode(column, QtWidgets.QHeaderView.ResizeMode.Fixed)
343-
self.resizeSection(column, COLUMN_ICON_SIZE)
344-
else:
345-
self.setSectionResizeMode(column, QtWidgets.QHeaderView.ResizeMode.Interactive)
346-
elif column in self._visible_columns:
347-
self._visible_columns.remove(column)
348-
349-
def update_visible_columns(self, columns):
350-
for i, column in enumerate(MainPanel.columns):
351-
self.show_column(i, i in columns)
290+
else:
291+
self._visible_columns.discard(column)
352292

353293
def contextMenuEvent(self, event):
354294
menu = QtWidgets.QMenu(self)
355295
parent = self.parent()
356296

357-
for i, column in enumerate(MainPanel.columns):
358-
if i == 0:
297+
for i, column in enumerate(DEFAULT_COLUMNS):
298+
if i == ITEM_ICON_COLUMN:
359299
continue
360-
action = QtGui.QAction(_(column[0]), parent)
300+
action = QtGui.QAction(_(column.title), parent)
361301
action.setCheckable(True)
362302
action.setChecked(i in self._visible_columns)
363303
action.setEnabled(not self.is_locked)
@@ -383,16 +323,17 @@ def restore_defaults(self):
383323
self.parent().restore_default_columns()
384324

385325
def paintSection(self, painter, rect, index):
386-
if index == MainPanel.FINGERPRINT_COLUMN:
326+
column = DEFAULT_COLUMNS[index]
327+
if column.is_icon:
387328
painter.save()
388329
super().paintSection(painter, rect, index)
389330
painter.restore()
390-
paint_column_icon(painter, rect, FileItem.icon_fingerprint_gray)
331+
column.paint_icon(painter, rect)
391332
else:
392333
super().paintSection(painter, rect, index)
393334

394335
def on_sort_indicator_changed(self, index, order):
395-
if index == MainPanel.FINGERPRINT_COLUMN:
336+
if DEFAULT_COLUMNS[index].is_icon:
396337
self.setSortIndicator(-1, QtCore.Qt.SortOrder.AscendingOrder)
397338

398339
def lock(self, is_locked):
@@ -696,13 +637,23 @@ def save_state(self):
696637
config.persist[self.header_locked] = header.is_locked
697638

698639
def restore_default_columns(self):
699-
labels = [_(h) if n != '~fingerprint' else '' for h, n in MainPanel.columns]
640+
labels = [_(c.title) if not c.is_icon else '' for c in DEFAULT_COLUMNS]
700641
self.setHeaderLabels(labels)
701642

702643
header = self.header()
703-
header.update_visible_columns([0, 1, 2])
704-
for i, size in enumerate([250, 50, 100]):
705-
header.resizeSection(i, size)
644+
header.setStretchLastSection(True)
645+
header.setDefaultAlignment(QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter)
646+
header.setDefaultSectionSize(DEFAULT_SECTION_SIZE)
647+
648+
for i, c in enumerate(DEFAULT_COLUMNS):
649+
header.show_column(i, c.is_default)
650+
if c.is_icon:
651+
header.resizeSection(i, c.header_icon_size_with_border.width())
652+
header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeMode.Fixed)
653+
else:
654+
header.resizeSection(i, c.size if c.size is not None else DEFAULT_SECTION_SIZE)
655+
header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeMode.Interactive)
656+
706657
self.sortByColumn(-1, QtCore.Qt.SortOrder.AscendingOrder)
707658

708659
def _init_header(self):
@@ -897,7 +848,7 @@ def remove_file_cluster(self, cluster):
897848
self.set_clusters_text()
898849

899850
def set_clusters_text(self):
900-
self.clusters.setText(MainPanel.TITLE_COLUMN, "%s (%d)" % (_("Clusters"), len(self.tagger.clusters)))
851+
self.clusters.setText(ITEM_ICON_COLUMN, "%s (%d)" % (_("Clusters"), len(self.tagger.clusters)))
901852

902853
@property
903854
def default_drop_target(self):
@@ -923,12 +874,12 @@ def add_album(self, album):
923874
self.insertTopLevelItem(0, item)
924875
else:
925876
item = AlbumItem(album, True, self)
926-
item.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd)
927-
for i, column in enumerate(MainPanel.columns):
877+
item.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd)
878+
for i, column in enumerate(DEFAULT_COLUMNS):
928879
font = item.font(i)
929880
font.setBold(True)
930881
item.setFont(i, font)
931-
item.setText(i, album.column(column[1]))
882+
item.setText(i, album.column(column.key))
932883
self.add_cluster(album.unmatched_files, item)
933884

934885
def remove_album(self, album):
@@ -945,14 +896,6 @@ def __init__(self, obj, sortable, *args):
945896
obj.item = self
946897
self.sortable = sortable
947898
self._sortkeys = {}
948-
for column in (
949-
MainPanel.LENGTH_COLUMN,
950-
MainPanel.FILESIZE_COLUMN,
951-
MainPanel.TRACKNUMBER_COLUMN,
952-
MainPanel.DISCNUMBER_COLUMN,
953-
):
954-
self.setTextAlignment(column, QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
955-
self.setSizeHint(MainPanel.FINGERPRINT_COLUMN, ICON_SIZE)
956899

957900
def setText(self, column, text):
958901
self._sortkeys[column] = None
@@ -970,30 +913,39 @@ def sortkey(self, column):
970913
if sortkey is not None:
971914
return sortkey
972915

973-
if column == MainPanel.LENGTH_COLUMN:
974-
sortkey = self.obj.metadata.length or 0
975-
elif column == MainPanel.FILESIZE_COLUMN:
976-
try:
977-
sortkey = int(self.obj.metadata['~filesize'] or self.obj.orig_metadata['~filesize'])
978-
except ValueError:
979-
sortkey = 0
980-
elif column in MainPanel.NAT_SORT_COLUMNS:
916+
this_column = DEFAULT_COLUMNS[column]
917+
918+
if this_column.sort_type == ColumnSortType.SORTKEY:
919+
sortkey = this_column.sortkey(self.obj)
920+
elif this_column.sort_type == ColumnSortType.NAT:
981921
sortkey = natsort.natkey(self.text(column))
982922
else:
983923
sortkey = strxfrm(self.text(column))
984924
self._sortkeys[column] = sortkey
985925
return sortkey
986926

927+
def update_colums_text(self, color=None, bgcolor=None):
928+
for i, column in enumerate(DEFAULT_COLUMNS):
929+
if color is not None:
930+
self.setForeground(i, color)
931+
if bgcolor is not None:
932+
self.setBackground(i, bgcolor)
933+
if column.is_icon:
934+
self.setSizeHint(i, column.header_icon_size_with_border)
935+
else:
936+
if column.align == ColumnAlign.RIGHT:
937+
self.setTextAlignment(i, QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
938+
self.setText(i, self.obj.column(column.key))
939+
987940

988941
class ClusterItem(TreeItem):
989942

990943
def __init__(self, *args):
991944
super().__init__(*args)
992-
self.setIcon(MainPanel.TITLE_COLUMN, ClusterItem.icon_dir)
945+
self.setIcon(ITEM_ICON_COLUMN, ClusterItem.icon_dir)
993946

994947
def update(self, update_selection=True):
995-
for i, column in enumerate(MainPanel.columns):
996-
self.setText(i, self.obj.column(column[1]))
948+
self.update_colums_text()
997949
album = self.obj.related_album
998950
if self.obj.special and album and album.loaded:
999951
album.item.update(update_tracks=False)
@@ -1064,24 +1016,23 @@ def update(self, update_tracks=True, update_selection=True):
10641016
for item in items: # Update after insertChildren so that setExpanded works
10651017
item.update(update_album=False)
10661018
if album.errors:
1067-
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_error)
1068-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Processing error(s): See the Errors tab in the Album Info dialog"))
1019+
self.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_error)
1020+
self.setToolTip(ITEM_ICON_COLUMN, _("Processing error(s): See the Errors tab in the Album Info dialog"))
10691021
elif album.is_complete():
10701022
if album.is_modified():
1071-
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_saved_modified)
1072-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album modified and complete"))
1023+
self.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd_saved_modified)
1024+
self.setToolTip(ITEM_ICON_COLUMN, _("Album modified and complete"))
10731025
else:
1074-
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_saved)
1075-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album unchanged and complete"))
1026+
self.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd_saved)
1027+
self.setToolTip(ITEM_ICON_COLUMN, _("Album unchanged and complete"))
10761028
else:
10771029
if album.is_modified():
1078-
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_modified)
1079-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album modified"))
1030+
self.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd_modified)
1031+
self.setToolTip(ITEM_ICON_COLUMN, _("Album modified"))
10801032
else:
1081-
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd)
1082-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album unchanged"))
1083-
for i, column in enumerate(MainPanel.columns):
1084-
self.setText(i, album.column(column[1]))
1033+
self.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd)
1034+
self.setToolTip(ITEM_ICON_COLUMN, _("Album unchanged"))
1035+
self.update_colums_text()
10851036
if selection_changed and update_selection:
10861037
TreeItem.window.update_selection(new_selection=False)
10871038
# Workaround for PICARD-1446: Expand/collapse indicator for the release
@@ -1110,6 +1061,7 @@ class TrackItem(TreeItem):
11101061
def update(self, update_album=True, update_files=True, update_selection=True):
11111062
track = self.obj
11121063
num_linked_files = track.num_linked_files
1064+
fingerprint_column = DEFAULT_COLUMNS.pos('~fingerprint')
11131065
if num_linked_files == 1:
11141066
file = track.files[0]
11151067
file.item = self
@@ -1119,16 +1071,16 @@ def update(self, update_album=True, update_files=True, update_selection=True):
11191071
self.takeChildren()
11201072
self.setExpanded(False)
11211073
fingerprint_icon, fingerprint_tooltip = FileItem.decide_fingerprint_icon_info(file)
1122-
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, fingerprint_tooltip)
1123-
self.setIcon(MainPanel.FINGERPRINT_COLUMN, fingerprint_icon)
1074+
self.setToolTip(fingerprint_column, fingerprint_tooltip)
1075+
self.setIcon(fingerprint_column, fingerprint_icon)
11241076
else:
11251077
if num_linked_files == 0:
11261078
icon_tooltip = _("There are no files matched to this track")
11271079
else:
11281080
icon_tooltip = ngettext('%i matched file', '%i matched files',
11291081
num_linked_files) % num_linked_files
1130-
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, "")
1131-
self.setIcon(MainPanel.FINGERPRINT_COLUMN, QtGui.QIcon())
1082+
self.setToolTip(fingerprint_column, "")
1083+
self.setIcon(fingerprint_column, QtGui.QIcon())
11321084
if track.ignored_for_completeness():
11331085
color = TreeItem.text_color_secondary
11341086
else:
@@ -1162,15 +1114,12 @@ def update(self, update_album=True, update_files=True, update_selection=True):
11621114
self.addChildren(items)
11631115
self.setExpanded(True)
11641116
if track.errors:
1165-
self.setIcon(MainPanel.TITLE_COLUMN, TrackItem.icon_error)
1166-
self.setToolTip(MainPanel.TITLE_COLUMN, _("Processing error(s): See the Errors tab in the Track Info dialog"))
1117+
self.setIcon(ITEM_ICON_COLUMN, TrackItem.icon_error)
1118+
self.setToolTip(ITEM_ICON_COLUMN, _("Processing error(s): See the Errors tab in the Track Info dialog"))
11671119
else:
1168-
self.setIcon(MainPanel.TITLE_COLUMN, icon)
1169-
self.setToolTip(MainPanel.TITLE_COLUMN, icon_tooltip)
1170-
for i, column in enumerate(MainPanel.columns):
1171-
self.setText(i, track.column(column[1]))
1172-
self.setForeground(i, color)
1173-
self.setBackground(i, bgcolor)
1120+
self.setIcon(ITEM_ICON_COLUMN, icon)
1121+
self.setToolTip(ITEM_ICON_COLUMN, icon_tooltip)
1122+
self.update_colums_text(color=color, bgcolor=bgcolor)
11741123
if update_selection and self.isSelected():
11751124
TreeItem.window.update_selection(new_selection=False)
11761125
if update_album:
@@ -1182,17 +1131,17 @@ class FileItem(TreeItem):
11821131
def update(self, update_track=True, update_selection=True):
11831132
file = self.obj
11841133
icon, icon_tooltip = FileItem.decide_file_icon_info(file)
1185-
self.setIcon(MainPanel.TITLE_COLUMN, icon)
1186-
self.setToolTip(MainPanel.TITLE_COLUMN, icon_tooltip)
1134+
self.setIcon(ITEM_ICON_COLUMN, icon)
1135+
self.setToolTip(ITEM_ICON_COLUMN, icon_tooltip)
1136+
1137+
fingerprint_column = DEFAULT_COLUMNS.pos('~fingerprint')
11871138
fingerprint_icon, fingerprint_tooltip = FileItem.decide_fingerprint_icon_info(file)
1188-
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, fingerprint_tooltip)
1189-
self.setIcon(MainPanel.FINGERPRINT_COLUMN, fingerprint_icon)
1139+
self.setToolTip(fingerprint_column, fingerprint_tooltip)
1140+
self.setIcon(fingerprint_column, fingerprint_icon)
1141+
11901142
color = FileItem.file_colors[file.state]
11911143
bgcolor = get_match_color(file.similarity, TreeItem.base_color)
1192-
for i, column in enumerate(MainPanel.columns):
1193-
self.setText(i, file.column(column[1]))
1194-
self.setForeground(i, color)
1195-
self.setBackground(i, bgcolor)
1144+
self.update_colums_text(color=color, bgcolor=bgcolor)
11961145
if update_selection and self.isSelected():
11971146
TreeItem.window.update_selection(new_selection=False)
11981147
parent = self.parent()

0 commit comments

Comments
 (0)