From 9baba44c9895529a78878a8102385f7faebb7adc Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 29 Nov 2019 14:05:56 +0100 Subject: [PATCH 1/3] Refactored top tags editor into generic TagListEditor widget --- picard/ui/options/interface_top_tags.py | 9 - picard/ui/ui_options_interface_top_tags.py | 64 +---- picard/ui/ui_widget_taglisteditor.py | 73 ++++++ .../editablelistview.py} | 73 +++--- picard/ui/widgets/taglisteditor.py | 78 +++++++ ui/options_interface_top_tags.ui | 212 ++--------------- ui/widget_taglisteditor.ui | 220 ++++++++++++++++++ 7 files changed, 422 insertions(+), 307 deletions(-) create mode 100644 picard/ui/ui_widget_taglisteditor.py rename picard/ui/{taglistview.py => widgets/editablelistview.py} (78%) create mode 100644 picard/ui/widgets/taglisteditor.py create mode 100644 ui/widget_taglisteditor.ui diff --git a/picard/ui/options/interface_top_tags.py b/picard/ui/options/interface_top_tags.py index 870f3d777..fc7748430 100644 --- a/picard/ui/options/interface_top_tags.py +++ b/picard/ui/options/interface_top_tags.py @@ -51,9 +51,6 @@ class InterfaceTopTagsOptionsPage(OptionsPage): super().__init__(parent) self.ui = Ui_InterfaceTopTagsOptionsPage() self.ui.setupUi(self) - selection = self.ui.top_tags_list.selectionModel() - selection.selectionChanged.connect(self.on_selection_changed) - self.on_selection_changed([], []) def load(self): tags = config.setting["metadatabox_top_tags"] @@ -69,11 +66,5 @@ class InterfaceTopTagsOptionsPage(OptionsPage): self.ui.top_tags_list.clear() super().restore_defaults() - def on_selection_changed(self, selected, deselected): - buttons_enabled = len(self.ui.top_tags_list.selectedIndexes()) > 0 - self.ui.tags_remove_btn.setEnabled(buttons_enabled) - self.ui.tags_move_up_btn.setEnabled(buttons_enabled) - self.ui.tags_move_down_btn.setEnabled(buttons_enabled) - register_options_page(InterfaceTopTagsOptionsPage) diff --git a/picard/ui/ui_options_interface_top_tags.py b/picard/ui/ui_options_interface_top_tags.py index dccf49e13..438a2516c 100644 --- a/picard/ui/ui_options_interface_top_tags.py +++ b/picard/ui/ui_options_interface_top_tags.py @@ -10,7 +10,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_InterfaceTopTagsOptionsPage(object): def setupUi(self, InterfaceTopTagsOptionsPage): InterfaceTopTagsOptionsPage.setObjectName("InterfaceTopTagsOptionsPage") - InterfaceTopTagsOptionsPage.resize(893, 698) + InterfaceTopTagsOptionsPage.resize(418, 310) self.vboxlayout = QtWidgets.QVBoxLayout(InterfaceTopTagsOptionsPage) self.vboxlayout.setContentsMargins(9, 9, 9, 9) self.vboxlayout.setSpacing(6) @@ -18,65 +18,19 @@ class Ui_InterfaceTopTagsOptionsPage(object): self.label = QtWidgets.QLabel(InterfaceTopTagsOptionsPage) self.label.setObjectName("label") self.vboxlayout.addWidget(self.label) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.verticalLayout_2 = QtWidgets.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.top_tags_list = EditableTagListView(InterfaceTopTagsOptionsPage) - self.top_tags_list.setDragEnabled(True) - self.top_tags_list.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + self.top_tags_list = TagListEditor(InterfaceTopTagsOptionsPage) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.top_tags_list.sizePolicy().hasHeightForWidth()) + self.top_tags_list.setSizePolicy(sizePolicy) self.top_tags_list.setObjectName("top_tags_list") - self.verticalLayout_2.addWidget(self.top_tags_list) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout_2.addItem(spacerItem) - self.tags_remove_btn = QtWidgets.QPushButton(InterfaceTopTagsOptionsPage) - self.tags_remove_btn.setObjectName("tags_remove_btn") - self.horizontalLayout_2.addWidget(self.tags_remove_btn) - self.tags_add_btn = QtWidgets.QPushButton(InterfaceTopTagsOptionsPage) - self.tags_add_btn.setAccessibleName("") - self.tags_add_btn.setObjectName("tags_add_btn") - self.horizontalLayout_2.addWidget(self.tags_add_btn) - self.verticalLayout_2.addLayout(self.horizontalLayout_2) - self.horizontalLayout.addLayout(self.verticalLayout_2) - self.verticalLayout = QtWidgets.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") - spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem1) - self.tags_move_up_btn = QtWidgets.QPushButton(InterfaceTopTagsOptionsPage) - self.tags_move_up_btn.setText("") - icon = QtGui.QIcon.fromTheme("go-up") - self.tags_move_up_btn.setIcon(icon) - self.tags_move_up_btn.setObjectName("tags_move_up_btn") - self.verticalLayout.addWidget(self.tags_move_up_btn) - self.tags_move_down_btn = QtWidgets.QPushButton(InterfaceTopTagsOptionsPage) - self.tags_move_down_btn.setText("") - icon = QtGui.QIcon.fromTheme("go-down") - self.tags_move_down_btn.setIcon(icon) - self.tags_move_down_btn.setObjectName("tags_move_down_btn") - self.verticalLayout.addWidget(self.tags_move_down_btn) - spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout.addItem(spacerItem2) - self.horizontalLayout.addLayout(self.verticalLayout) - self.vboxlayout.addLayout(self.horizontalLayout) + self.vboxlayout.addWidget(self.top_tags_list) self.retranslateUi(InterfaceTopTagsOptionsPage) - self.tags_add_btn.clicked.connect(self.top_tags_list.add_empty_row) - self.tags_remove_btn.clicked.connect(self.top_tags_list.remove_selected_rows) - self.tags_move_up_btn.clicked.connect(self.top_tags_list.move_selected_rows_up) - self.tags_move_down_btn.clicked.connect(self.top_tags_list.move_selected_rows_down) QtCore.QMetaObject.connectSlotsByName(InterfaceTopTagsOptionsPage) def retranslateUi(self, InterfaceTopTagsOptionsPage): _translate = QtCore.QCoreApplication.translate self.label.setText(_("Show the below tags above all other tags in the metadata view")) - self.tags_remove_btn.setToolTip(_("Remove selected tags")) - self.tags_remove_btn.setAccessibleName(_("Remove selected tags")) - self.tags_remove_btn.setText(_("Remove tags")) - self.tags_add_btn.setText(_("Add new tag")) - self.tags_move_up_btn.setToolTip(_("Move tag up")) - self.tags_move_up_btn.setAccessibleName(_("Move tag up")) - self.tags_move_down_btn.setToolTip(_("Move tag down")) - self.tags_move_down_btn.setAccessibleName(_("Move tag down")) -from picard.ui.taglistview import EditableTagListView +from picard.ui.widgets.taglisteditor import TagListEditor diff --git a/picard/ui/ui_widget_taglisteditor.py b/picard/ui/ui_widget_taglisteditor.py new file mode 100644 index 000000000..818f5377d --- /dev/null +++ b/picard/ui/ui_widget_taglisteditor.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Automatically generated - don't edit. +# Use `python setup.py build_ui` to update it. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_TagListEditor(object): + def setupUi(self, TagListEditor): + TagListEditor.setObjectName("TagListEditor") + TagListEditor.resize(400, 300) + self.horizontalLayout = QtWidgets.QHBoxLayout(TagListEditor) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(6) + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.tag_list_view = EditableListView(TagListEditor) + self.tag_list_view.setObjectName("tag_list_view") + self.verticalLayout.addWidget(self.tag_list_view) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.tags_remove_btn = QtWidgets.QPushButton(TagListEditor) + self.tags_remove_btn.setObjectName("tags_remove_btn") + self.horizontalLayout_2.addWidget(self.tags_remove_btn) + self.tags_add_btn = QtWidgets.QPushButton(TagListEditor) + self.tags_add_btn.setObjectName("tags_add_btn") + self.horizontalLayout_2.addWidget(self.tags_add_btn) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.horizontalLayout.addLayout(self.verticalLayout) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem1) + self.tags_move_up_btn = QtWidgets.QPushButton(TagListEditor) + self.tags_move_up_btn.setText("") + icon = QtGui.QIcon.fromTheme("go-up") + self.tags_move_up_btn.setIcon(icon) + self.tags_move_up_btn.setObjectName("tags_move_up_btn") + self.verticalLayout_2.addWidget(self.tags_move_up_btn) + self.tags_move_down_btn = QtWidgets.QPushButton(TagListEditor) + self.tags_move_down_btn.setText("") + icon = QtGui.QIcon.fromTheme("go-down") + self.tags_move_down_btn.setIcon(icon) + self.tags_move_down_btn.setObjectName("tags_move_down_btn") + self.verticalLayout_2.addWidget(self.tags_move_down_btn) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem2) + self.horizontalLayout.addLayout(self.verticalLayout_2) + + self.retranslateUi(TagListEditor) + self.tags_remove_btn.clicked.connect(self.tag_list_view.remove_selected_rows) + self.tags_add_btn.clicked.connect(self.tag_list_view.add_empty_row) + self.tags_move_up_btn.clicked.connect(self.tag_list_view.move_selected_rows_up) + self.tags_move_down_btn.clicked.connect(self.tag_list_view.move_selected_rows_down) + QtCore.QMetaObject.connectSlotsByName(TagListEditor) + + def retranslateUi(self, TagListEditor): + _translate = QtCore.QCoreApplication.translate + TagListEditor.setWindowTitle(_("Form")) + self.tags_remove_btn.setToolTip(_("Remove selected tags")) + self.tags_remove_btn.setAccessibleName(_("Remove selected tags")) + self.tags_remove_btn.setText(_("Remove tags")) + self.tags_add_btn.setText(_("Add new tag")) + self.tags_move_up_btn.setToolTip(_("Move tag up")) + self.tags_move_up_btn.setAccessibleName(_("Move tag up")) + self.tags_move_down_btn.setToolTip(_("Move tag down")) + self.tags_move_down_btn.setAccessibleName(_("Move tag down")) +from picard.ui.widgets.editablelistview import EditableListView diff --git a/picard/ui/taglistview.py b/picard/ui/widgets/editablelistview.py similarity index 78% rename from picard/ui/taglistview.py rename to picard/ui/widgets/editablelistview.py index 214cc0c50..342e04f62 100644 --- a/picard/ui/taglistview.py +++ b/picard/ui/widgets/editablelistview.py @@ -22,18 +22,10 @@ from PyQt5 import ( QtWidgets, ) -from picard.util.tags import ( - TAG_NAMES, - display_tag_name, -) - -class EditableTagListView(QtWidgets.QListView): +class EditableListView(QtWidgets.QListView): def __init__(self, parent=None): super().__init__(parent) - model = TagListModel() - self.setModel(model) - self.setItemDelegate(TagItemDelegate()) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def keyPressEvent(self, event): @@ -60,26 +52,26 @@ class EditableTagListView(QtWidgets.QListView): else: super().closeEditor(editor, hint) - def add_tag(self, tag=""): + def add_item(self, value=""): model = self.model() row = model.rowCount() model.insertRow(row) index = model.createIndex(row, 0) - model.setData(index, tag) + model.setData(index, value) return index def clear(self): self.model().update([]) - def update(self, tags): - self.model().update(tags) + def update(self, values): + self.model().update(values) @property - def tags(self): - return self.model().tags + def items(self): + return self.model().items def add_empty_row(self): - index = self.add_tag() + index = self.add_item() self.setCurrentIndex(index) self.edit(index) @@ -128,20 +120,23 @@ class EditableTagListView(QtWidgets.QListView): selection.setCurrentIndex(new_index, QtCore.QItemSelectionModel.Current) -class TagListModel(QtCore.QAbstractListModel): - def __init__(self, tags=None, parent=None): +class EditableListModel(QtCore.QAbstractListModel): + def __init__(self, items=None, parent=None): super().__init__(parent) - self._tags = [(tag, display_tag_name(tag)) for tag in tags or []] + self._items = [(item, self.get_display_name(item)) for item in items or []] + + def get_display_name(self, item): # pylint: disable=no-self-use + return item def rowCount(self, parent=QtCore.QModelIndex()): - return len(self._tags) + return len(self._items) def data(self, index, role=QtCore.Qt.DisplayRole): if not index.isValid() or role not in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): return None field = 1 if role == QtCore.Qt.DisplayRole else 0 try: - return self._tags[index.row()][field] + return self._items[index.row()][field] except IndexError: return None @@ -151,11 +146,11 @@ class TagListModel(QtCore.QAbstractListModel): i = index.row() try: if role == QtCore.Qt.EditRole: - display_name = display_tag_name(value) if value else value - self._tags[i] = (value, display_name) + display_name = self.get_display_name(value) if value else value + self._items[i] = (value, display_name) elif role == QtCore.Qt.DisplayRole: - current = self._tags[i] - self._tags[i] = (current[0], value) + current = self._items[i] + self._items[i] = (current[0], value) return True except IndexError: return False @@ -174,13 +169,13 @@ class TagListModel(QtCore.QAbstractListModel): def insertRows(self, row, count, parent=QtCore.QModelIndex()): super().beginInsertRows(parent, row, row + count - 1) for i in range(count): - self._tags.insert(row, ("", "")) + self._items.insert(row, ("", "")) super().endInsertRows() return True def removeRows(self, row, count, parent=QtCore.QModelIndex()): super().beginRemoveRows(parent, row, row + count - 1) - self._tags = self._tags[:row] + self._tags[row + count:] + self._items = self._items[:row] + self._items[row + count:] super().endRemoveRows() return True @@ -192,13 +187,13 @@ class TagListModel(QtCore.QAbstractListModel): def supportedDropActions(): return QtCore.Qt.MoveAction - def update(self, tags): + def update(self, items): self.beginResetModel() - self._tags = [(tag, display_tag_name(tag)) for tag in tags] + self._items = [(item, self.get_display_name(item)) for item in items] self.endResetModel() def move_row(self, row, new_row): - item = self._tags[row] + item = self._items[row] self.removeRow(row) self.insertRow(new_row) index = self.index(new_row, 0) @@ -206,19 +201,5 @@ class TagListModel(QtCore.QAbstractListModel): self.setData(index, item[1], QtCore.Qt.DisplayRole) @property - def tags(self): - return (t[0] for t in self._tags) - - -class TagItemDelegate(QtWidgets.QItemDelegate): - @staticmethod - def createEditor(parent, option, index): - editor = QtWidgets.QLineEdit(parent) - completer = QtWidgets.QCompleter(TAG_NAMES.keys(), parent) - - def complete(text): - parent.setFocus() - - completer.activated.connect(complete) - editor.setCompleter(completer) - return editor + def items(self): + return (t[0] for t in self._items) diff --git a/picard/ui/widgets/taglisteditor.py b/picard/ui/widgets/taglisteditor.py new file mode 100644 index 000000000..ac38870f3 --- /dev/null +++ b/picard/ui/widgets/taglisteditor.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2019 Philipp Wolfer +# +# 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 import QtWidgets + +from picard.util.tags import ( + TAG_NAMES, + display_tag_name, +) + +from picard.ui.ui_widget_taglisteditor import Ui_TagListEditor +from picard.ui.widgets.editablelistview import EditableListModel + + +class TagListEditor(QtWidgets.QWidget): + def __init__(self, parent): + super().__init__(parent) + self.ui = Ui_TagListEditor() + self.ui.setupUi(self) + list_view = self.ui.tag_list_view + model = TagListModel() + list_view.setModel(model) + list_view.setItemDelegate(TagItemDelegate()) + + selection = list_view.selectionModel() + selection.selectionChanged.connect(self.on_selection_changed) + self.on_selection_changed([], []) + + def on_selection_changed(self, selected, deselected): + buttons_enabled = len(self.ui.tag_list_view.selectedIndexes()) > 0 + self.ui.tags_remove_btn.setEnabled(buttons_enabled) + self.ui.tags_move_up_btn.setEnabled(buttons_enabled) + self.ui.tags_move_down_btn.setEnabled(buttons_enabled) + + def clear(self): + self.ui.tag_list_view.update([]) + + def update(self, tags): + self.ui.tag_list_view.update(tags) + + @property + def tags(self): + return self.ui.tag_list_view.tags + + +class TagListModel(EditableListModel): + def get_display_name(self, item): + return display_tag_name(item) + + +class TagItemDelegate(QtWidgets.QItemDelegate): + @staticmethod + def createEditor(parent, option, index): + editor = QtWidgets.QLineEdit(parent) + completer = QtWidgets.QCompleter(TAG_NAMES.keys(), parent) + + def complete(text): + parent.setFocus() + + completer.activated.connect(complete) + editor.setCompleter(completer) + return editor diff --git a/ui/options_interface_top_tags.ui b/ui/options_interface_top_tags.ui index 324b08e15..2abfb5cc0 100644 --- a/ui/options_interface_top_tags.ui +++ b/ui/options_interface_top_tags.ui @@ -6,8 +6,8 @@ 0 0 - 893 - 698 + 418 + 310 @@ -34,207 +34,25 @@ - - - - - - - true - - - QAbstractItemView::InternalMove - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Remove selected tags - - - Remove selected tags - - - Remove tags - - - - - - - - - - Add new tag - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Move tag up - - - Move tag up - - - - - - - .. - - - - - - - Move tag down - - - Move tag down - - - - - - - .. - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + 0 + 0 + + + - EditableTagListView - QListView -
picard.ui.taglistview
- - add_empty_row() - remove_selected_rows() - move_selected_rows_up() - move_selected_rows_down() - + TagListEditor + QWidget +
picard.ui.widgets.taglisteditor
+ 1
- - - tags_add_btn - clicked() - top_tags_list - add_empty_row() - - - 804 - 673 - - - 428 - 343 - - - - - tags_remove_btn - clicked() - top_tags_list - remove_selected_rows() - - - 716 - 673 - - - 428 - 343 - - - - - tags_move_up_btn - clicked() - top_tags_list - move_selected_rows_up() - - - 867 - 344 - - - 428 - 343 - - - - - tags_move_down_btn - clicked() - top_tags_list - move_selected_rows_down() - - - 867 - 374 - - - 428 - 343 - - - - + diff --git a/ui/widget_taglisteditor.ui b/ui/widget_taglisteditor.ui new file mode 100644 index 000000000..41dc04bc4 --- /dev/null +++ b/ui/widget_taglisteditor.ui @@ -0,0 +1,220 @@ + + + TagListEditor + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remove selected tags + + + Remove selected tags + + + Remove tags + + + + + + + Add new tag + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move tag up + + + Move tag up + + + + + + + + + + + + + Move tag down + + + Move tag down + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + EditableListView + QListView +
picard.ui.widgets.editablelistview
+ + add_empty_row() + remove_selected_rows() + move_selected_rows_up() + move_selected_rows_down() + +
+
+ + + + tags_remove_btn + clicked() + tag_list_view + remove_selected_rows() + + + 182 + 285 + + + 155 + 133 + + + + + tags_add_btn + clicked() + tag_list_view + add_empty_row() + + + 269 + 285 + + + 155 + 133 + + + + + tags_move_up_btn + clicked() + tag_list_view + move_selected_rows_up() + + + 384 + 134 + + + 181 + 133 + + + + + tags_move_down_btn + clicked() + tag_list_view + move_selected_rows_down() + + + 384 + 164 + + + 181 + 133 + + + + +
From c6fcf3f1a01499bff844c7066a5195fa9d5c9a91 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 29 Nov 2019 14:35:38 +0100 Subject: [PATCH 2/3] PICARD-1677: Allow ignoring tags for metadata comparisson Files that only differ in the tags set in the ignore list will not show up as changed even if those tags have changed. --- picard/file.py | 6 ++++-- picard/metadata.py | 8 ++++++-- picard/ui/options/advanced.py | 9 +++++++++ picard/ui/ui_options_advanced.py | 15 +++++++++++++-- test/test_metadata.py | 12 ++++++++++++ ui/options_advanced.ui | 30 +++++++++++++++++++++--------- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/picard/file.py b/picard/file.py index ff7914698..7b767dc6a 100644 --- a/picard/file.py +++ b/picard/file.py @@ -545,15 +545,17 @@ class File(QtCore.QObject, Item): names = set(new_metadata.keys()) names.update(self.orig_metadata.keys()) clear_existing_tags = config.setting["clear_existing_tags"] + ignored_tags = config.setting["compare_ignore_tags"] for name in names: - if not name.startswith('~') and self.supports_tag(name): + if (not name.startswith('~') and self.supports_tag(name) + and name not in ignored_tags): new_values = new_metadata.getall(name) if not (new_values or clear_existing_tags or name in new_metadata.deleted_tags): continue orig_values = self.orig_metadata.getall(name) if orig_values != new_values: - self.similarity = self.orig_metadata.compare(new_metadata) + self.similarity = self.orig_metadata.compare(new_metadata, ignored_tags) if self.state == File.NORMAL: self.state = File.CHANGED break diff --git a/picard/metadata.py b/picard/metadata.py index 0ba9d0a2c..c19f778c9 100644 --- a/picard/metadata.py +++ b/picard/metadata.py @@ -99,14 +99,18 @@ class Metadata(MutableMapping): return (1.0 - min(abs(a - b), LENGTH_SCORE_THRES_MS) / float(LENGTH_SCORE_THRES_MS)) - def compare(self, other): + def compare(self, other, ignored=None): parts = [] + if ignored is None: + ignored = [] - if self.length and other.length: + if self.length and other.length and '~length' not in ignored: score = self.length_score(self.length, other.length) parts.append((score, 8)) for name, weight in self.__weights: + if name in ignored: + continue a = self[name] b = other[name] if a and b: diff --git a/picard/ui/options/advanced.py b/picard/ui/options/advanced.py index 5315dfd37..aee4ee2f4 100644 --- a/picard/ui/options/advanced.py +++ b/picard/ui/options/advanced.py @@ -43,6 +43,7 @@ class AdvancedOptionsPage(OptionsPage): config.BoolOption("setting", "completeness_ignore_pregap", False), config.BoolOption("setting", "completeness_ignore_data", False), config.BoolOption("setting", "completeness_ignore_silence", False), + config.ListOption("setting", "compare_ignore_tags", []), ] def __init__(self, parent=None): @@ -60,6 +61,7 @@ class AdvancedOptionsPage(OptionsPage): self.ui.completeness_ignore_pregap.setChecked(config.setting["completeness_ignore_pregap"]) self.ui.completeness_ignore_data.setChecked(config.setting["completeness_ignore_data"]) self.ui.completeness_ignore_silence.setChecked(config.setting["completeness_ignore_silence"]) + self.ui.compare_ignore_tags.update(config.setting["compare_ignore_tags"]) def save(self): config.setting["ignore_regex"] = self.ui.ignore_regex.text() @@ -70,6 +72,13 @@ class AdvancedOptionsPage(OptionsPage): config.setting["completeness_ignore_pregap"] = self.ui.completeness_ignore_pregap.isChecked() config.setting["completeness_ignore_data"] = self.ui.completeness_ignore_data.isChecked() config.setting["completeness_ignore_silence"] = self.ui.completeness_ignore_silence.isChecked() + tags = list(self.ui.compare_ignore_tags.tags) + if tags != config.setting["compare_ignore_tags"]: + config.setting["compare_ignore_tags"] = tags + + def restore_defaults(self): + self.ui.compare_ignore_tags.clear() + super().restore_defaults() register_options_page(AdvancedOptionsPage) diff --git a/picard/ui/ui_options_advanced.py b/picard/ui/ui_options_advanced.py index 808404f8d..affa06ffc 100644 --- a/picard/ui/ui_options_advanced.py +++ b/picard/ui/ui_options_advanced.py @@ -86,8 +86,17 @@ class Ui_AdvancedOptionsPage(object): self.completeness_ignore_silence.setObjectName("completeness_ignore_silence") self.verticalLayout_2.addWidget(self.completeness_ignore_silence) self.vboxlayout.addWidget(self.groupBox_completeness) - spacerItem = QtWidgets.QSpacerItem(181, 21, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.vboxlayout.addItem(spacerItem) + self.compare_ignore_tags_label = QtWidgets.QLabel(AdvancedOptionsPage) + self.compare_ignore_tags_label.setObjectName("compare_ignore_tags_label") + self.vboxlayout.addWidget(self.compare_ignore_tags_label) + self.compare_ignore_tags = TagListEditor(AdvancedOptionsPage) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.compare_ignore_tags.sizePolicy().hasHeightForWidth()) + self.compare_ignore_tags.setSizePolicy(sizePolicy) + self.compare_ignore_tags.setObjectName("compare_ignore_tags") + self.vboxlayout.addWidget(self.compare_ignore_tags) self.retranslateUi(AdvancedOptionsPage) QtCore.QMetaObject.connectSlotsByName(AdvancedOptionsPage) @@ -111,3 +120,5 @@ class Ui_AdvancedOptionsPage(object): self.completeness_ignore_pregap.setText(_("Pregap tracks")) self.completeness_ignore_data.setText(_("Data tracks")) self.completeness_ignore_silence.setText(_("Silent tracks")) + self.compare_ignore_tags_label.setText(_("Tags to ignore for comparisson:")) +from picard.ui.widgets.taglisteditor import TagListEditor diff --git a/test/test_metadata.py b/test/test_metadata.py index 1d355d109..354583b0f 100644 --- a/test/test_metadata.py +++ b/test/test_metadata.py @@ -208,6 +208,18 @@ class MetadataTest(PicardTestCase): self.assertEqual(m1.compare(m2), m2.compare(m1)) self.assertEqual(m1.compare(m2), 1) + def test_compare_with_ignored(self): + m1 = Metadata() + m1["title"] = "title1" + m1["tracknumber"] = "2" + m1.length = 360 + m2 = Metadata() + m2["title"] = "title1" + m2["tracknumber"] = "3" + m2.length = 300 + self.assertNotEqual(m1.compare(m2), 1) + self.assertEqual(m1.compare(m2, ignored=['tracknumber', '~length']), 1) + def test_compare_lengths(self): m1 = Metadata() m1.length = 360 diff --git a/ui/options_advanced.ui b/ui/options_advanced.ui index 66c26780c..e5a3d2cb1 100644 --- a/ui/options_advanced.ui +++ b/ui/options_advanced.ui @@ -155,20 +155,32 @@ - - - Qt::Vertical + + + Tags to ignore for comparisson: - - - 181 - 21 - + + + + + + + 0 + 0 + - + + + + TagListEditor + QWidget +
picard.ui.widgets.taglisteditor
+ 1 +
+
ignore_regex ignore_hidden_files From c82b2b6f5be924c5cef363b56a4b7371a91d5d40 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 29 Nov 2019 15:38:49 +0100 Subject: [PATCH 3/3] Added generic AutocompleteItemDelegate for autocompletion in list views --- picard/ui/widgets/editablelistview.py | 17 +++++++++++++++++ picard/ui/widgets/taglisteditor.py | 22 ++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/picard/ui/widgets/editablelistview.py b/picard/ui/widgets/editablelistview.py index 342e04f62..eb350ba71 100644 --- a/picard/ui/widgets/editablelistview.py +++ b/picard/ui/widgets/editablelistview.py @@ -203,3 +203,20 @@ class EditableListModel(QtCore.QAbstractListModel): @property def items(self): return (t[0] for t in self._items) + + +class AutocompleteItemDelegate(QtWidgets.QItemDelegate): + def __init__(self, completions, parent=None): + super().__init__(parent) + self._completions = completions + + def createEditor(self, parent, option, index): + editor = QtWidgets.QLineEdit(parent) + completer = QtWidgets.QCompleter(self._completions, parent) + + def complete(text): + parent.setFocus() + + completer.activated.connect(complete) + editor.setCompleter(completer) + return editor diff --git a/picard/ui/widgets/taglisteditor.py b/picard/ui/widgets/taglisteditor.py index ac38870f3..88de90fef 100644 --- a/picard/ui/widgets/taglisteditor.py +++ b/picard/ui/widgets/taglisteditor.py @@ -25,7 +25,10 @@ from picard.util.tags import ( ) from picard.ui.ui_widget_taglisteditor import Ui_TagListEditor -from picard.ui.widgets.editablelistview import EditableListModel +from picard.ui.widgets.editablelistview import ( + AutocompleteItemDelegate, + EditableListModel, +) class TagListEditor(QtWidgets.QWidget): @@ -36,7 +39,8 @@ class TagListEditor(QtWidgets.QWidget): list_view = self.ui.tag_list_view model = TagListModel() list_view.setModel(model) - list_view.setItemDelegate(TagItemDelegate()) + list_view.setItemDelegate(AutocompleteItemDelegate( + sorted(TAG_NAMES.keys()))) selection = list_view.selectionModel() selection.selectionChanged.connect(self.on_selection_changed) @@ -62,17 +66,3 @@ class TagListEditor(QtWidgets.QWidget): class TagListModel(EditableListModel): def get_display_name(self, item): return display_tag_name(item) - - -class TagItemDelegate(QtWidgets.QItemDelegate): - @staticmethod - def createEditor(parent, option, index): - editor = QtWidgets.QLineEdit(parent) - completer = QtWidgets.QCompleter(TAG_NAMES.keys(), parent) - - def complete(text): - parent.setFocus() - - completer.activated.connect(complete) - editor.setCompleter(completer) - return editor