Skip to content

Commit cf193ff

Browse files
authored
Merge pull request #2614 from phw/PICARD-3042-discid-format
PICARD-3042: Include format in disc ID lookup result table
2 parents e8a5a03 + 13a5ff5 commit cf193ff

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

picard/ui/cdlookup.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Picard, the next-generation MusicBrainz tagger
44
#
55
# Copyright (C) 2006-2007 Lukáš Lalinský
6-
# Copyright (C) 2009, 2018-2023 Philipp Wolfer
6+
# Copyright (C) 2009, 2018-2023, 2025 Philipp Wolfer
77
# Copyright (C) 2011-2013 Michael Wiencek
88
# Copyright (C) 2012 Chad Wilson
99
# Copyright (C) 2013-2014, 2018, 2020-2021, 2023-2024 Laurent Monin
@@ -25,6 +25,7 @@
2525
# along with this program; if not, write to the Free Software
2626
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2727

28+
from html import escape
2829

2930
from PyQt6 import (
3031
QtCore,
@@ -41,6 +42,7 @@
4142
from picard.mbjson import (
4243
artist_credit_from_node,
4344
label_info_from_node,
45+
media_formats_from_node,
4446
release_dates_and_countries_from_node,
4547
)
4648
from picard.util import (
@@ -53,6 +55,7 @@
5355
Column,
5456
Columns,
5557
)
58+
from picard.ui.formattedtextdelegate import FormattedTextDelegate
5659
from picard.ui.forms.ui_cdlookup import Ui_CDLookupDialog
5760

5861

@@ -64,10 +67,12 @@
6467
Column(N_("Labels"), 'labels'),
6568
Column(N_("Catalog #s"), 'catnos'),
6669
Column(N_("Barcode"), 'barcode'),
70+
Column(N_("Format"), 'format'),
6771
Column(N_("Disambiguation"), 'disambiguation'),
6872
))
6973

7074
_DATA_COLUMN = _COLUMNS.pos('album')
75+
_FORMAT_COLUMN = _COLUMNS.pos('format')
7176

7277

7378
class CDLookupDialog(PicardDialog):
@@ -86,6 +91,8 @@ def __init__(self, releases, disc, parent=None):
8691
release_list.setAlternatingRowColors(True)
8792
labels = [_(c.title) for c in _COLUMNS]
8893
release_list.setHeaderLabels(labels)
94+
delegate = FormattedTextDelegate(release_list)
95+
release_list.setItemDelegateForColumn(_FORMAT_COLUMN, delegate)
8996
self.ui.submit_button.setIcon(QtGui.QIcon(":/images/cdrom.png"))
9097
if self.releases:
9198
def myjoin(values):
@@ -108,6 +115,7 @@ def myjoin(values):
108115
'labels': myjoin(labels),
109116
'catnos': myjoin(catalog_numbers),
110117
'barcode': barcode,
118+
'format': self._get_format(release),
111119
'disambiguation': release.get('disambiguation', ''),
112120
}
113121
for i, column in enumerate(_COLUMNS):
@@ -163,3 +171,16 @@ def save_header_state(self):
163171
config = get_config()
164172
config.persist[self.dialog_header_state] = state
165173
log.debug("save_state: %s", self.dialog_header_state)
174+
175+
def _get_format(self, release):
176+
format = escape(media_formats_from_node(release.get('media', [])))
177+
selected_medium = self._get_selected_medium(release)
178+
if selected_medium:
179+
selected_format = escape(selected_medium.get('format', format))
180+
format = format.replace(selected_format, f"<b>{selected_format}</b>")
181+
return format
182+
183+
def _get_selected_medium(self, release):
184+
for medium in release.get('media', []):
185+
if any(disc.get('id') == self.disc.id for disc in medium.get('discs', [])):
186+
return medium

picard/ui/formattedtextdelegate.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Picard, the next-generation MusicBrainz tagger
4+
#
5+
# Copyright (C) 2025 Philipp Wolfer
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License
9+
# as published by the Free Software Foundation; either version 2
10+
# of the License, or (at your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20+
21+
import math
22+
23+
from PyQt6 import (
24+
QtCore,
25+
QtGui,
26+
QtWidgets,
27+
)
28+
29+
30+
class FormattedTextDelegate(QtWidgets.QStyledItemDelegate):
31+
def __init__(self, parent=None, markup_format='html'):
32+
"""
33+
A QStyledItemDelegate that renders formatted text in a QTreeWidget.
34+
35+
:param markup_format: Specify the format of the markup, either "html" or "markdown".
36+
"""
37+
super().__init__(parent)
38+
self.markup_format = markup_format
39+
40+
def paint(self, painter, option, index):
41+
# Initialize the style option
42+
self.initStyleOption(option, index)
43+
44+
# Draw the background
45+
if option.state & QtWidgets.QStyle.StateFlag.State_Selected:
46+
fill_brush = option.palette.highlight()
47+
text_color_role = QtGui.QPalette.ColorRole.HighlightedText
48+
else:
49+
fill_brush = option.palette.base()
50+
text_color_role = QtGui.QPalette.ColorRole.Text
51+
painter.fillRect(option.rect, fill_brush)
52+
53+
# Get the formatted text from the model
54+
text = index.data(QtCore.Qt.ItemDataRole.DisplayRole)
55+
if not text:
56+
return
57+
58+
# Create a QTextDocument to render the formatted text
59+
doc = self._create_doc(text, option)
60+
61+
# Set the text color based on the current palette
62+
text_color = option.palette.color(text_color_role)
63+
doc.setDefaultStyleSheet(f"body {{ color: {text_color.name()}; }}")
64+
65+
# Draw the text
66+
painter.save()
67+
painter.translate(option.rect.topLeft())
68+
doc.drawContents(painter)
69+
painter.restore()
70+
71+
def sizeHint(self, option, index):
72+
# Provide a size hint based on the QTextDocument's size
73+
text = index.data(QtCore.Qt.ItemDataRole.DisplayRole)
74+
if not text:
75+
return super().sizeHint(option, index)
76+
77+
# Create a QTextDocument to render the formatted text
78+
doc = self._create_doc(text, option)
79+
80+
return QtCore.QSize(
81+
math.ceil(doc.idealWidth()),
82+
math.ceil(doc.size().height()))
83+
84+
def _create_doc(self, text, option):
85+
doc = QtGui.QTextDocument()
86+
if self.markup_format == 'html':
87+
doc.setHtml(text)
88+
elif self.markup_format == 'markdown':
89+
doc.setMarkdown(text)
90+
else:
91+
doc.setPlainText(text)
92+
93+
doc.setDefaultFont(option.font)
94+
doc.setTextWidth(option.rect.width())
95+
text_option = doc.defaultTextOption()
96+
text_option.setWrapMode(QtGui.QTextOption.WrapMode.NoWrap)
97+
doc.setDefaultTextOption(text_option)
98+
return doc

0 commit comments

Comments
 (0)