Hello community,

here is the log from the commit of package picard for openSUSE:Factory checked 
in at 2019-11-12 11:56:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/picard (Old)
 and      /work/SRC/openSUSE:Factory/.picard.new.2990 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "picard"

Tue Nov 12 11:56:44 2019 rev:37 rq:747515 version:2.2.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/picard/picard.changes    2019-10-14 
12:43:48.974337149 +0200
+++ /work/SRC/openSUSE:Factory/.picard.new.2990/picard.changes  2019-11-12 
11:58:23.387533039 +0100
@@ -1,0 +2,23 @@
+Mon Nov 11 21:10:07 UTC 2019 - Jaime Marquínez Ferrándiz 
<jaime.marquinez.ferran...@fastmail.net>
+
+- Update to 2.2.3:
+  * Bug
+    * [PICARD-1633] – macOS: Automatic locale detection broken
+    * [PICARD-1634] – macOS: File browser sets wrong horizontal scroll position
+    * [PICARD-1635] – Terminated randomly when deleting files when saving
+    * [PICARD-1636] – Default locale detection fails if locale categories have 
different locales
+    * [PICARD-1637] – Crash when saving after removing some underlying files
+    * [PICARD-1640] – Picard with –config-file parameter copies over legacy 
configuration
+    * [PICARD-1642] – Picard crashes on launch (AttributeError: ‘NoneType’ 
object has no attribute ‘setPopupMode’)
+    * [PICARD-1643] – Performer with instruments containing non-ASCII 
characters are not written to Vorbis and APE tags
+    * [PICARD-1644] – Crash when initializing translations on Python 3.8
+    * [PICARD-1647] – macOS: Plugin enable/disable button does not always 
update the icon
+    * [PICARD-1648] – Crashes when using search dialogs
+    * [PICARD-1651] – File and release counts in status bar not updated when 
files get removed
+    * [PICARD-1654] – macOS: Logout button / username stays visible after 
logout
+    * [PICARD-1655] – macOS: Login dialog can be hidden behind options
+  * Improvement
+    * [PICARD-1630] – Ensure FLAC metadata is visible/editable in Windows 
Explorer
+    * [PICARD-1632] – Tooltips for genre filter help hide too quickly
+
+-------------------------------------------------------------------

Old:
----
  picard-2.2.2.tar.gz

New:
----
  picard-2.2.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ picard.spec ++++++
--- /var/tmp/diff_new_pack.lqj6nh/_old  2019-11-12 11:58:23.791533469 +0100
+++ /var/tmp/diff_new_pack.lqj6nh/_new  2019-11-12 11:58:23.795533473 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           picard
-Version:        2.2.2
+Version:        2.2.3
 Release:        0
 Summary:        The Next Generation MusicBrainz Tagger
 License:        GPL-2.0-or-later

++++++ picard-2.2.2.tar.gz -> picard-2.2.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/.circleci/config.yml 
new/picard-release-2.2.3/.circleci/config.yml
--- old/picard-release-2.2.2/.circleci/config.yml       1970-01-01 
01:00:00.000000000 +0100
+++ new/picard-release-2.2.3/.circleci/config.yml       2019-11-06 
13:33:23.000000000 +0100
@@ -0,0 +1,55 @@
+# Python CircleCI 2.0 configuration file
+#
+# Check https://circleci.com/docs/2.0/language-python/ for more details
+#
+version: 2
+jobs:
+  build:
+    docker:
+      # specify the version you desire here
+      # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
+      - image: circleci/python:3.6.1
+
+      # Specify service dependencies here if necessary
+      # CircleCI maintains a library of pre-built images
+      # documented at https://circleci.com/docs/2.0/circleci-images/
+      # - image: circleci/postgres:9.4
+
+    working_directory: ~/repo
+
+    steps:
+      - checkout
+
+      # Download and cache dependencies
+      - restore_cache:
+          keys:
+            - v1-dependencies-{{ checksum "requirements.txt" }}
+            # fallback to using the latest cache if no exact match is found
+            - v1-dependencies-
+
+      - run:
+          name: install dependencies
+          command: |
+            python3 -m venv venv
+            . venv/bin/activate
+            pip install -r requirements.txt
+
+      - save_cache:
+          paths:
+            - ./venv
+          key: v1-dependencies-{{ checksum "requirements.txt" }}
+
+      # run tests!
+      # this example uses Django's built-in test-runner
+      # other common Python testing frameworks include pytest and nose
+      # https://pytest.org
+      # https://nose.readthedocs.io
+      - run:
+          name: run tests
+          command: |
+            . venv/bin/activate
+            python setup.py test
+
+      - store_artifacts:
+          path: test-reports
+          destination: test-reports
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/.travis.yml 
new/picard-release-2.2.3/.travis.yml
--- old/picard-release-2.2.2/.travis.yml        2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/.travis.yml        2019-11-06 13:33:23.000000000 
+0100
@@ -30,8 +30,7 @@
   - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.11.3 mutagen       discid"
   - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.10   mutagen       discid"
   - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.9    mutagen       discid"
