|
|
@@ -0,0 +1,357 @@ |
|
|
# -*- coding: utf-8 -*- |
|
|
# |
|
|
# Picard, the next-generation MusicBrainz tagger |
|
|
# |
|
|
# Copyright (C) 2007 Oliver Charles |
|
|
# Copyright (C) 2007, 2010-2011 Lukáš Lalinský |
|
|
# Copyright (C) 2007-2011, 2015, 2018-2023 Philipp Wolfer |
|
|
# Copyright (C) 2011 Michael Wiencek |
|
|
# Copyright (C) 2011-2012 Wieland Hoffmann |
|
|
# Copyright (C) 2013-2015, 2018-2023 Laurent Monin |
|
|
# Copyright (C) 2015-2016 Rahul Raturi |
|
|
# Copyright (C) 2016-2017 Sambhav Kothari |
|
|
# Copyright (C) 2017 Frederik “Freso” S. Olesen |
|
|
# Copyright (C) 2018 Bob Swift |
|
|
# Copyright (C) 2018 Vishal Choudhary |
|
|
# |
|
|
# This program is free software; you can redistribute it and/or |
|
|
# modify it under the terms of the GNU General Public License |
|
|
# as published by the Free Software Foundation; either version 2 |
|
|
# of the License, or (at your option) any later version. |
|
|
# |
|
|
# This program is distributed in the hope that it will be useful, |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
# GNU General Public License for more details. |
|
|
# |
|
|
# You should have received a copy of the GNU General Public License |
|
|
# along with this program; if not, write to the Free Software |
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
|
|
|
|
|
|
|
from functools import partial |
|
|
|
|
|
from PyQt5 import ( |
|
|
QtCore, |
|
|
QtGui, |
|
|
QtWidgets, |
|
|
) |
|
|
|
|
|
from picard.ui import PicardDialog |
|
|
from picard.ui.util import ( |
|
|
StandardButton, |
|
|
qlistwidget_items, |
|
|
) |
|
|
|
|
|
|
|
|
class ArrowButton(QtWidgets.QPushButton): |
|
|
"""Standard arrow button for CAA image type selection dialog. |
|
|
|
|
|
Keyword Arguments: |
|
|
label {string} -- Label to display on the button |
|
|
command {command} -- Command to execute when the button is clicked (default: {None}) |
|
|
parent {[type]} -- Parent of the QPushButton object being created (default: {None}) |
|
|
""" |
|
|
|
|
|
def __init__(self, icon_name, command=None, parent=None): |
|
|
icon = QtGui.QIcon(":/images/16x16/" + icon_name + '.png') |
|
|
super().__init__(icon, "", parent=parent) |
|
|
if command is not None: |
|
|
self.clicked.connect(command) |
|
|
|
|
|
|
|
|
class ArrowsColumn(QtWidgets.QWidget): |
|
|
"""Standard arrow buttons column for CAA image type selection dialog. |
|
|
|
|
|
Keyword Arguments: |
|
|
selection_list {ListBox} -- ListBox of selected items associated with this arrow column |
|
|
ignore_list {ListBox} -- ListBox of unselected items associated with this arrow column |
|
|
callback {command} -- Command to execute after items are moved between lists (default: {None}) |
|
|
reverse {bool} -- Determines whether the arrow directions should be reversed (default: {False}) |
|
|
parent {[type]} -- Parent of the QWidget object being created (default: {None}) |
|
|
""" |
|
|
|
|
|
def __init__(self, selection_list, ignore_list, callback=None, reverse=False, parent=None): |
|
|
super().__init__(parent=parent) |
|
|
self.selection_list = selection_list |
|
|
self.ignore_list = ignore_list |
|
|
self.callback = callback |
|
|
spacer_item = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) |
|
|
arrows_layout = QtWidgets.QVBoxLayout() |
|
|
arrows_layout.addItem(QtWidgets.QSpacerItem(spacer_item)) |
|
|
self.button_add = ArrowButton('go-next' if reverse else 'go-previous', self.move_from_ignore) |
|
|
arrows_layout.addWidget(self.button_add) |
|
|
self.button_add_all = ArrowButton('move-all-right' if reverse else 'move-all-left', self.move_all_from_ignore) |
|
|
arrows_layout.addWidget(self.button_add_all) |
|
|
self.button_remove = ArrowButton('go-previous' if reverse else 'go-next', self.move_to_ignore) |
|
|
arrows_layout.addWidget(self.button_remove) |
|
|
self.button_remove_all = ArrowButton('move-all-left' if reverse else 'move-all-right', self.move_all_to_ignore) |
|
|
arrows_layout.addWidget(self.button_remove_all) |
|
|
arrows_layout.addItem(QtWidgets.QSpacerItem(spacer_item)) |
|
|
self.setLayout(arrows_layout) |
|
|
|
|
|
def move_from_ignore(self): |
|
|
self.ignore_list.move_selected_items(self.selection_list, callback=self.callback) |
|
|
|
|
|
def move_all_from_ignore(self): |
|
|
self.ignore_list.move_all_items(self.selection_list, callback=self.callback) |
|
|
|
|
|
def move_to_ignore(self): |
|
|
self.selection_list.move_selected_items(self.ignore_list, callback=self.callback) |
|
|
|
|
|
def move_all_to_ignore(self): |
|
|
self.selection_list.move_all_items(self.ignore_list, callback=self.callback) |
|
|
|
|
|
|
|
|
class ListBox(QtWidgets.QListWidget): |
|
|
"""Standard list box for CAA image type selection dialog. |
|
|
|
|
|
Keyword Arguments: |
|
|
parent {[type]} -- Parent of the QListWidget object being created (default: {None}) |
|
|
""" |
|
|
|
|
|
LISTBOX_WIDTH = 100 |
|
|
LISTBOX_HEIGHT = 250 |
|
|
|
|
|
def __init__(self, parent=None): |
|
|
super().__init__(parent=parent) |
|
|
self.setMinimumSize(QtCore.QSize(self.LISTBOX_WIDTH, self.LISTBOX_HEIGHT)) |
|
|
self.setSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Expanding) |
|
|
self.setSortingEnabled(True) |
|
|
self.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) |
|
|
|
|
|
def move_item(self, item, target_list): |
|
|
"""Move the specified item to another listbox.""" |
|
|
self.takeItem(self.row(item)) |
|
|
target_list.addItem(item) |
|
|
|
|
|
def move_selected_items(self, target_list, callback=None): |
|
|
"""Move the selected item to another listbox.""" |
|
|
for item in self.selectedItems(): |
|
|
self.move_item(item, target_list) |
|
|
if callback: |
|
|
callback() |
|
|
|
|
|
def move_all_items(self, target_list, callback=None): |
|
|
"""Move all items to another listbox.""" |
|
|
while self.count(): |
|
|
self.move_item(self.item(0), target_list) |
|
|
if callback: |
|
|
callback() |
|
|
|
|
|
def all_items_data(self, role=QtCore.Qt.ItemDataRole.UserRole): |
|
|
for item in qlistwidget_items(self): |
|
|
yield item.data(role) |
|
|
|
|
|
|
|
|
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}) |
|
|
""" |
|
|
|
|
|
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 |
|
|
): |
|
|
super().__init__(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 {} |
|
|
|
|
|
self.setWindowTitle(_("Cover art types")) |
|
|
self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) |
|
|
self.layout = QtWidgets.QVBoxLayout(self) |
|
|
self.layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetFixedSize) |
|
|
|
|
|
# Create list boxes for dialog |
|
|
self.list_include = ListBox() |
|
|
self.list_exclude = ListBox() |
|
|
self.list_ignore = ListBox() |
|
|
|
|
|
# Populate list boxes from current settings |
|
|
self.fill_lists(types_include, types_exclude) |
|
|
|
|
|
# Set triggers when the lists receive the current focus |
|
|
self.list_include.clicked.connect(partial(self.clear_focus, [self.list_ignore, self.list_exclude])) |
|
|
self.list_exclude.clicked.connect(partial(self.clear_focus, [self.list_ignore, self.list_include])) |
|
|
self.list_ignore.clicked.connect(partial(self.clear_focus, [self.list_include, self.list_exclude])) |
|
|
|
|
|
# Add instructions to the dialog box |
|
|
instructions = QtWidgets.QLabel() |
|
|
instructions.setText(_("Please select the contents of the image type 'Include' and 'Exclude' lists.")) |
|
|
instructions.setWordWrap(True) |
|
|
instructions.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) |
|
|
self.layout.addWidget(instructions) |
|
|
|
|
|
self.arrows_include = ArrowsColumn( |
|
|
self.list_include, |
|
|
self.list_ignore, |
|
|
callback=self.set_buttons_enabled_state, |
|
|
) |
|
|
|
|
|
self.arrows_exclude = ArrowsColumn( |
|
|
self.list_exclude, |
|
|
self.list_ignore, |
|
|
callback=self.set_buttons_enabled_state, |
|
|
reverse=True |
|
|
) |
|
|
|
|
|
lists_layout = QtWidgets.QHBoxLayout() |
|
|
|
|
|
include_list_layout = QtWidgets.QVBoxLayout() |
|
|
include_list_layout.addWidget(QtWidgets.QLabel(_("Include types list"))) |
|
|
include_list_layout.addWidget(self.list_include) |
|
|
lists_layout.addLayout(include_list_layout) |
|
|
|
|
|
lists_layout.addWidget(self.arrows_include) |
|
|
|
|
|
ignore_list_layout = QtWidgets.QVBoxLayout() |
|
|
ignore_list_layout.addWidget(QtWidgets.QLabel("")) |
|
|
ignore_list_layout.addWidget(self.list_ignore) |
|
|
lists_layout.addLayout(ignore_list_layout) |
|
|
|
|
|
lists_layout.addWidget(self.arrows_exclude) |
|
|
|
|
|
exclude_list_layout = QtWidgets.QVBoxLayout() |
|
|
exclude_list_layout.addWidget(QtWidgets.QLabel(_("Exclude types list"))) |
|
|
exclude_list_layout.addWidget(self.list_exclude) |
|
|
lists_layout.addLayout(exclude_list_layout) |
|
|
|
|
|
self.layout.addLayout(lists_layout) |
|
|
|
|
|
# Add usage explanation to the dialog box |
|
|
instructions = QtWidgets.QLabel() |
|
|
instructions.setText(_( |
|
|
"CAA images with an image type found in the 'Include' list will be downloaded and used " |
|
|
"UNLESS they also have an image type found in the 'Exclude' list. Images with types " |
|
|
"found in the 'Exclude' list will NEVER be used. Image types not appearing in the 'Include' " |
|
|
"or 'Exclude' lists will not be considered when determining whether or not to download and " |
|
|
"use a CAA image.\n") |
|
|
) |
|
|
instructions.setWordWrap(True) |
|
|
instructions.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) |
|
|
self.layout.addWidget(instructions) |
|
|
|
|
|
self.buttonbox = QtWidgets.QDialogButtonBox(self) |
|
|
self.buttonbox.setOrientation(QtCore.Qt.Orientation.Horizontal) |
|
|
self.buttonbox.addButton( |
|
|
StandardButton(StandardButton.OK), QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) |
|
|
self.buttonbox.addButton(StandardButton(StandardButton.CANCEL), |
|
|
QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) |
|
|
self.buttonbox.addButton( |
|
|
StandardButton(StandardButton.HELP), QtWidgets.QDialogButtonBox.ButtonRole.HelpRole) |
|
|
|
|
|
extrabuttons = [ |
|
|
(N_("I&nclude all"), self.move_all_to_include_list), |
|
|
(N_("E&xclude all"), self.move_all_to_exclude_list), |
|
|
(N_("C&lear all"), self.move_all_to_ignore_list), |
|
|
(N_("Restore &Defaults"), self.reset_to_defaults), |
|
|
] |
|
|
for label, callback in extrabuttons: |
|
|
button = QtWidgets.QPushButton(_(label)) |
|
|
self.buttonbox.addButton(button, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole) |
|
|
button.clicked.connect(callback) |
|
|
|
|
|
self.layout.addWidget(self.buttonbox) |
|
|
|
|
|
self.buttonbox.accepted.connect(self.accept) |
|
|
self.buttonbox.rejected.connect(self.reject) |
|
|
self.buttonbox.helpRequested.connect(self.show_help) |
|
|
|
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def move_all_to_include_list(self): |
|
|
self.list_ignore.move_all_items(self.list_include) |
|
|
self.list_exclude.move_all_items(self.list_include) |
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def move_all_to_exclude_list(self): |
|
|
self.list_ignore.move_all_items(self.list_exclude) |
|
|
self.list_include.move_all_items(self.list_exclude) |
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def move_all_to_ignore_list(self): |
|
|
self.list_include.move_all_items(self.list_ignore) |
|
|
self.list_exclude.move_all_items(self.list_ignore) |
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def fill_lists(self, includes, excludes): |
|
|
"""Fill dialog listboxes. |
|
|
|
|
|
First clears the contents of the three listboxes, and then populates the listboxes |
|
|
from the dictionary of standard CAA types, using the provided 'includes' and |
|
|
'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 |
|
|
""" |
|
|
self.list_include.clear() |
|
|
self.list_exclude.clear() |
|
|
self.list_ignore.clear() |
|
|
for name, title in self._known_types.items(): |
|
|
item = QtWidgets.QListWidgetItem(title) |
|
|
item.setData(QtCore.Qt.ItemDataRole.UserRole, name) |
|
|
if name in includes: |
|
|
self.list_include.addItem(item) |
|
|
elif name in excludes: |
|
|
self.list_exclude.addItem(item) |
|
|
else: |
|
|
self.list_ignore.addItem(item) |
|
|
|
|
|
@property |
|
|
def included(self): |
|
|
return list(self.list_include.all_items_data()) or ['front'] |
|
|
|
|
|
@property |
|
|
def excluded(self): |
|
|
return list(self.list_exclude.all_items_data()) or ['none'] |
|
|
|
|
|
def clear_focus(self, lists): |
|
|
for temp_list in lists: |
|
|
temp_list.clearSelection() |
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def reset_to_defaults(self): |
|
|
self.fill_lists(self._default_include, self._default_exclude) |
|
|
self.set_buttons_enabled_state() |
|
|
|
|
|
def set_buttons_enabled_state(self): |
|
|
has_items_include = self.list_include.count() |
|
|
has_items_exclude = self.list_exclude.count() |
|
|
has_items_ignore = self.list_ignore.count() |
|
|
|
|
|
has_selected_include = bool(self.list_include.selectedItems()) |
|
|
has_selected_exclude = bool(self.list_exclude.selectedItems()) |
|
|
has_selected_ignore = bool(self.list_ignore.selectedItems()) |
|
|
|
|
|
# "Include" list buttons |
|
|
self.arrows_include.button_add.setEnabled(has_items_ignore and has_selected_ignore) |
|
|
self.arrows_include.button_add_all.setEnabled(has_items_ignore) |
|
|
self.arrows_include.button_remove.setEnabled(has_items_include and has_selected_include) |
|
|
self.arrows_include.button_remove_all.setEnabled(has_items_include) |
|
|
|
|
|
# "Exclude" list buttons |
|
|
self.arrows_exclude.button_add.setEnabled(has_items_ignore and has_selected_ignore) |
|
|
self.arrows_exclude.button_add_all.setEnabled(has_items_ignore) |
|
|
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) |