Skip to content

Commit 8562aa9

Browse files
authored
Merge pull request #2615 from zas/ui_columns
UI Columns can be used outside itemviews
2 parents 6352fd4 + 7c90d7a commit 8562aa9

File tree

4 files changed

+231
-153
lines changed

4 files changed

+231
-153
lines changed

picard/ui/cdlookup.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@
3434

3535
from picard import log
3636
from picard.config import get_config
37-
from picard.i18n import gettext as _
37+
from picard.i18n import (
38+
N_,
39+
gettext as _,
40+
)
3841
from picard.mbjson import (
3942
artist_credit_from_node,
4043
label_info_from_node,
@@ -46,9 +49,27 @@
4649
)
4750

4851
from picard.ui import PicardDialog
52+
from picard.ui.columns import (
53+
Column,
54+
Columns,
55+
)
4956
from picard.ui.forms.ui_cdlookup import Ui_CDLookupDialog
5057

5158

59+
_COLUMNS = Columns((
60+
Column(N_("Album"), 'album'),
61+
Column(N_("Artist"), 'artist'),
62+
Column(N_("Date"), 'dates'),
63+
Column(N_("Country"), 'countries'),
64+
Column(N_("Labels"), 'labels'),
65+
Column(N_("Catalog #s"), 'catnos'),
66+
Column(N_("Barcode"), 'barcode'),
67+
Column(N_("Disambiguation"), 'disambiguation'),
68+
))
69+
70+
_DATA_COLUMN = _COLUMNS.pos('album')
71+
72+
5273
class CDLookupDialog(PicardDialog):
5374

5475
dialog_header_state = 'cdlookupdialog_header_state'
@@ -63,9 +84,8 @@ def __init__(self, releases, disc, parent=None):
6384
release_list.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
6485
release_list.setSortingEnabled(True)
6586
release_list.setAlternatingRowColors(True)
66-
release_list.setHeaderLabels([_("Album"), _("Artist"), _("Date"), _("Country"),
67-
_("Labels"), _("Catalog #s"), _("Barcode"),
68-
_("Disambiguation")])
87+
labels = [_(c.title) for c in _COLUMNS]
88+
release_list.setHeaderLabels(labels)
6989
self.ui.submit_button.setIcon(QtGui.QIcon(":/images/cdrom.png"))
7090
if self.releases:
7191
def myjoin(values):
@@ -80,22 +100,26 @@ def myjoin(values):
80100
item = QtWidgets.QTreeWidgetItem(release_list)
81101
if disc.mcn and compare_barcodes(barcode, disc.mcn):
82102
selected = item
83-
item.setText(0, release['title'])
84-
item.setText(1, artist_credit_from_node(release['artist-credit'])[0])
85-
item.setText(2, myjoin(dates))
86-
item.setText(3, myjoin(countries))
87-
item.setText(4, myjoin(labels))
88-
item.setText(5, myjoin(catalog_numbers))
89-
item.setText(6, barcode)
90-
item.setText(7, release.get('disambiguation', ''))
91-
item.setData(0, QtCore.Qt.ItemDataRole.UserRole, release['id'])
103+
values = {
104+
'album': release['title'],
105+
'artist': artist_credit_from_node(release['artist-credit'])[0],
106+
'dates': myjoin(dates),
107+
'countries': myjoin(countries),
108+
'labels': myjoin(labels),
109+
'catnos': myjoin(catalog_numbers),
110+
'barcode': barcode,
111+
'disambiguation': release.get('disambiguation', ''),
112+
}
113+
for i, column in enumerate(_COLUMNS):
114+
item.setText(i, values.get(column.key, ""))
115+
item.setData(_DATA_COLUMN, QtCore.Qt.ItemDataRole.UserRole, release['id'])
92116
release_list.setCurrentItem(selected or release_list.topLevelItem(0))
93117
self.ui.ok_button.setEnabled(True)
94118
for i in range(release_list.columnCount() - 1):
95119
release_list.resizeColumnToContents(i)
96120
# Sort by descending date, then ascending country
97-
release_list.sortByColumn(3, QtCore.Qt.SortOrder.AscendingOrder)
98-
release_list.sortByColumn(2, QtCore.Qt.SortOrder.DescendingOrder)
121+
release_list.sortByColumn(_COLUMNS.pos('countries'), QtCore.Qt.SortOrder.AscendingOrder)
122+
release_list.sortByColumn(_COLUMNS.pos('dates'), QtCore.Qt.SortOrder.DescendingOrder)
99123
else:
100124
self.ui.results_view.setCurrentIndex(1)
101125
if self.disc.submission_url:
@@ -110,7 +134,7 @@ def myjoin(values):
110134
def accept(self):
111135
release_list = self.ui.release_list
112136
for index in release_list.selectionModel().selectedRows():
113-
release_id = release_list.itemFromIndex(index).data(0, QtCore.Qt.ItemDataRole.UserRole)
137+
release_id = release_list.itemFromIndex(index).data(_DATA_COLUMN, QtCore.Qt.ItemDataRole.UserRole)
114138
self.tagger.load_album(release_id, discid=self.disc.id)
115139
super().accept()
116140

