diff --git a/picard/ui/checkbox_list_item.py b/picard/ui/checkbox_list_item.py new file mode 100644 index 000000000..d318fbefa --- /dev/null +++ b/picard/ui/checkbox_list_item.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2018 Sambhav Kothari +# +# 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 PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QListWidgetItem + +class CheckboxListItem(QListWidgetItem): + + def __init__(self, text='', checked=False, data=None): + super().__init__() + self.setText(text) + self.setFlags(self.flags() | Qt.ItemIsUserCheckable) + self.setCheckState(Qt.Checked if checked else Qt.Unchecked) + self.data = data + + @property + def checked(self): + return self.checkState() == Qt.Checked diff --git a/picard/ui/moveable_list_view.py b/picard/ui/moveable_list_view.py new file mode 100644 index 000000000..348656245 --- /dev/null +++ b/picard/ui/moveable_list_view.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2018 Sambhav Kothari +# +# 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 QtWidgets, QtCore + +class MoveableListView: + + def __init__(self, list_widget, up_button, down_button, callback=None): + self.list_widget = list_widget + self.up_button = up_button + self.down_button = down_button + self.update_callback = callback + self.up_button.clicked.connect(partial(self.move_item, 1)) + self.down_button.clicked.connect(partial(self.move_item, -1)) + self.list_widget.currentRowChanged.connect(self.update_buttons) + self.list_widget.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) + self.list_widget.setDefaultDropAction(QtCore.Qt.MoveAction) + + def move_item(self, offset): + current_index = self.list_widget.currentRow() + offset_index = current_index - offset + offset_item = self.list_widget.item(offset_index) + if offset_item: + current_item = self.list_widget.takeItem(current_index) + self.list_widget.insertItem(offset_index, current_item) + self.list_widget.setCurrentItem(current_item) + self.update_buttons() + + def update_buttons(self): + current_row = self.list_widget.currentRow() + self.up_button.setEnabled(current_row > 0) + self.down_button.setEnabled(current_row < self.list_widget.count() - 1) + if self.update_callback: + self.update_callback() diff --git a/picard/ui/options/cover.py b/picard/ui/options/cover.py index fe1ffdea1..e60989535 100644 --- a/picard/ui/options/cover.py +++ b/picard/ui/options/cover.py @@ -21,10 +21,8 @@ from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_cover import Ui_CoverOptionsPage from picard.coverart.providers import cover_art_providers, is_provider_enabled -from picard.ui.sortablecheckboxlist import ( - SortableCheckboxListWidget, - SortableCheckboxListItem -) +from picard.ui.moveable_list_view import MoveableListView +from picard.ui.checkbox_list_item import CheckboxListItem class CoverOptionsPage(OptionsPage): @@ -56,9 +54,8 @@ class CoverOptionsPage(OptionsPage): self.ui.setupUi(self) self.ui.save_images_to_files.clicked.connect(self.update_filename) self.ui.save_images_to_tags.clicked.connect(self.update_save_images_to_tags) - self.provider_list_widget = SortableCheckboxListWidget() - self.ui.ca_providers_list.insertWidget(0, self.provider_list_widget) - self.ca_providers = [] + self.move_view = MoveableListView(self.ui.ca_providers_list, self.ui.up_button, + self.ui.down_button) def load_cover_art_providers(self): """Load available providers, initialize provider-specific options, restore state of each @@ -70,25 +67,28 @@ class CoverOptionsPage(OptionsPage): except AttributeError: title = provider.NAME checked = is_provider_enabled(provider.NAME) - self.provider_list_widget.addItem(SortableCheckboxListItem(title, checked=checked, data=provider.NAME)) - - def update_providers_options(items): - self.ca_providers = [(item.data, item.checked) for item in items] - self.provider_list_widget.changed.connect(update_providers_options) + self.ui.ca_providers_list.addItem(CheckboxListItem(title, checked=checked, data=provider.NAME)) def restore_defaults(self): # Remove previous entries self.provider_list_widget.clear() super().restore_defaults() + def ca_providers(self): + items = [] + for i in range(self.ui.ca_providers_list.count()): + item = self.ui.ca_providers_list.item(i) + items.append((item.data, item.checked)) + return items + def load(self): self.ui.save_images_to_tags.setChecked(config.setting["save_images_to_tags"]) self.ui.cb_embed_front_only.setChecked(config.setting["embed_only_one_front_image"]) self.ui.save_images_to_files.setChecked(config.setting["save_images_to_files"]) self.ui.cover_image_filename.setText(config.setting["cover_image_filename"]) self.ui.save_images_overwrite.setChecked(config.setting["save_images_overwrite"]) - self.ca_providers = config.setting["ca_providers"] self.load_cover_art_providers() + self.ui.ca_providers_list.setCurrentRow(0) self.update_all() def save(self): @@ -97,7 +97,7 @@ class CoverOptionsPage(OptionsPage): config.setting["save_images_to_files"] = self.ui.save_images_to_files.isChecked() config.setting["cover_image_filename"] = self.ui.cover_image_filename.text() config.setting["save_images_overwrite"] = self.ui.save_images_overwrite.isChecked() - config.setting["ca_providers"] = self.ca_providers + config.setting["ca_providers"] = self.ca_providers() def update_all(self): self.update_filename() diff --git a/picard/ui/options/interface.py b/picard/ui/options/interface.py index db421b127..b60d13a3f 100644 --- a/picard/ui/options/interface.py +++ b/picard/ui/options/interface.py @@ -23,6 +23,7 @@ from PyQt5 import QtCore, QtWidgets from PyQt5.QtCore import QStandardPaths from picard import config from picard.util import icontheme +from picard.ui.moveable_list_view import MoveableListView from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_interface import Ui_InterfaceOptionsPage from picard.ui.util import enabledSlot @@ -151,11 +152,9 @@ class InterfaceOptionsPage(OptionsPage): self.ui.add_button.clicked.connect(self.add_to_toolbar) self.ui.insert_separator_button.clicked.connect(self.insert_separator) self.ui.remove_button.clicked.connect(self.remove_action) - self.ui.up_button.clicked.connect(partial(self.move_item, 1)) - self.ui.down_button.clicked.connect(partial(self.move_item, -1)) - self.ui.toolbar_layout_list.currentRowChanged.connect(self.update_buttons) - self.ui.toolbar_layout_list.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) - self.ui.toolbar_layout_list.setDefaultDropAction(QtCore.Qt.MoveAction) + self.move_view = MoveableListView(self.ui.toolbar_layout_list, self.ui.up_button, + self.ui.down_button, self.update_action_buttons) + self.update_buttons = self.move_view.update_buttons def load(self): self.ui.toolbar_show_labels.setChecked(config.setting["toolbar_show_labels"]) @@ -234,11 +233,8 @@ class InterfaceOptionsPage(OptionsPage): if name in self.ACTION_NAMES or name == 'separator': self._insert_item(name) - def update_buttons(self): + def update_action_buttons(self): self.ui.add_button.setEnabled(self._added_actions() != self.ACTION_NAMES) - current_row = self.ui.toolbar_layout_list.currentRow() - self.ui.up_button.setEnabled(current_row > 0) - self.ui.down_button.setEnabled(current_row < self.ui.toolbar_layout_list.count() - 1) def add_to_toolbar(self): display_list = set.difference(self.ACTION_NAMES, self._added_actions()) @@ -252,16 +248,6 @@ class InterfaceOptionsPage(OptionsPage): insert_index = self.ui.toolbar_layout_list.currentRow() + 1 self._insert_item('separator', insert_index) - def move_item(self, offset): - current_index = self.ui.toolbar_layout_list.currentRow() - offset_index = current_index - offset - offset_item = self.ui.toolbar_layout_list.item(offset_index) - if offset_item: - current_item = self.ui.toolbar_layout_list.takeItem(current_index) - self.ui.toolbar_layout_list.insertItem(offset_index, current_item) - self.ui.toolbar_layout_list.setCurrentItem(current_item) - self.update_buttons() - def remove_action(self): item = self.ui.toolbar_layout_list.takeItem(self.ui.toolbar_layout_list.currentRow()) del item diff --git a/picard/ui/sortablecheckboxlist.py b/picard/ui/sortablecheckboxlist.py deleted file mode 100644 index 2f47c6f82..000000000 --- a/picard/ui/sortablecheckboxlist.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Picard, the next-generation MusicBrainz tagger -# Copyright (C) 2015 Laurent Monin -# -# 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, QtWidgets -from PyQt5.QtCore import pyqtSignal - - -class SortableCheckboxListWidget(QtWidgets.QWidget): - _CHECKBOX_POS = 0 - _BUTTON_UP = 1 - _BUTTON_DOWN = 2 - - __no_emit = False - changed = pyqtSignal(list) - - def __init__(self, parent=None): - super().__init__(parent) - layout = QtWidgets.QGridLayout() - layout.setHorizontalSpacing(5) - layout.setVerticalSpacing(2) - layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(layout) - self.__items = [] - - def addItems(self, items): - for item in items: - self.addItem(item) - - def setSignals(self, row): - layout = self.layout() - checkbox = layout.itemAtPosition(row, self._CHECKBOX_POS).widget() - up = layout.itemAtPosition(row, self._BUTTON_UP).widget() - down = layout.itemAtPosition(row, self._BUTTON_DOWN).widget() - checkbox.stateChanged.connect(partial(self.checkbox_toggled, row)) - up.clicked.connect(partial(self.move_button_clicked, row, up=True)) - down.clicked.connect(partial(self.move_button_clicked, row, up=False)) - - def moveItem(self, from_row, to_row): - to_row = to_row % len(self.__items) - self.__items[to_row], self.__items[from_row] = \ - self.__items[from_row], self.__items[to_row] - self.updateRow(to_row) - self.updateRow(from_row) - self._emit_changed() - - def checkbox_toggled(self, row, state): - self.__items[row].setChecked(state == QtCore.Qt.Checked) - self._emit_changed() - - def move_button_clicked(self, row, up): - if up: - to = row - 1 - else: - to = row + 1 - self.moveItem(row, to) - - def updateRow(self, row): - self.__no_emit = True - item = self.__items[row] - layout = self.layout() - checkbox = layout.itemAtPosition(row, self._CHECKBOX_POS).widget() - checkbox.setText(item.text) - checkbox.setChecked(item.checked) - self.__no_emit = False - - def addItem(self, item): - self.__items.append(item) - row = len(self.__items) - 1 - layout = self.layout() - layout.addWidget(QtWidgets.QCheckBox(), row, self._CHECKBOX_POS) - self.updateRow(row) - up_button = QtWidgets.QToolButton() - up_button.setArrowType(QtCore.Qt.UpArrow) - up_button.setMaximumSize(QtCore.QSize(16, 16)) - down_button = QtWidgets.QToolButton() - down_button.setArrowType(QtCore.Qt.DownArrow) - down_button.setMaximumSize(QtCore.QSize(16, 16)) - layout.addWidget(up_button, row, self._BUTTON_UP) - layout.addWidget(down_button, row, self._BUTTON_DOWN) - self.setSignals(row) - - def _emit_changed(self): - if not self.__no_emit: - self.changed.emit(self.__items) - - def clear(self): - for i in reversed(range(len(self.__items))): - self._remove(i) - self.__items = [] - - def _remove(self, row): - self.layout().itemAtPosition(row, self._CHECKBOX_POS).widget().setParent(None) - self.layout().itemAtPosition(row, self._BUTTON_UP).widget().setParent(None) - self.layout().itemAtPosition(row, self._BUTTON_DOWN).widget().setParent(None) - - -class SortableCheckboxListItem(object): - - def __init__(self, text='', checked=False, data=None): - self._checked = checked - self._text = text - self._data = data - - @property - def text(self): - return self._text - - def setText(self, text): - self._text = text - - @property - def checked(self): - return self._checked - - def setChecked(self, state): - self._checked = state - - @property - def data(self): - return self._data - - def setData(self, data): - self._data = data - - def __repr__(self): - params = [] - params.append('text=' + repr(self.text)) - params.append('checked=' + repr(self.checked)) - if self.data is not None: - params.append('data=' + repr(self.data)) - return "%s(%s)" % (self.__class__.__name__, ", ".join(params)) diff --git a/picard/ui/ui_options_cover.py b/picard/ui/ui_options_cover.py index 7b3a7c24b..9bf99d03f 100644 --- a/picard/ui/ui_options_cover.py +++ b/picard/ui/ui_options_cover.py @@ -45,12 +45,32 @@ class Ui_CoverOptionsPage(object): self.ca_providers_groupbox.setObjectName("ca_providers_groupbox") self.ca_providers_layout = QtWidgets.QVBoxLayout(self.ca_providers_groupbox) self.ca_providers_layout.setObjectName("ca_providers_layout") - self.ca_providers_list = QtWidgets.QHBoxLayout() + self.ca_providers_list = QtWidgets.QListWidget(self.ca_providers_groupbox) self.ca_providers_list.setObjectName("ca_providers_list") + self.ca_providers_layout.addWidget(self.ca_providers_list) + self.ca_layout = QtWidgets.QHBoxLayout() + self.ca_layout.setObjectName("ca_layout") + self.move_label = QtWidgets.QLabel(self.ca_providers_groupbox) + self.move_label.setObjectName("move_label") + self.ca_layout.addWidget(self.move_label) + self.up_button = QtWidgets.QToolButton(self.ca_providers_groupbox) + self.up_button.setLayoutDirection(QtCore.Qt.LeftToRight) + self.up_button.setText("") + self.up_button.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.up_button.setAutoRaise(False) + self.up_button.setArrowType(QtCore.Qt.UpArrow) + self.up_button.setObjectName("up_button") + self.ca_layout.addWidget(self.up_button) + self.down_button = QtWidgets.QToolButton(self.ca_providers_groupbox) + self.down_button.setText("") + self.down_button.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.down_button.setArrowType(QtCore.Qt.DownArrow) + self.down_button.setObjectName("down_button") + self.ca_layout.addWidget(self.down_button) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.ca_providers_list.addItem(spacerItem) - self.ca_providers_layout.addLayout(self.ca_providers_list) - self.verticalLayout.addWidget(self.ca_providers_groupbox) + self.ca_layout.addItem(spacerItem) + self.ca_providers_layout.addLayout(self.ca_layout) + self.verticalLayout.addWidget(self.ca_providers_groupbox, 0, QtCore.Qt.AlignTop) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem1) @@ -70,4 +90,7 @@ class Ui_CoverOptionsPage(object): self.label_use_filename.setText(_("Use the following file name for images:")) self.save_images_overwrite.setText(_("Overwrite the file if it already exists")) self.ca_providers_groupbox.setTitle(_("Cover Art Providers")) + self.move_label.setText(_("Reorder Priority: ")) + self.up_button.setToolTip(_("Move selected item up")) + self.down_button.setToolTip(_("Move selected item down")) diff --git a/ui/options_cover.ui b/ui/options_cover.ui index 2c7c94f2c..9ae4f6380 100644 --- a/ui/options_cover.ui +++ b/ui/options_cover.ui @@ -20,7 +20,16 @@ 2 - + + 9 + + + 9 + + + 9 + + 9 @@ -64,7 +73,7 @@ - + @@ -77,7 +86,55 @@ - + + + + + + + + Reorder Priority: + + + + + + + Move selected item up + + + Qt::LeftToRight + + + + + + Qt::ToolButtonIconOnly + + + false + + + Qt::UpArrow + + + + + + + Move selected item down + + + + + + Qt::ToolButtonIconOnly + + + Qt::DownArrow + + +