Merge pull request #2504 from zas/parents2

More Q* subclasses improvements
This commit is contained in:
Laurent Monin
2024-06-02 18:35:40 +02:00
committed by GitHub
21 changed files with 128 additions and 139 deletions

View File

@@ -47,10 +47,7 @@ from collections import (
from enum import IntEnum
import traceback
from PyQt6 import (
QtCore,
QtNetwork,
)
from PyQt6 import QtNetwork
from picard import log
from picard.cluster import Cluster
@@ -129,7 +126,7 @@ class Album(DataObject, MetadataItem):
def __init__(self, album_id, discid=None):
DataObject.__init__(self, album_id)
self.tagger = QtCore.QCoreApplication.instance()
MetadataItem.__init__(self)
self.tracks = []
self.loaded = False
self.load_task = None

View File

@@ -102,7 +102,6 @@ class Cluster(FileList):
def __init__(self, name, artist="", special=False, related_album=None, hide_if_empty=False):
super().__init__()
self.tagger = QtCore.QCoreApplication.instance()
self.item = None
self.metadata['album'] = name
self.metadata['albumartist'] = artist

View File

@@ -55,17 +55,13 @@ from picard.coverart.providers.provider import (
CoverArtProvider,
ProviderOptions,
)
from picard.coverart.utils import (
CAA_TYPES,
translate_caa_type,
)
from picard.i18n import (
N_,
gettext as _,
)
from picard.webservice import ratecontrol
from picard.ui.caa_types_selector import display_caa_types_selector
from picard.ui.caa_types_selector import CAATypesSelectorDialog
from picard.ui.forms.ui_provider_options_caa import Ui_CaaOptions
@@ -167,14 +163,10 @@ class ProviderOptionsCaa(ProviderOptions):
self.ui.select_caa_types.setEnabled(enabled)
def select_caa_types(self):
known_types = {t['name']: translate_caa_type(t['name']) for t in CAA_TYPES}
(types, types_to_omit, ok) = display_caa_types_selector(
parent=self,
(types, types_to_omit, ok) = CAATypesSelectorDialog.display(
types_include=self.caa_image_types,
types_exclude=self.caa_image_types_to_omit,
default_include=DEFAULT_CAA_IMAGE_TYPE_INCLUDE,
default_exclude=DEFAULT_CAA_IMAGE_TYPE_EXCLUDE,
known_types=known_types,
parent=self,
)
if ok:
self.caa_image_types = types
@@ -282,7 +274,7 @@ class CoverArtProviderCaa(CoverArtProvider):
self.error("CAA JSON error: %s" % (http.errorString()))
else:
if self.restrict_types:
log.debug("CAA types: included: %s, excluded: %s", self.included_types, self.excluded_types)
log.debug("CAA types: included: %s, excluded: %s", list(self.included_types), list(self.excluded_types))
try:
config = get_config()
for image in data['images']:

View File

@@ -28,11 +28,12 @@
from collections import Counter
from PyQt6 import QtCore
from picard.config import get_config
from picard.util import LockableObject
class DataObject(LockableObject):
class DataObject(QtCore.QObject):
def __init__(self, obj_id):
super().__init__()

View File

@@ -54,8 +54,8 @@ class BaseAction(QtGui.QAction):
NAME = "Unknown"
MENU = []
def __init__(self):
super().__init__(self.NAME, None)
def __init__(self, parent=None):
super().__init__(self.NAME, parent=parent)
self.tagger = QtCore.QCoreApplication.instance()
self.triggered.connect(self.__callback)

View File

@@ -163,7 +163,6 @@ class File(QtCore.QObject, MetadataItem):
def __init__(self, filename):
super().__init__()
self.tagger = QtCore.QCoreApplication.instance()
self.filename = filename
self.base_filename = os.path.basename(filename)
self._state = File.UNDEFINED

View File

@@ -31,6 +31,7 @@ from picard import log
from picard.i18n import ngettext
from picard.metadata import Metadata
from picard.util import IgnoreUpdatesContext
from picard.util.imagelist import ImageList
class Item:
@@ -158,6 +159,23 @@ class Item:
number_of_images) % number_of_images
class ImageListState:
def __init__(self):
self.images = {}
self.has_common_images = True
self.first_obj = True
def process_images(self, src_obj_metadata):
src_dict = src_obj_metadata.images.hash_dict()
prev_len = len(self.images)
self.images.update(src_dict)
if len(self.images) != prev_len:
if not self.first_obj:
self.has_common_images = False
if self.first_obj:
self.first_obj = False
class MetadataItem(Item):
metadata_images_changed = QtCore.pyqtSignal()
@@ -169,6 +187,19 @@ class MetadataItem(Item):
self.iter_children_items_metadata_ignore_attrs = {}
self.suspend_metadata_images_update = IgnoreUpdatesContext()
@property
def tagger(self):
return QtCore.QCoreApplication.instance()
@tagger.setter
def tagger(self, value):
# We used to set tagger property in subclasses, but that's not needed anymore
assert value == QtCore.QCoreApplication.instance()
import inspect
stack = inspect.stack()
f = stack[1]
log.warning("MetadataItem.tagger property set at %s:%d in %s", f.filename, f.lineno, f.function)
def update_metadata_images(self):
if not self.suspend_metadata_images_update and self.can_show_coverart:
if self.update_metadata_images_from_children():
@@ -241,24 +272,6 @@ class MetadataItem(Item):
Returns:
bool: True, if images where changed, False otherwise
"""
from picard.util.imagelist import ImageList
class ImageListState:
def __init__(self):
self.images = {}
self.has_common_images = True
self.first_obj = True
def process_images(self, src_obj_metadata):
src_dict = src_obj_metadata.images.hash_dict()
prev_len = len(self.images)
self.images.update(src_dict)
if len(self.images) != prev_len:
if not self.first_obj:
self.has_common_images = False
if self.first_obj:
self.first_obj = False
changed = False
for metadata_attr in self.update_children_metadata_attrs:

View File

@@ -381,7 +381,7 @@ class Tagger(QtWidgets.QApplication):
# Load release version information
if self.autoupdate_enabled:
self.updatecheckmanager = UpdateCheckManager(parent=self.window)
self.updatecheckmanager = UpdateCheckManager(self)
@property
def is_wayland(self):

View File

@@ -129,7 +129,6 @@ class Track(DataObject, FileListItem):
def __init__(self, track_id, album=None):
DataObject.__init__(self, track_id)
FileListItem.__init__(self)
self.tagger = QtCore.QCoreApplication.instance()
self.album = album
self.scripted_metadata = Metadata()
self._track_artists = []

View File

@@ -182,8 +182,8 @@ class PicardDialog(QtWidgets.QDialog, PreserveGeometry):
ready_for_display = QtCore.pyqtSignal()
def __init__(self, parent=None):
self.tagger = QtCore.QCoreApplication.instance()
super().__init__(parent=parent, f=self.flags)
self.tagger = QtCore.QCoreApplication.instance()
self.__shown = False
self.ready_for_display.connect(self.restore_geometry)
@@ -212,27 +212,22 @@ class PicardDialog(QtWidgets.QDialog, PreserveGeometry):
# With py3, QObjects are no longer hashable unless they have
# an explicit __hash__ implemented.
# See: http://python.6.x6.nabble.com/QTreeWidgetItem-is-not-hashable-in-Py3-td5212216.html
class HashableTreeWidgetItem(QtWidgets.QTreeWidgetItem):
class HashableItem:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = uuid.uuid4()
self.__id = uuid.uuid4()
self.__hash = hash(self.__id)
def __eq__(self, other):
return self.id == other.id
return self.__id == other.__id
def __hash__(self):
return hash(str(self.id))
return self.__hash
class HashableListWidgetItem(QtWidgets.QListWidgetItem):
class HashableTreeWidgetItem(HashableItem, QtWidgets.QTreeWidgetItem):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.id = uuid.uuid4()
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return hash(str(self.id))
class HashableListWidgetItem(HashableItem, QtWidgets.QListWidgetItem):
pass

View File

@@ -37,6 +37,14 @@ from PyQt6 import (
QtWidgets,
)
from picard.const.defaults import (
DEFAULT_CAA_IMAGE_TYPE_EXCLUDE,
DEFAULT_CAA_IMAGE_TYPE_INCLUDE,
)
from picard.coverart.utils import (
CAA_TYPES,
translate_caa_type,
)
from picard.i18n import (
N_,
gettext as _,
@@ -153,28 +161,25 @@ class CAATypesSelectorDialog(PicardDialog):
"""Display dialog box to select the CAA image types to include and exclude from download and use.
Keyword Arguments:
parent {[type]} -- Parent of the QDialog object being created (default: {None})
types_include {[string]} -- List of CAA image types to include (default: {None})
types_exclude {[string]} -- List of CAA image types to exclude (default: {None})
default_include {[string]} -- List of CAA image types to include by default (default: {None})
default_exclude {[string]} -- List of CAA image types to exclude by default (default: {None})
known_types {{string: string}} -- Dict. of all known CAA image types, unique name as key, translated title as value (default: {None})
parent {[type]} -- Parent of the QDialog object being created (default: {None})
"""
help_url = 'doc_cover_art_types'
def __init__(
self, parent=None, types_include=None, types_exclude=None,
default_include=None, default_exclude=None, known_types=None
self,
types_include=None,
types_exclude=None,
parent=None,
):
super().__init__(parent=parent)
if types_include is None:
types_include = []
if types_exclude is None:
types_exclude = []
self._default_include = default_include or []
self._default_exclude = default_exclude or []
self._known_types = known_types or {}
types_include = set(types_include or ())
types_exclude = set(types_exclude or ())
self._default_include = DEFAULT_CAA_IMAGE_TYPE_INCLUDE
self._default_exclude = DEFAULT_CAA_IMAGE_TYPE_EXCLUDE
self._known_types = {t['name']: translate_caa_type(t['name']) for t in CAA_TYPES}
self.setWindowTitle(_("Cover art types"))
self.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
@@ -301,8 +306,8 @@ class CAATypesSelectorDialog(PicardDialog):
'excludes' lists to determine the appropriate list for each type.
Arguments:
includes -- list of standard image types to place in the "Include" listbox
excludes -- list of standard image types to place in the "Exclude" listbox
includes -- set of standard image types to place in the "Include" listbox
excludes -- set of standard image types to place in the "Exclude" listbox
"""
self.list_include.clear()
self.list_exclude.clear()
@@ -319,11 +324,11 @@ class CAATypesSelectorDialog(PicardDialog):
@property
def included(self):
return list(self.list_include.all_items_data()) or ['front']
return tuple(self.list_include.all_items_data()) or ('front', )
@property
def excluded(self):
return list(self.list_exclude.all_items_data()) or ['none']
return tuple(self.list_exclude.all_items_data())
def _on_list_clicked(self, lists, index):
for temp_list in lists:
@@ -355,8 +360,17 @@ class CAATypesSelectorDialog(PicardDialog):
self.arrows_exclude.button_remove.setEnabled(has_items_exclude and has_selected_exclude)
self.arrows_exclude.button_remove_all.setEnabled(has_items_exclude)
def display_caa_types_selector(**kwargs):
dialog = CAATypesSelectorDialog(**kwargs)
result = dialog.exec()
return (dialog.included, dialog.excluded, result == QtWidgets.QDialog.DialogCode.Accepted)
@classmethod
def display(
cls,
types_include=None,
types_exclude=None,
parent=None,
):
dialog = cls(
types_include=types_include,
types_exclude=types_exclude,
parent=parent,
)
result = dialog.exec()
return (dialog.included, dialog.excluded, result == QtWidgets.QDialog.DialogCode.Accepted)

View File

@@ -241,10 +241,10 @@ class FileTreeView(BaseTreeView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.unmatched_files = ClusterItem(self.tagger.unclustered_files, False, self)
self.unmatched_files = ClusterItem(self.tagger.unclustered_files, parent=self)
self.unmatched_files.update()
self.unmatched_files.setExpanded(True)
self.clusters = ClusterItem(self.tagger.clusters, False, self)
self.clusters = ClusterItem(self.tagger.clusters, parent=self)
self.set_clusters_text()
self.clusters.setExpanded(True)
self.tagger.cluster_added.connect(self.add_file_cluster)
@@ -282,10 +282,10 @@ class AlbumTreeView(BaseTreeView):
def add_album(self, album):
if isinstance(album, NatAlbum):
item = NatAlbumItem(album, True)
item = NatAlbumItem(album, sortable=True)
self.insertTopLevelItem(0, item)
else:
item = AlbumItem(album, True, self)
item = AlbumItem(album, sortable=True, parent=self)
item.setIcon(ITEM_ICON_COLUMN, AlbumItem.icon_cd)
for i, column in enumerate(DEFAULT_COLUMNS):
font = item.font(i)
@@ -301,13 +301,17 @@ class AlbumTreeView(BaseTreeView):
class TreeItem(QtWidgets.QTreeWidgetItem):
def __init__(self, obj, sortable, *args):
super().__init__(*args)
def __init__(self, obj, sortable=False, parent=None):
super().__init__(parent)
self.obj = obj
if obj is not None:
obj.item = self
self.sortable = sortable
self._sortkeys = {}
self.post_init()
def post_init(self):
pass
def setText(self, column, text):
self._sortkeys[column] = None
@@ -352,8 +356,7 @@ class TreeItem(QtWidgets.QTreeWidgetItem):
class ClusterItem(TreeItem):
def __init__(self, *args):
super().__init__(*args)
def post_init(self):
self.setIcon(ITEM_ICON_COLUMN, ClusterItem.icon_dir)
def update(self, update_selection=True):
@@ -375,7 +378,7 @@ class ClusterItem(TreeItem):
# to be certain about item order in the cluster (addChildren adds in reverse order).
# Benchmarked performance was not noticeably different.
for file in files:
item = FileItem(file, True)
item = FileItem(file, sortable=True)
self.addChild(item)
item.update()
@@ -412,7 +415,7 @@ class AlbumItem(TreeItem):
if newnum > oldnum: # add new items
items = []
for i in range(oldnum, newnum):
item = TrackItem(album.tracks[i], False)
item = TrackItem(album.tracks[i])
item.setHidden(False) # Workaround to make sure the parent state gets updated
items.append(item)
# insertChildren behaves differently if sorting is disabled / enabled, which results
@@ -520,7 +523,7 @@ class TrackItem(TreeItem):
if newnum > oldnum: # add new items
items = []
for i in range(newnum - 1, oldnum - 1, -1):
item = FileItem(track.files[i], False)
item = FileItem(track.files[i])
item.update(update_track=False, update_selection=update_selection)
items.append(item)
self.addChildren(items)

View File

@@ -633,7 +633,7 @@ class BaseTreeView(QtWidgets.QTreeWidget):
if parent_item is None:
parent_item = self.clusters
from picard.ui.itemviews import ClusterItem
cluster_item = ClusterItem(cluster, not cluster.special, parent_item)
cluster_item = ClusterItem(cluster, sortable=not cluster.special, parent=parent_item)
if cluster.hide_if_empty and not cluster.files:
cluster_item.update()
cluster_item.setHidden(True)

View File

@@ -72,8 +72,8 @@ class LogViewDialog(PicardDialog):
class LogViewCommon(LogViewDialog):
def __init__(self, log_tail, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, log_tail, title, parent=None):
super().__init__(title, parent=parent)
self.displaying = False
self.log_tail = log_tail
self._init_doc()

View File

@@ -38,11 +38,11 @@ from picard.i18n import (
gettext as _,
)
from picard.ui.checkbox_list_item import CheckboxListItem
from picard.ui.forms.ui_options_cover import Ui_CoverOptionsPage
from picard.ui.moveable_list_view import MoveableListView
from picard.ui.options import OptionsPage
from picard.ui.util import qlistwidget_items
from picard.ui.widgets.checkbox_list_item import CheckboxListItem
class CoverOptionsPage(OptionsPage):

View File

@@ -64,8 +64,8 @@ class TipSlider(ClickableSlider):
_minimum = 0
_maximum = 100
def __init__(self, *args):
super().__init__(*args)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.style = QtWidgets.QApplication.style()
self.opt = QtWidgets.QStyleOptionSlider()
@@ -117,7 +117,7 @@ class ReleaseTypeScore:
self.label = QtWidgets.QLabel(self.group)
self.label.setText(label)
self.layout.addWidget(self.label, row, column, 1, 1)
self.slider = TipSlider(self.group)
self.slider = TipSlider(parent=self.group)
self.layout.addWidget(self.slider, row, column + 1, 1, 1)
self.reset()

View File

@@ -306,7 +306,7 @@ class PlaybackProgressSlider(QtWidgets.QWidget):
tool_font = QtWidgets.QApplication.font('QToolButton')
self.progress_slider = ClickableSlider(self)
self.progress_slider = ClickableSlider(parent=self)
self.progress_slider.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.progress_slider.setEnabled(False)
self.progress_slider.setMinimumWidth(30)

View File

@@ -162,7 +162,7 @@ class SliderPopover(Popover):
self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
vbox.addWidget(self.label)
self.slider = ClickableSlider(self)
self.slider = ClickableSlider(parent=self)
self.slider.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.slider.setValue(int(value))
self.slider.valueChanged.connect(self.value_changed)

View File

@@ -21,17 +21,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QListWidgetItem
from PyQt6 import (
QtCore,
QtWidgets,
)
class CheckboxListItem(QListWidgetItem):
class CheckboxListItem(QtWidgets.QListWidgetItem):
def __init__(self, text='', checked=False):
super().__init__(text)
self.setFlags(self.flags() | Qt.ItemFlag.ItemIsUserCheckable)
self.setCheckState(Qt.CheckState.Checked if checked else Qt.CheckState.Unchecked)
def __init__(self, text='', checked=False, parent=None):
super().__init__(text, parent=parent)
self.setFlags(self.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable)
self.setCheckState(QtCore.Qt.CheckState.Checked if checked else QtCore.Qt.CheckState.Unchecked)
@property
def checked(self):
return self.checkState() == Qt.CheckState.Checked
return self.checkState() == QtCore.Qt.CheckState.Checked

View File

@@ -126,28 +126,6 @@ class ReadWriteLockContext:
return self._entered > 0
class LockableObject(QtCore.QObject):
"""Read/write lockable object."""
def __init__(self):
super().__init__()
self.__context = ReadWriteLockContext()
def lock_for_read(self):
"""Lock the object for read operations."""
self.__context.lock_for_read()
return self.__context
def lock_for_write(self):
"""Lock the object for write operations."""
self.__context.lock_for_write()
return self.__context
def unlock(self):
"""Unlock the object."""
self.__context.unlock()
def process_events_iter(iterable, interval=0.1):
"""
Creates an iterator over iterable that calls QCoreApplication.processEvents()

View File

@@ -23,7 +23,6 @@
from functools import partial
from PyQt6 import QtCore
from PyQt6.QtWidgets import QMessageBox
from picard import (
@@ -47,12 +46,10 @@ from picard.version import (
)
class UpdateCheckManager(QtCore.QObject):
class UpdateCheckManager:
def __init__(self, parent=None):
super().__init__(parent=parent)
self.tagger = QtCore.QCoreApplication.instance()
self._parent = parent
def __init__(self, tagger):
self.tagger = tagger
self._available_versions = {}
self._show_always = False
self._update_level = 0
@@ -107,7 +104,7 @@ class UpdateCheckManager(QtCore.QObject):
log.error(_("Error loading Picard releases list: {error_message}").format(error_message=reply.errorString(),))
if self._show_always:
QMessageBox.information(
self._parent,
self.tagger.window,
_("Picard Update"),
_("Unable to retrieve the latest version information from the website.\n({url})").format(
url=PLUGINS_API['urls']['releases'],
@@ -141,7 +138,7 @@ class UpdateCheckManager(QtCore.QObject):
high_version = test_version
if key:
if QMessageBox.information(
self._parent,
self.tagger.window,
_("Picard Update"),
_("A new version of Picard is available.\n\n"
"This version: {picard_old_version}\n"
@@ -161,7 +158,7 @@ class UpdateCheckManager(QtCore.QObject):
else:
update_level = N_("unknown")
QMessageBox.information(
self._parent,
self.tagger.window,
_("Picard Update"),
_("There is no update currently available for your subscribed update level: {update_level}\n\n"
"Your version: {picard_old_version}\n").format(