-  - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8    mutagen       discid"
-  - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8    mutagen==1.37 discid"
+  - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8    mutagen==1.37 discid sip==4.19.8"
 matrix:
   include:
   - os: osx
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/NEWS.md 
new/picard-release-2.2.3/NEWS.md
--- old/picard-release-2.2.2/NEWS.md    2019-10-08 14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/NEWS.md    2019-11-06 13:33:23.000000000 +0100
@@ -1,3 +1,26 @@
+# Version 2.2.3 - 2019-11-06
+
+## Bug-fix
+- [PICARD-1633](https://tickets.metabrainz.org/browse/PICARD-1633) - macOS: 
Automatic locale detection broken
+- [PICARD-1634](https://tickets.metabrainz.org/browse/PICARD-1634) - macOS: 
File browser sets wrong horizontal scroll position
+- [PICARD-1635](https://tickets.metabrainz.org/browse/PICARD-1635) - 
Terminated randomly when deleting files when saving
+- [PICARD-1636](https://tickets.metabrainz.org/browse/PICARD-1636) - Default 
locale detection fails if locale categories have different locales
+- [PICARD-1637](https://tickets.metabrainz.org/browse/PICARD-1637) - Crash 
when saving after removing some underlying files
+- [PICARD-1640](https://tickets.metabrainz.org/browse/PICARD-1640) - Picard 
with --config-file parameter copies over legacy configuration
+- [PICARD-1642](https://tickets.metabrainz.org/browse/PICARD-1642) - Picard 
crashes on launch (AttributeError: 'NoneType' object has no attribute 
'setPopupMode')
+- [PICARD-1643](https://tickets.metabrainz.org/browse/PICARD-1643) - Performer 
with instruments containing non-ASCII characters are not written to Vorbis and 
APE tags
+- [PICARD-1644](https://tickets.metabrainz.org/browse/PICARD-1644) - Crash 
when initializing translations on Python 3.8
+- [PICARD-1647](https://tickets.metabrainz.org/browse/PICARD-1647) - macOS: 
Plugin enable/disable button does not always update the icon
+- [PICARD-1648](https://tickets.metabrainz.org/browse/PICARD-1648) - Crashes 
when using search dialogs
+- [PICARD-1651](https://tickets.metabrainz.org/browse/PICARD-1651) - File and 
release counts in status bar not updated when files get removed
+- [PICARD-1654](https://tickets.metabrainz.org/browse/PICARD-1654) - macOS: 
Logout button / username stays visible after logout
+- [PICARD-1655](https://tickets.metabrainz.org/browse/PICARD-1655) - macOS: 
Login dialog can be hidden behind options
+
+## Improvement
+- [PICARD-1630](https://tickets.metabrainz.org/browse/PICARD-1630) - Ensure 
FLAC metadata is visible/editable in Windows Explorer
+- [PICARD-1632](https://tickets.metabrainz.org/browse/PICARD-1632) - Tooltips 
for genre filter help hide too quickly
+
+
 # Version 2.2.2 - 2019-10-08
 
 ## Bug-fix
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/__init__.py 
new/picard-release-2.2.3/picard/__init__.py
--- old/picard-release-2.2.2/picard/__init__.py 2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/picard/__init__.py 2019-11-06 13:33:23.000000000 
+0100
@@ -24,7 +24,7 @@
 PICARD_APP_NAME = "Picard"
 PICARD_APP_ID = "org.musicbrainz.Picard"
 PICARD_DESKTOP_NAME = PICARD_APP_ID + ".desktop"
-PICARD_VERSION = (2, 2, 2, 'final', 0)
+PICARD_VERSION = (2, 2, 3, 'final', 0)
 
 
 # optional build version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/config.py 
new/picard-release-2.2.3/picard/config.py
--- old/picard-release-2.2.2/picard/config.py   2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/picard/config.py   2019-11-06 13:33:23.000000000 
+0100
@@ -113,14 +113,6 @@
         """Common initializer method for :meth:`from_app` and
         :meth:`from_file`."""
 
-        # If there are no settings, copy existing settings from old format
-        # (registry on windows systems)
-        if not self.allKeys():
-            oldFormat = QtCore.QSettings(PICARD_ORG_NAME, PICARD_APP_NAME)
-            for k in oldFormat.allKeys():
-                self.setValue(k, oldFormat.value(k))
-            self.sync()
-
         self.application = ConfigSection(self, "application")
         self.setting = ConfigSection(self, "setting")
         self.persist = ConfigSection(self, "persist")
@@ -145,6 +137,15 @@
         QtCore.QSettings.__init__(this, QtCore.QSettings.IniFormat,
                                   QtCore.QSettings.UserScope, PICARD_ORG_NAME,
                                   PICARD_APP_NAME, parent)
+
+        # If there are no settings, copy existing settings from old format
+        # (registry on windows systems)
+        if not this.allKeys():
+            oldFormat = QtCore.QSettings(PICARD_ORG_NAME, PICARD_APP_NAME)
+            for k in oldFormat.allKeys():
+                this.setValue(k, oldFormat.value(k))
+            this.sync()
+
         this.__initialize()
         return this
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/file.py 
new/picard-release-2.2.3/picard/file.py
--- old/picard-release-2.2.2/picard/file.py     2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/picard/file.py     2019-11-06 13:33:23.000000000 
+0100
@@ -258,11 +258,7 @@
             if config.setting["preserve_timestamps"]:
                 try:
                     self._preserve_times(old_filename, save)
-                except self.PreserveTimesStatError as why:
-                    log.warning(why)
-                    # we didn't save the file yet, bail out
-                    return None
-                except self.FilePreserveTimesUtimeError as why:
+                except self.PreserveTimesUtimeError as why:
                     log.warning(why)
             else:
                 save()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/formats/apev2.py 
new/picard-release-2.2.3/picard/formats/apev2.py
--- old/picard-release-2.2.2/picard/formats/apev2.py    2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/formats/apev2.py    2019-11-06 
13:33:23.000000000 +0100
@@ -250,8 +250,11 @@
 
     @classmethod
     def supports_tag(cls, name):
-        return (bool(name) and is_valid_key(name)
-                and name not in UNSUPPORTED_TAGS)
+        return (bool(name) and name not in UNSUPPORTED_TAGS
+                and (is_valid_key(name)
+                    or name.startswith('comment:')
+                    or name.startswith('lyrics:')
+                    or name.startswith('performer:')))
 
 
 class MusepackFile(APEv2File):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/formats/vorbis.py 
new/picard-release-2.2.3/picard/formats/vorbis.py
--- old/picard-release-2.2.2/picard/formats/vorbis.py   2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/formats/vorbis.py   2019-11-06 
13:33:23.000000000 +0100
@@ -69,6 +69,32 @@
     return INVALID_CHARS.search(key) is None
 
 
+def flac_sort_pics_after_tags(metadata_blocks):
+    """
+    Reorder the metadata_blocks so that all picture blocks are located after
+    the first Vorbis comment block.
+
+    Windows fails to read FLAC tags if the picture blocks are located before
+    the Vorbis comments. Reordering the blocks fixes this.
+    """
+    # First remember all picture blocks that are located before the tag block.
+    tagindex = 0
+    picblocks = []
+    for block in metadata_blocks:
+        if block.code == mutagen.flac.VCFLACDict.code:
+            tagindex = metadata_blocks.index(block)
+            break
+        elif block.code == mutagen.flac.Picture.code:
+            picblocks.append(block)
+    else:
+        return  # No tags found, nothing to sort
+
+    # Now move those picture block after the tag block, maintaining their 
order.
+    for pic in picblocks:
+        metadata_blocks.remove(pic)
+        metadata_blocks.insert(tagindex, pic)
+
+
 class VCommentFile(File):
 
     """Generic VComment-based file."""
@@ -244,7 +270,7 @@
             picture.width = image.width
             picture.height = image.height
             picture.type = image_type_as_id3_num(image.maintype)
-            if self._File == mutagen.flac.FLAC:
+            if is_flac:
                 file.add_picture(picture)
             else:
                 tags.setdefault("METADATA_BLOCK_PICTURE", []).append(
@@ -254,6 +280,9 @@
 
         self._remove_deleted_tags(metadata, file.tags)
 
+        if is_flac:
+            flac_sort_pics_after_tags(file.metadata_blocks)
+
         kwargs = {}
         if is_flac and config.setting["remove_id3_from_flac"]:
             kwargs["deleteid3"] = True
@@ -305,7 +334,10 @@
     def supports_tag(cls, name):
         unsupported_tags = ['r128_album_gain', 'r128_track_gain']
         return (bool(name) and name not in unsupported_tags
-                and is_valid_key(name))
+                and (is_valid_key(name)
+                    or name.startswith('comment:')
+                    or name.startswith('lyrics:')
+                    or name.startswith('performer:')))
 
 
 class FLACFile(VCommentFile):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/i18n.py 
new/picard-release-2.2.3/picard/i18n.py
--- old/picard-release-2.2.2/picard/i18n.py     2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/picard/i18n.py     2019-11-06 13:33:23.000000000 
+0100
@@ -63,12 +63,14 @@
                 import Foundation
                 defaults = Foundation.NSUserDefaults.standardUserDefaults()
                 current_locale = defaults.objectForKey_('AppleLanguages')[0]
+                current_locale = current_locale.replace('-', '_')
                 locale.setlocale(locale.LC_ALL, current_locale)
             except Exception as e:
                 logger(e)
         else:
             try:
-                current_locale = locale.setlocale(locale.LC_ALL, '')
+                locale.setlocale(locale.LC_ALL, '')
+                current_locale = '.'.join(locale.getlocale(locale.LC_MESSAGES))
             except Exception as e:
                 logger(e)
     os.environ['LANGUAGE'] = os.environ['LANG'] = current_locale
@@ -77,7 +79,7 @@
     try:
         logger("Loading gettext translation, localedir=%r", localedir)
         trans = gettext.translation("picard", localedir)
-        trans.install(True)
+        trans.install()
         _ngettext = trans.ngettext
         logger("Loading gettext translation (picard-countries), localedir=%r", 
localedir)
         trans_countries = gettext.translation("picard-countries", localedir)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/tagger.py 
new/picard-release-2.2.3/picard/tagger.py
--- old/picard-release-2.2.2/picard/tagger.py   2019-10-08 14:22:20.000000000 
+0200
+++ new/picard-release-2.2.3/picard/tagger.py   2019-11-06 13:33:23.000000000 
+0100
@@ -285,9 +285,13 @@
         webbrowser2.open(authorization_url)
         if not parent:
             parent = self.window
-        authorization_code, ok = QtWidgets.QInputDialog.getText(parent,
-            _("MusicBrainz Account"), _("Authorization code:"))
-        if ok:
+        dialog = QtWidgets.QInputDialog(parent)
+        dialog.setWindowModality(QtCore.Qt.WindowModal)
+        dialog.setWindowTitle(_("MusicBrainz Account"))
+        dialog.setLabelText(_("Authorization code:"))
+        status = dialog.exec_()
+        if status == QtWidgets.QDialog.Accepted:
+            authorization_code = dialog.textValue()
             self.webservice.oauth_manager.exchange_authorization_code(
                 authorization_code, scopes,
                 partial(self.on_mb_authorization_finished, callback))
@@ -677,6 +681,7 @@
                 self._acoustid.stop_analyze(file)
                 del self.files[file.filename]
                 file.remove(from_parent)
+        self.tagger_stats_changed.emit()
 
     def remove_album(self, album):
         """Remove the specified album."""
@@ -692,6 +697,7 @@
             self.nats = None
         self.album_removed.emit(album)
         run_album_post_removal_processors(album)
+        self.tagger_stats_changed.emit()
 
     def remove_nat(self, track):
         """Remove the specified non-album track."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/filebrowser.py 
new/picard-release-2.2.3/picard/ui/filebrowser.py
--- old/picard-release-2.2.2/picard/ui/filebrowser.py   2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/filebrowser.py   2019-11-06 
13:33:23.000000000 +0100
@@ -108,7 +108,8 @@
         level = -1
         super().scrollTo(index, scrolltype)
         parent = self.currentIndex().parent()
-        while parent.isValid():
+        root = self.rootIndex()
+        while parent.isValid() and parent != root:
             parent = parent.parent()
             level += 1
         pos_x = max(self.indentation() * level, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/itemviews.py 
new/picard-release-2.2.3/picard/ui/itemviews.py
--- old/picard-release-2.2.2/picard/ui/itemviews.py     2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/itemviews.py     2019-11-06 
13:33:23.000000000 +0100
@@ -17,6 +17,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 
USA.
 
+from collections import defaultdict
 from functools import partial
 from heapq import (
     heappop,
@@ -141,18 +142,18 @@
         TreeItem.text_color = self.palette().text().color()
         TreeItem.text_color_secondary = self.palette() \
             .brush(QtGui.QPalette.Disabled, QtGui.QPalette.Text).color()
-        TrackItem.track_colors = {
+        TrackItem.track_colors = defaultdict(lambda: TreeItem.text_color, {
             File.NORMAL: interface_colors.get_qcolor('entity_saved'),
             File.CHANGED: TreeItem.text_color,
             File.PENDING: interface_colors.get_qcolor('entity_pending'),
             File.ERROR: interface_colors.get_qcolor('entity_error'),
-        }
-        FileItem.file_colors = {
+        })
+        FileItem.file_colors = defaultdict(lambda: TreeItem.text_color, {
             File.NORMAL: TreeItem.text_color,
             File.CHANGED: TreeItem.text_color,
             File.PENDING: interface_colors.get_qcolor('entity_pending'),
             File.ERROR: interface_colors.get_qcolor('entity_error'),
-        }
+        })
 
     def save_state(self):
         config.persist["splitter_state"] = self.saveState()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/mainwindow.py 
new/picard-release-2.2.3/picard/ui/mainwindow.py
--- old/picard-release-2.2.2/picard/ui/mainwindow.py    2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/mainwindow.py    2019-11-06 
13:33:23.000000000 +0100
@@ -589,7 +589,8 @@
     def _update_cd_lookup_button(self):
         if len(self.cd_lookup_menu.actions()) > 1:
             button = self.toolbar.widgetForAction(self.cd_lookup_action)
-            button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
+            if button:
+                button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
             self.cd_lookup_action.setMenu(self.cd_lookup_menu)
         else:
             self.cd_lookup_action.setMenu(None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/general.py 
new/picard-release-2.2.3/picard/ui/options/general.py
--- old/picard-release-2.2.2/picard/ui/options/general.py       2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/options/general.py       2019-11-06 
13:33:23.000000000 +0100
@@ -103,6 +103,9 @@
             self.ui.logged_in.hide()
             self.ui.login.show()
             self.ui.logout.hide()
+        # Workaround for Qt not repainting the view on macOS after the changes.
+        # See https://tickets.metabrainz.org/browse/PICARD-1654
+        self.ui.vboxlayout.parentWidget().repaint()
 
     def login(self):
         self.tagger.mb_login(self.on_login_finished, self)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/genres.py 
new/picard-release-2.2.3/picard/ui/options/genres.py
--- old/picard-release-2.2.2/picard/ui/options/genres.py        2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/options/genres.py        2019-11-06 
13:33:23.000000000 +0100
@@ -94,11 +94,9 @@
         self.ui.setupUi(self)
 
         self.ui.genres_filter.setToolTip(_(TOOLTIP_GENRES_FILTER))
-        self.ui.genres_filter.setToolTipDuration(5000)
         
self.ui.genres_filter.textChanged.connect(self.update_test_genres_filter)
 
         self.ui.test_genres_filter.setToolTip(_(TOOLTIP_TEST_GENRES_FILTER))
-        self.ui.test_genres_filter.setToolTipDuration(5000)
         
self.ui.test_genres_filter.textChanged.connect(self.update_test_genres_filter)
 
         #FIXME: colors aren't great from accessibility POV
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/plugins.py 
new/picard-release-2.2.3/picard/ui/options/plugins.py
--- old/picard-release-2.2.2/picard/ui/options/plugins.py       2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/options/plugins.py       2019-11-06 
13:33:23.000000000 +0100
@@ -114,6 +114,9 @@
     @staticmethod
     def set_icon(button, stdicon):
         button.setIcon(button.style().standardIcon(getattr(QtWidgets.QStyle, 
stdicon)))
+        # Workaround for Qt sometimes not updating the icon.
+        # See https://tickets.metabrainz.org/browse/PICARD-1647
+        button.repaint()
 
     def show_install(self, button, mode):
         if mode == 'hide':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/picard-release-2.2.2/picard/ui/searchdialog/__init__.py 
new/picard-release-2.2.3/picard/ui/searchdialog/__init__.py
--- old/picard-release-2.2.2/picard/ui/searchdialog/__init__.py 2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/searchdialog/__init__.py 2019-11-06 
13:33:23.000000000 +0100
@@ -3,6 +3,7 @@
 # Picard, the next-generation MusicBrainz tagger
 # Copyright (C) 2016 Rahul Raturi
 # Copyright (C) 2018 Laurent Monin
+# 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
@@ -47,9 +48,8 @@
 
 class ResultTable(QtWidgets.QTableWidget):
 
-    def __init__(self, parent, column_titles):
-        super().__init__(0, len(column_titles), parent)
-        self.setHorizontalHeaderLabels(column_titles)
+    def __init__(self, parent):
+        super().__init__(parent)
         self.setSelectionMode(
             QtWidgets.QAbstractItemView.SingleSelection)
         self.setSelectionBehavior(
@@ -70,6 +70,13 @@
         self.verticalScrollBar().valueChanged.connect(emit_scrolled)
         
self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
 
+    def prepare(self, headers):
+        self.clear()
+        self.setColumnCount(len(headers))
+        self.setHorizontalHeaderLabels(headers)
+        self.setRowCount(0)
+        self.setSortingEnabled(False)
+
 
 class SearchBox(QtWidgets.QWidget):
 
@@ -193,7 +200,6 @@
     def __init__(self, parent, accept_button_title, show_search=True, 
search_type=None):
         super().__init__(parent)
         self.search_results = []
-        self.table = None
         self.show_search = show_search
         self.search_type = search_type
         self.search_box = None
@@ -203,6 +209,7 @@
         # matching label as values
         self.columns = None
         self.sorting_enabled = True
+        self.create_table()
         self.finished.connect(self.save_state)
 
     @property
@@ -283,20 +290,23 @@
     def add_widget_to_center_layout(self, widget):
         """Update center widget with new child. If child widget exists,
         schedule it for deletion."""
-        wid = self.center_layout.takeAt(0)
-        if wid:
-            if wid.widget().objectName() == "results_table":
-                self.table = None
-            wid.widget().deleteLater()
+        widget_item = self.center_layout.takeAt(0)
+        if widget_item:
+            current_widget = widget_item.widget()
+            current_widget.hide()
+            self.center_layout.removeWidget(current_widget)
+            if current_widget != self.table:
+                current_widget.deleteLater()
         self.center_layout.addWidget(widget)
+        widget.show()
 
     def show_progress(self):
-        self.progress_widget = QtWidgets.QWidget(self)
-        self.progress_widget.setObjectName("progress_widget")
-        layout = QtWidgets.QVBoxLayout(self.progress_widget)
-        text_label = QtWidgets.QLabel(_('<strong>Loading...</strong>'), 
self.progress_widget)
+        progress_widget = QtWidgets.QWidget(self)
+        progress_widget.setObjectName("progress_widget")
+        layout = QtWidgets.QVBoxLayout(progress_widget)
+        text_label = QtWidgets.QLabel(_('<strong>Loading...</strong>'), 
progress_widget)
         text_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom)
-        gif_label = QtWidgets.QLabel(self.progress_widget)
+        gif_label = QtWidgets.QLabel(progress_widget)
         movie = QtGui.QMovie(":/images/loader.gif")
         gif_label.setMovie(movie)
         movie.start()
@@ -304,8 +314,8 @@
         layout.addWidget(text_label)
         layout.addWidget(gif_label)
         layout.setContentsMargins(1, 1, 1, 1)
-        self.progress_widget.setLayout(layout)
-        self.add_widget_to_center_layout(self.progress_widget)
+        progress_widget.setLayout(layout)
+        self.add_widget_to_center_layout(progress_widget)
 
     def show_error(self, error, show_retry_button=False):
         """Display the error string.
@@ -314,42 +324,45 @@
             error -- Error string
             show_retry_button -- Whether to display retry button or not
         """
-        self.error_widget = QtWidgets.QWidget(self)
-        self.error_widget.setObjectName("error_widget")
-        layout = QtWidgets.QVBoxLayout(self.error_widget)
-        error_label = QtWidgets.QLabel(error, self.error_widget)
+        error_widget = QtWidgets.QWidget(self)
+        error_widget.setObjectName("error_widget")
+        layout = QtWidgets.QVBoxLayout(error_widget)
+        error_label = QtWidgets.QLabel(error, error_widget)
         error_label.setWordWrap(True)
         error_label.setAlignment(QtCore.Qt.AlignCenter)
         error_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
         layout.addWidget(error_label)
         if show_retry_button:
-            retry_widget = QtWidgets.QWidget(self.error_widget)
+            retry_widget = QtWidgets.QWidget(error_widget)
             retry_layout = QtWidgets.QHBoxLayout(retry_widget)
-            retry_button = QtWidgets.QPushButton(_("Retry"), self.error_widget)
+            retry_button = QtWidgets.QPushButton(_("Retry"), error_widget)
             retry_button.clicked.connect(self.retry)
             
retry_button.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, 
QtWidgets.QSizePolicy.Fixed))
             retry_layout.addWidget(retry_button)
             retry_layout.setAlignment(QtCore.Qt.AlignHCenter | 
QtCore.Qt.AlignTop)
             retry_widget.setLayout(retry_layout)
             layout.addWidget(retry_widget)
-        self.error_widget.setLayout(layout)
-        self.add_widget_to_center_layout(self.error_widget)
+        error_widget.setLayout(layout)
+        self.add_widget_to_center_layout(error_widget)
 
-    def prepare_table(self):
-        self.table = ResultTable(self, self.table_headers)
+    def create_table(self):
+        self.table = ResultTable(self)
         self.table.verticalHeader().setDefaultSectionSize(100)
         self.table.setSortingEnabled(False)
-        self.table.setObjectName("results_table")
         self.table.cellDoubleClicked.connect(self.accept)
-        self.restore_table_header_state()
-        self.add_widget_to_center_layout(self.table)
+        self.table.hide()
 
         def enable_accept_button():
             self.accept_button.setEnabled(True)
         self.table.itemSelectionChanged.connect(
             enable_accept_button)
 
+    def prepare_table(self):
+        self.table.prepare(self.table_headers)
+        self.restore_table_header_state()
+
     def show_table(self, sort_column=None, 
sort_order=QtCore.Qt.DescendingOrder):
+        self.add_widget_to_center_layout(self.table)
         
self.table.horizontalHeader().setSortIndicatorShown(self.sorting_enabled)
         self.table.setSortingEnabled(self.sorting_enabled)
         if self.sorting_enabled and sort_column:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/searchdialog/album.py 
new/picard-release-2.2.3/picard/ui/searchdialog/album.py
--- old/picard-release-2.2.2/picard/ui/searchdialog/album.py    2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/picard/ui/searchdialog/album.py    2019-11-06 
13:33:23.000000000 +0100
@@ -253,9 +253,6 @@
         If server replies without error, try to get small thumbnail of front
         coverart of the release.
         """
-        if not self.table:
-            return
-
         cover_cell.fetch_task = None
 
         if error:
@@ -292,9 +289,6 @@
         Args:
             row -- Album's row in results table
         """
-        if not self.table:
-            return
-
         cover_cell.fetch_task = None
 
         if error:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/requirements-macos.txt 
new/picard-release-2.2.3/requirements-macos.txt
--- old/picard-release-2.2.2/requirements-macos.txt     2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/requirements-macos.txt     2019-11-06 
13:33:23.000000000 +0100
@@ -1,3 +1,5 @@
 discid==1.2.0
 mutagen==1.42.0
+pyobjc-core==5.2
+pyobjc-framework-Cocoa==5.2
 PyQt5==5.13.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/test/formats/common.py 
new/picard-release-2.2.3/test/formats/common.py
--- old/picard-release-2.2.2/test/formats/common.py     2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/test/formats/common.py     2019-11-06 
13:33:23.000000000 +0100
@@ -354,6 +354,18 @@
                 self.assertNotIn('performer:piano', new_metadata)
 
         @skipUnlessTestfile
+        def test_save_performer(self):
+            if not self.format.supports_tag('performer:'):
+                return
+            instrument = "accordéon clavier « boutons »"
+            artist = "桑山哲也"
+            tag = "performer:" + instrument
+            metadata = Metadata({ tag: artist })
+            loaded_metadata = save_and_load_metadata(self.filename, metadata)
+            self.assertIn(tag, loaded_metadata)
+            self.assertEqual(artist, loaded_metadata[tag])
+
+        @skipUnlessTestfile
         def test_ratings(self):
             if not self.supports_ratings:
                 raise unittest.SkipTest("Ratings not supported")
@@ -409,7 +421,16 @@
             self.assertNotIn('artist', loaded_metadata)
             self.assertEqual(new_metadata['title'], loaded_metadata['title'])
 
-        def test_lyrcis_with_description(self):
-            metadata = Metadata({'lyrics:foo': 'bar'})
+        @skipUnlessTestfile
+        def test_lyrics_with_description(self):
+            metadata = Metadata({'lyrics:foó': 'bar'})
+            loaded_metadata = save_and_load_metadata(self.filename, metadata)
+            self.assertEqual(metadata['lyrics:foó'], loaded_metadata['lyrics'])
+
+        @skipUnlessTestfile
+        def test_comments_with_description(self):
+            if not self.format.supports_tag('comment:foó'):
+                return
+            metadata = Metadata({'comment:foó': 'bar'})
             loaded_metadata = save_and_load_metadata(self.filename, metadata)
-            self.assertEqual(metadata['lyrics:foo'], loaded_metadata['lyrics'])
+            self.assertEqual(metadata['comment:foó'], 
loaded_metadata['comment:foó'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_apev2.py 
new/picard-release-2.2.3/test/formats/test_apev2.py
--- old/picard-release-2.2.2/test/formats/test_apev2.py 2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/test/formats/test_apev2.py 2019-11-06 
13:33:23.000000000 +0100
@@ -71,6 +71,12 @@
             loaded_metadata = load_metadata(self.filename)
             self.assertEqual(0, len(loaded_metadata.images))
 
+        def test_supports_extended_tags(self):
+            performer_tag = "performer:accordéon clavier « boutons »"
+            self.assertTrue(self.format.supports_tag(performer_tag))
+            self.assertTrue(self.format.supports_tag('lyrics:foó'))
+            self.assertTrue(self.format.supports_tag('comment:foó'))
+
 
 class MonkeysAudioTest(CommonApeTests.ApeTestCase):
     testfile = 'test.ape'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_id3.py 
new/picard-release-2.2.3/test/formats/test_id3.py
--- old/picard-release-2.2.2/test/formats/test_id3.py   2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/test/formats/test_id3.py   2019-11-06 
13:33:23.000000000 +0100
@@ -229,7 +229,7 @@
             self.assertEqual(1, 
len(raw_metadata['TXXX:Replaygain_Album_Peak'].text))
             self.assertNotIn('TXXX:REPLAYGAIN_ALBUM_PEAK', raw_metadata)
 
-        def test_lyrcis_with_description(self):
+        def test_lyrics_with_description(self):
             metadata = Metadata({'lyrics:foo': 'bar'})
             loaded_metadata = save_and_load_metadata(self.filename, metadata)
             self.assertEqual(metadata['lyrics:foo'], 
loaded_metadata['lyrics:foo'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_vorbis.py 
new/picard-release-2.2.3/test/formats/test_vorbis.py
--- old/picard-release-2.2.2/test/formats/test_vorbis.py        2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/test/formats/test_vorbis.py        2019-11-06 
13:33:23.000000000 +0100
@@ -4,6 +4,12 @@
 import shutil
 from tempfile import mkstemp
 
+from mutagen.flac import (
+    Padding,
+    Picture,
+    VCFLACDict,
+)
+
 from test.picardtestcase import PicardTestCase
 
 from picard import (
@@ -12,6 +18,7 @@
 )
 from picard.coverart.image import CoverArtImage
 from picard.formats import vorbis
+from picard.metadata import Metadata
 
 from .common import (
     TAGS,
@@ -19,12 +26,14 @@
     load_metadata,
     load_raw,
     save_and_load_metadata,
+    save_metadata,
     save_raw,
     skipUnlessTestfile,
 )
 from .coverart import (
     CommonCoverArtTests,
     file_save_image,
+    load_coverart_file,
 )
 
 
@@ -117,6 +126,12 @@
             loaded_metadata = load_metadata(self.filename)
             self.assertEqual(0, len(loaded_metadata.images))
 
+        def test_supports_extended_tags(self):
+            performer_tag = "performer:accordéon clavier « boutons »"
+            self.assertTrue(self.format.supports_tag(performer_tag))
+            self.assertTrue(self.format.supports_tag('lyrics:foó'))
+            self.assertTrue(self.format.supports_tag('comment:foó'))
+
 
 class FLACTest(CommonVorbisTests.VorbisTestCase):
     testfile = 'test.flac'
@@ -137,6 +152,29 @@
         new_metadata = save_and_load_metadata(self.filename, original_metadata)
         self.assertEqual(new_metadata['~waveformatextensible_channel_mask'], 
'0x3')
 
+    @skipUnlessTestfile
+    def test_sort_pics_after_tags(self):
+        # First save file with pic block before tags
+        pic = Picture()
+        pic.data = load_coverart_file('mb.png')
+        f = load_raw(self.filename)
+        f.metadata_blocks.insert(1, pic)
+        f.save()
+
+        # Save the file with Picard
+        metadata = Metadata()
+        save_metadata(self.filename, metadata)
+
+        # Load raw file and verify picture block position
+        f = load_raw(self.filename)
+        tagindex = f.metadata_blocks.index(f.tags)
+        haspics = False
+        for b in f.metadata_blocks:
+            if b.code == Picture.code:
+                haspics = True
+                self.assertGreater(f.metadata_blocks.index(b), tagindex)
+        self.assertTrue(haspics, "Picture block expected, none found")
+
 
 class OggVorbisTest(CommonVorbisTests.VorbisTestCase):
     testfile = 'test.ogg'
@@ -208,6 +246,33 @@
         for key in INVALID_KEYS:
             self.assertFalse(vorbis.is_valid_key(key), '%r is invalid' % key)
 
+    def test_flac_sort_pics_after_tags(self):
+        pic1 = Picture()
+        pic2 = Picture()
+        pic3 = Picture()
+        tags = VCFLACDict()
+        pad = Padding()
+
+        blocks = []
+        vorbis.flac_sort_pics_after_tags(blocks)
+        self.assertEqual([], blocks)
+
+        blocks = [tags]
+        vorbis.flac_sort_pics_after_tags(blocks)
+        self.assertEqual([tags], blocks)
+
+        blocks = [tags, pad, pic1]
+        vorbis.flac_sort_pics_after_tags(blocks)
+        self.assertEqual([tags, pad, pic1], blocks)
+
+        blocks = [pic1, pic2, tags, pad, pic3]
+        vorbis.flac_sort_pics_after_tags(blocks)
+        self.assertEqual([tags, pic1, pic2, pad, pic3], blocks)
+
+        blocks = [pic1, pic2, pad, pic3]
+        vorbis.flac_sort_pics_after_tags(blocks)
+        self.assertEqual([pic1, pic2, pad, pic3], blocks)
+
 
 class FlacCoverArtTest(CommonCoverArtTests.CoverArtTestCase):
     testfile = 'test.flac'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/picard-release-2.2.2/test/test_taggenrefilter.py 
new/picard-release-2.2.3/test/test_taggenrefilter.py
--- old/picard-release-2.2.2/test/test_taggenrefilter.py        2019-10-08 
14:22:20.000000000 +0200
+++ new/picard-release-2.2.3/test/test_taggenrefilter.py        2019-11-06 
13:33:23.000000000 +0100
@@ -73,7 +73,6 @@
             -/r[io]ck$/
             -/disco+/
             +/discoooo/
-            +/*/
         """
         tag_filter = TagGenreFilter(filters)
 
@@ -92,6 +91,31 @@
         self.assertTrue(tag_filter.skip('xdiscooox'))
         self.assertFalse(tag_filter.skip('xdiscoooox'))
 
+    def test_regex_filter_keep_all(self):
+        filters = """
+            -/^j.zz/
+            -/r[io]ck$/
+            -/disco+/
+            +/discoooo/
+            +/.*/
+        """
+        tag_filter = TagGenreFilter(filters)
+
+        self.assertFalse(tag_filter.skip('jazz'))
+        self.assertFalse(tag_filter.skip('jizz'))
+        self.assertFalse(tag_filter.skip('jazz blues'))
+        self.assertFalse(tag_filter.skip('blues jazz'))
+
+        self.assertFalse(tag_filter.skip('rock'))
+        self.assertFalse(tag_filter.skip('blues rock'))
+        self.assertFalse(tag_filter.skip('blues rick'))
+        self.assertFalse(tag_filter.skip('rock blues'))
+
+        self.assertFalse(tag_filter.skip('disco'))
+        self.assertFalse(tag_filter.skip('xdiscox'))
+        self.assertFalse(tag_filter.skip('xdiscooox'))
+        self.assertFalse(tag_filter.skip('xdiscoooox'))
+
     def test_uppercased_filter(self):
         filters = """
             -JAZZ*


Reply via email to