diff --git a/picard/coverart/providers/caa.py b/picard/coverart/providers/caa.py index 344d78431e..e88f23fa8b 100644 --- a/picard/coverart/providers/caa.py +++ b/picard/coverart/providers/caa.py @@ -7,7 +7,7 @@ # 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-2022 Laurent Monin +# 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 @@ -33,13 +33,7 @@ OrderedDict, namedtuple, ) -from functools import partial -from PyQt5 import ( - QtCore, - QtGui, - QtWidgets, -) from PyQt5.QtNetwork import ( QNetworkReply, QNetworkRequest, @@ -67,12 +61,8 @@ ) from picard.webservice import ratecontrol -from picard.ui import PicardDialog +from picard.ui.caa_types_selector import display_caa_types_selector from picard.ui.ui_provider_options_caa import Ui_CaaOptions -from picard.ui.util import ( - StandardButton, - qlistwidget_items, -) CaaSizeItem = namedtuple('CaaSizeItem', ['thumbnail', 'label']) @@ -122,314 +112,6 @@ def caa_url_fallback_list(desired_size, thumbnails): return urls -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}) - """ - - help_url = 'doc_cover_art_types' - - def __init__(self, parent=None, types_include=None, types_exclude=None): - super().__init__(parent) - if types_include is None: - types_include = [] - if types_exclude is None: - types_exclude = [] - - 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 caa_type in CAA_TYPES: - name = caa_type['name'] - title = translate_caa_type(name) - 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) - - def get_selected_types_include(self): - return list(self.list_include.all_items_data()) or ['front'] - - def get_selected_types_exclude(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(_CAA_IMAGE_TYPE_DEFAULT_INCLUDE, _CAA_IMAGE_TYPE_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) - - @staticmethod - def run(parent=None, types_include=None, types_exclude=None): - if types_include is None: - types_include = [] - if types_exclude is None: - types_exclude = [] - dialog = CAATypesSelectorDialog(parent, types_include, types_exclude) - result = dialog.exec_() - return (dialog.get_selected_types_include(), dialog.get_selected_types_exclude(), result == QtWidgets.QDialog.DialogCode.Accepted) - - class ProviderOptionsCaa(ProviderOptions): """ Options for Cover Art Archive cover art provider @@ -493,8 +175,15 @@ def update_caa_types(self): self.ui.select_caa_types.setEnabled(enabled) def select_caa_types(self): - (types, types_to_omit, ok) = CAATypesSelectorDialog.run( - self, self.caa_image_types, self.caa_image_types_to_omit) + 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_include=self.caa_image_types, + types_exclude=self.caa_image_types_to_omit, + default_include=_CAA_IMAGE_TYPE_DEFAULT_INCLUDE, + default_exclude=_CAA_IMAGE_TYPE_DEFAULT_EXCLUDE, + known_types=known_types, + ) if ok: self.caa_image_types = types self.caa_image_types_to_omit = types_to_omit diff --git a/picard/ui/caa_types_selector.py b/picard/ui/caa_types_selector.py new file mode 100644 index 0000000000..ad6e2c5274 --- /dev/null +++ b/picard/ui/caa_types_selector.py @@ -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)