Exemple #1
0
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)
Exemple #3
0
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()
Exemple #4
0
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)
Exemple #5
0
    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")
Exemple #6
0
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())
Exemple #7
0
    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)
Exemple #8
0
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'])
Exemple #10
0
    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())
Exemple #11
0
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()
Exemple #12
0
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()
Exemple #13
0
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
Exemple #14
0
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()
Exemple #15
0
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)
Exemple #16
0
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)
Exemple #17
0
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)
Exemple #18
0
 def test_int_opt_convert(self):
     opt = IntOption("setting", "int_option", 666)
     self.assertEqual(opt.convert("123"), 123)
Exemple #19
0
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)
Exemple #20
0
    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)
Exemple #21
0
    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)
Exemple #22
0
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)
Exemple #23
0
    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)
Exemple #24
0
    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)
Exemple #25
0
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()
Exemple #26
0
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)
Exemple #27
0
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()
Exemple #28
0
    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)
Exemple #29
0
 def test_int_opt_convert(self):
     opt = IntOption("setting", "int_option", 666)
     self.assertEqual(opt.convert("123"), 123)
Exemple #30
0
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)