Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions picard/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class ConfigSection(QtCore.QObject):

"""Configuration section."""

# Signal emitted when the value of a setting has changed.
setting_changed = QtCore.pyqtSignal(str, object, object)

def __init__(self, config, name):
super().__init__()
self.__qt_config = config
Expand All @@ -76,9 +79,12 @@ def __getitem__(self, name):
return self.value(name, opt, opt.default)

def __setitem__(self, name, value):
old_value = self.__getitem__(name)
key = self.key(name)
self.__qt_config.setValue(key, value)
self._memoization[key].dirty = True
if value != old_value:
self.setting_changed.emit(name, old_value, value)

def __contains__(self, name):
return self.__qt_config.contains(self.key(name))
Expand Down Expand Up @@ -136,9 +142,6 @@ class SettingConfigSection(ConfigSection):
PROFILES_KEY = 'user_profiles'
SETTINGS_KEY = 'user_profile_settings'

# Signal emitted when the value of a setting has changed.
setting_changed = QtCore.pyqtSignal(str, object, object)

@classmethod
def init_profile_options(cls):
ListOption.add_if_missing('profiles', cls.PROFILES_KEY, [])
Expand Down
4 changes: 3 additions & 1 deletion picard/extension_points/options_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# Copyright (C) 2006-2007 Lukáš Lalinský
# Copyright (C) 2009 Nikolai Prokoschenko
# Copyright (C) 2009, 2019-2022 Philipp Wolfer
# Copyright (C) 2009, 2019-2022, 2025 Philipp Wolfer
# Copyright (C) 2013, 2015, 2018-2024 Laurent Monin
# Copyright (C) 2016-2017 Sambhav Kothari
#
Expand All @@ -30,3 +30,5 @@

def register_options_page(page_class):
ext_point_options_pages.register(page_class.__module__, page_class)
for opt_name, opt_highlights in page_class.OPTIONS:
page_class.register_setting(opt_name, opt_highlights)
12 changes: 5 additions & 7 deletions picard/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,15 @@
# The translated title will be displayed in Profiles option page.
#
# 2. If the option is a 'setting' which is edited in one of the option pages,
# then the option can be registered in the `__init__()` method of the
# matching `OptionPage` declaration with a call to the page's
# `register_setting()` method.
# then the option must be added to the OPTIONS tuple in the class. The
# first parameter is the option name, the second is a list of UI elements
# to highlight if the option is part of an option profile. If the setting
# can be overridden in profiles, the `highlights` has to be a list of
# widget names associated with the option.
#
# Registering a setting allows it to be reset to the default when the user
# asks for it on the corresponding option page.
#
# If the setting can be overriden in profiles, the `highlights` parameter
# has to be set a list of widget names associated with the option.
#
#
# Please, try to keep options ordered by section and name in their own group.


Expand Down
19 changes: 18 additions & 1 deletion picard/ui/mainwindow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def setupUi(self):
function(self)

get_config().setting.setting_changed.connect(self.handle_settings_changed)
get_config().profiles.setting_changed.connect(self.handle_profiles_changed)

def handle_settings_changed(self, name, old_value, new_value):
if name == 'rename_files':
Expand All @@ -267,6 +268,10 @@ def handle_settings_changed(self, name, old_value, new_value):
elif name in {'file_renaming_scripts', 'selected_file_naming_script_id'}:
self._make_script_selector_menu()

def handle_profiles_changed(self, name, old_value, new_value):
if name == SettingConfigSection.PROFILES_KEY:
self._make_profile_selector_menu()

def set_processing(self, processing=True):
self.panel.set_processing(processing)

Expand Down Expand Up @@ -497,6 +502,9 @@ def _create_cd_lookup_menu(self):
menu.setIcon(icontheme.lookup('media-optical'))
menu.triggered.connect(self.tagger.lookup_cd)
self.cd_lookup_menu = menu
self._init_cd_lookup_menu()

def _init_cd_lookup_menu(self):
if discid is None:
log.warning("CDROM: discid library not found - Lookup CD functionality disabled")
self.enable_action(MainAction.CD_LOOKUP, False)
Expand Down Expand Up @@ -566,6 +574,14 @@ def toggle_tag_saving(self, checked):
config = get_config()
config.setting['dont_write_tags'] = not checked

