class FolksonomyOptionsPage(OptionsPage): NAME = "folsonomy" TITLE = N_("Folksonomy Tags") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True options = [ IntOption("setting", "max_tags", 5), IntOption("setting", "min_tag_usage", 90), TextOption("setting", "ignore_tags", "seen live,favorites,fixme,owned"), TextOption("setting", "join_tags", ""), BoolOption("setting", "only_my_tags", False), ] def __init__(self, parent=None): super(FolksonomyOptionsPage, self).__init__(parent) self.ui = Ui_FolksonomyOptionsPage() self.ui.setupUi(self) def load(self): self.ui.max_tags.setValue(self.config.setting["max_tags"]) self.ui.min_tag_usage.setValue(self.config.setting["min_tag_usage"]) self.ui.join_tags.setEditText(self.config.setting["join_tags"]) self.ui.ignore_tags.setText(self.config.setting["ignore_tags"]) self.ui.only_my_tags.setChecked(self.config.setting["only_my_tags"]) def save(self): self.config.setting["max_tags"] = self.ui.max_tags.value() self.config.setting["min_tag_usage"] = self.ui.min_tag_usage.value() self.config.setting["join_tags"] = self.ui.join_tags.currentText() self.config.setting["ignore_tags"] = self.ui.ignore_tags.text() self.config.setting["only_my_tags"] = self.ui.only_my_tags.isChecked()
def test_upgrade_to_v2_1_0_dev_1(self): BoolOption("setting", "use_genres", False) IntOption("setting", "max_genres", 5) IntOption("setting", "min_genre_usage", 90) TextOption("setting", "ignore_genres", "seen live, favorites, fixme, owned") TextOption("setting", "join_genres", "") BoolOption("setting", "only_my_genres", False) BoolOption("setting", "artists_genres", False) BoolOption("setting", "folksonomy_tags", False) self.config.setting['folksonomy_tags'] = True self.config.setting['max_tags'] = 6 self.config.setting['min_tag_usage'] = 85 self.config.setting['ignore_tags'] = "abc" self.config.setting['join_tags'] = "abc" self.config.setting['only_my_tags'] = True self.config.setting['artists_tags'] = True upgrade_to_v2_1_0_dev_1(self.config) self.assertEqual(self.config.setting['use_genres'], True) self.assertEqual(self.config.setting['max_genres'], 6) self.assertEqual(self.config.setting['min_genre_usage'], 85) self.assertEqual(self.config.setting['ignore_genres'], "abc") self.assertEqual(self.config.setting['join_genres'], "abc") self.assertEqual(self.config.setting['only_my_genres'], True) self.assertEqual(self.config.setting['artists_genres'], True) self.assertIn('folksonomy_tags', self.config.setting) self.assertNotIn('max_tags', self.config.setting) self.assertNotIn('min_tag_usage', self.config.setting) self.assertNotIn('ignore_tags', self.config.setting) self.assertNotIn('join_tags', self.config.setting) self.assertNotIn('only_my_tags', self.config.setting) self.assertNotIn('artists_tags', self.config.setting)
class RatingsOptionsPage(OptionsPage): NAME = "ratings" TITLE = N_("Ratings") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True HELP_URL = '/config/options_ratings.html' options = [ BoolOption("setting", "enable_ratings", False), TextOption("setting", "rating_user_email", "*****@*****.**"), BoolOption("setting", "submit_ratings", True), IntOption("setting", "rating_steps", 6), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RatingsOptionsPage() self.ui.setupUi(self) def load(self): config = get_config() self.ui.enable_ratings.setChecked(config.setting["enable_ratings"]) self.ui.rating_user_email.setText(config.setting["rating_user_email"]) self.ui.submit_ratings.setChecked(config.setting["submit_ratings"]) def save(self): config = get_config() config.setting["enable_ratings"] = self.ui.enable_ratings.isChecked() config.setting["rating_user_email"] = self.ui.rating_user_email.text() config.setting["submit_ratings"] = self.ui.submit_ratings.isChecked()
class BPMOptionsPage(OptionsPage): NAME = "bpm" TITLE = "BPM" PARENT = "plugins" ACTIVE = True options = [ IntOption("setting", "bpm_slider_parameter", 1) ] def __init__(self, parent=None): super(BPMOptionsPage, self).__init__(parent) self.ui = Ui_BPMOptionsPage() self.ui.setupUi(self) self.ui.slider_parameter.valueChanged.connect(self.update_parameters) def load(self): cfg = self.config.setting self.ui.slider_parameter.setValue(cfg["bpm_slider_parameter"]) def save(self): cfg = self.config.setting cfg["bpm_slider_parameter"] = self.ui.slider_parameter.value() def update_parameters(self): val = self.ui.slider_parameter.value() samplerate, buf_size, hop_size = [unicode(v) for v in bpm_slider_settings[val]] self.ui.samplerate_value.setText(samplerate) self.ui.win_s_value.setText(buf_size) self.ui.hop_s_value.setText(hop_size)
def setUp(self): super().setUp() self.tmp_directory = self.mktmpdir() self.configpath = os.path.join(self.tmp_directory, 'test.ini') shutil.copy(os.path.join('test', 'data', 'test.ini'), self.configpath) self.addCleanup(os.remove, self.configpath) self.config = Config.from_file(None, self.configpath) self.addCleanup(self.cleanup_config_obj) self.config.application["version"] = "testing" logging.disable(logging.ERROR) Option.registry = {} ListOption('profiles', self.PROFILES_KEY, []) Option('profiles', self.SETTINGS_KEY, {}) # Get valid profile option settings for testing option_settings = list(UserProfileGroups.get_all_settings_list()) self.test_setting_0 = option_settings[0] self.test_setting_1 = option_settings[1] self.test_setting_2 = option_settings[2] self.test_setting_3 = option_settings[3] TextOption("setting", self.test_setting_0, "abc") BoolOption("setting", self.test_setting_1, True) IntOption("setting", self.test_setting_2, 42) TextOption("setting", self.test_setting_3, "xyz")
class LastfmOptionsPage(OptionsPage): NAME = "lastfm" TITLE = "Last.fm" PARENT = "plugins" options = [ BoolOption("setting", "lastfm_use_track_tags", False), BoolOption("setting", "lastfm_use_artist_tags", False), IntOption("setting", "lastfm_min_tag_usage", 15), TextOption("setting", "lastfm_ignore_tags", "seen live,favorites"), TextOption("setting", "lastfm_join_tags", ""), ] def __init__(self, parent=None): super(LastfmOptionsPage, self).__init__(parent) self.ui = Ui_LastfmOptionsPage() self.ui.setupUi(self) def load(self): self.ui.use_track_tags.setChecked(self.config.setting["lastfm_use_track_tags"]) self.ui.use_artist_tags.setChecked(self.config.setting["lastfm_use_artist_tags"]) self.ui.min_tag_usage.setValue(self.config.setting["lastfm_min_tag_usage"]) self.ui.ignore_tags.setText(self.config.setting["lastfm_ignore_tags"]) self.ui.join_tags.setEditText(self.config.setting["lastfm_join_tags"]) def save(self): self.config.setting["lastfm_use_track_tags"] = self.ui.use_track_tags.isChecked() self.config.setting["lastfm_use_artist_tags"] = self.ui.use_artist_tags.isChecked() self.config.setting["lastfm_min_tag_usage"] = self.ui.min_tag_usage.value() self.config.setting["lastfm_ignore_tags"] = unicode(self.ui.ignore_tags.text()) self.config.setting["lastfm_join_tags"] = unicode(self.ui.join_tags.currentText())
def test_int_opt_set_read_back(self): IntOption("setting", "int_option", 666) # set option and read back self.config.setting["int_option"] = 333 self.assertEqual(self.config.setting["int_option"], 333) self.assertIs(type(self.config.setting["int_option"]), int)
class AdvancedOptionsPage(OptionsPage): NAME = "advanced" TITLE = N_("Advanced") PARENT = None SORT_ORDER = 90 ACTIVE = True HELP_URL = '/config/options_advanced.html' options = [ TextOption("setting", "ignore_regex", ""), BoolOption("setting", "ignore_hidden_files", False), BoolOption("setting", "recursively_add_files", True), IntOption("setting", "ignore_track_duration_difference_under", 2), BoolOption("setting", "completeness_ignore_videos", False), BoolOption("setting", "completeness_ignore_pregap", False), BoolOption("setting", "completeness_ignore_data", False), BoolOption("setting", "completeness_ignore_silence", False), ListOption("setting", "compare_ignore_tags", []), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_AdvancedOptionsPage() self.ui.setupUi(self) self.init_regex_checker(self.ui.ignore_regex, self.ui.regex_error) def load(self): config = get_config() self.ui.ignore_regex.setText(config.setting["ignore_regex"]) self.ui.ignore_hidden_files.setChecked(config.setting["ignore_hidden_files"]) self.ui.recursively_add_files.setChecked(config.setting["recursively_add_files"]) self.ui.ignore_track_duration_difference_under.setValue(config.setting["ignore_track_duration_difference_under"]) self.ui.completeness_ignore_videos.setChecked(config.setting["completeness_ignore_videos"]) 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"]) self.ui.compare_ignore_tags.set_user_sortable(False) def save(self): config = get_config() config.setting["ignore_regex"] = self.ui.ignore_regex.text() config.setting["ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked() config.setting["recursively_add_files"] = self.ui.recursively_add_files.isChecked() config.setting["ignore_track_duration_difference_under"] = self.ui.ignore_track_duration_difference_under.value() config.setting["completeness_ignore_videos"] = self.ui.completeness_ignore_videos.isChecked() 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()
def test_upgrade_to_v2_0_0_dev_3(self): IntOption("setting", "caa_image_size", 500) self.config.setting['caa_image_size'] = 0 upgrade_to_v2_0_0_dev_3(self.config) self.assertEqual(250, self.config.setting['caa_image_size']) self.config.setting['caa_image_size'] = 501 upgrade_to_v2_0_0_dev_3(self.config) self.assertEqual(501, self.config.setting['caa_image_size'])
def test_as_dict(self): TextOption("setting", "text_option", "abc") BoolOption("setting", "bool_option", True) IntOption("setting", "int_option", 42) self.config.setting["int_option"] = 123 expected = { "text_option": "abc", "bool_option": True, "int_option": 123, } self.assertEqual(expected, self.config.setting.as_dict())
class GeneralOptionsPage(OptionsPage): NAME = "general" TITLE = N_("General") PARENT = None SORT_ORDER = 1 ACTIVE = True options = [ TextOption("setting", "server_host", "musicbrainz.org"), IntOption("setting", "server_port", 80), TextOption("setting", "username", ""), PasswordOption("setting", "password", ""), BoolOption("setting", "analyze_new_files", False), BoolOption("setting", "ignore_file_mbids", False), ] def __init__(self, parent=None): super(GeneralOptionsPage, self).__init__(parent) self.ui = Ui_GeneralOptionsPage() self.ui.setupUi(self) mirror_servers = [ "musicbrainz.org", ] self.ui.server_host.addItems(sorted(mirror_servers)) def load(self): self.ui.server_host.setEditText(self.config.setting["server_host"]) self.ui.server_port.setValue(self.config.setting["server_port"]) self.ui.username.setText(self.config.setting["username"]) self.ui.password.setText(self.config.setting["password"]) self.ui.analyze_new_files.setChecked(self.config.setting["analyze_new_files"]) self.ui.ignore_file_mbids.setChecked(self.config.setting["ignore_file_mbids"]) def save(self): self.config.setting["server_host"] = unicode(self.ui.server_host.currentText()).strip() self.config.setting["server_port"] = self.ui.server_port.value() self.config.setting["username"] = unicode(self.ui.username.text()) # trivially encode the password, just to not make it so apparent self.config.setting["password"] = rot13(unicode(self.ui.password.text())) self.config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() self.config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked()
class ProxyOptionsPage(OptionsPage): NAME = "proxy" TITLE = N_("Web Proxy") PARENT = "advanced" SORT_ORDER = 10 ACTIVE = True options = [ BoolOption("setting", "use_proxy", False), TextOption("setting", "proxy_server_host", ""), IntOption("setting", "proxy_server_port", 80), TextOption("setting", "proxy_username", ""), TextOption("setting", "proxy_password", ""), ] def __init__(self, parent=None): super(ProxyOptionsPage, self).__init__(parent) self.ui = Ui_ProxyOptionsPage() self.ui.setupUi(self) def load(self): self.ui.web_proxy.setChecked(self.config.setting["use_proxy"]) self.ui.server_host.setText(self.config.setting["proxy_server_host"]) self.ui.server_port.setValue(self.config.setting["proxy_server_port"]) self.ui.username.setText(self.config.setting["proxy_username"]) self.ui.password.setText(self.config.setting["proxy_password"]) def save(self): self.config.setting["use_proxy"] = self.ui.web_proxy.isChecked() self.config.setting["proxy_server_host"] = unicode( self.ui.server_host.text()) self.config.setting["proxy_server_port"] = self.ui.server_port.value() self.config.setting["proxy_username"] = unicode( self.ui.username.text()) self.config.setting["proxy_password"] = unicode( self.ui.password.text()) self.tagger.xmlws.setup_proxy()
class ProviderOptionsCaa(ProviderOptions): """ Options for Cover Art Archive cover art provider """ HELP_URL = '/config/options_cover_art_archive.html' options = [ BoolOption("setting", "caa_approved_only", False), IntOption("setting", "caa_image_size", _CAA_IMAGE_SIZE_DEFAULT), ListOption("setting", "caa_image_types", _CAA_IMAGE_TYPE_DEFAULT_INCLUDE), BoolOption("setting", "caa_restrict_image_types", True), ListOption("setting", "caa_image_types_to_omit", _CAA_IMAGE_TYPE_DEFAULT_EXCLUDE), ] _options_ui = Ui_CaaOptions def __init__(self, parent=None): super().__init__(parent) self.ui.restrict_images_types.clicked.connect(self.update_caa_types) self.ui.select_caa_types.clicked.connect(self.select_caa_types) def restore_defaults(self): self.caa_image_types = _CAA_IMAGE_TYPE_DEFAULT_INCLUDE self.caa_image_types_to_omit = _CAA_IMAGE_TYPE_DEFAULT_EXCLUDE super().restore_defaults() def load(self): self.ui.cb_image_size.clear() for item_id, item in _CAA_THUMBNAIL_SIZE_MAP.items(): self.ui.cb_image_size.addItem(_(item.label), userData=item_id) config = get_config() size = config.setting["caa_image_size"] index = self.ui.cb_image_size.findData(size) if index < 0: index = self.ui.cb_image_size.findData(_CAA_IMAGE_SIZE_DEFAULT) self.ui.cb_image_size.setCurrentIndex(index) self.ui.cb_approved_only.setChecked(config.setting["caa_approved_only"]) self.ui.restrict_images_types.setChecked( config.setting["caa_restrict_image_types"]) self.caa_image_types = config.setting["caa_image_types"] self.caa_image_types_to_omit = config.setting["caa_image_types_to_omit"] self.update_caa_types() def save(self): config = get_config() size = self.ui.cb_image_size.currentData() config.setting["caa_image_size"] = size config.setting["caa_approved_only"] = \ self.ui.cb_approved_only.isChecked() config.setting["caa_restrict_image_types"] = \ self.ui.restrict_images_types.isChecked() config.setting["caa_image_types"] = self.caa_image_types config.setting["caa_image_types_to_omit"] = self.caa_image_types_to_omit def update_caa_types(self): enabled = self.ui.restrict_images_types.isChecked() self.ui.select_caa_types.setEnabled(enabled) def select_caa_types(self): (types, types_to_omit, ok) = CAATypesSelectorDialog.run( self, self.caa_image_types, self.caa_image_types_to_omit) if ok: self.caa_image_types = types self.caa_image_types_to_omit = types_to_omit
class NetworkOptionsPage(OptionsPage): NAME = "network" TITLE = N_("Network") PARENT = "advanced" SORT_ORDER = 10 ACTIVE = True HELP_URL = '/config/options_network.html' options = [ BoolOption("setting", "use_proxy", False), TextOption("setting", "proxy_type", "http"), TextOption("setting", "proxy_server_host", ""), IntOption("setting", "proxy_server_port", 80), TextOption("setting", "proxy_username", ""), TextOption("setting", "proxy_password", ""), BoolOption("setting", "browser_integration", True), IntOption("setting", "browser_integration_port", 8000), BoolOption("setting", "browser_integration_localhost_only", True), IntOption("setting", "network_transfer_timeout_seconds", 30), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_NetworkOptionsPage() self.ui.setupUi(self) def load(self): config = get_config() self.ui.web_proxy.setChecked(config.setting["use_proxy"]) if config.setting["proxy_type"] == 'socks': self.ui.proxy_type_socks.setChecked(True) else: self.ui.proxy_type_http.setChecked(True) self.ui.server_host.setText(config.setting["proxy_server_host"]) self.ui.server_port.setValue(config.setting["proxy_server_port"]) self.ui.username.setText(config.setting["proxy_username"]) self.ui.password.setText(config.setting["proxy_password"]) self.ui.transfer_timeout.setValue( config.setting["network_transfer_timeout_seconds"]) self.ui.browser_integration.setChecked( config.setting["browser_integration"]) self.ui.browser_integration_port.setValue( config.setting["browser_integration_port"]) self.ui.browser_integration_localhost_only.setChecked( config.setting["browser_integration_localhost_only"]) def save(self): config = get_config() config.setting["use_proxy"] = self.ui.web_proxy.isChecked() if self.ui.proxy_type_socks.isChecked(): config.setting["proxy_type"] = 'socks' else: config.setting["proxy_type"] = 'http' config.setting["proxy_server_host"] = self.ui.server_host.text() config.setting["proxy_server_port"] = self.ui.server_port.value() config.setting["proxy_username"] = self.ui.username.text() config.setting["proxy_password"] = self.ui.password.text() self.tagger.webservice.setup_proxy() transfer_timeout = self.ui.transfer_timeout.value() config.setting["network_transfer_timeout_seconds"] = transfer_timeout self.tagger.webservice.set_transfer_timeout(transfer_timeout) config.setting[ "browser_integration"] = self.ui.browser_integration.isChecked() config.setting[ "browser_integration_port"] = self.ui.browser_integration_port.value( ) config.setting["browser_integration_localhost_only"] = \ self.ui.browser_integration_localhost_only.isChecked() self.tagger.update_browser_integration()
class ProfilesOptionsPage(OptionsPage): NAME = "profiles" TITLE = N_("Option Profiles") PARENT = None SORT_ORDER = 10 ACTIVE = True HELP_URL = '/config/options_profiles.html' PROFILES_KEY = SettingConfigSection.PROFILES_KEY SETTINGS_KEY = SettingConfigSection.SETTINGS_KEY POSITION_KEY = "last_selected_profile_pos" EXPANDED_KEY = "profile_settings_tree_expanded_list" TREEWIDGETITEM_COLUMN = 0 options = [ IntOption("persist", POSITION_KEY, 0), ListOption("persist", EXPANDED_KEY, []) ] signal_refresh = QtCore.pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_ProfileEditorDialog() self.ui.setupUi(self) self.make_buttons() self.ui.profile_editor_splitter.setStretchFactor(1, 1) self.move_view = MoveableListView(self.ui.profile_list, self.ui.move_up_button, self.ui.move_down_button) self.ui.profile_list.itemChanged.connect(self.profile_item_changed) self.ui.profile_list.currentItemChanged.connect(self.current_item_changed) self.ui.profile_list.itemChanged.connect(self.reload_all_page_settings) self.ui.settings_tree.itemChanged.connect(self.set_profile_settings_changed) self.ui.settings_tree.itemExpanded.connect(self.update_current_expanded_items_list) self.ui.settings_tree.itemCollapsed.connect(self.update_current_expanded_items_list) self.current_profile_id = None self.expanded_sections = [] self.building_tree = False self.loading = False self.settings_changed = False self.ui.settings_tree.installEventFilter(self) def eventFilter(self, object, event): """Process selected events. """ event_type = event.type() if event_type == QtCore.QEvent.FocusOut and object == self.ui.settings_tree: if self.settings_changed: self.settings_changed = False self.update_values_in_profile_options() self.reload_all_page_settings() return False def make_buttons(self): """Make buttons and add them to the button bars. """ self.new_profile_button = QtWidgets.QPushButton(_('New')) self.new_profile_button.setToolTip(_("Create a new profile")) self.new_profile_button.clicked.connect(self.new_profile) self.ui.profile_list_buttonbox.addButton(self.new_profile_button, QtWidgets.QDialogButtonBox.ActionRole) self.copy_profile_button = QtWidgets.QPushButton(_('Copy')) self.copy_profile_button.setToolTip(_("Copy to a new profile")) self.copy_profile_button.clicked.connect(self.copy_profile) self.ui.profile_list_buttonbox.addButton(self.copy_profile_button, QtWidgets.QDialogButtonBox.ActionRole) self.delete_profile_button = QtWidgets.QPushButton(_('Delete')) self.delete_profile_button.setToolTip(_("Delete the profile")) self.delete_profile_button.clicked.connect(self.delete_profile) self.ui.profile_list_buttonbox.addButton(self.delete_profile_button, QtWidgets.QDialogButtonBox.ActionRole) def restore_defaults(self): """Remove all profiles and profile settings. """ self.ui.profile_list.clear() self.profile_settings = {} self.profile_selected() self.update_config_overrides() self.reload_all_page_settings() def load(self): """Load initial configuration. """ self.loading = True config = get_config() # Use deepcopy() to avoid changes made locally from being cascaded into `config.profiles` # before the user clicks "Make It So!" self.profile_settings = deepcopy(config.profiles[self.SETTINGS_KEY]) self.ui.profile_list.clear() for profile in config.profiles[self.PROFILES_KEY]: list_item = ProfileListWidgetItem(profile['title'], profile['enabled'], profile['id']) self.ui.profile_list.addItem(list_item) # Select the last selected profile item last_selected_profile_pos = config.persist[self.POSITION_KEY] self.expanded_sections = config.persist[self.EXPANDED_KEY] last_selected_profile = self.ui.profile_list.item(last_selected_profile_pos) settings = None if last_selected_profile: self.ui.profile_list.setCurrentItem(last_selected_profile) last_selected_profile.setSelected(True) id = last_selected_profile.profile_id self.current_profile_id = id settings = self.get_settings_for_profile(id) self.make_setting_tree(settings=settings) self.update_config_overrides() self.loading = False def update_config_overrides(self, reset=False): """Update the profile overrides used in `config.settings` when retrieving or saving a setting. Args: reset (bool, optional): Remove the profile overrides. Defaults to False. """ config = get_config() if reset: config.setting.set_profiles_override(None) config.setting.set_settings_override(None) else: config.setting.set_profiles_override(self._clean_and_get_all_profiles()) config.setting.set_settings_override(self.profile_settings) def get_settings_for_profile(self, id): """Get the settings for the specified profile ID. Automatically adds an empty settings dictionary if there is no settings dictionary found for the ID. Args: id (str): ID of the profile Returns: dict: Profile settings """ # Add empty settings dictionary if no dictionary found for the profile. # This happens when a new profile is created. if id not in self.profile_settings: self.profile_settings[id] = {} return self.profile_settings[id] def _all_profiles(self): """Get all profiles from the profiles list in order from top to bottom. Yields: dict: Profile information in a format for saving to the user settings """ for row in range(self.ui.profile_list.count()): item = self.ui.profile_list.item(row) yield item.get_dict() def make_setting_tree(self, settings=None): """Update the profile settings tree based on the settings provided. If no settings are provided, displays an empty tree. Args: settings (dict, optional): Dictionary of settings for the profile. Defaults to None. """ self.set_button_states() self.ui.settings_tree.clear() self.ui.settings_tree.setHeaderItem(QtWidgets.QTreeWidgetItem()) self.ui.settings_tree.setHeaderLabels([_("Settings to include in profile")]) if settings is None: return self.building_tree = True for id, group in UserProfileGroups.SETTINGS_GROUPS.items(): title = group["title"] group_settings = group["settings"] widget_item = QtWidgets.QTreeWidgetItem([title]) widget_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsAutoTristate) widget_item.setCheckState(self.TREEWIDGETITEM_COLUMN, QtCore.Qt.Unchecked) for setting in group_settings: child_item = QtWidgets.QTreeWidgetItem([_(setting.title)]) child_item.setData(0, QtCore.Qt.UserRole, setting.name) child_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) state = QtCore.Qt.Checked if settings and setting.name in settings else QtCore.Qt.Unchecked child_item.setCheckState(self.TREEWIDGETITEM_COLUMN, state) if setting.name in settings and settings[setting.name] is not None: value = settings[setting.name] else: value = None child_item.setToolTip(self.TREEWIDGETITEM_COLUMN, self.make_setting_value_text(setting.name, value)) widget_item.addChild(child_item) self.ui.settings_tree.addTopLevelItem(widget_item) if title in self.expanded_sections: widget_item.setExpanded(True) self.building_tree = False def _get_naming_script(self, config, value): if value in config.setting["file_renaming_scripts"]: return config.setting["file_renaming_scripts"][value]["title"] presets = {x["id"]: x["title"] for x in get_file_naming_script_presets()} if value in presets: return presets[value] return _("Unknown script") def _get_scripts_list(self, config, key, template, none_text): if not config.setting[key]: return _("No scripts in list") flag = False scripts = config.setting[key] value_text = _("Enabled tagging scripts of %i found:") % len(scripts) for (pos, name, enabled, script) in scripts: if enabled: flag = True value_text += template % name if not flag: value_text += " %s" % none_text return value_text def _get_ca_providers_list(self, config, key, template, none_text): flag = False providers = config.setting[key] value_text = _("Enabled providers of %i listed:") % len(providers) for (name, enabled) in providers: if enabled: flag = True value_text += template % name if not flag: value_text += " %s" % none_text return value_text def make_setting_value_text(self, key, value): ITEMS_TEMPLATE = "\n - %s" NONE_TEXT = _("None") config = get_config() if value is None: return NONE_TEXT if key == "selected_file_naming_script_id": return self._get_naming_script(config, value) if key == "list_of_scripts": return self._get_scripts_list(config, key, ITEMS_TEMPLATE, NONE_TEXT) if key == "ca_providers": return self._get_ca_providers_list(config, key, ITEMS_TEMPLATE, NONE_TEXT) if isinstance(value, str): return '"%s"' % value if type(value) in {bool, int, float}: return str(value) if type(value) in {set, tuple, list, dict}: return _("List of %i items") % len(value) return _("Unknown value format") def update_current_expanded_items_list(self): """Update the list of expanded sections in the settings tree for persistent settings. """ if self.building_tree: return self.expanded_sections = [] for i in range(self.ui.settings_tree.topLevelItemCount()): tl_item = self.ui.settings_tree.topLevelItem(i) if tl_item.isExpanded(): self.expanded_sections.append(tl_item.text(self.TREEWIDGETITEM_COLUMN)) def get_current_selected_item(self): """Gets the profile item currently selected in the profiles list. Returns: ProfileListWidgetItem: Currently selected item """ items = self.ui.profile_list.selectedItems() if items: return items[0] return None def profile_selected(self, update_settings=True): """Update working profile information for the selected item in the profiles list. Args: update_settings (bool, optional): Update settings tree. Defaults to True. """ item = self.get_current_selected_item() if item: id = item.profile_id self.current_profile_id = id if update_settings: settings = self.get_settings_for_profile(id) self.make_setting_tree(settings=settings) else: self.current_profile_id = None self.make_setting_tree(settings=None) def reload_all_page_settings(self): """Trigger a reload of the settings and highlights for all pages containing options that can be managed in a profile. """ self.signal_refresh.emit() def update_values_in_profile_options(self): """Update the current profile's settings dictionary from the settings tree. Note that this update is delayed to avoid losing a profile's option setting value when a selected option (with an associated value) is de-selected and then re-selected. """ if not self.current_profile_id: return checked_items = set(self.get_checked_items_from_tree()) settings = set(self.profile_settings[self.current_profile_id].keys()) # Add new items to settings for item in checked_items.difference(settings): self.profile_settings[self.current_profile_id][item] = None # Remove unchecked items from settings for item in settings.difference(checked_items): del self.profile_settings[self.current_profile_id][item] def profile_item_changed(self, item): """Check title is not blank and remove leading and trailing spaces. Args: item (ProfileListWidgetItem): Item that changed """ if not self.loading: text = item.text().strip() if not text: QtWidgets.QMessageBox( QtWidgets.QMessageBox.Warning, _("Invalid Title"), _("The profile title cannot be blank."), QtWidgets.QMessageBox.Ok, self ).exec_() item.setText(self.ui.profile_list.unique_profile_name()) elif text != item.text(): # Remove leading and trailing spaces from new title. item.setText(text) self.update_config_overrides() self.reload_all_page_settings() def current_item_changed(self, new_item, old_item): """Update the display when a new item is selected in the profile list. Args: new_item (ProfileListWidgetItem): Newly selected item old_item (ProfileListWidgetItem): Previously selected item """ if self.loading: return # Set self.loading to avoid looping through the `.currentItemChanged` event. self.loading = True self.ui.profile_list.setCurrentItem(new_item) self.loading = False self.profile_selected() def get_checked_items_from_tree(self): """Get the keys for the settings that are checked in the profile settings tree. Yields: str: Settings key """ for i in range(self.ui.settings_tree.topLevelItemCount()): tl_item = self.ui.settings_tree.topLevelItem(i) for j in range(tl_item.childCount()): item = tl_item.child(j) if item.checkState(self.TREEWIDGETITEM_COLUMN) == QtCore.Qt.Checked: yield item.data(self.TREEWIDGETITEM_COLUMN, QtCore.Qt.UserRole) def set_profile_settings_changed(self): """Set flag to trigger option page updates later (when focus is lost from the settings tree) to avoid updating after each change to the settings selected for a profile. """ if self.current_profile_id: self.settings_changed = True def copy_profile(self): """Make a copy of the currently selected profile. """ item = self.get_current_selected_item() id = str(uuid.uuid4()) settings = deepcopy(self.profile_settings[self.current_profile_id]) self.profile_settings[id] = settings base_title = "%s %s" % (get_base_title(item.name), _(DEFAULT_COPY_TEXT)) name = self.ui.profile_list.unique_profile_name(base_title) self.ui.profile_list.add_profile(name=name, profile_id=id) self.update_config_overrides() self.reload_all_page_settings() def new_profile(self): """Add a new profile with no settings selected. """ self.ui.profile_list.add_profile() self.update_config_overrides() self.reload_all_page_settings() def delete_profile(self): """Delete the current profile. """ self.ui.profile_list.remove_selected_profile() self.profile_selected() self.update_config_overrides() self.reload_all_page_settings() def _clean_and_get_all_profiles(self): """Returns the list of profiles, adds any missing profile settings, and removes any "orphan" profile settings (i.e. settings dictionaries not associated with an existing profile). Returns: list: List of profiles suitable for storing in `config.profiles`. """ all_profiles = list(self._all_profiles()) all_profile_ids = set(x['id'] for x in all_profiles) keys = set(self.profile_settings.keys()) # Add any missing profile settings for id in all_profile_ids.difference(keys): self.profile_settings[id] = {} # Remove any "orphan" profile settings for id in keys.difference(all_profile_ids): del self.profile_settings[id] return all_profiles def save(self): """Save any changes to the current profile's settings, and save all updated profile information to the user settings. """ config = get_config() config.profiles[self.PROFILES_KEY] = self._clean_and_get_all_profiles() config.profiles[self.SETTINGS_KEY] = self.profile_settings config.persist[self.POSITION_KEY] = self.ui.profile_list.currentRow() config.persist[self.EXPANDED_KEY] = self.expanded_sections def set_button_states(self): """Set the enabled / disabled states of the buttons. """ state = self.current_profile_id is not None self.copy_profile_button.setEnabled(state) self.delete_profile_button.setEnabled(state)
class GeneralOptionsPage(OptionsPage): NAME = "general" TITLE = N_("General") PARENT = None SORT_ORDER = 1 ACTIVE = True HELP_URL = '/config/options_general.html' options = [ TextOption("setting", "server_host", MUSICBRAINZ_SERVERS[0]), IntOption("setting", "server_port", 443), BoolOption("setting", "use_server_for_submission", False), BoolOption("setting", "analyze_new_files", False), BoolOption("setting", "cluster_new_files", False), BoolOption("setting", "ignore_file_mbids", False), TextOption("persist", "oauth_refresh_token", ""), TextOption("persist", "oauth_refresh_token_scopes", ""), TextOption("persist", "oauth_access_token", ""), IntOption("persist", "oauth_access_token_expires", 0), TextOption("persist", "oauth_username", ""), BoolOption("setting", "check_for_updates", True), IntOption("setting", "update_check_days", 7), IntOption("setting", "update_level", 0), IntOption("persist", "last_update_check", 0), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_GeneralOptionsPage() self.ui.setupUi(self) self.ui.server_host.addItems(MUSICBRAINZ_SERVERS) self.ui.server_host.currentTextChanged.connect(self.update_server_host) self.ui.login.clicked.connect(self.login) self.ui.logout.clicked.connect(self.logout) self.ui.analyze_new_files.toggled.connect(self._update_cluster_new_files) self.ui.cluster_new_files.toggled.connect(self._update_analyze_new_files) self.ui.login_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.login_error.hide() self.update_login_logout() def load(self): config = get_config() self.ui.server_host.setEditText(config.setting["server_host"]) self.ui.server_port.setValue(config.setting["server_port"]) self.ui.use_server_for_submission.setChecked(config.setting["use_server_for_submission"]) self.update_server_host() self.ui.analyze_new_files.setChecked(config.setting["analyze_new_files"]) self.ui.cluster_new_files.setChecked(config.setting["cluster_new_files"]) self.ui.ignore_file_mbids.setChecked(config.setting["ignore_file_mbids"]) if self.tagger.autoupdate_enabled: self.ui.check_for_updates.setChecked(config.setting["check_for_updates"]) self.ui.update_level.clear() for level, description in PROGRAM_UPDATE_LEVELS.items(): # TODO: Remove temporary workaround once https://github.com/python-babel/babel/issues/415 has been resolved. babel_415_workaround = description['title'] self.ui.update_level.addItem(_(babel_415_workaround), level) self.ui.update_level.setCurrentIndex(self.ui.update_level.findData(config.setting["update_level"])) self.ui.update_check_days.setValue(config.setting["update_check_days"]) else: self.ui.update_check_groupbox.hide() def save(self): config = get_config() config.setting["server_host"] = self.ui.server_host.currentText().strip() config.setting["server_port"] = self.ui.server_port.value() config.setting["use_server_for_submission"] = self.ui.use_server_for_submission.isChecked() config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() config.setting["cluster_new_files"] = self.ui.cluster_new_files.isChecked() config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked() if self.tagger.autoupdate_enabled: config.setting["check_for_updates"] = self.ui.check_for_updates.isChecked() config.setting["update_level"] = self.ui.update_level.currentData(QtCore.Qt.ItemDataRole.UserRole) config.setting["update_check_days"] = self.ui.update_check_days.value() def update_server_host(self): host = self.ui.server_host.currentText().strip() if host and is_official_server(host): self.ui.server_host_primary_warning.hide() else: self.ui.server_host_primary_warning.show() def update_login_logout(self, error_msg=None): if self.deleted: return if self.tagger.webservice.oauth_manager.is_logged_in(): config = get_config() self.ui.logged_in.setText(_("Logged in as <b>%s</b>.") % config.persist["oauth_username"]) self.ui.logged_in.show() self.ui.login_error.hide() self.ui.login.hide() self.ui.logout.show() elif error_msg: self.ui.logged_in.hide() self.ui.login_error.setText(_('Login failed: %s') % error_msg) self.ui.login_error.show() self.ui.login.show() self.ui.logout.hide() else: self.ui.logged_in.hide() self.ui.login_error.hide() self.ui.login.show() self.ui.logout.hide() def login(self): self.tagger.mb_login(self.on_login_finished, self) def restore_defaults(self): super().restore_defaults() self.logout() def on_login_finished(self, successful, error_msg=None): self.update_login_logout(error_msg) def logout(self): self.tagger.mb_logout() self.update_login_logout() def _update_analyze_new_files(self, cluster_new_files): if cluster_new_files: self.ui.analyze_new_files.setChecked(False) def _update_cluster_new_files(self, analyze_new_files): if analyze_new_files: self.ui.cluster_new_files.setChecked(False)
class ScriptingOptionsPage(OptionsPage): NAME = "scripting" TITLE = N_("Scripting") PARENT = None SORT_ORDER = 85 ACTIVE = True HELP_URL = '/config/options_scripting.html' options = [ BoolOption("setting", "enable_tagger_scripts", False), ListOption("setting", "list_of_scripts", []), IntOption("persist", "last_selected_script_pos", 0), Option("persist", "scripting_splitter", QtCore.QByteArray()), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_ScriptingOptionsPage() self.ui.setupUi(self) self.ui.tagger_script.setEnabled(False) self.ui.splitter.setStretchFactor(0, 1) self.ui.splitter.setStretchFactor(1, 2) self.move_view = MoveableListView(self.ui.script_list, self.ui.move_up_button, self.ui.move_down_button) self.ui.scripting_documentation_button.clicked.connect(self.show_scripting_documentation) self.scripting_documentation_shown = None def show_scripting_documentation(self): if not self.scripting_documentation_shown: self.scriptdoc_dialog = ScriptingDocumentationDialog(parent=self) self.scriptdoc_dialog.show() else: self.scriptdoc_dialog.raise_() self.scriptdoc_dialog.activateWindow() def enable_tagger_scripts_toggled(self, on): if on and self.ui.script_list.count() == 0: self.ui.script_list.add_script() def script_selected(self): items = self.ui.script_list.selectedItems() if items: item = items[0] self.ui.tagger_script.setEnabled(True) self.ui.tagger_script.setText(item.script) self.ui.tagger_script.setFocus(QtCore.Qt.OtherFocusReason) else: self.ui.tagger_script.setEnabled(False) self.ui.tagger_script.setText("") def live_update_and_check(self): items = self.ui.script_list.selectedItems() if items: script = items[0] script.script = self.ui.tagger_script.toPlainText() self.ui.script_error.setStyleSheet("") self.ui.script_error.setText("") try: self.check() except OptionsCheckError as e: self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.script_error.setText(e.info) return def check(self): parser = ScriptParser() try: parser.eval(self.ui.tagger_script.toPlainText()) except Exception as e: raise ScriptCheckError(_("Script Error"), str(e)) def restore_defaults(self): # Remove existing scripts self.ui.script_list.clear() self.ui.tagger_script.setText("") super().restore_defaults() def load(self): config = get_config() self.ui.enable_tagger_scripts.setChecked(config.setting["enable_tagger_scripts"]) for pos, name, enabled, text in config.setting["list_of_scripts"]: list_item = ScriptListWidgetItem(name, enabled, text) self.ui.script_list.addItem(list_item) # Select the last selected script item last_selected_script_pos = config.persist["last_selected_script_pos"] last_selected_script = self.ui.script_list.item(last_selected_script_pos) if last_selected_script: self.ui.script_list.setCurrentItem(last_selected_script) last_selected_script.setSelected(True) self.restore_state() def _all_scripts(self): for row in range(0, self.ui.script_list.count()): item = self.ui.script_list.item(row) yield item.get_all() @restore_method def restore_state(self): # Preserve previous splitter position config = get_config() self.ui.splitter.restoreState(config.persist["scripting_splitter"]) def save(self): config = get_config() config.setting["enable_tagger_scripts"] = self.ui.enable_tagger_scripts.isChecked() config.setting["list_of_scripts"] = list(self._all_scripts()) config.persist["last_selected_script_pos"] = self.ui.script_list.currentRow() config.persist["scripting_splitter"] = self.ui.splitter.saveState() def display_error(self, error): # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): super().display_error(error)
def test_int_opt_convert(self): opt = IntOption("setting", "int_option", 666) self.assertEqual(opt.convert("123"), 123)
class LogView(LogViewCommon): options = [ IntOption("setting", "log_verbosity", log.VERBOSITY_DEFAULT), ] def __init__(self, parent=None): super().__init__(log.main_tail, _("Log"), parent=parent) self.verbosity = log.get_effective_level() self._setup_formats() self.hl_text = '' self.hl = None self.hbox = QtWidgets.QHBoxLayout() self.vbox.addLayout(self.hbox) self.verbosity_menu_button = QtWidgets.QPushButton(_("Verbosity")) self.hbox.addWidget(self.verbosity_menu_button) self.verbosity_menu = VerbosityMenu() self.verbosity_menu.set_verbosity(self.verbosity) self.verbosity_menu.verbosity_changed.connect(self._verbosity_changed) self.verbosity_menu_button.setMenu(self.verbosity_menu) # highlight input self.highlight_text = QtWidgets.QLineEdit() self.highlight_text.setPlaceholderText(_("String to highlight")) self.highlight_text.textEdited.connect(self._highlight_text_edited) self.hbox.addWidget(self.highlight_text) # highlight button self.highlight_button = QtWidgets.QPushButton(_("Highlight")) self.hbox.addWidget(self.highlight_button) self.highlight_button.setDefault(True) self.highlight_button.setEnabled(False) self.highlight_button.clicked.connect(self._highlight_do) self.highlight_text.returnPressed.connect(self.highlight_button.click) # clear highlight button self.clear_highlight_button = QtWidgets.QPushButton( _("Clear Highlight")) self.hbox.addWidget(self.clear_highlight_button) self.clear_highlight_button.setEnabled(False) self.clear_highlight_button.clicked.connect(self._clear_highlight_do) # clear log self.clear_log_button = QtWidgets.QPushButton(_("Clear Log")) self.hbox.addWidget(self.clear_log_button) self.clear_log_button.clicked.connect(self._clear_log_do) # save as self.save_log_as_button = QtWidgets.QPushButton(_("Save As...")) self.hbox.addWidget(self.save_log_as_button) self.save_log_as_button.clicked.connect(self._save_log_as_do) self._prev_logitem_level = log.VERBOSITY_DEFAULT def _clear_highlight_do(self): self.highlight_text.setText('') self.highlight_button.setEnabled(False) self._highlight_do() def _highlight_text_edited(self, text): if text and self.hl_text != text: self.highlight_button.setEnabled(True) else: self.highlight_button.setEnabled(False) if not text: self.clear_highlight_button.setEnabled(bool(self.hl)) def _highlight_do(self): new_hl_text = self.highlight_text.text() if new_hl_text != self.hl_text: self.hl_text = new_hl_text if self.hl is not None: self.hl.setDocument(None) self.hl = None if self.hl_text: self.hl = Highlighter(self.hl_text, parent=self.doc) self.clear_highlight_button.setEnabled(bool(self.hl)) def _setup_formats(self): interface_colors.load_from_config() self.formats = {} for level, feat in log.levels_features.items(): text_fmt = QtGui.QTextCharFormat() text_fmt.setFontFamily(FONT_FAMILY_MONOSPACE) text_fmt.setForeground(interface_colors.get_qcolor(feat.color_key)) self.formats[level] = text_fmt def _format(self, level): return self.formats[level] def _save_log_as_do(self): path, ok = QtWidgets.QFileDialog.getSaveFileName( self, caption=_("Save Log View to File"), options=QtWidgets.QFileDialog.DontConfirmOverwrite) if ok and path: if os.path.isfile(path): reply = QtWidgets.QMessageBox.question( self, _("Save Log View to File"), _("File already exists, do you really want to save to this file?" ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: return writer = QtGui.QTextDocumentWriter(path) writer.setFormat(b"plaintext") success = writer.write(self.doc) if not success: QtWidgets.QMessageBox.critical( self, _("Failed to save Log View to file"), _("Something prevented data to be written to '%s'") % writer.fileName()) def show(self): self.highlight_text.setFocus(QtCore.Qt.OtherFocusReason) super().show() def _clear_log_do(self): reply = QtWidgets.QMessageBox.question( self, _("Clear Log"), _("Are you sure you want to clear the log?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: return self.log_tail.clear() self.display(clear=True) def is_shown(self, logitem): return logitem.level >= self.verbosity def _add_entry(self, logitem): if not self.is_shown(logitem): return if self._prev_logitem_level != logitem.level: self.textCursor.setBlockCharFormat(self._format(logitem.level)) self._prev_logitem_level = logitem.level super()._add_entry(logitem) def _set_verbosity(self, level): self.verbosity = level self.verbosity_menu.set_verbosity(self.verbosity) def _verbosity_changed(self, level): if level != self.verbosity: config = get_config() config.setting['log_verbosity'] = level QtCore.QObject.tagger.set_log_level(level) self.verbosity = level self.display(clear=True)
def test_int_opt_set_none(self): IntOption("setting", "int_option", 666) # set option to None self.config.setting["int_option"] = None self.assertEqual(self.config.setting["int_option"], 666)
def test_int_opt_direct_invalid(self): IntOption("setting", "int_option", 666) # store invalid int value in config file directly self.config.setValue('setting/int_option', 'x333') self.assertEqual(self.config.setting["int_option"], 666)
class GenresOptionsPage(OptionsPage): NAME = "genres" TITLE = N_("Genres") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True HELP_URL = '/config/options_genres.html' options = [ BoolOption("setting", "use_genres", False), IntOption("setting", "max_genres", 5), IntOption("setting", "min_genre_usage", 90), TextOption("setting", "genres_filter", "-seen live\n-favorites\n-fixme\n-owned"), TextOption("setting", "join_genres", ""), BoolOption("setting", "only_my_genres", False), BoolOption("setting", "artists_genres", False), BoolOption("setting", "folksonomy_tags", False), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_GenresOptionsPage() self.ui.setupUi(self) self.ui.genres_filter.setToolTip(_(TOOLTIP_GENRES_FILTER)) 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.textChanged.connect( self.update_test_genres_filter) # FIXME: colors aren't great from accessibility POV self.fmt_keep = QTextBlockFormat() self.fmt_keep.setBackground(Qt.green) self.fmt_skip = QTextBlockFormat() self.fmt_skip.setBackground(Qt.red) self.fmt_clear = QTextBlockFormat() self.fmt_clear.clearBackground() def load(self): config = get_config() self.ui.use_genres.setChecked(config.setting["use_genres"]) self.ui.max_genres.setValue(config.setting["max_genres"]) self.ui.min_genre_usage.setValue(config.setting["min_genre_usage"]) self.ui.join_genres.setEditText(config.setting["join_genres"]) self.ui.genres_filter.setPlainText(config.setting["genres_filter"]) self.ui.only_my_genres.setChecked(config.setting["only_my_genres"]) self.ui.artists_genres.setChecked(config.setting["artists_genres"]) self.ui.folksonomy_tags.setChecked(config.setting["folksonomy_tags"]) def save(self): config = get_config() config.setting["use_genres"] = self.ui.use_genres.isChecked() config.setting["max_genres"] = self.ui.max_genres.value() config.setting["min_genre_usage"] = self.ui.min_genre_usage.value() config.setting["join_genres"] = self.ui.join_genres.currentText() config.setting["genres_filter"] = self.ui.genres_filter.toPlainText() config.setting["only_my_genres"] = self.ui.only_my_genres.isChecked() config.setting["artists_genres"] = self.ui.artists_genres.isChecked() config.setting["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked() def update_test_genres_filter(self): test_text = self.ui.test_genres_filter.toPlainText() filters = self.ui.genres_filter.toPlainText() tagfilter = TagGenreFilter(filters) # FIXME: very simple error reporting, improve self.ui.label_test_genres_filter_error.setText("\n".join([ _("Error line %d: %s") % (lineno + 1, error) for lineno, error in tagfilter.errors.items() ])) def set_line_fmt(lineno, textformat): obj = self.ui.test_genres_filter if lineno < 0: # use current cursor position cursor = obj.textCursor() else: cursor = QTextCursor(obj.document().findBlockByNumber(lineno)) obj.blockSignals(True) cursor.setBlockFormat(textformat) obj.blockSignals(False) set_line_fmt(-1, self.fmt_clear) for lineno, line in enumerate(test_text.splitlines()): line = line.strip() fmt = self.fmt_clear if line: if tagfilter.skip(line): fmt = self.fmt_skip else: fmt = self.fmt_keep set_line_fmt(lineno, fmt)
def test_int_opt_not_int(self): IntOption("setting", "int_option", 666) # set option to invalid value self.config.setting["int_option"] = 'invalid' self.assertEqual(self.config.setting["int_option"], 666)
def test_int_opt_no_config(self): IntOption("setting", "int_option", 666) # test default, nothing in config yet self.assertEqual(self.config.setting["int_option"], 666) self.assertIs(type(self.config.setting["int_option"]), int)
class GeneralOptionsPage(OptionsPage): NAME = "general" TITLE = N_("General") PARENT = None SORT_ORDER = 1 ACTIVE = True HELP_URL = '/config/options_general.html' options = [ TextOption("setting", "server_host", MUSICBRAINZ_SERVERS[0]), IntOption("setting", "server_port", 443), TextOption("persist", "oauth_refresh_token", ""), BoolOption("setting", "analyze_new_files", False), BoolOption("setting", "ignore_file_mbids", False), TextOption("persist", "oauth_refresh_token", ""), TextOption("persist", "oauth_refresh_token_scopes", ""), TextOption("persist", "oauth_access_token", ""), IntOption("persist", "oauth_access_token_expires", 0), TextOption("persist", "oauth_username", ""), BoolOption("setting", "check_for_updates", True), IntOption("setting", "update_check_days", 7), IntOption("setting", "update_level", 0), IntOption("persist", "last_update_check", 0), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_GeneralOptionsPage() self.ui.setupUi(self) self.ui.server_host.addItems(MUSICBRAINZ_SERVERS) self.ui.login.clicked.connect(self.login) self.ui.logout.clicked.connect(self.logout) self.update_login_logout() def load(self): config = get_config() self.ui.server_host.setEditText(config.setting["server_host"]) self.ui.server_port.setValue(config.setting["server_port"]) self.ui.analyze_new_files.setChecked(config.setting["analyze_new_files"]) self.ui.ignore_file_mbids.setChecked(config.setting["ignore_file_mbids"]) if self.tagger.autoupdate_enabled: self.ui.check_for_updates.setChecked(config.setting["check_for_updates"]) self.ui.update_level.clear() for level, description in PROGRAM_UPDATE_LEVELS.items(): # TODO: Remove temporary workaround once https://github.com/python-babel/babel/issues/415 has been resolved. babel_415_workaround = description['title'] self.ui.update_level.addItem(_(babel_415_workaround), level) self.ui.update_level.setCurrentIndex(self.ui.update_level.findData(config.setting["update_level"])) self.ui.update_check_days.setValue(config.setting["update_check_days"]) else: self.ui.update_check_groupbox.hide() def save(self): config = get_config() config.setting["server_host"] = self.ui.server_host.currentText().strip() config.setting["server_port"] = self.ui.server_port.value() config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked() if self.tagger.autoupdate_enabled: config.setting["check_for_updates"] = self.ui.check_for_updates.isChecked() config.setting["update_level"] = self.ui.update_level.currentData(QtCore.Qt.UserRole) config.setting["update_check_days"] = self.ui.update_check_days.value() def update_login_logout(self): if self.tagger.webservice.oauth_manager.is_logged_in(): config = get_config() self.ui.logged_in.setText(_("Logged in as <b>%s</b>.") % config.persist["oauth_username"]) self.ui.logged_in.show() self.ui.login.hide() self.ui.logout.show() else: 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) def restore_defaults(self): super().restore_defaults() self.logout() def on_login_finished(self, successful): self.update_login_logout() def logout(self): self.tagger.mb_logout() self.update_login_logout()
class ProfileEditorDialog(SingletonDialog, PicardDialog): """User Profile Editor Page """ TITLE = N_("Option profile editor") STYLESHEET_ERROR = OptionsPage.STYLESHEET_ERROR help_url = PICARD_URLS["doc_profile_edit"] PROFILES_KEY = SettingConfigSection.PROFILES_KEY SETTINGS_KEY = SettingConfigSection.SETTINGS_KEY POSITION_KEY = "last_selected_profile_pos" EXPANDED_KEY = "profile_settings_tree_expanded_list" TREEWIDGETITEM_COLUMN = 0 options = [ IntOption("persist", POSITION_KEY, 0), ListOption("persist", EXPANDED_KEY, []) ] signal_save = QtCore.pyqtSignal() def __init__(self, parent=None): """Option profile editor. """ super().__init__(parent) self.main_window = parent self.ITEMS_TEMPLATES = { True: "\n ■ %s", False: "\n □ %s", } self.setWindowTitle(_(self.TITLE)) self.displaying = False self.loading = True self.ui = Ui_ProfileEditorDialog() self.ui.setupUi(self) self.setModal(True) self.make_buttons() self.ui.profile_editor_splitter.setStretchFactor(1, 1) self.move_view = MoveableListView(self.ui.profile_list, self.ui.move_up_button, self.ui.move_down_button) self.ui.profile_list.itemChanged.connect(self.profile_item_changed) self.ui.profile_list.currentItemChanged.connect(self.current_item_changed) self.ui.profile_list.itemSelectionChanged.connect(self.item_selection_changed) self.ui.profile_list.itemChanged.connect(self.profile_data_changed) self.ui.settings_tree.itemChanged.connect(self.save_profile) self.ui.settings_tree.itemExpanded.connect(self.update_current_expanded_items_list) self.ui.settings_tree.itemCollapsed.connect(self.update_current_expanded_items_list) self.current_profile_id = None self.expanded_sections = [] self.building_tree = False self.load() self.loading = False def make_buttons(self): """Make buttons and add them to the button bars. """ self.make_it_so_button = QtWidgets.QPushButton(_("Make It So!")) self.make_it_so_button.setToolTip(_("Save all profile information to the user settings")) self.ui.buttonbox.addButton(self.make_it_so_button, QtWidgets.QDialogButtonBox.AcceptRole) self.ui.buttonbox.accepted.connect(self.make_it_so) self.new_profile_button = QtWidgets.QPushButton(_('New')) self.new_profile_button.setToolTip(_("Create a new profile")) self.new_profile_button.clicked.connect(self.new_profile) self.ui.profile_list_buttonbox.addButton(self.new_profile_button, QtWidgets.QDialogButtonBox.ActionRole) self.copy_profile_button = QtWidgets.QPushButton(_('Copy')) self.copy_profile_button.setToolTip(_("Copy to a new profile")) self.copy_profile_button.clicked.connect(self.copy_profile) self.ui.profile_list_buttonbox.addButton(self.copy_profile_button, QtWidgets.QDialogButtonBox.ActionRole) self.delete_profile_button = QtWidgets.QPushButton(_('Delete')) self.delete_profile_button.setToolTip(_("Delete the profile")) self.delete_profile_button.clicked.connect(self.delete_profile) self.ui.profile_list_buttonbox.addButton(self.delete_profile_button, QtWidgets.QDialogButtonBox.ActionRole) self.cancel_button = QtWidgets.QPushButton(_('Cancel')) self.cancel_button.setToolTip(_("Close the profile editor without saving changes to the profiles")) self.ui.buttonbox.addButton(self.cancel_button, QtWidgets.QDialogButtonBox.RejectRole) self.ui.buttonbox.rejected.connect(self.close) self.ui.buttonbox.addButton(QtWidgets.QDialogButtonBox.Help) self.ui.buttonbox.helpRequested.connect(self.show_help) def load(self): """Load initial configuration. """ config = get_config() # Use deepcopy() to avoid changes made locally from being cascaded into `config.profiles` # before the user clicks "Make It So!" self.profile_settings = deepcopy(config.profiles[self.SETTINGS_KEY]) for profile in config.profiles[self.PROFILES_KEY]: list_item = ProfileListWidgetItem(profile['title'], profile['enabled'], profile['id']) self.ui.profile_list.addItem(list_item) self.all_profiles = list(self._all_profiles()) # Select the last selected profile item last_selected_profile_pos = config.persist[self.POSITION_KEY] self.expanded_sections = config.persist[self.EXPANDED_KEY] last_selected_profile = self.ui.profile_list.item(last_selected_profile_pos) settings = None if last_selected_profile: self.ui.profile_list.setCurrentItem(last_selected_profile) last_selected_profile.setSelected(True) id = last_selected_profile.profile_id self.current_profile_id = id settings = self.get_settings_for_profile(id) self.make_setting_tree(settings=settings) def get_settings_for_profile(self, id): """Get the settings for the specified profile ID. Automatically adds an empty settings dictionary if there is no settings dictionary found for the ID. Args: id (str): ID of the profile Returns: dict: Profile settings """ # Add empty settings dictionary if no dictionary found for the profile. # This happens when a new profile is created. if id not in self.profile_settings: self.profile_settings[id] = {} return self.profile_settings[id] def get_current_selected_item(self): """Gets the profile item currently selected in the profiles list. Returns: ProfileListWidgetItem: Currently selected item """ items = self.ui.profile_list.selectedItems() if items: return items[0] return None def update_current_expanded_items_list(self): if self.building_tree: return self.expanded_sections = [] for i in range(self.ui.settings_tree.topLevelItemCount()): tl_item = self.ui.settings_tree.topLevelItem(i) if tl_item.isExpanded(): self.expanded_sections.append(tl_item.text(self.TREEWIDGETITEM_COLUMN)) def profile_selected(self, update_settings=True): """Update working profile information for the selected item in the profiles list. Args: update_settings (bool, optional): Update settings tree. Defaults to True. """ item = self.get_current_selected_item() if item: id = item.profile_id self.current_profile_id = id if update_settings: settings = self.get_settings_for_profile(id) self.make_setting_tree(settings=settings) else: self.current_profile_id = None self.make_setting_tree(settings=None) def make_setting_tree(self, settings=None): """Update the profile settings tree based on the settings provided. If no settings are provided, displays an empty tree. Args: settings (dict, optional): Dictionary of settings for the profile. Defaults to None. """ self.set_button_states() self.ui.settings_tree.clear() self.ui.settings_tree.setHeaderItem(QtWidgets.QTreeWidgetItem()) self.ui.settings_tree.setHeaderLabels([_("Settings to include in profile")]) if settings is None: return self.building_tree = True for id, group in UserProfileGroups.SETTINGS_GROUPS.items(): title = group["title"] group_settings = group["settings"] widget_item = QtWidgets.QTreeWidgetItem([title]) widget_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsAutoTristate) widget_item.setCheckState(self.TREEWIDGETITEM_COLUMN, QtCore.Qt.Unchecked) for setting in group_settings: child_item = QtWidgets.QTreeWidgetItem([_(setting.title)]) child_item.setData(0, QtCore.Qt.UserRole, setting.name) child_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) state = QtCore.Qt.Checked if settings and setting.name in settings else QtCore.Qt.Unchecked child_item.setCheckState(self.TREEWIDGETITEM_COLUMN, state) if settings and setting.name in settings and settings[setting.name] is not None: value = settings[setting.name] else: value = None child_item.setToolTip(self.TREEWIDGETITEM_COLUMN, self.make_setting_value_text(setting.name, value)) widget_item.addChild(child_item) self.ui.settings_tree.addTopLevelItem(widget_item) if title in self.expanded_sections: widget_item.setExpanded(True) self.building_tree = False def make_setting_value_text(self, key, value): if value is None: profile = self.get_controlling_profile(key) return _("Value not set (set in \"%s\" profile)") % profile if key == "selected_file_naming_script_id": return self.get_file_naming_script_name(value) if key == "list_of_scripts": return self.get_list_of_scripts() if key == "ca_providers": return self.get_list_of_ca_providers() if isinstance(value, str): return '"%s"' % value if isinstance(value, bool) or isinstance(value, int) or isinstance(value, float): return str(value) if isinstance(value, set) or isinstance(value, tuple) or isinstance(value, list) or isinstance(value, dict): return _("List of %i items") % len(value) return _("Unknown value format") def get_file_naming_script_name(self, script_id): config = get_config() scripts = config.setting["file_renaming_scripts"] if script_id in scripts: return scripts[script_id]["title"] presets = {x["id"]: x["title"] for x in get_file_naming_script_presets()} if script_id in presets: return presets[script_id] return _("Unknown script") def get_list_of_scripts(self): config = get_config() scripts = config.setting["list_of_scripts"] if scripts: value_text = _("Tagging scripts (%i found):") % len(scripts) for (pos, name, enabled, script) in scripts: value_text += self.ITEMS_TEMPLATES[enabled] % name else: value_text = _("No scripts in list") return value_text def get_list_of_ca_providers(self): config = get_config() providers = config.setting["ca_providers"] value_text = _("CA providers (%i found):") % len(providers) for (name, enabled) in providers: value_text += self.ITEMS_TEMPLATES[enabled] % name return value_text def get_controlling_profile(self, key): below_current_profile_flag = False for profile in self.all_profiles: if below_current_profile_flag: if profile["enabled"]: settings = self.profile_settings[profile["id"]] if key in settings and settings[key] is not None: return profile["title"] elif profile["id"] == self.current_profile_id: below_current_profile_flag = True return _("Default") def profile_data_changed(self): """Update the profile settings values displayed. """ self.all_profiles = list(self._all_profiles()) settings = self.get_settings_for_profile(id) for i in range(self.ui.settings_tree.topLevelItemCount()): group = self.ui.settings_tree.topLevelItem(i) for j in range(group.childCount()): child = group.child(j) key = child.data(self.TREEWIDGETITEM_COLUMN, QtCore.Qt.UserRole) if key in settings: value = settings[key] else: value = None child.setToolTip(self.TREEWIDGETITEM_COLUMN, self.make_setting_value_text(key, value)) def profile_item_changed(self, item): """Check title is not blank and remove leading and trailing spaces. Args: item (ProfileListWidgetItem): Item that changed """ if not self.loading: text = item.text().strip() if not text: QtWidgets.QMessageBox( QtWidgets.QMessageBox.Warning, _("Invalid Title"), _("The profile title cannot be blank."), QtWidgets.QMessageBox.Ok, self ).exec_() item.setText(_("Unnamed profile")) elif text != item.text(): # Remove leading and trailing spaces from new title. item.setText(text) def current_item_changed(self, new_item, old_item): """Update the display when a new item is selected in the profile list. Args: new_item (ProfileListWidgetItem): Newly selected item old_item (ProfileListWidgetItem): Previously selected item """ if self.loading: return self.save_profile() self.all_profiles = list(self._all_profiles()) self.set_current_item(new_item) self.profile_selected() def item_selection_changed(self): """Set tree list highlight bar to proper line if selection change canceled. """ item = self.ui.profile_list.currentItem() if item: item.setSelected(True) def set_current_item(self, item): """Sets the specified item as the current selection in the profiles list. Args: item (ProfileListWidgetItem): Item to set as current selection """ self.loading = True self.ui.profile_list.setCurrentItem(item) self.loading = False def save_profile(self): """Save changes to the currently selected profile. """ if not self.current_profile_id: return checked_items = set(self.get_checked_items_from_tree()) settings = set(self.profile_settings[self.current_profile_id].keys()) # Add new items to settings for item in checked_items.difference(settings): self.profile_settings[self.current_profile_id][item] = None # Remove unchecked items from settings for item in settings.difference(checked_items): del self.profile_settings[self.current_profile_id][item] def copy_profile(self): """Make a copy of the currently selected profile. """ item = self.get_current_selected_item() id = str(uuid.uuid4()) settings = deepcopy(self.profile_settings[self.current_profile_id]) self.profile_settings[id] = settings name = _("%s (copy)") % item.name self.ui.profile_list.add_profile(name=name, profile_id=id) def new_profile(self): """Add a new profile with no settings selected. """ self.ui.profile_list.add_profile() def delete_profile(self): """Delete the current profile. """ self.ui.profile_list.remove_selected_profile() self.profile_selected() def make_it_so(self): """Save any changes to the current profile's settings, save all updated profile information to the user settings, and close the profile editor dialog. """ all_profiles = list(self._all_profiles()) all_profile_ids = set(x['id'] for x in all_profiles) keys = set(self.profile_settings.keys()) for id in keys.difference(all_profile_ids): del self.profile_settings[id] config = get_config() config.profiles[self.PROFILES_KEY] = all_profiles config.profiles[self.SETTINGS_KEY] = self.profile_settings self.main_window.enable_renaming_action.setChecked(config.setting["rename_files"]) self.main_window.enable_moving_action.setChecked(config.setting["move_files"]) self.main_window.enable_tag_saving_action.setChecked(not config.setting["dont_write_tags"]) self.close() def closeEvent(self, event): """Custom close event handler to save editor settings. """ config = get_config() config.persist[self.POSITION_KEY] = self.ui.profile_list.currentRow() config.persist[self.EXPANDED_KEY] = self.expanded_sections super().closeEvent(event) def _all_profiles(self): """Get all profiles from the profiles list in order from top to bottom. Yields: dict: Profile information in a format for saving to the user settings """ for row in range(self.ui.profile_list.count()): item = self.ui.profile_list.item(row) yield item.get_dict() def set_button_states(self): """Set the enabled / disabled states of the buttons. """ state = self.current_profile_id is not None self.copy_profile_button.setEnabled(state) self.delete_profile_button.setEnabled(state) def get_checked_items_from_tree(self): """Get the keys for the settings that are checked in the profile settings tree. Yields: str: Settings key """ for i in range(self.ui.settings_tree.topLevelItemCount()): tl_item = self.ui.settings_tree.topLevelItem(i) for j in range(tl_item.childCount()): item = tl_item.child(j) if item.checkState(self.TREEWIDGETITEM_COLUMN) == QtCore.Qt.Checked: yield item.data(self.TREEWIDGETITEM_COLUMN, QtCore.Qt.UserRole)
class MetadataOptionsPage(OptionsPage): NAME = "metadata" TITLE = N_("Metadata") PARENT = None SORT_ORDER = 20 ACTIVE = True HELP_URL = '/config/options_metadata.html' options = [ TextOption("setting", "va_name", "Various Artists"), TextOption("setting", "nat_name", "[non-album tracks]"), ListOption("setting", "artist_locales", ["en"]), BoolOption("setting", "translate_artist_names", False), BoolOption("setting", "translate_artist_names_script_exception", False), ListOption("setting", "artist_script_exceptions", []), IntOption("setting", "artist_script_exception_weighting", 0), BoolOption("setting", "release_ars", True), BoolOption("setting", "track_ars", False), BoolOption("setting", "convert_punctuation", True), BoolOption("setting", "standardize_artists", False), BoolOption("setting", "standardize_instruments", True), BoolOption("setting", "guess_tracknumber_and_title", True), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_MetadataOptionsPage() self.ui.setupUi(self) self.ui.va_name_default.clicked.connect(self.set_va_name_default) self.ui.nat_name_default.clicked.connect(self.set_nat_name_default) self.ui.select_locales.clicked.connect(self.open_locale_selector) self.ui.translate_artist_names.stateChanged.connect( self.set_enabled_states) self.ui.translate_artist_names_script_exception.stateChanged.connect( self.set_enabled_states) def load(self): config = get_config() self.ui.translate_artist_names.setChecked( config.setting["translate_artist_names"]) self.current_locales = config.setting["artist_locales"] self.make_locales_text() self.ui.translate_artist_names_script_exception.setChecked( config.setting["translate_artist_names_script_exception"]) self.ui.ignore_tx_scripts.clear() for script_id in SCRIPTS: enabled = script_id in config.setting["artist_script_exceptions"] item = QtWidgets.QListWidgetItem(_(SCRIPTS[script_id])) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setData(QtCore.Qt.UserRole, script_id) item.setCheckState( QtCore.Qt.Checked if enabled else QtCore.Qt.Unchecked) self.ui.ignore_tx_scripts.addItem(item) self.ui.minimum_weighting.setValue( config.setting["artist_script_exception_weighting"]) self.ui.convert_punctuation.setChecked( config.setting["convert_punctuation"]) self.ui.release_ars.setChecked(config.setting["release_ars"]) self.ui.track_ars.setChecked(config.setting["track_ars"]) self.ui.va_name.setText(config.setting["va_name"]) self.ui.nat_name.setText(config.setting["nat_name"]) self.ui.standardize_artists.setChecked( config.setting["standardize_artists"]) self.ui.standardize_instruments.setChecked( config.setting["standardize_instruments"]) self.ui.guess_tracknumber_and_title.setChecked( config.setting["guess_tracknumber_and_title"]) self.set_enabled_states() def make_locales_text(self): def translated_locales(): for locale in self.current_locales: yield _(ALIAS_LOCALES[locale]) self.ui.selected_locales.setText('; '.join(translated_locales())) def save(self): config = get_config() config.setting[ "translate_artist_names"] = self.ui.translate_artist_names.isChecked( ) config.setting["artist_locales"] = self.current_locales config.setting[ "translate_artist_names_script_exception"] = self.ui.translate_artist_names_script_exception.isChecked( ) script_exceptions = [] for idx in range(self.ui.ignore_tx_scripts.count()): item = self.ui.ignore_tx_scripts.item(idx) if item.checkState() == QtCore.Qt.Checked: script_exceptions.append(item.data(QtCore.Qt.UserRole)) config.setting["artist_script_exceptions"] = script_exceptions config.setting["artist_script_exception_weighting"] = min( 100, max(0, self.ui.minimum_weighting.value())) config.setting[ "convert_punctuation"] = self.ui.convert_punctuation.isChecked() config.setting["release_ars"] = self.ui.release_ars.isChecked() config.setting["track_ars"] = self.ui.track_ars.isChecked() config.setting["va_name"] = self.ui.va_name.text() nat_name = self.ui.nat_name.text() if nat_name != config.setting["nat_name"]: config.setting["nat_name"] = nat_name if self.tagger.nats is not None: self.tagger.nats.update() config.setting[ "standardize_artists"] = self.ui.standardize_artists.isChecked() config.setting[ "standardize_instruments"] = self.ui.standardize_instruments.isChecked( ) config.setting[ "guess_tracknumber_and_title"] = self.ui.guess_tracknumber_and_title.isChecked( ) def set_va_name_default(self): self.ui.va_name.setText(self.options[0].default) self.ui.va_name.setCursorPosition(0) def set_nat_name_default(self): self.ui.nat_name.setText(self.options[1].default) self.ui.nat_name.setCursorPosition(0) def set_enabled_states(self): translate_checked = self.ui.translate_artist_names.isChecked() translate_exception_checked = self.ui.translate_artist_names_script_exception.isChecked( ) self.ui.select_locales.setEnabled(translate_checked) self.ui.selected_locales.setEnabled(translate_checked) self.ui.translate_artist_names_script_exception.setEnabled( translate_checked) self.ui.ignore_script_frame.setEnabled(translate_checked and translate_exception_checked) def open_locale_selector(self): dialog = MultiLocaleSelector(self) dialog.show()
def test_int_opt_direct_validstr(self): IntOption("setting", "int_option", 666) # store int as string directly, it should be ok, due to conversion self.config.setValue('setting/int_option', '333') self.assertEqual(self.config.setting["int_option"], 333)
class ScriptingOptionsPage(OptionsPage): NAME = "scripting" TITLE = N_("Scripting") PARENT = None SORT_ORDER = 75 ACTIVE = True HELP_URL = '/config/options_scripting.html' options = [ BoolOption("setting", "enable_tagger_scripts", False), ListOption("setting", "list_of_scripts", []), IntOption("persist", "last_selected_script_pos", 0), ] default_script_directory = os.path.normpath( QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.StandardLocation.DocumentsLocation)) default_script_extension = "ptsp" def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_ScriptingOptionsPage() self.ui.setupUi(self) self.ui.tagger_script.setEnabled(False) self.ui.scripting_options_splitter.setStretchFactor(1, 2) self.move_view = MoveableListView(self.ui.script_list, self.ui.move_up_button, self.ui.move_down_button) self.ui.scripting_documentation_button.clicked.connect( self.show_scripting_documentation) self.ui.scripting_documentation_button.setToolTip( _("Show scripting documentation in new window.")) self.ui.import_button.clicked.connect(self.import_script) self.ui.import_button.setToolTip( _("Import a script file as a new script.")) self.ui.export_button.clicked.connect(self.export_script) self.ui.export_button.setToolTip( _("Export the current script to a file.")) self.FILE_TYPE_ALL = _("All files") + " (*)" self.FILE_TYPE_SCRIPT = _("Picard script files") + " (*.pts *.txt)" self.FILE_TYPE_PACKAGE = _( "Picard tagging script package") + " (*.ptsp *.yaml)" self.ui.script_list.signal_reset_selected_item.connect( self.reset_selected_item) def show_scripting_documentation(self): ScriptingDocumentationDialog.show_instance(parent=self) def output_error(self, title, fmt, filename, msg): """Log error and display error message dialog. Args: title (str): Title to display on the error dialog box fmt (str): Format for the error type being displayed filename (str): Name of the file being imported or exported msg (str): Error message to display """ log.error(fmt, filename, msg) error_message = _(fmt) % (filename, _(msg)) self.display_error(ScriptFileError(_(title), error_message)) def output_file_error(self, fmt, filename, msg): """Log file error and display error message dialog. Args: fmt (str): Format for the error type being displayed filename (str): Name of the file being imported or exported msg (str): Error message to display """ self.output_error(_("File Error"), fmt, filename, msg) def import_script(self): """Import from an external text file to a new script. Import can be either a plain text script or a Picard script package. """ try: script_item = TaggingScript().import_script(self) except ScriptImportExportError as error: self.output_file_error(error.format, error.filename, error.error_msg) return if script_item: title = _("%s (imported)") % script_item["title"] list_item = ScriptListWidgetItem(title, False, script_item["script"]) self.ui.script_list.addItem(list_item) self.ui.script_list.setCurrentRow(self.ui.script_list.count() - 1) def export_script(self): """Export the current script to an external file. Export can be either as a plain text script or a naming script package. """ items = self.ui.script_list.selectedItems() if not items: return item = items[0] script_text = item.script script_title = item.name if item.name.strip() else _("Unnamed Script") if script_text: script_item = TaggingScript(title=script_title, script=script_text) try: script_item.export_script(parent=self) except ScriptImportExportError as error: self.output_file_error(error.format, error.filename, error.error_msg) def enable_tagger_scripts_toggled(self, on): if on and self.ui.script_list.count() == 0: self.ui.script_list.add_script() def script_selected(self): items = self.ui.script_list.selectedItems() if items: item = items[0] self.ui.tagger_script.setEnabled(True) self.ui.tagger_script.setText(item.script) self.ui.tagger_script.setFocus( QtCore.Qt.FocusReason.OtherFocusReason) self.ui.export_button.setEnabled(True) else: self.ui.tagger_script.setEnabled(False) self.ui.tagger_script.setText("") self.ui.export_button.setEnabled(False) def live_update_and_check(self): items = self.ui.script_list.selectedItems() if not items: return script = items[0] script.script = self.ui.tagger_script.toPlainText() self.ui.script_error.setStyleSheet("") self.ui.script_error.setText("") try: self.check() except OptionsCheckError as e: script.has_error = True self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.script_error.setText(e.info) return script.has_error = False def reset_selected_item(self): widget = self.ui.script_list widget.setCurrentRow(widget.bad_row) def check(self): parser = ScriptParser() try: parser.eval(self.ui.tagger_script.toPlainText()) except Exception as e: raise ScriptCheckError(_("Script Error"), str(e)) def restore_defaults(self): # Remove existing scripts self.ui.script_list.clear() self.ui.tagger_script.setText("") super().restore_defaults() def load(self): config = get_config() self.ui.enable_tagger_scripts.setChecked( config.setting["enable_tagger_scripts"]) self.ui.script_list.clear() for pos, name, enabled, text in config.setting["list_of_scripts"]: list_item = ScriptListWidgetItem(name, enabled, text) self.ui.script_list.addItem(list_item) # Select the last selected script item last_selected_script_pos = config.persist["last_selected_script_pos"] last_selected_script = self.ui.script_list.item( last_selected_script_pos) if last_selected_script: self.ui.script_list.setCurrentItem(last_selected_script) last_selected_script.setSelected(True) def _all_scripts(self): for row in range(0, self.ui.script_list.count()): item = self.ui.script_list.item(row) yield item.get_all() def save(self): config = get_config() config.setting[ "enable_tagger_scripts"] = self.ui.enable_tagger_scripts.isChecked( ) config.setting["list_of_scripts"] = list(self._all_scripts()) config.persist[ "last_selected_script_pos"] = self.ui.script_list.currentRow() def display_error(self, error): # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): super().display_error(error)