picard/ui/columns.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Picard, the next-generation MusicBrainz tagger
4+
#
5+
# Copyright (C) 2006-2008, 2011-2012 Lukáš Lalinský
6+
# Copyright (C) 2007 Robert Kaye
7+
# Copyright (C) 2008 Gary van der Merwe
8+
# Copyright (C) 2008 Hendrik van Antwerpen
9+
# Copyright (C) 2008-2011, 2014-2015, 2018-2024 Philipp Wolfer
10+
# Copyright (C) 2009 Carlin Mangar
11+
# Copyright (C) 2009 Nikolai Prokoschenko
12+
# Copyright (C) 2011 Tim Blechmann
13+
# Copyright (C) 2011-2012 Chad Wilson
14+
# Copyright (C) 2011-2013 Michael Wiencek
15+
# Copyright (C) 2012 Your Name
16+
# Copyright (C) 2012-2013 Wieland Hoffmann
17+
# Copyright (C) 2013-2014, 2016, 2018-2025 Laurent Monin
18+
# Copyright (C) 2013-2014, 2017, 2020 Sophist-UK
19+
# Copyright (C) 2016 Rahul Raturi
20+
# Copyright (C) 2016 Simon Legner
21+
# Copyright (C) 2016 Suhas
22+
# Copyright (C) 2016-2017 Sambhav Kothari
23+
# Copyright (C) 2018 Vishal Choudhary
24+
# Copyright (C) 2020-2021 Gabriel Ferreira
25+
# Copyright (C) 2021 Bob Swift
26+
# Copyright (C) 2021 Louis Sautier
27+
# Copyright (C) 2021 Petit Minion
28+
# Copyright (C) 2023 certuna
29+
# Copyright (C) 2024 Giorgio Fontanive
30+
# Copyright (C) 2024 Suryansh Shakya
31+
#
32+
# This program is free software; you can redistribute it and/or
33+
# modify it under the terms of the GNU General Public License
34+
# as published by the Free Software Foundation; either version 2
35+
# of the License, or (at your option) any later version.
36+
#
37+
# This program is distributed in the hope that it will be useful,
38+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
39+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40+
# GNU General Public License for more details.
41+
#
42+
# You should have received a copy of the GNU General Public License
43+
# along with this program; if not, write to the Free Software
44+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
45+
46+
47+
from collections.abc import MutableSequence
48+
from enum import IntEnum
49+
50+
from PyQt6 import QtCore
51+
52+
53+
class ColumnAlign(IntEnum):
54+
LEFT = 0
55+
RIGHT = 1
56+
57+
def __repr__(self):
58+
cls_name = self.__class__.__name__
59+
return f'{cls_name}.{self.name}'
60+
61+
62+
class ColumnSortType(IntEnum):
63+
TEXT = 0
64+
NAT = 1
65+
SORTKEY = 2 # special case, use sortkey property
66+
67+
def __repr__(self):
68+
cls_name = self.__class__.__name__
69+
return f'{cls_name}.{self.name}'
70+
71+
72+
class Column:
73+
is_icon = False
74+
is_default = False
75+
76+
def __init__(self, title, key, size=None, align=ColumnAlign.LEFT, sort_type=ColumnSortType.TEXT, sortkey=None):
77+
self.title = title
78+
self.key = key
79+
self.size = size
80+
self.align = align
81+
self.sort_type = sort_type
82+
if self.sort_type == ColumnSortType.SORTKEY:
83+
if not callable(sortkey):
84+
raise TypeError("sortkey should be a callable")
85+
self.sortkey = sortkey
86+
else:
87+
self.sortkey = None
88+
89+
def __repr__(self):
90+
def parms():
91+
yield from (repr(getattr(self, a)) for a in ('title', 'key'))
92+
yield from (a + '=' + repr(getattr(self, a)) for a in ('size', 'align', 'sort_type', 'sortkey'))
93+
94+
return 'Column(' + ', '.join(parms()) + ')'
95+
96+
def __str__(self):
97+
return repr(self)
98+
99+
100+
class DefaultColumn(Column):
101+
is_default = True
102+
103+
104+
class IconColumn(Column):
105+
is_icon = True
106+
_header_icon = None
107+
header_icon_func = None
108+
header_icon_size = QtCore.QSize(0, 0)
109+
header_icon_border = 0
110+
header_icon_size_with_border = QtCore.QSize(0, 0)
111+
112+
@property
113+
def header_icon(self):
114+
# icon cannot be set before QApplication is created
115+
# so create it during runtime and cache it
116+
# Avoid error: QPixmap: Must construct a QGuiApplication before a QPixmap
117+
if self._header_icon is None:
118+
self._header_icon = self.header_icon_func()
119+
return self._header_icon
120+
121+
def set_header_icon_size(self, width, height, border):
122+
self.header_icon_size = QtCore.QSize(width, height)
123+
self.header_icon_border = border
124+
self.header_icon_size_with_border = QtCore.QSize(width + 2*border, height + 2*border)
125+
126+
def paint_icon(self, painter, rect):
127+
icon = self.header_icon
128+
if not icon:
129+
return
130+
h = self.header_icon_size.height()
131+
w = self.header_icon_size.width()
132+
border = self.header_icon_border
133+
padding_v = (rect.height() - h) // 2
134+
target_rect = QtCore.QRect(
135+
rect.x() + border, rect.y() + padding_v,
136+
w, h
137+
)
138+
painter.drawPixmap(target_rect, icon.pixmap(self.header_icon_size))
139+
140+
141+
class Columns(MutableSequence):
142+
def __init__(self, iterable=None):
143+
self._list = list()
144+
self._index = dict()
145+
self._index_dirty = True
146+
if iterable is not None:
147+
for e in iterable:
148+
self.append(e)
149+
150+
def __len__(self):
151+
return len(self._list)
152+
153+
def __delitem__(self, index):
154+
self._index_dirty = True
155+
self._list.__delitem__(index)
156+
157+
def insert(self, index, column):
158+
if not isinstance(column, Column):
159+
raise TypeError("Not an instance of Column")
160+
self._list.insert(index, column)
161+
self._index_dirty = True
162+
163+
def __setitem__(self, index, column):
164+
if not isinstance(column, Column):
165+
raise TypeError("Not an instance of Column")
166+
self._list.__setitem__(index, column)
167+
self._index_dirty = True
168+
169+
def __getitem__(self, index):
170+
return self._list.__getitem__(index)
171+
172+
def pos(self, key):
173+
if self._index_dirty:
174+
self._index = {c.key: i for i, c in enumerate(self._list)}
175+
self._index_dirty = False
176+
return self._index[key]
177+
178+
def __repr__(self):
179+
return "Columns([\n" + "".join(" %r,\n" % e for e in self._list) + '])'
180+
181+
def __str__(self):
182+
return repr(self)

0 commit comments

Comments
 (0)