def _reset_option_menu_state(self):
config = get_config()
self.actions[MainAction.ENABLE_RENAMING].setChecked(config.setting['rename_files'])
self.actions[MainAction.ENABLE_MOVING].setChecked(config.setting['move_files'])
self.actions[MainAction.ENABLE_TAG_SAVING].setChecked(not config.setting['dont_write_tags'])
self._make_script_selector_menu()
self._init_cd_lookup_menu()

def _get_selected_or_unmatched_files(self):
if self.selected_objects:
files = list(iter_files_from_objects(self.selected_objects))
Expand Down Expand Up @@ -1589,7 +1605,8 @@ def _update_profile_selection(self, profile_id):
for profile in option_profiles:
if profile['id'] == profile_id:
profile['enabled'] = not profile['enabled']
self._make_script_selector_menu()
config.profiles[SettingConfigSection.PROFILES_KEY] = option_profiles
self._reset_option_menu_state()
return

def show_new_user_dialog(self):
Expand Down
13 changes: 7 additions & 6 deletions picard/ui/options/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# Copyright (C) 2006-2007 Lukáš Lalinský
# Copyright (C) 2009 Nikolai Prokoschenko
# Copyright (C) 2009, 2019-2022 Philipp Wolfer
# Copyright (C) 2009, 2019-2022, 2025 Philipp Wolfer
# Copyright (C) 2013, 2015, 2018-2024 Laurent Monin
# Copyright (C) 2016-2017 Sambhav Kothari
#
Expand Down Expand Up @@ -53,7 +53,9 @@ class OptionsPage(QtWidgets.QWidget):
HELP_URL = None
STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold; padding: 2px; }"
STYLESHEET = "QLabel { qproperty-wordWrap: true; }"
OPTIONS = ()

_registered_settings = []
initialized = False
loaded = False

Expand All @@ -72,8 +74,6 @@ def on_destroyed(obj=None):
self.deleted = True
self.destroyed.connect(on_destroyed)

self._registered_settings = []

def set_dialog(self, dialog):
self.dialog = dialog

Expand Down Expand Up @@ -131,12 +131,13 @@ def live_checker(text):

regex_edit.textChanged.connect(live_checker)

def register_setting(self, name, highlights=None):
@classmethod
def register_setting(cls, name, highlights=None):
"""Register a setting edited in the page, used to restore defaults
and to highlight when profiles are used"""
option = Option.get('setting', name)
if option is None:
raise Exception(f"Cannot register setting for non-existing option {name}")
self._registered_settings.append(option)
OptionsPage._registered_settings.append(option)
if highlights is not None:
profile_groups_add_setting(self.NAME, name, tuple(highlights), title=self.TITLE)
profile_groups_add_setting(cls.NAME, name, tuple(highlights), title=cls.TITLE)
24 changes: 13 additions & 11 deletions picard/ui/options/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,25 @@ class AdvancedOptionsPage(OptionsPage):
ACTIVE = True
HELP_URL = "/config/options_advanced.html"

OPTIONS = (
('ignore_regex', ['ignore_regex']),
('ignore_hidden_files', ['ignore_hidden_files']),
('recursively_add_files', ['recursively_add_files']),
('ignore_track_duration_difference_under', ['ignore_track_duration_difference_under', 'label_track_duration_diff']),
('query_limit', ['query_limit', 'label_query_limit']),
('completeness_ignore_videos', ['completeness_ignore_videos']),
('completeness_ignore_pregap', ['completeness_ignore_pregap']),
('completeness_ignore_data', ['completeness_ignore_data']),
('completeness_ignore_silence', ['completeness_ignore_silence']),
('compare_ignore_tags', ['groupBox_ignore_tags']),
)

def __init__(self, parent=None):
super().__init__(parent=parent)
self.ui = Ui_AdvancedOptionsPage()
self.ui.setupUi(self)
self.init_regex_checker(self.ui.ignore_regex, self.ui.regex_error)

