From d1dc5f35ad391abcb072044d10ddebd6d40ae122 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 14 May 2021 08:33:38 +0200 Subject: [PATCH 1/8] PICARD-2209: Ensure DesktopStatusIndicator is registered only once Otherwise this can lead to performance issues if the user minimizes / maximizes the main window multiple times, causing multiple DesktopStatusIndicator getting registered. --- picard/ui/mainwindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index ca12da3c1..c57ad8656 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -183,6 +183,7 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): def __init__(self, parent=None, disable_player=False): super().__init__(parent) + self._shown = False self.selected_objects = [] self.ignore_selection_changes = IgnoreSelectionContext(self.update_selection) self.toolbar = None @@ -286,8 +287,9 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry): def showEvent(self, event): super().showEvent(event) - if DesktopStatusIndicator: + if not self._shown and DesktopStatusIndicator: self.register_status_indicator(DesktopStatusIndicator(self.windowHandle())) + self._shown = True def closeEvent(self, event): config = get_config() From fe88a345afe0dbbe03f887146d72856090ae7b69 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 2 Jun 2021 09:02:43 +0200 Subject: [PATCH 2/8] PICARD-2219: Fix empty renaming script breaking the filename A renaming script evaluating to an empty name would cause files to be renamed to e.g. "_mp3", breaking the file extension. Now the original filename will be kept. --- picard/file.py | 4 ++++ test/test_file.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/picard/file.py b/picard/file.py index 6d422f9dc..940e3e9df 100644 --- a/picard/file.py +++ b/picard/file.py @@ -420,6 +420,8 @@ class File(QtCore.QObject, Item): metadata.update(file_metadata) (filename, new_metadata) = script_to_filename_with_metadata( naming_format, metadata, file=self, settings=settings) + if not filename: + return None # NOTE: the script_to_filename strips the extension away ext = new_metadata.get('~extension', file_extension) return filename + '.' + ext.lstrip('.') @@ -443,6 +445,8 @@ class File(QtCore.QObject, Item): naming_format = settings['file_naming_format'] if naming_format: new_filename = self._script_to_filename(naming_format, metadata, ext, settings) + if not new_filename: + new_filename = old_filename if not settings['rename_files']: new_filename = os.path.join(os.path.dirname(new_filename), old_filename) if not settings['move_files']: diff --git a/test/test_file.py b/test/test_file.py index 5cc37c4fa..98b329e54 100644 --- a/test/test_file.py +++ b/test/test_file.py @@ -218,6 +218,12 @@ class FileNamingTest(PicardTestCase): os.path.realpath('/somepath/subdir/somealbum/somefile.mp3'), filename) + def test_make_filename_empty_script(self): + config.setting['rename_files'] = True + config.setting['file_naming_format'] = '$noop()' + filename = self.file.make_filename(self.file.filename, self.metadata) + self.assertEqual(os.path.realpath('/somepath/somefile.mp3'), filename) + def test_make_filename_replace_trailing_dots(self): config.setting['rename_files'] = True config.setting['move_files'] = True From a6c7170b45293674b98b09083cf68aa0c11943b2 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 4 Jun 2021 17:42:21 +0200 Subject: [PATCH 3/8] PICARD-2226: Removed config object per thread again Syncing between those instances does not work as advertised, which causes missing updates on config changes. --- picard/config.py | 44 +----------------------------------------- test/picardtestcase.py | 3 --- test/test_config.py | 13 ------------- 3 files changed, 1 insertion(+), 59 deletions(-) diff --git a/picard/config.py b/picard/config.py index 09b0c156d..ee8d3e3fa 100644 --- a/picard/config.py +++ b/picard/config.py @@ -359,9 +359,6 @@ config = None setting = None persist = None -_thread_configs = {} -_thread_config_lock = threading.RLock() - def setup_config(app, filename=None): global config, setting, persist @@ -369,10 +366,8 @@ def setup_config(app, filename=None): config = Config.from_app(app) else: config = Config.from_file(app, filename) - _thread_configs[threading.get_ident()] = config setting = config.setting persist = config.persist - _init_purge_config_timer() def get_config(): @@ -380,41 +375,4 @@ def get_config(): Config objects for threads are created on demand and cached for later use. """ - thread_id = threading.get_ident() - thread_config = _thread_configs.get(thread_id) - if not thread_config: - if not config: - return None # Not yet initialized - _thread_config_lock.acquire() - try: - config_file = config.fileName() - log.debug('Instantiating Config for thread %s using %s.', thread_id, config_file) - thread_config = Config.from_file(None, config_file) - _thread_configs[thread_id] = thread_config - finally: - _thread_config_lock.release() - return thread_config - - -def _init_purge_config_timer(purge_interval_milliseconds=60000): - def run_purge_config_timer(): - purge_config_instances() - start_purge_config_timer() - - def start_purge_config_timer(): - QtCore.QTimer.singleShot(purge_interval_milliseconds, run_purge_config_timer) - - start_purge_config_timer() - - -def purge_config_instances(): - """Removes cached config instances for no longer active threads.""" - _thread_config_lock.acquire() - try: - all_threads = set([thread.ident for thread in threading.enumerate()]) - threads_config = set(_thread_configs) - for thread_id in threads_config.difference(all_threads): - log.debug('Purging config instance for thread %s.', thread_id) - del _thread_configs[thread_id] - finally: - _thread_config_lock.release() + return config diff --git a/test/picardtestcase.py b/test/picardtestcase.py index 030f20feb..620499566 100644 --- a/test/picardtestcase.py +++ b/test/picardtestcase.py @@ -29,7 +29,6 @@ from tempfile import ( mkdtemp, mkstemp, ) -import threading import unittest from unittest.mock import Mock @@ -81,8 +80,6 @@ class PicardTestCase(unittest.TestCase): fake_config = Mock() fake_config.setting = {} fake_config.persist = {} - # Make config object available to current thread - config._thread_configs[threading.get_ident()] = fake_config # Make config object available for legacy use config.config = fake_config config.setting = fake_config.setting diff --git a/test/test_config.py b/test/test_config.py index 974aa6e2d..c023d7219 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -23,11 +23,9 @@ import logging import os import shutil -import threading from test.picardtestcase import PicardTestCase -import picard.config from picard.config import ( BoolOption, Config, @@ -373,14 +371,3 @@ class TestPicardConfigVarOption(TestPicardConfigCommon): # store invalid value in config file directly self.config.setValue('setting/var_option', object) self.assertEqual(self.config.setting["var_option"], set(["a", "b"])) - - -class TestPurgeConfigInstancesTimer(TestPicardConfigCommon): - - def test_purge_inactive_config_instances(self): - thread_id = threading.get_ident() - self.assertIn(thread_id, picard.config._thread_configs) - picard.config._thread_configs['foo'] = {} - picard.config.purge_config_instances() - self.assertIn(thread_id, picard.config._thread_configs) - self.assertNotIn('foo', picard.config._thread_configs) From a7c854d30a6736e92646d18011dbd2b64f376989 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 19 May 2021 14:12:18 +0200 Subject: [PATCH 4/8] Added Malay translation for NSIS installer --- installer/i18n/sources/ms_MY.json | 16 ++++++++++++++++ installer/picard-setup.nsi.in | 1 + 2 files changed, 17 insertions(+) create mode 100644 installer/i18n/sources/ms_MY.json diff --git a/installer/i18n/sources/ms_MY.json b/installer/i18n/sources/ms_MY.json new file mode 100644 index 000000000..db9b28212 --- /dev/null +++ b/installer/i18n/sources/ms_MY.json @@ -0,0 +1,16 @@ +{ + "MsgAlreadyInstalled": "${PRODUCT_NAME} telah pun terpasang serta harus dirombak sekiranya mahu memasang naik taraf ini ke edaran ${PRODUCT_VERSION}. \n\nMaju dengan menekan \"OK\" atau batal dengan menekan \"Batal\".", + "MsgApplicationRunning": "Perisian ${PRODUCT_NAME} sedang berjalan. Sila tutup lalu cuba lagi.", + "MsgRequires64Bit": "Edaran ${PRODUCT_NAME} ini memerlukan sistem Windows 64 bit.", + "MuiDescriptionRequired": "Pasang ${PRODUCT_NAME} bersama fail perlu untuk dijalankan.", + "MuiDescriptionLang": "Pasang terjemahan ${PRODUCT_NAME} dalam pelbagai bahasa.", + "MuiDescriptionShortcuts": "Pasang pintasan bagi melancarkan ${PRODUCT_NAME}.", + "MuiDescriptionDesktop": "Pasang pintasan di paparan utama", + "MuiDescriptionStartMenu": "Pasang pintasan di Menu Mula", + "OptionRemoveSettings": "Alih keluar tetapan dan data peribadi", + "SectionDesktop": "Atas meja", + "SectionLanguages": "Bahasa", + "SectionRequired": "Fail atur cara (wajib)", + "SectionShortcuts": "Pintasan", + "SectionStartMenu": "Menu mula" +} diff --git a/installer/picard-setup.nsi.in b/installer/picard-setup.nsi.in index 8e3ddec41..a8589a027 100644 --- a/installer/picard-setup.nsi.in +++ b/installer/picard-setup.nsi.in @@ -100,6 +100,7 @@ ReserveFile "${NSISDIR}\Plugins\x86-unicode\InstallOptions.dll" !insertmacro LOAD_LANGUAGE "Italian" !insertmacro LOAD_LANGUAGE "Japanese" ; !insertmacro LOAD_LANGUAGE "Korean" +!insertmacro LOAD_LANGUAGE "Malay" ; !insertmacro LOAD_LANGUAGE "Norwegian" ; !insertmacro LOAD_LANGUAGE "Polish" ; !insertmacro LOAD_LANGUAGE "Portuguese" From ee58d90fc626e09d11e7fb5c3937e370a2ba68ac Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 7 May 2021 16:11:20 +0200 Subject: [PATCH 5/8] PICARD-2206: Fix tab order for option pages --- picard/ui/options/releases.py | 31 ++++++++++++------- picard/ui/ui_options_interface.py | 6 ++-- picard/ui/ui_options_metadata.py | 6 ++-- picard/ui/ui_options_network.py | 7 +++-- picard/ui/ui_options_script.py | 8 +++-- .../ui/ui_options_tags_compatibility_id3.py | 3 +- ui/options_interface.ui | 2 ++ ui/options_metadata.ui | 1 + ui/options_network.ui | 3 ++ ui/options_script.ui | 6 +++- ui/options_tags_compatibility_id3.ui | 1 + 11 files changed, 53 insertions(+), 21 deletions(-) diff --git a/picard/ui/options/releases.py b/picard/ui/options/releases.py index 24f341803..9f9df3483 100644 --- a/picard/ui/options/releases.py +++ b/picard/ui/options/releases.py @@ -175,11 +175,11 @@ class ReleasesOptionsPage(OptionsPage): def add_slider(name, griditer, context): label = pgettext_attributes(context, name) - self._release_type_sliders[name] = \ - ReleaseTypeScore(self.ui.type_group, - self.ui.gridLayout, - label, - next(griditer)) + self._release_type_sliders[name] = ReleaseTypeScore( + self.ui.type_group, + self.ui.gridLayout, + label, + next(griditer)) griditer = RowColIter(len(RELEASE_PRIMARY_GROUPS) + len(RELEASE_SECONDARY_GROUPS) @@ -190,16 +190,25 @@ class ReleasesOptionsPage(OptionsPage): key=lambda v: pgettext_attributes('release_group_secondary_type', v)): add_slider(name, griditer, context='release_group_secondary_type') - self.reset_preferred_types_btn = QtWidgets.QPushButton(self.ui.type_group) - self.reset_preferred_types_btn.setText(_("Reset all")) + reset_types_btn = QtWidgets.QPushButton(self.ui.type_group) + reset_types_btn.setText(_("Reset all")) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth()) - self.reset_preferred_types_btn.setSizePolicy(sizePolicy) + sizePolicy.setHeightForWidth(reset_types_btn.sizePolicy().hasHeightForWidth()) + reset_types_btn.setSizePolicy(sizePolicy) r, c = next(griditer) - self.ui.gridLayout.addWidget(self.reset_preferred_types_btn, r, c, 1, 2) - self.reset_preferred_types_btn.clicked.connect(self.reset_preferred_types) + self.ui.gridLayout.addWidget(reset_types_btn, r, c, 1, 2) + reset_types_btn.clicked.connect(self.reset_preferred_types) + + self.setTabOrder(reset_types_btn, self.ui.country_list) + self.setTabOrder(self.ui.country_list, self.ui.preferred_country_list) + self.setTabOrder(self.ui.preferred_country_list, self.ui.add_countries) + self.setTabOrder(self.ui.add_countries, self.ui.remove_countries) + self.setTabOrder(self.ui.remove_countries, self.ui.format_list) + self.setTabOrder(self.ui.format_list, self.ui.preferred_format_list) + self.setTabOrder(self.ui.preferred_format_list, self.ui.add_formats) + self.setTabOrder(self.ui.add_formats, self.ui.remove_formats) self.ui.add_countries.clicked.connect(self.add_preferred_countries) self.ui.remove_countries.clicked.connect(self.remove_preferred_countries) diff --git a/picard/ui/ui_options_interface.py b/picard/ui/ui_options_interface.py index 8ae1ebbb6..082383826 100644 --- a/picard/ui/ui_options_interface.py +++ b/picard/ui/ui_options_interface.py @@ -139,11 +139,13 @@ class Ui_InterfaceOptionsPage(object): InterfaceOptionsPage.setTabOrder(self.toolbar_multiselect, self.builtin_search) InterfaceOptionsPage.setTabOrder(self.builtin_search, self.use_adv_search_syntax) InterfaceOptionsPage.setTabOrder(self.use_adv_search_syntax, self.quit_confirmation) - InterfaceOptionsPage.setTabOrder(self.quit_confirmation, self.starting_directory) + InterfaceOptionsPage.setTabOrder(self.quit_confirmation, self.filebrowser_horizontal_autoscroll) + InterfaceOptionsPage.setTabOrder(self.filebrowser_horizontal_autoscroll, self.starting_directory) InterfaceOptionsPage.setTabOrder(self.starting_directory, self.starting_directory_path) InterfaceOptionsPage.setTabOrder(self.starting_directory_path, self.starting_directory_browse) InterfaceOptionsPage.setTabOrder(self.starting_directory_browse, self.ui_language) - InterfaceOptionsPage.setTabOrder(self.ui_language, self.toolbar_layout_list) + InterfaceOptionsPage.setTabOrder(self.ui_language, self.ui_theme) + InterfaceOptionsPage.setTabOrder(self.ui_theme, self.toolbar_layout_list) InterfaceOptionsPage.setTabOrder(self.toolbar_layout_list, self.add_button) InterfaceOptionsPage.setTabOrder(self.add_button, self.insert_separator_button) InterfaceOptionsPage.setTabOrder(self.insert_separator_button, self.up_button) diff --git a/picard/ui/ui_options_metadata.py b/picard/ui/ui_options_metadata.py index 1b98d547b..7afc0c9fb 100644 --- a/picard/ui/ui_options_metadata.py +++ b/picard/ui/ui_options_metadata.py @@ -3,8 +3,10 @@ # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_MetadataOptionsPage(object): def setupUi(self, MetadataOptionsPage): MetadataOptionsPage.setObjectName("MetadataOptionsPage") @@ -83,7 +85,8 @@ class Ui_MetadataOptionsPage(object): QtCore.QMetaObject.connectSlotsByName(MetadataOptionsPage) MetadataOptionsPage.setTabOrder(self.translate_artist_names, self.artist_locale) MetadataOptionsPage.setTabOrder(self.artist_locale, self.standardize_artists) - MetadataOptionsPage.setTabOrder(self.standardize_artists, self.convert_punctuation) + MetadataOptionsPage.setTabOrder(self.standardize_artists, self.standardize_instruments) + MetadataOptionsPage.setTabOrder(self.standardize_instruments, self.convert_punctuation) MetadataOptionsPage.setTabOrder(self.convert_punctuation, self.release_ars) MetadataOptionsPage.setTabOrder(self.release_ars, self.track_ars) MetadataOptionsPage.setTabOrder(self.track_ars, self.va_name) @@ -105,4 +108,3 @@ class Ui_MetadataOptionsPage(object): self.label_7.setText(_("Non-album tracks:")) self.nat_name_default.setText(_("Default")) self.va_name_default.setText(_("Default")) - diff --git a/picard/ui/ui_options_network.py b/picard/ui/ui_options_network.py index fea790560..565b0621a 100644 --- a/picard/ui/ui_options_network.py +++ b/picard/ui/ui_options_network.py @@ -124,11 +124,14 @@ class Ui_NetworkOptionsPage(object): self.retranslateUi(NetworkOptionsPage) QtCore.QMetaObject.connectSlotsByName(NetworkOptionsPage) - NetworkOptionsPage.setTabOrder(self.web_proxy, self.server_host) + NetworkOptionsPage.setTabOrder(self.web_proxy, self.proxy_type_http) + NetworkOptionsPage.setTabOrder(self.proxy_type_http, self.proxy_type_socks) + NetworkOptionsPage.setTabOrder(self.proxy_type_socks, self.server_host) NetworkOptionsPage.setTabOrder(self.server_host, self.server_port) NetworkOptionsPage.setTabOrder(self.server_port, self.username) NetworkOptionsPage.setTabOrder(self.username, self.password) - NetworkOptionsPage.setTabOrder(self.password, self.browser_integration) + NetworkOptionsPage.setTabOrder(self.password, self.transfer_timeout) + NetworkOptionsPage.setTabOrder(self.transfer_timeout, self.browser_integration) NetworkOptionsPage.setTabOrder(self.browser_integration, self.browser_integration_port) NetworkOptionsPage.setTabOrder(self.browser_integration_port, self.browser_integration_localhost_only) diff --git a/picard/ui/ui_options_script.py b/picard/ui/ui_options_script.py index 870ef23ac..eb3fcff24 100644 --- a/picard/ui/ui_options_script.py +++ b/picard/ui/ui_options_script.py @@ -95,9 +95,13 @@ class Ui_ScriptingOptionsPage(object): self.remove_button.clicked.connect(self.script_list.remove_selected_script) self.enable_tagger_scripts.toggled['bool'].connect(ScriptingOptionsPage.enable_tagger_scripts_toggled) QtCore.QMetaObject.connectSlotsByName(ScriptingOptionsPage) - ScriptingOptionsPage.setTabOrder(self.enable_tagger_scripts, self.add_button) - ScriptingOptionsPage.setTabOrder(self.add_button, self.script_list) + ScriptingOptionsPage.setTabOrder(self.enable_tagger_scripts, self.script_list) ScriptingOptionsPage.setTabOrder(self.script_list, self.tagger_script) + ScriptingOptionsPage.setTabOrder(self.tagger_script, self.add_button) + ScriptingOptionsPage.setTabOrder(self.add_button, self.scripting_documentation_button) + ScriptingOptionsPage.setTabOrder(self.scripting_documentation_button, self.move_up_button) + ScriptingOptionsPage.setTabOrder(self.move_up_button, self.move_down_button) + ScriptingOptionsPage.setTabOrder(self.move_down_button, self.remove_button) def retranslateUi(self, ScriptingOptionsPage): _translate = QtCore.QCoreApplication.translate diff --git a/picard/ui/ui_options_tags_compatibility_id3.py b/picard/ui/ui_options_tags_compatibility_id3.py index 840746d93..26d5e43d2 100644 --- a/picard/ui/ui_options_tags_compatibility_id3.py +++ b/picard/ui/ui_options_tags_compatibility_id3.py @@ -107,7 +107,8 @@ class Ui_TagsCompatibilityOptionsPage(object): TagsCompatibilityOptionsPage.setTabOrder(self.enc_utf8, self.enc_utf16) TagsCompatibilityOptionsPage.setTabOrder(self.enc_utf16, self.enc_iso88591) TagsCompatibilityOptionsPage.setTabOrder(self.enc_iso88591, self.id3v23_join_with) - TagsCompatibilityOptionsPage.setTabOrder(self.id3v23_join_with, self.write_id3v1) + TagsCompatibilityOptionsPage.setTabOrder(self.id3v23_join_with, self.itunes_compatible_grouping) + TagsCompatibilityOptionsPage.setTabOrder(self.itunes_compatible_grouping, self.write_id3v1) def retranslateUi(self, TagsCompatibilityOptionsPage): _translate = QtCore.QCoreApplication.translate diff --git a/ui/options_interface.ui b/ui/options_interface.ui index 67fab7485..1604ed7b4 100644 --- a/ui/options_interface.ui +++ b/ui/options_interface.ui @@ -305,10 +305,12 @@ builtin_search use_adv_search_syntax quit_confirmation + filebrowser_horizontal_autoscroll starting_directory starting_directory_path starting_directory_browse ui_language + ui_theme toolbar_layout_list add_button insert_separator_button diff --git a/ui/options_metadata.ui b/ui/options_metadata.ui index d78660740..1f2a607a2 100644 --- a/ui/options_metadata.ui +++ b/ui/options_metadata.ui @@ -166,6 +166,7 @@ translate_artist_names artist_locale standardize_artists + standardize_instruments convert_punctuation release_ars track_ars diff --git a/ui/options_network.ui b/ui/options_network.ui index 8e2a23643..71c639d8f 100644 --- a/ui/options_network.ui +++ b/ui/options_network.ui @@ -272,10 +272,13 @@ web_proxy + proxy_type_http + proxy_type_socks server_host server_port username password + transfer_timeout browser_integration browser_integration_port browser_integration_localhost_only diff --git a/ui/options_script.ui b/ui/options_script.ui index 8e0bf4c8b..0bd1576c1 100644 --- a/ui/options_script.ui +++ b/ui/options_script.ui @@ -209,9 +209,13 @@ enable_tagger_scripts - add_button script_list tagger_script + add_button + scripting_documentation_button + move_up_button + move_down_button + remove_button diff --git a/ui/options_tags_compatibility_id3.ui b/ui/options_tags_compatibility_id3.ui index 1c4b63def..ce3b77d2a 100644 --- a/ui/options_tags_compatibility_id3.ui +++ b/ui/options_tags_compatibility_id3.ui @@ -236,6 +236,7 @@ enc_utf16 enc_iso88591 id3v23_join_with + itunes_compatible_grouping write_id3v1 From b07b43dcdd9c34ceb0d5f19f023ab36b227f3daf Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Fri, 28 May 2021 12:02:18 +0200 Subject: [PATCH 6/8] PICARD-2214: Fix exception if script ends with backslash --- picard/script/parser.py | 2 ++ test/test_script.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/picard/script/parser.py b/picard/script/parser.py index 4da155dfe..247abf932 100644 --- a/picard/script/parser.py +++ b/picard/script/parser.py @@ -293,6 +293,8 @@ Grammar: text.append('\n') elif ch == 't': text.append('\t') + elif ch is None: + self.__raise_eof() elif ch not in "$%(),\\": self.__raise_char(ch) else: diff --git a/test/test_script.py b/test/test_script.py index 414d4cfba..765a94dd6 100644 --- a/test/test_script.py +++ b/test/test_script.py @@ -992,6 +992,12 @@ class ScriptParserTest(PicardTestCase): def test_char_escape(self): self.assertScriptResultEquals(r"\n\t\$\%\(\)\,\\", "\n\t$%(),\\") + def test_char_escape_unexpected_char(self): + self.assertRaises(ScriptSyntaxError, self.parser.eval, r'\x') + + def test_char_escape_end_of_file(self): + self.assertRaises(ScriptEndOfFile, self.parser.eval, 'foo\\') + def test_raise_unknown_function(self): self.assertRaises(ScriptUnknownFunction, self.parser.eval, '$unknownfn()') From 0817417fdf69a5f9098a69d30b46f8e2ddb468a0 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Wed, 5 May 2021 12:54:40 +0200 Subject: [PATCH 7/8] PICARD-2205: Update syntax highlighting to allow functions starting with _ or 0-9 The scripting syntax definition and parser implementation allow such functions. --- picard/script/parser.py | 4 ++-- picard/ui/widgets/scripttextedit.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/picard/script/parser.py b/picard/script/parser.py index 247abf932..72c37197f 100644 --- a/picard/script/parser.py +++ b/picard/script/parser.py @@ -204,8 +204,8 @@ Grammar: text ::= [^$%] | '\$' | '\%' | '\(' | '\)' | '\,' argtext ::= [^$%(),] | '\$' | '\%' | '\(' | '\)' | '\,' identifier ::= [a-zA-Z0-9_] - variable ::= '%' identifier '%' - function ::= '$' identifier '(' (argument (',' argument)*)? ')' + variable ::= '%' (identifier | ':')+ '%' + function ::= '$' (identifier)+ '(' (argument (',' argument)*)? ')' expression ::= (variable | function | text)* argument ::= (variable | function | argtext)* """ diff --git a/picard/ui/widgets/scripttextedit.py b/picard/ui/widgets/scripttextedit.py index 5258181b2..3c634b542 100644 --- a/picard/ui/widgets/scripttextedit.py +++ b/picard/ui/widgets/scripttextedit.py @@ -79,7 +79,7 @@ class TaggerScriptSyntaxHighlighter(QtGui.QSyntaxHighlighter): def __init__(self, document): super().__init__(document) syntax_theme = theme.syntax_theme - self.func_re = QtCore.QRegExp(r"\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*\(") + self.func_re = QtCore.QRegExp(r"\$(?!noop)[_a-zA-Z0-9]*\(") self.func_fmt = QtGui.QTextCharFormat() self.func_fmt.setFontWeight(QtGui.QFont.Bold) self.func_fmt.setForeground(syntax_theme.func) From d896013e8de1f2192456770e464d3bf4c544425c Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 6 Jun 2021 12:45:12 +0200 Subject: [PATCH 8/8] Release 2.6.3 --- NEWS.md | 12 ++++++++++++ picard/__init__.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 133f56584..2124443a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,15 @@ +# Version 2.6.3 - 2021-06-07 + +## Bugfixes + +- [PICARD-2205](https://tickets.metabrainz.org/browse/PICARD-2205) - Syntax highlighting ignores functions starting with underscore or numbers +- [PICARD-2206](https://tickets.metabrainz.org/browse/PICARD-2206) - Fix tab order in option pages +- [PICARD-2209](https://tickets.metabrainz.org/browse/PICARD-2209) - Minimizing / maximizing Picard window registers desktop status indicator multiple times +- [PICARD-2214](https://tickets.metabrainz.org/browse/PICARD-2214) - Backslash at end of script raises TypeError +- [PICARD-2219](https://tickets.metabrainz.org/browse/PICARD-2219) - Empty file naming script causes files to be renamed to _ext +- [PICARD-2226](https://tickets.metabrainz.org/browse/PICARD-2226) - Some config changes are not applied until restart + + # Version 2.6.2 - 2021-04-27 ## Bugfixes diff --git a/picard/__init__.py b/picard/__init__.py index 5cac2a8c4..742311f6f 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -41,7 +41,7 @@ PICARD_APP_NAME = "Picard" PICARD_DISPLAY_NAME = "MusicBrainz Picard" PICARD_APP_ID = "org.musicbrainz.Picard" PICARD_DESKTOP_NAME = PICARD_APP_ID + ".desktop" -PICARD_VERSION = Version(2, 6, 2, 'final', 0) +PICARD_VERSION = Version(2, 6, 3, 'final', 0) # optional build version