예제 #1
0
파일: __init__.py 프로젝트: weisslj/picard
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())
예제 #2
0
파일: ratings.py 프로젝트: wjyoung65/picard
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()
예제 #3
0
    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)
예제 #4
0
    def test_upgrade_to_v2_6_0_beta_2(self):
        BoolOption('setting', 'image_type_as_filename', False)
        BoolOption('setting', 'save_only_one_front_image', False)

        self.config.setting['caa_image_type_as_filename'] = True
        self.config.setting['caa_save_single_front_image'] = True
        upgrade_to_v2_6_0_beta_2(self.config)
        self.assertNotIn('caa_image_type_as_filename', self.config.setting)
        self.assertTrue(self.config.setting['image_type_as_filename'])
        self.assertNotIn('caa_save_single_front_image', self.config.setting)
        self.assertTrue(self.config.setting['save_only_one_front_image'])
예제 #5
0
파일: tags.py 프로젝트: wjyoung65/picard
class TagsOptionsPage(OptionsPage):

    NAME = "tags"
    TITLE = N_("Tags")
    PARENT = None
    SORT_ORDER = 30
    ACTIVE = True
    HELP_URL = '/config/options_tags.html'

    options = [
        BoolOption("setting", "dont_write_tags", False),
        BoolOption("setting", "preserve_timestamps", False),
        BoolOption("setting", "clear_existing_tags", False),
        BoolOption("setting", "remove_id3_from_flac", False),
        BoolOption("setting", "remove_ape_from_mp3", False),
        ListOption("setting", "preserved_tags", []),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_TagsOptionsPage()
        self.ui.setupUi(self)

    def load(self):
        config = get_config()
        self.ui.write_tags.setChecked(not config.setting["dont_write_tags"])
        self.ui.preserve_timestamps.setChecked(
            config.setting["preserve_timestamps"])
        self.ui.clear_existing_tags.setChecked(
            config.setting["clear_existing_tags"])
        self.ui.remove_ape_from_mp3.setChecked(
            config.setting["remove_ape_from_mp3"])
        self.ui.remove_id3_from_flac.setChecked(
            config.setting["remove_id3_from_flac"])
        self.ui.preserved_tags.update(config.setting["preserved_tags"])
        self.ui.preserved_tags.set_user_sortable(False)

    def save(self):
        config = get_config()
        config.setting["dont_write_tags"] = not self.ui.write_tags.isChecked()
        config.setting[
            "preserve_timestamps"] = self.ui.preserve_timestamps.isChecked()
        clear_existing_tags = self.ui.clear_existing_tags.isChecked()
        if clear_existing_tags != config.setting["clear_existing_tags"]:
            config.setting["clear_existing_tags"] = clear_existing_tags
            self.tagger.window.metadata_box.update()
        config.setting[
            "remove_ape_from_mp3"] = self.ui.remove_ape_from_mp3.isChecked()
        config.setting[
            "remove_id3_from_flac"] = self.ui.remove_id3_from_flac.isChecked()
        config.setting["preserved_tags"] = list(self.ui.preserved_tags.tags)
        self.tagger.window.enable_tag_saving_action.setChecked(
            not config.setting["dont_write_tags"])
예제 #6
0
class ProxyDialog(QtGui.QDialog):

    options = [
        BoolOption("persist", "save_authentication", True),
    ]

    def __init__(self, authenticator, proxy, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self._authenticator = authenticator
        self._proxy = proxy
        self.ui = Ui_PasswordDialog()
        self.ui.setupUi(self)
        self.ui.info_text.setText(
            _("The proxy %s requires you to login. Please enter your username and password."
              ) % self.config.setting["proxy_server_host"])
        self.ui.save_authentication.setChecked(
            self.config.persist["save_authentication"])
        self.ui.username.setText(self.config.setting["proxy_username"])
        self.ui.password.setText(self.config.setting["proxy_password"])
        self.ui.save_authentication.hide()
        self.connect(self.ui.buttonbox, QtCore.SIGNAL('accepted()'),
                     self.set_proxy_password)

    def set_proxy_password(self):
        self.config.setting["proxy_username"] = unicode(
            self.ui.username.text())
        self.config.setting["proxy_password"] = unicode(
            self.ui.password.text())
        self._authenticator.setUser(unicode(self.ui.username.text()))
        self._authenticator.setPassword(unicode(self.ui.password.text()))
        self.accept()
예제 #7
0
class NoReleaseOptionsPage(OptionsPage):
    NAME = 'norelease'
    TITLE = 'No release'
    PARENT = 'plugins'

    options = [
        BoolOption('setting', 'norelease_enable', False),
        TextOption(
            'setting', 'norelease_strip_tags',
            'asin,barcode,catalognumber,date,label,media,releasecountry,releasestatus'
        ),
    ]

    def __init__(self, parent=None):
        super(NoReleaseOptionsPage, self).__init__(parent)
        self.ui = Ui_NoReleaseOptionsPage()
        self.ui.setupUi(self)

    def load(self):
        self.ui.norelease_strip_tags.setText(
            config.setting['norelease_strip_tags'])
        self.ui.norelease_enable.setChecked(config.setting['norelease_enable'])

    def save(self):
        config.setting['norelease_strip_tags'] = str(
            self.ui.norelease_strip_tags.text())
        config.setting[
            'norelease_enable'] = self.ui.norelease_enable.isChecked()
예제 #8
0
class TheAudioDbOptionsPage(ProviderOptions):

    _options_ui = Ui_TheAudioDbOptionsPage

    options = [
        TextOption("setting", "theaudiodb_use_cdart", OPTION_CDART_NOALBUMART),
        BoolOption("setting", "theaudiodb_use_high_quality", False),
    ]

    def load(self):
        if config.setting["theaudiodb_use_cdart"] == OPTION_CDART_ALWAYS:
            self.ui.theaudiodb_cdart_use_always.setChecked(True)
        elif config.setting["theaudiodb_use_cdart"] == OPTION_CDART_NEVER:
            self.ui.theaudiodb_cdart_use_never.setChecked(True)
        elif config.setting["theaudiodb_use_cdart"] == OPTION_CDART_NOALBUMART:
            self.ui.theaudiodb_cdart_use_if_no_albumcover.setChecked(True)
        self.ui.theaudiodb_use_high_quality.setChecked(
            config.setting['theaudiodb_use_high_quality'])

    def save(self):
        if self.ui.theaudiodb_cdart_use_always.isChecked():
            config.setting["theaudiodb_use_cdart"] = OPTION_CDART_ALWAYS
        elif self.ui.theaudiodb_cdart_use_never.isChecked():
            config.setting["theaudiodb_use_cdart"] = OPTION_CDART_NEVER
        elif self.ui.theaudiodb_cdart_use_if_no_albumcover.isChecked():
            config.setting["theaudiodb_use_cdart"] = OPTION_CDART_NOALBUMART
        config.setting['theaudiodb_use_high_quality'] = self.ui.theaudiodb_use_high_quality.isChecked()
예제 #9
0
class ScriptingOptionsPage(OptionsPage):

    NAME = "scripting"
    TITLE = N_("Scripting")
    PARENT = "advanced"
    SORT_ORDER = 30
    ACTIVE = True

    options = [
        BoolOption("setting", "enable_tagger_script", False),
        TextOption("setting", "tagger_script", ""),
    ]

    STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }"

    def __init__(self, parent=None):
        super(ScriptingOptionsPage, self).__init__(parent)
        self.ui = Ui_ScriptingOptionsPage()
        self.ui.setupUi(self)
        self.highlighter = TaggerScriptSyntaxHighlighter(
            self.ui.tagger_script.document())
        self.connect(self.ui.tagger_script, QtCore.SIGNAL("textChanged()"),
                     self.live_checker)

    def live_checker(self):
        self.ui.script_error.setStyleSheet("")
        self.ui.script_error.setText("")
        try:
            self.check()
        except OptionsCheckError, e:
            self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR)
            self.ui.script_error.setText(e.info)
            return