self.register_setting('ignore_regex', ['ignore_regex'])
self.register_setting('ignore_hidden_files', ['ignore_hidden_files'])
self.register_setting('recursively_add_files', ['recursively_add_files'])
self.register_setting('ignore_track_duration_difference_under', ['ignore_track_duration_difference_under', 'label_track_duration_diff'])
self.register_setting('query_limit', ['query_limit', 'label_query_limit'])
self.register_setting('completeness_ignore_videos', ['completeness_ignore_videos'])
self.register_setting('completeness_ignore_pregap', ['completeness_ignore_pregap'])
self.register_setting('completeness_ignore_data', ['completeness_ignore_data'])
self.register_setting('completeness_ignore_silence', ['completeness_ignore_silence'])
self.register_setting('compare_ignore_tags', ['groupBox_ignore_tags'])

def load(self):
config = get_config()
self.ui.ignore_regex.setText(config.setting['ignore_regex'])
Expand Down
6 changes: 4 additions & 2 deletions picard/ui/options/cdlookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class CDLookupOptionsPage(OptionsPage):
ACTIVE = True
HELP_URL = "/config/options_cdlookup.html"

OPTIONS = (
('cd_lookup_device', None),
)

def __init__(self, parent=None):
super().__init__(parent=parent)
self.ui = Ui_CDLookupOptionsPage()
Expand All @@ -60,8 +64,6 @@ def __init__(self, parent=None):
self._device_list = get_cdrom_drives()
self.ui.cd_lookup_device.addItems(self._device_list)

self.register_setting('cd_lookup_device')

def load(self):
config = get_config()
device = config.setting['cd_lookup_device']
Expand Down
28 changes: 15 additions & 13 deletions picard/ui/options/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ class CoverOptionsPage(OptionsPage):
ACTIVE = True
HELP_URL = "/config/options_cover.html"

OPTIONS = (
('save_images_to_tags', ['save_images_to_tags']),
('embed_only_one_front_image', ['cb_embed_front_only']),
('dont_replace_with_smaller_cover', ['cb_dont_replace_with_smaller']),
('dont_replace_cover_of_types', ['cb_never_replace_types']),
('dont_replace_included_types', ['dont_replace_included_types']),
('dont_replace_excluded_types', ['dont_replace_excluded_types']),
('save_images_to_files', ['save_images_to_files']),
('cover_image_filename', ['cover_image_filename']),
('save_images_overwrite', ['save_images_overwrite']),
('save_only_one_front_image', ['save_only_one_front_image']),
('image_type_as_filename', ['image_type_as_filename']),
('ca_providers', ['ca_providers_list']),
)

def __init__(self, parent=None):
super().__init__(parent=parent)
self.ui = Ui_CoverOptionsPage()
Expand All @@ -72,19 +87,6 @@ def __init__(self, parent=None):
self.move_view = MoveableListView(self.ui.ca_providers_list, self.ui.up_button,
self.ui.down_button)

self.register_setting('save_images_to_tags', ['save_images_to_tags'])
self.register_setting('embed_only_one_front_image', ['cb_embed_front_only'])
self.register_setting('dont_replace_with_smaller_cover', ['dont_replace_with_smaller_cover'])
self.register_setting('dont_replace_cover_of_types', ['dont_replace_cover_of_types'])
self.register_setting('dont_replace_included_types', ['dont_replace_included_types'])
self.register_setting('dont_replace_excluded_types', ['dont_replace_excluded_types'])
self.register_setting('save_images_to_files', ['save_images_to_files'])
self.register_setting('cover_image_filename', ['cover_image_filename'])
self.register_setting('save_images_overwrite', ['save_images_overwrite'])
self.register_setting('save_only_one_front_image', ['save_only_one_front_image'])
self.register_setting('image_type_as_filename', ['image_type_as_filename'])
self.register_setting('ca_providers', ['ca_providers_list'])

def restore_defaults(self):
# Remove previous entries
self.ui.ca_providers_list.clear()
Expand Down
37 changes: 20 additions & 17 deletions picard/ui/options/cover_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,30 @@ class CoverProcessingOptionsPage(OptionsPage):
PARENT = 'cover'
SORT_ORDER = 0

OPTIONS = (
('filter_cover_by_size', None),
('cover_minimum_width', None),
('cover_minimum_height', None),
('cover_tags_enlarge', None),
('cover_tags_resize', None),
('cover_tags_resize_target_width', None),
('cover_tags_resize_target_height', None),
('cover_tags_resize_mode', None),
('cover_tags_convert_images', None),
('cover_tags_convert_to_format', None),
('cover_file_enlarge', None),
('cover_file_resize', None),
('cover_file_resize_target_width', None),
('cover_file_resize_target_height', None),
('cover_file_resize_mode', None),
('cover_file_convert_images', None),
('cover_file_convert_to_format', None),
)

