Merge pull request #873 from sambhav/picard1213

PICARD-1213: Refactor CoverArt and UI options code
This commit is contained in:
Sambhav Kothari
2018-03-10 22:16:24 +05:30
committed by GitHub
7 changed files with 191 additions and 188 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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))

View File

@@ -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"))

View File

@@ -20,7 +20,16 @@
<property name="spacing">
<number>2</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
@@ -64,7 +73,7 @@
</layout>
</widget>
</item>
<item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="ca_providers_groupbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -77,7 +86,55 @@
</property>
<layout class="QVBoxLayout" name="ca_providers_layout">
<item>
<layout class="QHBoxLayout" name="ca_providers_list">
<widget class="QListWidget" name="ca_providers_list"/>
</item>
<item>
<layout class="QHBoxLayout" name="ca_layout">
<item>
<widget class="QLabel" name="move_label">
<property name="text">
<string>Reorder Priority: </string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="up_button">
<property name="toolTip">
<string>Move selected item up</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string/>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
<property name="arrowType">
<enum>Qt::UpArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="down_button">
<property name="toolTip">
<string>Move selected item down</string>
</property>
<property name="text">
<string/>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="arrowType">
<enum>Qt::DownArrow</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">