예제 #10
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")
예제 #11
0
class FileTreeView(BaseTreeView):

    header_state = Option("persist", "file_view_header_state",
                          QtCore.QByteArray())
    header_locked = BoolOption("persist", "file_view_header_locked", False)

    def __init__(self, window, parent=None):
        super().__init__(window, parent)
        self.setAccessibleName(_("file view"))
        self.setAccessibleDescription(
            _("Contains unmatched files and clusters"))
        self.unmatched_files = ClusterItem(self.tagger.unclustered_files,
                                           False, self)
        self.unmatched_files.update()
        self.unmatched_files.setExpanded(True)
        self.clusters = ClusterItem(self.tagger.clusters, False, self)
        self.set_clusters_text()
        self.clusters.setExpanded(True)
        self.tagger.cluster_added.connect(self.add_file_cluster)
        self.tagger.cluster_removed.connect(self.remove_file_cluster)

    def add_file_cluster(self, cluster, parent_item=None):
        self.add_cluster(cluster, parent_item)
        self.set_clusters_text()

    def remove_file_cluster(self, cluster):
        cluster.item.setSelected(False)
        self.clusters.removeChild(cluster.item)
        self.set_clusters_text()

    def set_clusters_text(self):
        self.clusters.setText(
            MainPanel.TITLE_COLUMN,
            '%s (%d)' % (_("Clusters"), len(self.tagger.clusters)))
예제 #12
0
    def test_upgrade_to_v1_4_0_dev_7(self):
        BoolOption('setting', 'embed_only_one_front_image', False)

        self.config.setting['save_only_front_images_to_tags'] = True
        upgrade_to_v1_4_0_dev_7(self.config)
        self.assertNotIn('save_only_front_images_to_tags', self.config.setting)
        self.assertTrue(self.config.setting['embed_only_one_front_image'])
예제 #13
0
    def test_bool_opt_set_read_back(self):
        BoolOption("setting", "bool_option", True)

        # set option and read back
        self.config.setting["bool_option"] = False
        self.assertEqual(self.config.setting["bool_option"], False)
        self.assertIs(type(self.config.setting["bool_option"]), bool)
예제 #14
0
    def test_upgrade_to_v1_3_0_dev_1(self):
        BoolOption('setting', 'windows_compatibility', False)

        self.config.setting['windows_compatible_filenames'] = True
        upgrade_to_v1_3_0_dev_1(self.config)
        self.assertNotIn('windows_compatible_filenames', self.config.setting)
        self.assertTrue(self.config.setting['windows_compatibility'])
예제 #15
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()
예제 #16
0
class AutoMapperOptionsPage(OptionsPage):

    NAME = PLUGIN_NAME.casefold()
    TITLE = r'Non-standard Tags Mapping'
    PARENT = r'tags'  # r'plugins' ?

    options = [BoolOption(r'setting', r'purgeUnmapped', False)]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.box = QtWidgets.QVBoxLayout(self)
        self.purgeUnmapped = QtWidgets.QCheckBox(self)
        self.purgeUnmapped.setCheckable(True)
        self.purgeUnmapped.setChecked(False)
        self.purgeUnmapped.setText(r'Purge non-standard tags left unmapped')
        self.box.addWidget(self.purgeUnmapped)
        self.spacer = QtWidgets.QSpacerItem(0, 0,
                                            QtWidgets.QSizePolicy.Minimum,
                                            QtWidgets.QSizePolicy.Expanding)
        self.box.addItem(self.spacer)

    def load(self):
        self.purgeUnmapped.setChecked(config.setting[r'purgeUnmapped'])

    def save(self):
        config.setting[r'purgeUnmapped'] = self.purgeUnmapped.isChecked()
예제 #17
0
class AlbumTreeView(BaseTreeView):

    header_state = Option("persist", "album_view_header_state",
                          QtCore.QByteArray())
    header_locked = BoolOption("persist", "album_view_header_locked", False)

    def __init__(self, window, parent=None):
        super().__init__(window, parent)
        self.setAccessibleName(_("album view"))
        self.setAccessibleDescription(_("Contains albums and matched files"))
        self.tagger.album_added.connect(self.add_album)
        self.tagger.album_removed.connect(self.remove_album)

    def add_album(self, album):
        if isinstance(album, NatAlbum):
            item = NatAlbumItem(album, True)
            self.insertTopLevelItem(0, item)
        else:
            item = AlbumItem(album, True, self)
        item.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd)
        for i, column in enumerate(MainPanel.columns):
            font = item.font(i)
            font.setBold(True)
            item.setFont(i, font)
            item.setText(i, album.column(column[1]))
        self.add_cluster(album.unmatched_files, item)

    def remove_album(self, album):
        album.item.setSelected(False)
        self.takeTopLevelItem(self.indexOfTopLevelItem(album.item))