def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_CoverProcessingOptionsPage()
self.ui.setupUi(self)
self.register_setting('filter_cover_by_size')
self.register_setting('cover_minimum_width')
self.register_setting('cover_minimum_height')
self.register_setting('cover_tags_enlarge')
self.register_setting('cover_tags_resize')
self.register_setting('cover_tags_resize_target_width')
self.register_setting('cover_tags_resize_target_height')
self.register_setting('cover_tags_resize_mode')
self.register_setting('cover_tags_convert_images')
self.register_setting('cover_tags_convert_to_format')
self.register_setting('cover_file_enlarge')
self.register_setting('cover_file_resize')
self.register_setting('cover_file_resize_target_width')
self.register_setting('cover_file_resize_target_height')
self.register_setting('cover_file_resize_mode')
self.register_setting('cover_file_convert_images')
self.register_setting('cover_file_convert_to_format')

for resize_mode in COVER_RESIZE_MODES:
self.ui.tags_resize_mode.addItem(resize_mode.title, resize_mode.mode.value)
Expand Down
16 changes: 9 additions & 7 deletions picard/ui/options/fingerprinting.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ class FingerprintingOptionsPage(OptionsPage):
ACTIVE = True
HELP_URL = "/config/options_fingerprinting.html"

OPTIONS = (
('fingerprinting_system', None),
('acoustid_fpcalc', None),
('acoustid_apikey', None),
('ignore_existing_acoustid_fingerprints', None),
('save_acoustid_fingerprints', None),
('fpcalc_threads', None),
)

def __init__(self, parent=None):
super().__init__(parent=parent)
self._fpcalc_valid = True
Expand All @@ -79,13 +88,6 @@ def __init__(self, parent=None):
self.ui.acoustid_apikey_get.clicked.connect(self.acoustid_apikey_get)
self.ui.acoustid_apikey.setValidator(ApiKeyValidator())

self.register_setting('fingerprinting_system')
self.register_setting('acoustid_fpcalc')
self.register_setting('acoustid_apikey')
self.register_setting('ignore_existing_acoustid_fingerprints')
self.register_setting('save_acoustid_fingerprints')
self.register_setting('fpcalc_threads')

def load(self):
config = get_config()
if config.setting['fingerprinting_system'] == 'acoustid':
Expand Down
24 changes: 13 additions & 11 deletions picard/ui/options/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ class GeneralOptionsPage(OptionsPage):
ACTIVE = True
HELP_URL = "/config/options_general.html"

OPTIONS = (
('server_host', ['server_host']),
('server_port', ['server_port']),
('analyze_new_files', ['analyze_new_files']),
('cluster_new_files', ['cluster_new_files']),
('ignore_file_mbids', ['ignore_file_mbids']),
('check_for_plugin_updates', ['check_for_plugin_updates']),
('check_for_updates', ['check_for_updates']),
('update_check_days', ['update_check_days']),
('update_level', ['update_level']),
('use_server_for_submission', ['use_server_for_submission']),
)

def __init__(self, parent=None):
super().__init__(parent=parent)
self.ui = Ui_GeneralOptionsPage()
Expand All @@ -74,17 +87,6 @@ def __init__(self, parent=None):
self.ui.login_error.hide()
self.update_login_logout()

self.register_setting('server_host', ['server_host'])
self.register_setting('server_port', ['server_port'])
self.register_setting('analyze_new_files', ['analyze_new_files'])
self.register_setting('cluster_new_files', ['cluster_new_files'])
self.register_setting('ignore_file_mbids', ['ignore_file_mbids'])
self.register_setting('check_for_plugin_updates', ['check_for_plugin_updates'])
self.register_setting('check_for_updates', ['check_for_updates'])
self.register_setting('update_check_days', ['update_check_days'])
self.register_setting('update_level', ['update_level'])
self.register_setting('use_server_for_submission')

def load(self):
config = get_config()
self.ui.server_host.setEditText(config.setting['server_host'])
Expand Down
Loading
Loading