예제 #18
0
 def test_upgrade_to_v2_6_0_beta_3(self):
     from picard.ui.theme import UiTheme
     BoolOption('setting', 'use_system_theme', False)
     self.config.setting['use_system_theme'] = True
     upgrade_to_v2_6_0_beta_3(self.config)
     self.assertNotIn('use_system_theme', self.config.setting)
     self.assertIn('ui_theme', self.config.setting)
     self.assertEqual(str(UiTheme.SYSTEM), self.config.setting['ui_theme'])
예제 #19
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()
예제 #20
0
class TagsCompatibilityWaveOptionsPage(OptionsPage):

    NAME = "tags_compatibility_wave"
    TITLE = N_("WAVE")
    PARENT = "tags"
    SORT_ORDER = 60
    ACTIVE = True
    HELP_URL = '/config/options_tags_compatibility_wave.html'

    options = [
        BoolOption("setting", "write_wave_riff_info", True),
        BoolOption("setting", "remove_wave_riff_info", False),
        TextOption("setting", "wave_riff_info_encoding", "windows-1252"),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_TagsCompatibilityOptionsPage()
        self.ui.setupUi(self)

    def load(self):
        config = get_config()
        self.ui.write_wave_riff_info.setChecked(
            config.setting["write_wave_riff_info"])
        self.ui.remove_wave_riff_info.setChecked(
            config.setting["remove_wave_riff_info"])
        if config.setting["wave_riff_info_encoding"] == "utf-8":
            self.ui.wave_riff_info_enc_utf8.setChecked(True)
        else:
            self.ui.wave_riff_info_enc_cp1252.setChecked(True)

    def save(self):
        config = get_config()
        config.setting[
            "write_wave_riff_info"] = self.ui.write_wave_riff_info.isChecked()
        config.setting[
            "remove_wave_riff_info"] = self.ui.remove_wave_riff_info.isChecked(
            )
        if self.ui.wave_riff_info_enc_utf8.isChecked():
            config.setting["wave_riff_info_encoding"] = "utf-8"
        else:
            config.setting["wave_riff_info_encoding"] = "windows-1252"
예제 #21
0
    def test_upgrade_to_v1_4_0_dev_6(self):
        BoolOption('setting', 'enable_tagger_scripts', False)
        ListOption('setting', 'list_of_scripts', [])

        self.config.setting['enable_tagger_script'] = True
        self.config.setting['tagger_script'] = "abc"
        upgrade_to_v1_4_0_dev_6(self.config)

        self.assertNotIn('enable_tagger_script', self.config.setting)
        self.assertNotIn('tagger_script', self.config.setting)

        self.assertTrue(self.config.setting['enable_tagger_scripts'])
        self.assertEqual([(0, DEFAULT_NUMBERED_SCRIPT_NAME % 1, True, 'abc')], self.config.setting['list_of_scripts'])
예제 #22
0
파일: cover.py 프로젝트: weisslj/picard
class CoverOptionsPage(OptionsPage):

    NAME = "cover"
    TITLE = N_("Cover Art")
    PARENT = None
    SORT_ORDER = 35
    ACTIVE = True

    options = [
        BoolOption("setting", "save_images_to_tags", True),
        BoolOption("setting", "save_images_to_files", False),
        TextOption("setting", "cover_image_filename", "cover"),
        BoolOption("setting", "save_images_overwrite", False),
    ]

    def __init__(self, parent=None):
        super(CoverOptionsPage, self).__init__(parent)
        self.ui = Ui_CoverOptionsPage()
        self.ui.setupUi(self)
        self.connect(self.ui.save_images_to_files, QtCore.SIGNAL("clicked()"), self.update_filename)

    def load(self):
        self.ui.save_images_to_tags.setChecked(self.config.setting["save_images_to_tags"])
        self.ui.save_images_to_files.setChecked(self.config.setting["save_images_to_files"])
        self.ui.cover_image_filename.setText(self.config.setting["cover_image_filename"])
        self.ui.save_images_overwrite.setChecked(self.config.setting["save_images_overwrite"])
        self.update_filename()

    def save(self):
        self.config.setting["save_images_to_tags"] = self.ui.save_images_to_tags.isChecked()
        self.config.setting["save_images_to_files"] = self.ui.save_images_to_files.isChecked()
        self.config.setting["cover_image_filename"] = unicode(self.ui.cover_image_filename.text())
        self.config.setting["save_images_overwrite"] = self.ui.save_images_overwrite.isChecked()

    def update_filename(self):
        enabled = self.ui.save_images_to_files.isChecked()
        self.ui.cover_image_filename.setEnabled(enabled)
        self.ui.save_images_overwrite.setEnabled(enabled)
예제 #23
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())
예제 #24
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()
예제 #25
0
class TagsCompatibilityAC3OptionsPage(OptionsPage):

    NAME = "tags_compatibility_ac3"
    TITLE = N_("AC3")
    PARENT = "tags"
    SORT_ORDER = 50
    ACTIVE = True
    HELP_URL = '/config/options_tags_compatibility_ac3.html'

    options = [
        BoolOption("setting", "ac3_save_ape", True),
        BoolOption("setting", "remove_ape_from_ac3", False),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_TagsCompatibilityOptionsPage()
        self.ui.setupUi(self)
        self.ui.ac3_no_tags.toggled.connect(
            self.ui.remove_ape_from_ac3.setEnabled)

    def load(self):
        config = get_config()
        if config.setting["ac3_save_ape"]:
            self.ui.ac3_save_ape.setChecked(True)
        else:
            self.ui.ac3_no_tags.setChecked(True)
        self.ui.remove_ape_from_ac3.setChecked(
            config.setting["remove_ape_from_ac3"])
        self.ui.remove_ape_from_ac3.setEnabled(
            not config.setting["ac3_save_ape"])

    def save(self):
        config = get_config()
        config.setting["ac3_save_ape"] = self.ui.ac3_save_ape.isChecked()
        config.setting[
            "remove_ape_from_ac3"] = self.ui.remove_ape_from_ac3.isChecked()
예제 #26
0
파일: __init__.py 프로젝트: pscn/echonest
class EchoNestOptionsPage(OptionsPage):
    NAME = "echonest"
    TITLE = "echonest"
    PARENT = "plugins"

    options = [
        BoolOption("setting", "echonest_upload", False),
        BoolOption("setting", "echonest_artist_title_lookup", True),
        FloatOption("setting", "echonest_duration_diff", 5.0),
    ]

    def __init__(self, parent=None):
        super(EchoNestOptionsPage, self).__init__(parent)
        self.ui = Ui_EchoNestOptionsPage()
        self.ui.setupUi(self)

    def load(self):
        #self.ui.echonest_upload.setChecked(self.config.setting["echonest_upload"])
        self.ui.echonest_artist_title_lookup.setChecked(self.config.setting["echonest_artist_title_lookup"])
        #self.ui.echonest_duration_diff.setChecked(self.config.setting["echonest_duration_diff"])

    def save(self):
        #self.config.setting["echonest_upload"] = self.ui.echonest_upload.isChecked()
        self.config.setting["echonest_artist_title_lookup"] = self.ui.echonest_artist_title_lookup.isChecked()
예제 #27
0
class PasswordDialog(QtGui.QDialog):

    options = [
        BoolOption("persist", "save_authentication", True),
    ]

    def __init__(self, authenticator, reply, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self._authenticator = authenticator
        self.ui = Ui_PasswordDialog()
        self.ui.setupUi(self)
        self.ui.info_text.setText(
            _("The server %s requires you to login. Please enter your username and password."
              ) % reply.url().host())
        # TODO: Implement proper password storage for arbitrary servers
        if self._is_musicbrainz_server(reply.url().host(), reply.url().port()):
            self.ui.save_authentication.setChecked(
                self.config.persist["save_authentication"])
            self.ui.username.setText(self.config.setting["username"])
            self.ui.password.setText(self.config.setting["password"])
        else:
            self.ui.username.setText(reply.url().userName())
            self.ui.password.setText(reply.url().password())
            self.ui.save_authentication.setChecked(False)
            self.ui.save_authentication.hide()
        self.connect(self.ui.buttonbox, QtCore.SIGNAL('accepted()'),
                     self.set_new_password)

    def set_new_password(self):
        self.config.persist[
            "save_authentication"] = self.ui.save_authentication.isChecked()
        if self.config.persist["save_authentication"]:
            self.config.setting["username"] = unicode(self.ui.username.text())
            self.config.setting["password"] = rot13(
                unicode(self.ui.password.text()))
        self._authenticator.setUser(unicode(self.ui.username.text()))
        self._authenticator.setPassword(unicode(self.ui.password.text()))
        self.accept()

    def _is_musicbrainz_server(self, host, port):
        return host == self.config.setting["server_host"] and port == self.config.setting["server_port"] \
            or host == PUID_SUBMIT_HOST and port == PUID_SUBMIT_PORT
예제 #28
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()
예제 #29
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
예제 #30
0
 def test_bool_opt_convert(self):
     opt = BoolOption("setting", "bool_option", False)
     self.assertEqual(opt.convert(1), True)
예제 #31
0
class ScriptEditorDialog(PicardDialog):
    """File Naming Script Editor Page
    """
    TITLE = N_("File naming script editor")
    STYLESHEET_ERROR = OptionsPage.STYLESHEET_ERROR

    help_url = '/config/options_filerenaming_editor.html'

    options = [
        TextOption("setting", "file_naming_format",
                   DEFAULT_FILE_NAMING_FORMAT),
        ListOption("setting", "file_naming_scripts", []),
        TextOption("setting", "selected_file_naming_script_id", ""),
        BoolOption('persist', 'script_editor_show_documentation', False),
    ]

    signal_save = QtCore.pyqtSignal()
    signal_update = QtCore.pyqtSignal()
    signal_selection_changed = QtCore.pyqtSignal()

    default_script_directory = os.path.normpath(
        QtCore.QStandardPaths.writableLocation(
            QtCore.QStandardPaths.DocumentsLocation))
    default_script_filename = "picard_naming_script.ptsp"

    def __init__(self, parent=None, examples=None):
        """Stand-alone file naming script editor.

        Args:
            parent (QMainWindow or OptionsPage, optional): Parent object. Defaults to None.
            examples (ScriptEditorExamples, required): Object containing examples to display. Defaults to None.
        """
        super().__init__(parent)
        self.examples = examples

        self.FILE_TYPE_ALL = _("All Files") + " (*)"
        self.FILE_TYPE_SCRIPT = _("Picard Script Files") + " (*.pts *.txt)"
        self.FILE_TYPE_PACKAGE = _(
            "Picard Naming Script Package") + " (*.ptsp *.yaml)"

        self.SCRIPT_TITLE_SYSTEM = _("System: %s")
        self.SCRIPT_TITLE_USER = _("User: %s")

        # TODO: Make this work properly so that it can be accessed from both the main window and the options window.
        # self.setWindowFlags(QtCore.Qt.Window)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setWindowTitle(self.TITLE)
        self.displaying = False
        self.loading = True
        self.ui = Ui_ScriptEditor()
        self.ui.setupUi(self)
        self.make_menu()

        self.ui.label.setWordWrap(False)

        self.installEventFilter(self)

        # Dialog buttons
        self.reset_button = QtWidgets.QPushButton(_('Revert'))
        self.reset_button.setToolTip(self.reset_action.toolTip())
        self.reset_button.clicked.connect(self.reset_script)
        self.ui.buttonbox.addButton(self.reset_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)
        self.save_button = self.ui.buttonbox.addButton(
            QtWidgets.QDialogButtonBox.Save)
        self.save_button.setToolTip(self.save_action.toolTip())
        self.ui.buttonbox.accepted.connect(self.save_script)
        self.close_button = self.ui.buttonbox.addButton(
            QtWidgets.QDialogButtonBox.Close)
        self.close_button.setToolTip(self.close_action.toolTip())
        self.ui.buttonbox.rejected.connect(self.close_window)
        self.ui.buttonbox.addButton(QtWidgets.QDialogButtonBox.Help)
        self.ui.buttonbox.helpRequested.connect(self.show_help)

        self.ui.file_naming_format.setEnabled(True)

        # Add scripting documentation to parent frame.
        doc_widget = ScriptingDocumentationWidget(self, include_link=False)
        self.ui.documentation_frame_layout.addWidget(doc_widget)

        self.ui.file_naming_format.textChanged.connect(self.check_formats)

        self._sampled_example_files = []

        self.ui.example_filename_after.itemSelectionChanged.connect(
            self.match_before_to_after)
        self.ui.example_filename_before.itemSelectionChanged.connect(
            self.match_after_to_before)

        self.ui.preset_naming_scripts.currentIndexChanged.connect(
            partial(self.select_script, skip_check=False))

        self.synchronize_vertical_scrollbars(
            (self.ui.example_filename_before, self.ui.example_filename_after))

        self.toggle_documentation()  # Force update to display
        self.examples_current_row = -1

        self.script_metadata_changed = False

        # self.select_script()
        self.load()
        self.loading = False

    def make_menu(self):
        """Build the menu bar.
        """
        config = get_config()
        main_menu = QtWidgets.QMenuBar()

        # File menu settings
        file_menu = main_menu.addMenu(_('&File'))
        file_menu.setToolTipsVisible(True)

        self.import_action = QtWidgets.QAction(_("&Import a script file"),
                                               self)
        self.import_action.setToolTip(_("Import a file as a new script"))
        self.import_action.setIcon(icontheme.lookup('document-open'))
        self.import_action.triggered.connect(self.import_script)
        file_menu.addAction(self.import_action)

        self.export_action = QtWidgets.QAction(_("&Export a script file"),
                                               self)
        self.export_action.setToolTip(_("Export the script to a file"))
        self.export_action.setIcon(icontheme.lookup('document-save'))
        self.export_action.triggered.connect(self.export_script)
        file_menu.addAction(self.export_action)

        self.close_action = QtWidgets.QAction(_("E&xit / Close editor"), self)
        self.close_action.setToolTip(_("Close the script editor"))
        self.close_action.triggered.connect(self.close_window)
        file_menu.addAction(self.close_action)

        # Script menu settings
        script_menu = main_menu.addMenu(_('&Script'))
        script_menu.setToolTipsVisible(True)

        self.details_action = QtWidgets.QAction(_("Edit Script &Metadata"),
                                                self)
        self.details_action.setToolTip(_("Display the details for the script"))
        self.details_action.triggered.connect(self.view_script_details)
        self.details_action.setShortcut(QtGui.QKeySequence(_("Ctrl+M")))
        script_menu.addAction(self.details_action)

        self.add_action = QtWidgets.QAction(_("Add a &new script"), self)
        self.add_action.setToolTip(_("Create a new file naming script"))
        self.add_action.setIcon(icontheme.lookup('add-item'))
        self.add_action.triggered.connect(self.new_script)
        script_menu.addAction(self.add_action)

        self.copy_action = QtWidgets.QAction(_("&Copy the current script"),
                                             self)
        self.copy_action.setToolTip(
            _("Save a copy of the script as a new script"))
        self.copy_action.setIcon(icontheme.lookup('edit-copy'))
        self.copy_action.triggered.connect(self.copy_script)
        script_menu.addAction(self.copy_action)

        self.delete_action = QtWidgets.QAction(_("&Delete the current script"),
                                               self)
        self.delete_action.setToolTip(_("Delete the script"))
        self.delete_action.setIcon(icontheme.lookup('list-remove'))
        self.delete_action.triggered.connect(self.delete_script)
        script_menu.addAction(self.delete_action)

        self.reset_action = QtWidgets.QAction(_("&Revert the current script"),
                                              self)
        self.reset_action.setToolTip(
            _("Revert the script to the last saved value"))
        self.reset_action.setIcon(icontheme.lookup('view-refresh'))
        self.reset_action.triggered.connect(self.reset_script)
        script_menu.addAction(self.reset_action)

        self.save_action = QtWidgets.QAction(_("&Save the current script"),
                                             self)
        self.save_action.setToolTip(_("Save changes to the script"))
        self.save_action.setIcon(icontheme.lookup('document-save'))
        self.save_action.setShortcut(QtGui.QKeySequence(_("Ctrl+S")))
        self.save_action.triggered.connect(self.save_script)
        script_menu.addAction(self.save_action)

        # Display menu settings
        display_menu = main_menu.addMenu(_('&View'))
        display_menu.setToolTipsVisible(True)

        self.examples_action = QtWidgets.QAction(
            _("&Reload random example files"), self)
        self.examples_action.setToolTip(
            _(self.examples.tooltip_text) % self.examples.max_samples)
        self.examples_action.setIcon(icontheme.lookup('view-refresh'))
        self.examples_action.triggered.connect(self.update_example_files)
        display_menu.addAction(self.examples_action)

        display_menu.addAction(self.ui.file_naming_format.wordwrap_action)
        display_menu.addAction(self.ui.file_naming_format.show_tooltips_action)

        self.docs_action = QtWidgets.QAction(_("&Show documentation"), self)
        self.docs_action.setToolTip(
            _("View the scripting documentation in a sidebar"))
        self.docs_action.triggered.connect(self.toggle_documentation)
        self.docs_action.setShortcut(QtGui.QKeySequence(_("Ctrl+H")))
        self.docs_action.setCheckable(True)
        self.docs_action.setChecked(
            config.persist["script_editor_show_documentation"])
        display_menu.addAction(self.docs_action)

        # Help menu settings
        help_menu = main_menu.addMenu(_('&Help'))
        help_menu.setToolTipsVisible(True)

        self.help_action = QtWidgets.QAction(_("&Help..."), self)
        self.help_action.setShortcut(QtGui.QKeySequence.HelpContents)
        self.help_action.triggered.connect(self.show_help)
        help_menu.addAction(self.help_action)

        self.scripting_docs_action = QtWidgets.QAction(
            _("&Scripting documentation..."), self)
        self.scripting_docs_action.setToolTip(
            _("Open the scripting documentation in your browser"))
        self.scripting_docs_action.triggered.connect(self.docs_browser)
        help_menu.addAction(self.scripting_docs_action)

        self.ui.layout_for_menubar.addWidget(main_menu)

    def load(self):
        """Load initial configuration.
        """
        config = get_config()
        self.examples.settings = config.setting
        self.naming_scripts = config.setting["file_naming_scripts"]
        self.selected_script_id = config.setting[
            "selected_file_naming_script_id"]
        self.selected_script_index = 0
        idx = self.populate_script_selector()
        self.ui.preset_naming_scripts.blockSignals(True)
        self.ui.preset_naming_scripts.setCurrentIndex(idx)
        self.ui.preset_naming_scripts.blockSignals(False)
        self.select_script(skip_check=True)

    def docs_browser(self):
        """Open the scriping documentation in a browser.
        """
        webbrowser2.open('doc_scripting')

    @staticmethod
    def synchronize_vertical_scrollbars(widgets):
        """Synchronize position of vertical scrollbars and selections for listed widgets.
        """
        # Set highlight colors for selected list items
        example_style = widgets[0].palette()
        highlight_bg = example_style.color(QPalette.Active, QPalette.Highlight)
        highlight_fg = example_style.color(QPalette.Active,
                                           QPalette.HighlightedText)
        stylesheet = "QListView::item:selected { color: " + highlight_fg.name(
        ) + "; background-color: " + highlight_bg.name() + "; }"

        def _sync_scrollbar_vert(widget, value):
            widget.blockSignals(True)
            widget.verticalScrollBar().setValue(value)
            widget.blockSignals(False)

        widgets = set(widgets)
        for widget in widgets:
            for other in widgets - {widget}:
                widget.verticalScrollBar().valueChanged.connect(
                    partial(_sync_scrollbar_vert, other))

            widget.setStyleSheet(stylesheet)

    def eventFilter(self, object, event):
        """Process selected events.
        """
        evtype = event.type()
        if evtype in {QtCore.QEvent.WindowActivate, QtCore.QEvent.FocusIn}:
            self.update_examples()
        return False

    def close_window(self):
        """Close the window.
        """
        self.close()

    def closeEvent(self, event):
        """Custom close event handler to check for unsaved changes.
        """
        if self.unsaved_changes_confirmation():
            if self.has_changed():
                self.select_script(skip_check=True)
            super().closeEvent(event)
        else:
            event.ignore()

    def populate_script_selector(self):
        """Populate the script selection combo box.

        Returns:
            int: The index of the selected script in the combo box.
        """
        if not self.selected_script_id:
            script_item = FileNamingScript(
                script=get_config().setting["file_naming_format"],
                title=_("Primary file naming script"),
                readonly=False,
                deletable=True,
            )
            self.naming_scripts.insert(0, script_item.to_yaml())
            self.selected_script_id = script_item['id']

        self.ui.preset_naming_scripts.blockSignals(True)
        self.ui.preset_naming_scripts.clear()

        def _add_and_check(idx, count, title, item):
            self.ui.preset_naming_scripts.addItem(title, item)
            if item['id'] == self.selected_script_id:
                idx = count
            count += 1
            return idx, count

        idx = 0
        count = 0  # Use separate counter rather than `i` in case ScriptImportError triggers, resulting in an incorrect index count.
        for i in range(len(self.naming_scripts)):
            try:
                script_item = FileNamingScript().create_from_yaml(
                    self.naming_scripts[i], create_new_id=False)
            except ScriptImportError:
                pass
            else:
                self.naming_scripts[i] = script_item.to_yaml(
                )  # Ensure scripts are stored with id codes
                idx, count = _add_and_check(
                    idx, count, self.SCRIPT_TITLE_USER % script_item["title"],
                    script_item)

        for script_item in get_file_naming_script_presets():
            idx, count = _add_and_check(idx, count, script_item['title'],
                                        script_item)

        self.ui.preset_naming_scripts.blockSignals(False)
        self.update_scripts_list()
        return idx

    def toggle_documentation(self):
        """Toggle the display of the scripting documentation sidebar.
        """
        checked = self.docs_action.isChecked()
        config = get_config()
        config.persist["script_editor_show_documentation"] = checked
        self.ui.documentation_frame.setVisible(checked)

    def view_script_details(self):
        """View and edit (if not readonly) the metadata associated with the script.
        """
        selected_item = self.get_selected_item()
        details_page = ScriptDetailsEditor(self, selected_item)
        details_page.signal_save.connect(self.update_from_details)
        details_page.show()
        details_page.raise_()
        details_page.activateWindow()

    def has_changed(self):
        """Check if the current script has pending edits to the title or script that have not been saved.

        Returns:
            bool: True if there are unsaved changes, otherwise false.
        """
        script_item = self.ui.preset_naming_scripts.itemData(
            self.selected_script_index)
        return self.ui.script_title.text().strip() != script_item['title'] or \
            self.get_script() != script_item['script'] or \
            self.script_metadata_changed

    def update_from_details(self):
        """Update the script selection combo box and script list after updates from the script details dialog.
        """
        selected_item = self.get_selected_item()
        self.update_combo_box_item(
            self.ui.preset_naming_scripts.currentIndex(), selected_item)
        self.ui.script_title.setText(selected_item['title'])
        self.script_metadata_changed = True

    def _insert_item(self, script_item):
        """Insert a new item into the script selection combo box and update the script list in the settings.

        Args:
            script_item (FileNamingScript): File naming scrip to insert.
        """
        self.ui.preset_naming_scripts.blockSignals(True)
        idx = len(self.naming_scripts)
        self.ui.preset_naming_scripts.insertItem(
            idx, self.SCRIPT_TITLE_USER % script_item['title'], script_item)
        self.ui.preset_naming_scripts.setCurrentIndex(idx)
        self.ui.preset_naming_scripts.blockSignals(False)
        self.update_scripts_list()
        self.select_script(skip_check=True)

    def new_script(self):
        """Add a new (empty) script to the script selection combo box and script list.
        """
        if self.unsaved_changes_confirmation():
            script_item = FileNamingScript(script='$noop()')
            self._insert_item(script_item)

    def copy_script(self):
        """Add a copy of the script as a new editable script to the script selection combo box and script list.
        """
        if self.unsaved_changes_confirmation():
            selected = self.ui.preset_naming_scripts.currentIndex()
            script_item = self.ui.preset_naming_scripts.itemData(selected)
            new_item = script_item.copy()
            self._insert_item(new_item)

    def update_script_in_settings(self, script_item):
        self.signal_save.emit()

    def update_scripts_list(self):
        """Refresh the script list in the settings based on the contents of the script selection combo box.
        """
        self.naming_scripts = []
        for idx in range(self.ui.preset_naming_scripts.count()):
            script_item = self.ui.preset_naming_scripts.itemData(idx)
            # Only add items that can be removed -- no presets
            if script_item.deletable:
                self.naming_scripts.append(script_item.to_yaml())

    def get_selected_item(self):
        """Get the selected item from the script selection combo box.

        Returns:
            FileNamingScript: The selected script.
        """
        selected = self.ui.preset_naming_scripts.currentIndex()
        return self.ui.preset_naming_scripts.itemData(selected)

    def unsaved_changes_confirmation(self):
        """Check if there are unsaved changes and as the user to confirm the action resulting in their loss.

        Returns:
            bool: True if no unsaved changes or user confirms the action, otherwise False.
        """
        if not self.loading and self.has_changed() and not confirmation_dialog(
                self,
                _("There are unsaved changes to the current script.  Do you want to continue and lose these changes?"
                  )):
            self.ui.preset_naming_scripts.blockSignals(True)
            self.ui.preset_naming_scripts.setCurrentIndex(
                self.selected_script_index)
            self.ui.preset_naming_scripts.blockSignals(False)
            return False
        return True

    def select_script(self, skip_check=False):
        """Load the current script from the combo box into the editor.

        Args:
            skip_check (bool): Skip the check for unsaved edits.  Defaults to False.
        """
        if skip_check or self.unsaved_changes_confirmation():
            script_item = self.get_selected_item()
            self.ui.script_title.setText(script_item['title'])
            self.set_script(script_item['script'])
            self.selected_script_id = script_item['id']
            self.selected_script_index = self.ui.preset_naming_scripts.currentIndex(
            )
            self.script_metadata_changed = False
            self.update_script_in_settings(script_item)
            self.set_button_states()
            self.update_examples()
            self.signal_selection_changed.emit()

    def update_combo_box_item(self, idx, script_item):
        """Update the title and item data for the specified script selection combo box item.

        Args:
            idx (int): Index of the item to update
            script_item (FileNamingScript): Updated script information
        """
        self.ui.preset_naming_scripts.setItemData(idx, script_item)
        self.ui.preset_naming_scripts.setItemText(
            idx, self.SCRIPT_TITLE_USER % script_item['title'])
        self.update_script_in_settings(script_item)
        self.update_scripts_list()

    def set_button_states(self, save_enabled=True):
        """Set the button states based on the readonly and deletable attributes of the currently selected
        item in the script selection combo box.

        Args:
            save_enabled (bool, optional): Allow updates to be saved to this item. Defaults to True.
        """
        selected = self.ui.preset_naming_scripts.currentIndex()
        if selected < 0:
            return
        script_item = self.get_selected_item()
        readonly = script_item['readonly']
        self.ui.script_title.setReadOnly(readonly or selected < 1)

        # Buttons
        self.ui.file_naming_format.setReadOnly(readonly)
        self.save_button.setEnabled(save_enabled and not readonly)
        self.reset_button.setEnabled(not readonly)

        # Menu items
        self.save_action.setEnabled(save_enabled and not readonly)
        self.reset_action.setEnabled(not readonly)
        self.add_action.setEnabled(save_enabled)
        self.copy_action.setEnabled(save_enabled)
        self.delete_action.setEnabled(script_item['deletable']
                                      and save_enabled)
        self.import_action.setEnabled(save_enabled)
        self.export_action.setEnabled(save_enabled)

    @staticmethod
    def synchronize_selected_example_lines(current_row, source, target):
        """Matches selected item in target to source"""
        if source.currentRow() != current_row:
            current_row = source.currentRow()
            target.blockSignals(True)
            target.setCurrentRow(current_row)
            target.blockSignals(False)

    def match_after_to_before(self):
        """Sets the selected item in the 'after' list to the corresponding item in the 'before' list.
        """
        self.synchronize_selected_example_lines(
            self.examples_current_row, self.ui.example_filename_before,
            self.ui.example_filename_after)

    def match_before_to_after(self):
        """Sets the selected item in the 'before' list to the corresponding item in the 'after' list.
        """
        self.synchronize_selected_example_lines(
            self.examples_current_row, self.ui.example_filename_after,
            self.ui.example_filename_before)

    def delete_script(self):
        """Removes the currently selected script from the script selection combo box and script list.
        """
        if confirmation_dialog(
                self, _('Are you sure that you want to delete the script?')):
            idx = self.ui.preset_naming_scripts.currentIndex()
            self.ui.preset_naming_scripts.blockSignals(True)
            self.ui.preset_naming_scripts.removeItem(idx)
            if idx >= self.ui.preset_naming_scripts.count():
                idx = self.ui.preset_naming_scripts.count() - 1
            self.ui.preset_naming_scripts.setCurrentIndex(idx)
            self.ui.preset_naming_scripts.blockSignals(False)
            self.update_scripts_list()
            self.select_script(skip_check=True)

    def save_script(self):
        """Saves changes to the current script to the script list and combo box item.
        """
        selected = self.ui.preset_naming_scripts.currentIndex()
        self.signal_save.emit()
        title = str(self.ui.script_title.text()).strip()
        if title:
            script_item = self.ui.preset_naming_scripts.itemData(selected)
            script_item.title = title
            script_item.script = self.get_script()
            self.update_combo_box_item(selected, script_item)
            dialog = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Information, _("Save Script"),
                _("Changes to the script have been saved."),
                QtWidgets.QMessageBox.Ok, self)
            dialog.exec_()
        else:
            self.display_error(
                OptionsCheckError(_("Error"),
                                  _("The script title must not be empty.")))

    def get_script(self):
        """Provides the text of the file naming script currently loaded into the editor.

        Returns:
            str: File naming script
        """
        return str(self.ui.file_naming_format.toPlainText()).strip()

    def set_script(self, script_text):
        """Sets the text of the file naming script into the editor and settings.

        Args:
            script_text (str): File naming script text to set in the editor.
        """
        self.ui.file_naming_format.setPlainText(str(script_text).strip())

    def update_example_files(self):
        """Update the before and after file naming examples list.
        """
        self.examples.update_sample_example_files()
        self.display_examples()

    def update_examples(self):
        """Update the before and after file naming examples using the current file naming script in the editor.
        """
        override = {'file_naming_format': self.get_script()}
        self.examples.update_examples(override)
        self.display_examples()

    @staticmethod
    def update_example_listboxes(before_listbox, after_listbox, examples):
        """Update the contents of the file naming examples before and after listboxes.

        Args:
            before_listbox (QListBox): The before listbox
            after_listbox (QListBox): The after listbox
            examples (ScriptEditorExamples): The object to use for the examples
        """
        before_listbox.clear()
        after_listbox.clear()
        for before, after in sorted(examples, key=lambda x: x[1]):
            before_listbox.addItem(before)
            after_listbox.addItem(after)

    def display_examples(self):
        """Update the display of the before and after file naming examples.
        """
        self.examples_current_row = -1
        examples = self.examples.get_examples()
        self.update_example_listboxes(self.ui.example_filename_before,
                                      self.ui.example_filename_after, examples)
        self.signal_update.emit()

    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 naming script package.
        """
        FILE_ERROR_IMPORT = N_('Error importing "%s". %s.')
        FILE_ERROR_DECODE = N_('Error decoding "%s". %s.')

        if not self.unsaved_changes_confirmation():
            return

        dialog_title = _("Import Script File")
        dialog_file_types = self._get_dialog_filetypes()
        options = QtWidgets.QFileDialog.Options()
        options |= QtWidgets.QFileDialog.DontUseNativeDialog
        filename, file_type = QtWidgets.QFileDialog.getOpenFileName(
            self,
            dialog_title,
            self.default_script_directory,
            dialog_file_types,
            options=options)
        if filename:
            log.debug('Importing naming script file: %s' % filename)
            try:
                with open(filename, 'r', encoding='utf8') as i_file:
                    file_content = i_file.read()
            except OSError as error:
                self.output_file_error(FILE_ERROR_IMPORT, filename,
                                       error.strerror)
                return
            if not file_content.strip():
                self.output_file_error(FILE_ERROR_IMPORT, filename,
                                       _('The file was empty'))
                return
            if file_type == self.FILE_TYPE_PACKAGE:
                try:
                    script_item = FileNamingScript().create_from_yaml(
                        file_content)
                except ScriptImportError as error:
                    self.output_file_error(FILE_ERROR_DECODE, filename, error)
                    return
            else:
                script_item = FileNamingScript(title=_("Imported from %s") %
                                               filename,
                                               script=file_content.strip())
            self._insert_item(script_item)

    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.
        """
        FILE_ERROR_EXPORT = N_('Error exporting file "%s". %s.')

        script_item = self.get_selected_item()
        script_text = self.get_script()

        if script_text:
            default_path = os.path.normpath(
                os.path.join(self.default_script_directory,
                             self.default_script_filename))
            dialog_title = _("Export Script File")
            dialog_file_types = self._get_dialog_filetypes()
            options = QtWidgets.QFileDialog.Options()
            options |= QtWidgets.QFileDialog.DontUseNativeDialog
            filename, file_type = QtWidgets.QFileDialog.getSaveFileName(
                self,
                dialog_title,
                default_path,
                dialog_file_types,
                options=options)
            if filename:
                # Fix issue where Qt may set the extension twice
                (name, ext) = os.path.splitext(filename)
                if ext and str(name).endswith('.' + ext):
                    filename = name
                log.debug('Exporting naming script file: %s' % filename)
                if file_type == self.FILE_TYPE_PACKAGE:
                    script_text = script_item.to_yaml()
                try:
                    with open(filename, 'w', encoding='utf8') as o_file:
                        o_file.write(script_text)
                except OSError as error:
                    self.output_file_error(FILE_ERROR_EXPORT, filename,
                                           error.strerror)
                else:
                    dialog = QtWidgets.QMessageBox(
                        QtWidgets.QMessageBox.Information, _("Export Script"),
                        _("Script successfully exported to %s") % filename,
                        QtWidgets.QMessageBox.Ok, self)
                    dialog.exec_()

    def _get_dialog_filetypes(self):
        return ";;".join((
            self.FILE_TYPE_PACKAGE,
            self.FILE_TYPE_SCRIPT,
            self.FILE_TYPE_ALL,
        ))

    def reset_script(self):
        """Reset the script to the last saved value.
        """
        if self.has_changed():
            if confirmation_dialog(
                    self,
                    _("Are you sure that you want to reset the script to its last saved value?"
                      )):
                self.select_script(skip_check=True)
        else:
            dialog = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Information, _("Revert Script"),
                _("There have been no changes made since the last time the script was saved."
                  ), QtWidgets.QMessageBox.Ok, self)
            dialog.exec_()

    def check_formats(self):
        """Checks for valid file naming script and settings, and updates the examples.
        """
        self.test()
        self.update_examples()

    def check_format(self):
        """Parse the file naming script and check for errors.
        """
        config = get_config()
        parser = ScriptParser()
        script_text = self.get_script()
        try:
            parser.eval(script_text)
        except Exception as e:
            raise ScriptCheckError("", str(e))
        if config.setting["rename_files"]:
            if not self.get_script():
                raise ScriptCheckError(
                    "", _("The file naming format must not be empty."))

    def display_error(self, error):
        """Display an error message for the specified error.

        Args:
            error (Exception): The exception to display.
        """
        # Ignore scripting errors, those are handled inline
        if not isinstance(error, ScriptCheckError):
            dialog = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning,
                                           error.title, error.info,
                                           QtWidgets.QMessageBox.Ok, self)
            dialog.exec_()

    def test(self):
        """Parse the script and display any errors.
        """
        self.ui.renaming_error.setStyleSheet("")
        self.ui.renaming_error.setText("")
        save_enabled = True
        try:
            self.check_format()
        except ScriptCheckError as e:
            self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR)
            self.ui.renaming_error.setText(e.info)
            save_enabled = False
        self.set_button_states(save_enabled=save_enabled)