예제 #1
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 = [
        config.BoolOption("setting", "ac3_save_ape", True),
        config.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):
        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.setting["ac3_save_ape"] = self.ui.ac3_save_ape.isChecked()
        config.setting[
            "remove_ape_from_ac3"] = self.ui.remove_ape_from_ac3.isChecked()
예제 #2
0
class RatingsOptionsPage(OptionsPage):

    NAME = "ratings"
    TITLE = N_("Ratings")
    PARENT = "metadata"
    SORT_ORDER = 20
    ACTIVE = True

    options = [
        config.BoolOption("setting", "enable_ratings", False),
        config.TextOption("setting", "rating_user_email",
                          "*****@*****.**"),
        config.BoolOption("setting", "submit_ratings", True),
        config.IntOption("setting", "rating_steps", 6),
    ]

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

    def load(self):
        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.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
파일: caa.py 프로젝트: cartr/picard
class ProviderOptionsCaa(ProviderOptions):
    """
        Options for Cover Art Archive cover art provider
    """

    options = [
        config.BoolOption("setting", "caa_save_single_front_image", False),
        config.BoolOption("setting", "caa_approved_only", False),
        config.BoolOption("setting", "caa_image_type_as_filename", False),
        config.IntOption("setting", "caa_image_size", 1),
        config.ListOption("setting", "caa_image_types", [u"front"]),
        config.BoolOption("setting", "caa_restrict_image_types", True),
    ]

    _options_ui = Ui_CaaOptions

    def __init__(self, parent=None):
        super(ProviderOptionsCaa, self).__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 load(self):
        self.ui.cb_image_size.setCurrentIndex(config.setting["caa_image_size"])
        self.ui.cb_save_single_front_image.setChecked(
            config.setting["caa_save_single_front_image"])
        self.ui.cb_approved_only.setChecked(
            config.setting["caa_approved_only"])
        self.ui.cb_type_as_filename.setChecked(
            config.setting["caa_image_type_as_filename"])
        self.ui.restrict_images_types.setChecked(
            config.setting["caa_restrict_image_types"])
        self.update_caa_types()

    def save(self):
        config.setting["caa_image_size"] = \
            self.ui.cb_image_size.currentIndex()
        config.setting["caa_save_single_front_image"] = \
            self.ui.cb_save_single_front_image.isChecked()
        config.setting["caa_approved_only"] = \
            self.ui.cb_approved_only.isChecked()
        config.setting["caa_image_type_as_filename"] = \
            self.ui.cb_type_as_filename.isChecked()
        config.setting["caa_restrict_image_types"] = \
            self.ui.restrict_images_types.isChecked()

    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,
         ok) = CAATypesSelectorDialog.run(self,
                                          config.setting["caa_image_types"])
        if ok:
            config.setting["caa_image_types"] = types
예제 #4
0
class AdvancedOptionsPage(OptionsPage):

    NAME = "advanced"
    TITLE = N_("Advanced")
    PARENT = None
    SORT_ORDER = 90
    ACTIVE = True

    options = [
        config.TextOption("setting", "ignore_regex", ""),
        config.BoolOption("setting", "ignore_hidden_files", False),
        config.BoolOption("setting", "completeness_ignore_videos", False),
        config.BoolOption("setting", "completeness_ignore_pregap", False),
        config.BoolOption("setting", "completeness_ignore_data", False),
        config.BoolOption("setting", "completeness_ignore_silence", False),
    ]

    def __init__(self, parent=None):
        super(AdvancedOptionsPage, self).__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):
        self.ui.ignore_regex.setText(config.setting["ignore_regex"])
        self.ui.ignore_hidden_files.setChecked(
            config.setting["ignore_hidden_files"])
        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"])

    def save(self):
        config.setting["ignore_regex"] = unicode(self.ui.ignore_regex.text())
        config.setting[
            "ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked()
        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(
            )
예제 #5
0
class TagsOptionsPage(OptionsPage):

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

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

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

    def load(self):
        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.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 AlbumTreeView(BaseTreeView):

    header_state = config.Option("persist", "album_view_header_state",
                                 QtCore.QByteArray())
    header_locked = config.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))
예제 #7
0
class FileTreeView(BaseTreeView):

    header_state = config.Option("persist", "file_view_header_state",
                                 QtCore.QByteArray())
    header_locked = config.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)))
예제 #8
0
class ProxyDialog(QtGui.QDialog):

    options = [
        config.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.")
                                  % config.setting["proxy_server_host"])
        self.ui.save_authentication.setChecked(config.persist["save_authentication"])
        self.ui.username.setText(config.setting["proxy_username"])
        self.ui.password.setText(config.setting["proxy_password"])
        self.ui.save_authentication.hide()
        self.ui.buttonbox.accepted.connect(self.set_proxy_password)

    def set_proxy_password(self):
        config.setting["proxy_username"] = unicode(self.ui.username.text())
        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()
예제 #9
0
class PasswordDialog(QtGui.QDialog):

    options = [
        config.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(config.persist["save_authentication"])
            self.ui.username.setText(config.setting["username"])
            self.ui.password.setText(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.ui.buttonbox.accepted.connect(self.set_new_password)

    def set_new_password(self):
        config.persist["save_authentication"] = self.ui.save_authentication.isChecked()
        if config.persist["save_authentication"]:
            config.setting["username"] = unicode(self.ui.username.text())
            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 == config.setting["server_host"] and port == config.setting["server_port"]
예제 #10
0
class AcousticBrainzOptionsPage(OptionsPage):
    NAME = "acousticbrainz_tags"
    TITLE = "AcousticBrainz tags"
    PARENT = "plugins"

    options = [
        config.BoolOption("setting", "acousticbrainz_add_simplemood", True),
        config.BoolOption("setting", "acousticbrainz_add_simplegenre", True),
        config.BoolOption("setting", "acousticbrainz_add_keybpm", False),
        config.BoolOption("setting", "acousticbrainz_add_fullhighlevel",
                          False),
        config.BoolOption("setting", "acousticbrainz_add_sublowlevel", False)
    ]

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

    def load(self):
        setting = config.setting
        self.ui.add_simplemood.setChecked(
            setting["acousticbrainz_add_simplemood"])
        self.ui.add_simplegenre.setChecked(
            setting["acousticbrainz_add_simplegenre"])
        self.ui.add_fullhighlevel.setChecked(
            setting["acousticbrainz_add_fullhighlevel"])
        self.ui.add_keybpm.setChecked(setting["acousticbrainz_add_keybpm"])
        self.ui.add_sublowlevel.setChecked(
            setting["acousticbrainz_add_sublowlevel"])

    def save(self):
        setting = config.setting
        setting[
            "acousticbrainz_add_simplemood"] = self.ui.add_simplemood.isChecked(
            )
        setting[
            "acousticbrainz_add_simplegenre"] = self.ui.add_simplegenre.isChecked(
            )
        setting["acousticbrainz_add_keybpm"] = self.ui.add_keybpm.isChecked()
        setting[
            "acousticbrainz_add_fullhighlevel"] = self.ui.add_fullhighlevel.isChecked(
            )
        setting[
            "acousticbrainz_add_sublowlevel"] = self.ui.add_sublowlevel.isChecked(
            )
예제 #11
0
class ScriptingOptionsPage(OptionsPage):

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

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

    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.ui.tagger_script.textChanged.connect(self.live_checker)

    def live_checker(self):
        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(unicode(self.ui.tagger_script.toPlainText()))
        except Exception as e:
            raise OptionsCheckError(_("Script Error"), str(e))

    def load(self):
        self.ui.enable_tagger_script.setChecked(
            config.setting["enable_tagger_script"])
        self.ui.tagger_script.document().setPlainText(
            config.setting["tagger_script"])
        args = {
            "picard-doc-scripting-url": PICARD_URLS['doc_scripting'],
        }
        text = _(u'<a href="%(picard-doc-scripting-url)s">Open Scripting'
                 ' Documentation in your browser</a>') % args
        self.ui.scripting_doc_link.setText(text)

    def save(self):
        config.setting[
            "enable_tagger_script"] = self.ui.enable_tagger_script.isChecked()
        config.setting["tagger_script"] = self.ui.tagger_script.toPlainText()

    def display_error(self, error):
        pass
예제 #12
0
파일: genres.py 프로젝트: webiis/picard
class GenresOptionsPage(OptionsPage):

    NAME = "genres"
    TITLE = N_("Genres")
    PARENT = "metadata"
    SORT_ORDER = 20
    ACTIVE = True

    options = [
        config.BoolOption("setting", "use_genres", False),
        config.IntOption("setting", "max_genres", 5),
        config.IntOption("setting", "min_genre_usage", 90),
        config.TextOption("setting", "ignore_genres",
                          "seen live, favorites, fixme, owned"),
        config.TextOption("setting", "join_genres", ""),
        config.BoolOption("setting", "only_my_genres", False),
        config.BoolOption("setting", "artists_genres", False),
        config.BoolOption("setting", "folksonomy_tags", False),
    ]

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

    def load(self):
        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.ignore_genres.setText(config.setting["ignore_genres"])
        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.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["ignore_genres"] = self.ui.ignore_genres.text()
        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()
예제 #13
0
class GeneralOptionsPage(OptionsPage):

    NAME = "general"
    TITLE = N_("General")
    PARENT = None
    SORT_ORDER = 1
    ACTIVE = True

    options = [
        config.TextOption("setting", "server_host", MUSICBRAINZ_SERVERS[0]),
        config.IntOption("setting", "server_port", 80),
        config.TextOption("setting", "username", ""),
        config.PasswordOption("setting", "password", ""),
        config.BoolOption("setting", "analyze_new_files", False),
        config.BoolOption("setting", "ignore_file_mbids", False),
    ]

    def __init__(self, parent=None):
        super(GeneralOptionsPage, self).__init__(parent)
        self.ui = Ui_GeneralOptionsPage()
        self.ui.setupUi(self)
        self.ui.server_host.addItems(MUSICBRAINZ_SERVERS)

    def load(self):
        self.ui.server_host.setEditText(config.setting["server_host"])
        self.ui.server_port.setValue(config.setting["server_port"])
        self.ui.username.setText(config.setting["username"])
        self.ui.password.setText(config.setting["password"])
        self.ui.analyze_new_files.setChecked(config.setting["analyze_new_files"])
        self.ui.ignore_file_mbids.setChecked(config.setting["ignore_file_mbids"])

    def save(self):
        config.setting["server_host"] = unicode(self.ui.server_host.currentText()).strip()
        config.setting["server_port"] = self.ui.server_port.value()
        reload_collections = config.setting["username"] != unicode(self.ui.username.text()) \
            or config.setting["password"] != unicode(self.ui.password.text())
        config.setting["username"] = unicode(self.ui.username.text())
        # trivially encode the password, just to not make it so apparent
        config.setting["password"] = rot13(unicode(self.ui.password.text()))
        if reload_collections:
            load_user_collections()
        config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked()
        config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked()
예제 #14
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 = [
        config.BoolOption("setting", "write_wave_riff_info", True),
        config.BoolOption("setting", "remove_wave_riff_info", False),
        config.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):
        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.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"
예제 #15
0
class ScriptingOptionsPage(OptionsPage):

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

    options = [
        config.BoolOption("setting", "enable_tagger_script", False),
        config.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.ui.tagger_script.textChanged.connect(self.live_checker)

    def live_checker(self):
        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(unicode(self.ui.tagger_script.toPlainText()))
        except Exception as e:
            raise OptionsCheckError(_("Script Error"), str(e))

    def load(self):
        self.ui.enable_tagger_script.setChecked(config.setting["enable_tagger_script"])
        self.ui.tagger_script.document().setPlainText(config.setting["tagger_script"])

    def save(self):
        config.setting["enable_tagger_script"] = self.ui.enable_tagger_script.isChecked()
        config.setting["tagger_script"] = self.ui.tagger_script.toPlainText()

    def display_error(self, error):
        pass
예제 #16
0
class AdvancedOptionsPage(OptionsPage):

    NAME = "advanced"
    TITLE = N_("Advanced")
    PARENT = None
    SORT_ORDER = 90
    ACTIVE = True
    HELP_URL = '/config/options_advanced.html'

    options = [
        config.TextOption("setting", "ignore_regex", ""),
        config.BoolOption("setting", "ignore_hidden_files", False),
        config.BoolOption("setting", "recursively_add_files", True),
        config.IntOption("setting", "ignore_track_duration_difference_under", 2),
        config.BoolOption("setting", "completeness_ignore_videos", False),
        config.BoolOption("setting", "completeness_ignore_pregap", False),
        config.BoolOption("setting", "completeness_ignore_data", False),
        config.BoolOption("setting", "completeness_ignore_silence", False),
        config.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):
        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.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()
예제 #17
0
class MainWindow(QtGui.QMainWindow):

    selection_updated = QtCore.pyqtSignal(object)

    options = [
        config.Option("persist", "window_state", QtCore.QByteArray()),
        config.Option("persist", "window_position", QtCore.QPoint()),
        config.Option("persist", "window_size", QtCore.QSize(780, 560)),
        config.Option("persist", "bottom_splitter_state", QtCore.QByteArray()),
        config.BoolOption("persist", "window_maximized", False),
        config.BoolOption("persist", "view_cover_art", True),
        config.BoolOption("persist", "view_file_browser", False),
        config.TextOption("persist", "current_directory", ""),
    ]

    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.selected_objects = []
        self.ignore_selection_changes = False
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle(_("MusicBrainz Picard"))
        icon = QtGui.QIcon()
        icon.addFile(":/images/16x16/picard.png", QtCore.QSize(16, 16))
        icon.addFile(":/images/24x24/picard.png", QtCore.QSize(24, 24))
        icon.addFile(":/images/32x32/picard.png", QtCore.QSize(32, 32))
        icon.addFile(":/images/48x48/picard.png", QtCore.QSize(48, 48))
        icon.addFile(":/images/128x128/picard.png", QtCore.QSize(128, 128))
        icon.addFile(":/images/256x256/picard.png", QtCore.QSize(256, 256))
        self.setWindowIcon(icon)

        self.create_actions()
        self.create_statusbar()
        self.create_toolbar()
        self.create_menus()

        mainLayout = QtGui.QSplitter(QtCore.Qt.Vertical)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.setHandleWidth(1)

        self.panel = MainPanel(self, mainLayout)
        self.file_browser = FileBrowser(self.panel)
        if not self.show_file_browser_action.isChecked():
            self.file_browser.hide()
        self.panel.insertWidget(0, self.file_browser)
        self.panel.restore_state()

        self.metadata_box = MetadataBox(self)
        self.cover_art_box = CoverArtBox(self)
        if not self.show_cover_art_action.isChecked():
            self.cover_art_box.hide()

        bottomLayout = QtGui.QHBoxLayout()
        bottomLayout.setContentsMargins(0, 0, 0, 0)
        bottomLayout.setSpacing(0)
        bottomLayout.addWidget(self.metadata_box, 1)
        bottomLayout.addWidget(self.cover_art_box, 0)
        bottom = QtGui.QWidget()
        bottom.setLayout(bottomLayout)

        mainLayout.addWidget(self.panel)
        mainLayout.addWidget(bottom)
        self.setCentralWidget(mainLayout)

        # accessibility
        self.set_tab_order()

        # FIXME: use QApplication's clipboard
        self._clipboard = []

        for function in ui_init:
            function(self)

    def keyPressEvent(self, event):
        if event.matches(QtGui.QKeySequence.Delete):
            if self.metadata_box.hasFocus():
                self.metadata_box.remove_selected_tags()
            else:
                self.remove()
        else:
            QtGui.QMainWindow.keyPressEvent(self, event)

    def show(self):
        self.restoreWindowState()
        QtGui.QMainWindow.show(self)
        self.metadata_box.restore_state()

    def closeEvent(self, event):
        if config.setting[
                "quit_confirmation"] and not self.show_quit_confirmation():
            event.ignore()
            return
        self.saveWindowState()
        event.accept()

    def show_quit_confirmation(self):
        unsaved_files = sum(a.get_num_unsaved_files()
                            for a in self.tagger.albums.itervalues())
        QMessageBox = QtGui.QMessageBox

        if unsaved_files > 0:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setWindowModality(QtCore.Qt.WindowModal)
            msg.setWindowTitle(_(u"Unsaved Changes"))
            msg.setText(_(u"Are you sure you want to quit Picard?"))
            txt = ungettext(
                "There is %d unsaved file. Closing Picard will lose all unsaved changes.",
                "There are %d unsaved files. Closing Picard will lose all unsaved changes.",
                unsaved_files) % unsaved_files
            msg.setInformativeText(txt)
            cancel = msg.addButton(QMessageBox.Cancel)
            msg.setDefaultButton(cancel)
            msg.addButton(_(u"&Quit Picard"), QMessageBox.YesRole)
            ret = msg.exec_()

            if ret == QMessageBox.Cancel:
                return False

        return True

    def saveWindowState(self):
        config.persist["window_state"] = self.saveState()
        isMaximized = int(self.windowState()) & QtCore.Qt.WindowMaximized != 0
        if isMaximized:
            # FIXME: this doesn't include the window frame
            geom = self.normalGeometry()
            config.persist["window_position"] = geom.topLeft()
            config.persist["window_size"] = geom.size()
        else:
            pos = self.pos()
            if not pos.isNull():
                config.persist["window_position"] = pos
            config.persist["window_size"] = self.size()
        config.persist["window_maximized"] = isMaximized
        config.persist[
            "view_cover_art"] = self.show_cover_art_action.isChecked()
        config.persist[
            "view_file_browser"] = self.show_file_browser_action.isChecked()
        config.persist["bottom_splitter_state"] = self.centralWidget(
        ).saveState()
        self.file_browser.save_state()
        self.panel.save_state()
        self.metadata_box.save_state()

    def restoreWindowState(self):
        self.restoreState(config.persist["window_state"])
        pos = config.persist["window_position"]
        size = config.persist["window_size"]
        self._desktopgeo = self.tagger.desktop().screenGeometry()
        if (pos.x() > 0 and pos.y() > 0
                and pos.x() + size.width() < self._desktopgeo.width()
                and pos.y() + size.height() < self._desktopgeo.height()):
            self.move(pos)
        if size.width() <= 0 or size.height() <= 0:
            size = QtCore.QSize(780, 560)
        self.resize(size)
        if config.persist["window_maximized"]:
            self.setWindowState(QtCore.Qt.WindowMaximized)
        bottom_splitter_state = config.persist["bottom_splitter_state"]
        if bottom_splitter_state.isEmpty():
            self.centralWidget().setSizes([366, 194])
        else:
            self.centralWidget().restoreState(bottom_splitter_state)
        self.file_browser.restore_state()

    def create_statusbar(self):
        """Creates a new status bar."""
        self.statusBar().showMessage(_("Ready"))
        self.infostatus = InfoStatus(self)
        self.listening_label = QtGui.QLabel()
        self.listening_label.setVisible(False)
        self.listening_label.setToolTip("<qt/>" + _(
            "Picard listens on this port to integrate with your browser. When "
            "you \"Search\" or \"Open in Browser\" from Picard, clicking the "
            "\"Tagger\" button on the web page loads the release into Picard.")
                                        )
        self.statusBar().addPermanentWidget(self.infostatus)
        self.statusBar().addPermanentWidget(self.listening_label)
        self.tagger.tagger_stats_changed.connect(self.update_statusbar_stats)
        self.tagger.listen_port_changed.connect(
            self.update_statusbar_listen_port)
        self.update_statusbar_stats()

    @throttle(100)
    def update_statusbar_stats(self):
        """Updates the status bar information."""
        self.infostatus.setFiles(len(self.tagger.files))
        self.infostatus.setAlbums(len(self.tagger.albums))
        self.infostatus.setPendingFiles(File.num_pending_files)
        ws = self.tagger.xmlws
        self.infostatus.setPendingRequests(ws.num_pending_web_requests)

    def update_statusbar_listen_port(self, listen_port):
        if listen_port:
            self.listening_label.setVisible(True)
            self.listening_label.setText(
                _(" Listening on port %(port)d ") % {"port": listen_port})
        else:
            self.listening_label.setVisible(False)

    def set_statusbar_message(self, message, *args, **kwargs):
        """Set the status bar message.

        *args are passed to % operator, if args[0] is a mapping it is used for
        named place holders values
        >>> w.set_statusbar_message("File %(filename)s", {'filename': 'x.txt'})

        Keyword arguments:
        `echo` parameter defaults to `log.debug`, called before message is
        translated, it can be disabled passing None or replaced by ie.
        `log.error`. If None, skipped.

        `translate` is a method called on message before it is sent to history
        log and status bar, it defaults to `_()`. If None, skipped.

        `timeout` defines duration of the display in milliseconds

        `history` is a method called with translated message as argument, it
        defaults to `log.history_info`. If None, skipped.

        Empty messages are never passed to echo and history functions but they
        are sent to status bar (ie. to clear it).
        """
        def isdict(obj):
            return hasattr(obj, 'keys') and hasattr(obj, '__getitem__')

        echo = kwargs.get('echo', log.debug)
        # _ is defined using __builtin__.__dict__, so setting it as default named argument
        # value doesn't work as expected
        translate = kwargs.get('translate', _)
        timeout = kwargs.get('timeout', 0)
        history = kwargs.get('history', log.history_info)
        if len(args) == 1 and isdict(args[0]):
            # named place holders
            mparms = args[0]
        else:
            # simple place holders, ensure compatibility
            mparms = args
        if message:
            if echo:
                echo(message % mparms)
            if translate:
                message = translate(message)
            message = message % mparms
            if history:
                history(message)
        thread.to_main(self.statusBar().showMessage, message, timeout)

    def _on_submit(self):
        if self.tagger.use_acoustid:
            if not config.setting["acoustid_apikey"]:
                QtGui.QMessageBox.warning(
                    self, _(u"Submission Error"),
                    _(u"You need to configure your AcoustID API key before you can submit fingerprints."
                      ))
            else:
                self.tagger.acoustidmanager.submit()

    def create_actions(self):
        self.options_action = QtGui.QAction(
            icontheme.lookup('preferences-desktop'), _("&Options..."), self)
        self.options_action.setMenuRole(QtGui.QAction.PreferencesRole)
        self.options_action.triggered.connect(self.show_options)

        self.cut_action = QtGui.QAction(
            icontheme.lookup('edit-cut', icontheme.ICON_SIZE_MENU), _(u"&Cut"),
            self)
        self.cut_action.setShortcut(QtGui.QKeySequence.Cut)
        self.cut_action.setEnabled(False)
        self.cut_action.triggered.connect(self.cut)

        self.paste_action = QtGui.QAction(
            icontheme.lookup('edit-paste', icontheme.ICON_SIZE_MENU),
            _(u"&Paste"), self)
        self.paste_action.setShortcut(QtGui.QKeySequence.Paste)
        self.paste_action.setEnabled(False)
        self.paste_action.triggered.connect(self.paste)

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

        self.about_action = QtGui.QAction(_("&About..."), self)
        self.about_action.setMenuRole(QtGui.QAction.AboutRole)
        self.about_action.triggered.connect(self.show_about)

        self.donate_action = QtGui.QAction(_("&Donate..."), self)
        self.donate_action.triggered.connect(self.open_donation_page)

        self.report_bug_action = QtGui.QAction(_("&Report a Bug..."), self)
        self.report_bug_action.triggered.connect(self.open_bug_report)

        self.support_forum_action = QtGui.QAction(_("&Support Forum..."), self)
        self.support_forum_action.triggered.connect(self.open_support_forum)

        self.add_files_action = QtGui.QAction(
            icontheme.lookup('document-open'), _(u"&Add Files..."), self)
        self.add_files_action.setStatusTip(_(u"Add files to the tagger"))
        # TR: Keyboard shortcut for "Add Files..."
        self.add_files_action.setShortcut(QtGui.QKeySequence.Open)
        self.add_files_action.triggered.connect(self.add_files)

        self.add_directory_action = QtGui.QAction(icontheme.lookup('folder'),
                                                  _(u"A&dd Folder..."), self)
        self.add_directory_action.setStatusTip(
            _(u"Add a folder to the tagger"))
        # TR: Keyboard shortcut for "Add Directory..."
        self.add_directory_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+D")))
        self.add_directory_action.triggered.connect(self.add_directory)

        self.save_action = QtGui.QAction(icontheme.lookup('document-save'),
                                         _(u"&Save"), self)
        self.save_action.setStatusTip(_(u"Save selected files"))
        # TR: Keyboard shortcut for "Save"
        self.save_action.setShortcut(QtGui.QKeySequence.Save)
        self.save_action.setEnabled(False)
        self.save_action.triggered.connect(self.save)

        self.submit_action = QtGui.QAction(icontheme.lookup('picard-submit'),
                                           _(u"S&ubmit"), self)
        self.submit_action.setStatusTip(_(u"Submit acoustic fingerprints"))
        self.submit_action.setEnabled(False)
        self.submit_action.triggered.connect(self._on_submit)

        self.exit_action = QtGui.QAction(_(u"E&xit"), self)
        self.exit_action.setMenuRole(QtGui.QAction.QuitRole)
        # TR: Keyboard shortcut for "Exit"
        self.exit_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Q")))
        self.exit_action.triggered.connect(self.close)

        self.remove_action = QtGui.QAction(icontheme.lookup('list-remove'),
                                           _(u"&Remove"), self)
        self.remove_action.setStatusTip(_(u"Remove selected files/albums"))
        self.remove_action.setEnabled(False)
        self.remove_action.triggered.connect(self.remove)

        self.browser_lookup_action = QtGui.QAction(
            icontheme.lookup('lookup-musicbrainz'), _(u"Lookup in &Browser"),
            self)
        self.browser_lookup_action.setStatusTip(
            _(u"Lookup selected item on MusicBrainz website"))
        self.browser_lookup_action.setEnabled(False)
        self.browser_lookup_action.triggered.connect(self.browser_lookup)

        self.show_file_browser_action = QtGui.QAction(_(u"File &Browser"),
                                                      self)
        self.show_file_browser_action.setCheckable(True)
        if config.persist["view_file_browser"]:
            self.show_file_browser_action.setChecked(True)
        self.show_file_browser_action.setShortcut(
            QtGui.QKeySequence(_(u"Ctrl+B")))
        self.show_file_browser_action.triggered.connect(self.show_file_browser)

        self.show_cover_art_action = QtGui.QAction(_(u"&Cover Art"), self)
        self.show_cover_art_action.setCheckable(True)
        if config.persist["view_cover_art"]:
            self.show_cover_art_action.setChecked(True)
        self.show_cover_art_action.triggered.connect(self.show_cover_art)

        self.search_action = QtGui.QAction(icontheme.lookup('system-search'),
                                           _(u"Search"), self)
        self.search_action.triggered.connect(self.search)

        self.cd_lookup_action = QtGui.QAction(
            icontheme.lookup('media-optical'), _(u"Lookup &CD..."), self)
        self.cd_lookup_action.setStatusTip(
            _(u"Lookup the details of the CD in your drive"))
        # TR: Keyboard shortcut for "Lookup CD"
        self.cd_lookup_action.setShortcut(QtGui.QKeySequence(_("Ctrl+K")))
        self.cd_lookup_action.triggered.connect(self.tagger.lookup_cd)

        self.analyze_action = QtGui.QAction(icontheme.lookup('picard-analyze'),
                                            _(u"&Scan"), self)
        self.analyze_action.setStatusTip(
            _(u"Use AcoustID audio fingerprint to identify the files by the actual music, even if they have no metadata"
              ))
        self.analyze_action.setEnabled(False)
        # TR: Keyboard shortcut for "Analyze"
        self.analyze_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Y")))
        self.analyze_action.triggered.connect(self.analyze)

        self.cluster_action = QtGui.QAction(icontheme.lookup('picard-cluster'),
                                            _(u"Cl&uster"), self)
        self.cluster_action.setStatusTip(
            _(u"Cluster files into album clusters"))
        self.cluster_action.setEnabled(False)
        # TR: Keyboard shortcut for "Cluster"
        self.cluster_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+U")))
        self.cluster_action.triggered.connect(self.cluster)

        self.autotag_action = QtGui.QAction(
            icontheme.lookup('picard-auto-tag'), _(u"&Lookup"), self)
        tip = _(u"Lookup selected items in MusicBrainz")
        self.autotag_action.setToolTip(tip)
        self.autotag_action.setStatusTip(tip)
        self.autotag_action.setEnabled(False)
        # TR: Keyboard shortcut for "Lookup"
        self.autotag_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+L")))
        self.autotag_action.triggered.connect(self.autotag)

        self.view_info_action = QtGui.QAction(
            icontheme.lookup('picard-edit-tags'), _(u"&Info..."), self)
        self.view_info_action.setEnabled(False)
        # TR: Keyboard shortcut for "Info"
        self.view_info_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+I")))
        self.view_info_action.triggered.connect(self.view_info)

        self.refresh_action = QtGui.QAction(
            icontheme.lookup('view-refresh', icontheme.ICON_SIZE_MENU),
            _("&Refresh"), self)
        self.refresh_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+R")))
        self.refresh_action.triggered.connect(self.refresh)

        self.enable_renaming_action = QtGui.QAction(_(u"&Rename Files"), self)
        self.enable_renaming_action.setCheckable(True)
        self.enable_renaming_action.setChecked(config.setting["rename_files"])
        self.enable_renaming_action.triggered.connect(self.toggle_rename_files)

        self.enable_moving_action = QtGui.QAction(_(u"&Move Files"), self)
        self.enable_moving_action.setCheckable(True)
        self.enable_moving_action.setChecked(config.setting["move_files"])
        self.enable_moving_action.triggered.connect(self.toggle_move_files)

        self.enable_tag_saving_action = QtGui.QAction(_(u"Save &Tags"), self)
        self.enable_tag_saving_action.setCheckable(True)
        self.enable_tag_saving_action.setChecked(
            not config.setting["dont_write_tags"])
        self.enable_tag_saving_action.triggered.connect(self.toggle_tag_saving)

        self.tags_from_filenames_action = QtGui.QAction(
            _(u"Tags From &File Names..."), self)
        self.tags_from_filenames_action.triggered.connect(
            self.open_tags_from_filenames)
        self.tags_from_filenames_action.setEnabled(False)

        self.open_collection_in_browser_action = QtGui.QAction(
            _(u"&Open My Collections in Browser"), self)
        self.open_collection_in_browser_action.triggered.connect(
            self.open_collection_in_browser)
        self.open_collection_in_browser_action.setEnabled(
            config.setting["username"] != u'')

        self.view_log_action = QtGui.QAction(_(u"View Error/Debug &Log"), self)
        self.view_log_action.triggered.connect(self.show_log)

        self.view_history_action = QtGui.QAction(_(u"View Activity &History"),
                                                 self)
        self.view_history_action.triggered.connect(self.show_history)

        xmlws_manager = self.tagger.xmlws.manager
        xmlws_manager.authenticationRequired.connect(self.show_password_dialog)
        xmlws_manager.proxyAuthenticationRequired.connect(
            self.show_proxy_dialog)

        self.play_file_action = QtGui.QAction(icontheme.lookup('play-music'),
                                              _(u"&Play file"), self)
        self.play_file_action.setStatusTip(
            _(u"Play the file in your default media player"))
        self.play_file_action.setEnabled(False)
        self.play_file_action.triggered.connect(self.play_file)

        self.open_folder_action = QtGui.QAction(
            icontheme.lookup('folder', icontheme.ICON_SIZE_MENU),
            _(u"Open Containing &Folder"), self)
        self.open_folder_action.setStatusTip(
            _(u"Open the containing folder in your file explorer"))
        self.open_folder_action.setEnabled(False)
        self.open_folder_action.triggered.connect(self.open_folder)

    def toggle_rename_files(self, checked):
        config.setting["rename_files"] = checked

    def toggle_move_files(self, checked):
        config.setting["move_files"] = checked

    def toggle_tag_saving(self, checked):
        config.setting["dont_write_tags"] = not checked

    def get_selected_or_unmatched_files(self):
        files = self.tagger.get_files_from_objects(self.selected_objects)
        if not files:
            files = self.tagger.unmatched_files.files
        return files

    def open_tags_from_filenames(self):
        files = self.get_selected_or_unmatched_files()
        if files:
            dialog = TagsFromFileNamesDialog(files, self)
            dialog.exec_()

    def open_collection_in_browser(self):
        self.tagger.collection_lookup()

    def create_menus(self):
        menu = self.menuBar().addMenu(_(u"&File"))
        menu.addAction(self.add_directory_action)
        menu.addAction(self.add_files_action)
        menu.addSeparator()
        menu.addAction(self.play_file_action)
        menu.addAction(self.open_folder_action)
        menu.addSeparator()
        menu.addAction(self.save_action)
        menu.addAction(self.submit_action)
        menu.addSeparator()
        menu.addAction(self.exit_action)
        menu = self.menuBar().addMenu(_(u"&Edit"))
        menu.addAction(self.cut_action)
        menu.addAction(self.paste_action)
        menu.addSeparator()
        menu.addAction(self.view_info_action)
        menu.addAction(self.remove_action)
        menu = self.menuBar().addMenu(_(u"&View"))
        menu.addAction(self.show_file_browser_action)
        menu.addAction(self.show_cover_art_action)
        menu.addSeparator()
        menu.addAction(self.toolbar_toggle_action)
        menu.addAction(self.search_toolbar_toggle_action)
        menu = self.menuBar().addMenu(_(u"&Options"))
        menu.addAction(self.enable_renaming_action)
        menu.addAction(self.enable_moving_action)
        menu.addAction(self.enable_tag_saving_action)
        menu.addSeparator()
        menu.addAction(self.options_action)
        menu = self.menuBar().addMenu(_(u"&Tools"))
        menu.addAction(self.refresh_action)
        menu.addAction(self.cd_lookup_action)
        menu.addAction(self.autotag_action)
        menu.addAction(self.analyze_action)
        menu.addAction(self.cluster_action)
        menu.addAction(self.browser_lookup_action)
        menu.addSeparator()
        menu.addAction(self.tags_from_filenames_action)
        menu.addAction(self.open_collection_in_browser_action)
        self.menuBar().addSeparator()
        menu = self.menuBar().addMenu(_(u"&Help"))
        menu.addAction(self.help_action)
        menu.addSeparator()
        menu.addAction(self.view_history_action)
        menu.addSeparator()
        menu.addAction(self.support_forum_action)
        menu.addAction(self.report_bug_action)
        menu.addAction(self.view_log_action)
        menu.addSeparator()
        menu.addAction(self.donate_action)
        menu.addAction(self.about_action)

    def update_toolbar_style(self):
        if config.setting["toolbar_show_labels"]:
            self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        else:
            self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        self.cd_lookup_action.setEnabled(
            len(get_cdrom_drives()) > 0 and discid is not None)

    def create_toolbar(self):
        self.toolbar = toolbar = self.addToolBar(_(u"Actions"))
        self.toolbar_toggle_action = self.toolbar.toggleViewAction()
        self.update_toolbar_style()
        toolbar.setObjectName("main_toolbar")

        def add_toolbar_action(action):
            toolbar.addAction(action)
            widget = toolbar.widgetForAction(action)
            widget.setFocusPolicy(QtCore.Qt.TabFocus)
            widget.setAttribute(QtCore.Qt.WA_MacShowFocusRect)

        add_toolbar_action(self.add_directory_action)
        add_toolbar_action(self.add_files_action)
        toolbar.addSeparator()
        add_toolbar_action(self.play_file_action)
        toolbar.addSeparator()
        add_toolbar_action(self.save_action)
        add_toolbar_action(self.submit_action)
        toolbar.addSeparator()

        add_toolbar_action(self.cd_lookup_action)
        drives = get_cdrom_drives()
        if len(drives) > 1:
            self.cd_lookup_menu = QtGui.QMenu()
            for drive in drives:
                self.cd_lookup_menu.addAction(drive)
            self.cd_lookup_menu.triggered.connect(self.tagger.lookup_cd)
            button = toolbar.widgetForAction(self.cd_lookup_action)
            button.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
            button.setMenu(self.cd_lookup_menu)

        add_toolbar_action(self.cluster_action)
        add_toolbar_action(self.autotag_action)
        add_toolbar_action(self.analyze_action)
        add_toolbar_action(self.view_info_action)
        add_toolbar_action(self.remove_action)
        add_toolbar_action(self.browser_lookup_action)

        self.search_toolbar = toolbar = self.addToolBar(_(u"Search"))
        self.search_toolbar_toggle_action = self.search_toolbar.toggleViewAction(
        )
        toolbar.setObjectName("search_toolbar")
        search_panel = QtGui.QWidget(toolbar)
        hbox = QtGui.QHBoxLayout(search_panel)
        self.search_combo = QtGui.QComboBox(search_panel)
        self.search_combo.addItem(_(u"Album"), "album")
        self.search_combo.addItem(_(u"Artist"), "artist")
        self.search_combo.addItem(_(u"Track"), "track")
        hbox.addWidget(self.search_combo, 0)
        self.search_edit = ButtonLineEdit(search_panel)
        self.search_edit.returnPressed.connect(self.search)
        hbox.addWidget(self.search_edit, 0)
        self.search_button = QtGui.QToolButton(search_panel)
        self.search_button.setAutoRaise(True)
        self.search_button.setDefaultAction(self.search_action)
        self.search_button.setIconSize(QtCore.QSize(22, 22))
        self.search_button.setAttribute(QtCore.Qt.WA_MacShowFocusRect)
        hbox.addWidget(self.search_button)
        toolbar.addWidget(search_panel)

    def set_tab_order(self):
        tab_order = self.setTabOrder
        tw = self.toolbar.widgetForAction

        # toolbar
        tab_order(tw(self.add_directory_action), tw(self.add_files_action))
        tab_order(tw(self.add_files_action), tw(self.play_file_action))
        tab_order(tw(self.play_file_action), tw(self.save_action))
        tab_order(tw(self.save_action), tw(self.submit_action))
        tab_order(tw(self.submit_action), tw(self.cd_lookup_action))
        tab_order(tw(self.cd_lookup_action), tw(self.cluster_action))
        tab_order(tw(self.cluster_action), tw(self.autotag_action))
        tab_order(tw(self.autotag_action), tw(self.analyze_action))
        tab_order(tw(self.analyze_action), tw(self.view_info_action))
        tab_order(tw(self.view_info_action), tw(self.remove_action))
        tab_order(tw(self.remove_action), tw(self.browser_lookup_action))
        tab_order(tw(self.browser_lookup_action), self.search_combo)
        tab_order(self.search_combo, self.search_edit)
        tab_order(self.search_edit, self.search_button)
        # panels
        tab_order(self.search_button, self.file_browser)
        tab_order(self.file_browser, self.panel.views[0])
        tab_order(self.panel.views[0], self.panel.views[1])
        tab_order(self.panel.views[1], self.metadata_box)

    def enable_submit(self, enabled):
        """Enable/disable the 'Submit fingerprints' action."""
        self.submit_action.setEnabled(enabled)

    def enable_cluster(self, enabled):
        """Enable/disable the 'Cluster' action."""
        self.cluster_action.setEnabled(enabled)

    def search(self):
        """Search for album, artist or track on the MusicBrainz website."""
        text = self.search_edit.text()
        type = self.search_combo.itemData(self.search_combo.currentIndex())
        self.tagger.search(text, type, config.setting["use_adv_search_syntax"])

    def add_files(self):
        """Add files to the tagger."""
        current_directory = find_starting_directory()
        formats = []
        extensions = []
        for exts, name in supported_formats():
            exts = ["*" + e for e in exts]
            formats.append("%s (%s)" % (name, " ".join(exts)))
            extensions.extend(exts)
        formats.sort()
        extensions.sort()
        formats.insert(
            0,
            _("All Supported Formats") + " (%s)" % " ".join(extensions))
        files = QtGui.QFileDialog.getOpenFileNames(self, "", current_directory,
                                                   u";;".join(formats))
        if files:
            files = map(unicode, files)
            config.persist["current_directory"] = os.path.dirname(files[0])
            self.tagger.add_files(files)

    def add_directory(self):
        """Add directory to the tagger."""
        current_directory = find_starting_directory()

        dir_list = []
        if not config.setting["toolbar_multiselect"]:
            directory = QtGui.QFileDialog.getExistingDirectory(
                self, "", current_directory)
            if directory:
                dir_list.append(directory)
        else:
            # Use a custom file selection dialog to allow the selection of multiple directories
            file_dialog = QtGui.QFileDialog(self, "", current_directory)
            file_dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly)
            if sys.platform == "darwin":  # The native dialog doesn't allow selecting >1 directory
                file_dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog)
            tree_view = file_dialog.findChild(QtGui.QTreeView)
            tree_view.setSelectionMode(
                QtGui.QAbstractItemView.ExtendedSelection)
            list_view = file_dialog.findChild(QtGui.QListView, "listView")
            list_view.setSelectionMode(
                QtGui.QAbstractItemView.ExtendedSelection)

            if file_dialog.exec_() == QtGui.QDialog.Accepted:
                dir_list = file_dialog.selectedFiles()

        if len(dir_list) == 1:
            config.persist["current_directory"] = dir_list[0]
            self.set_statusbar_message(
                N_("Adding directory: '%(directory)s' ..."),
                {'directory': dir_list[0]})
        elif len(dir_list) > 1:
            (parent, dir) = os.path.split(str(dir_list[0]))
            config.persist["current_directory"] = parent
            self.set_statusbar_message(
                N_("Adding multiple directories from '%(directory)s' ..."),
                {'directory': parent})

        for directory in dir_list:
            directory = unicode(directory)
            self.tagger.add_directory(directory)

    def show_about(self):
        self.show_options("about")

    def show_options(self, page=None):
        dialog = OptionsDialog(page, self)
        dialog.exec_()

    def show_help(self):
        webbrowser2.goto('documentation')

    def show_log(self):
        from picard.ui.logview import LogView
        LogView(self).show()

    def show_history(self):
        from picard.ui.logview import HistoryView
        HistoryView(self).show()

    def open_bug_report(self):
        webbrowser2.goto('troubleshooting')

    def open_support_forum(self):
        webbrowser2.goto('forum')

    def open_donation_page(self):
        webbrowser2.goto('donate')

    def save(self):
        """Tell the tagger to save the selected objects."""
        self.tagger.save(self.selected_objects)

    def remove(self):
        """Tell the tagger to remove the selected objects."""
        self.panel.remove(self.selected_objects)

    def analyze(self):
        if not config.setting['fingerprinting_system']:
            if self.show_analyze_settings_info():
                self.show_options("fingerprinting")
            if not config.setting['fingerprinting_system']:
                return
        return self.tagger.analyze(self.selected_objects)

    def play_file(self):
        files = self.tagger.get_files_from_objects(self.selected_objects)
        for file in files:
            url = QtCore.QUrl.fromLocalFile(file.filename)
            QtGui.QDesktopServices.openUrl(url)

    def open_folder(self):
        files = self.tagger.get_files_from_objects(self.selected_objects)
        for file in files:
            url = QtCore.QUrl.fromLocalFile(os.path.dirname(file.filename))
            QtGui.QDesktopServices.openUrl(url)

    def show_analyze_settings_info(self):
        ret = QtGui.QMessageBox.question(
            self, _(u"Configuration Required"),
            _(u"Audio fingerprinting is not yet configured. Would you like to configure it now?"
              ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
            QtGui.QMessageBox.Yes)
        return ret == QtGui.QMessageBox.Yes

    def view_info(self):
        if isinstance(self.selected_objects[0], Album):
            album = self.selected_objects[0]
            dialog = AlbumInfoDialog(album, self)
        elif isinstance(self.selected_objects[0], Cluster):
            cluster = self.selected_objects[0]
            dialog = ClusterInfoDialog(cluster, self)
        else:
            file = self.tagger.get_files_from_objects(self.selected_objects)[0]
            dialog = FileInfoDialog(file, self)
        dialog.exec_()

    def cluster(self):
        self.tagger.cluster(self.selected_objects)
        self.update_actions()

    def refresh(self):
        self.tagger.refresh(self.selected_objects)

    def browser_lookup(self):
        self.tagger.browser_lookup(self.selected_objects[0])

    @throttle(100)
    def update_actions(self):
        can_remove = False
        can_save = False
        can_analyze = False
        can_refresh = False
        can_autotag = False
        single = self.selected_objects[0] if len(
            self.selected_objects) == 1 else None
        can_view_info = bool(single and single.can_view_info())
        can_browser_lookup = bool(single and single.can_browser_lookup())
        have_files = len(
            self.tagger.get_files_from_objects(self.selected_objects)) > 0
        for obj in self.selected_objects:
            if obj is None:
                continue
            if obj.can_analyze():
                can_analyze = True
            if obj.can_save():
                can_save = True
            if obj.can_remove():
                can_remove = True
            if obj.can_refresh():
                can_refresh = True
            if obj.can_autotag():
                can_autotag = True
            # Skip further loops if all values now True.
            if can_analyze and can_save and can_remove and can_refresh and can_autotag:
                break
        self.remove_action.setEnabled(can_remove)
        self.save_action.setEnabled(can_save)
        self.view_info_action.setEnabled(can_view_info)
        self.analyze_action.setEnabled(can_analyze)
        self.refresh_action.setEnabled(can_refresh)
        self.autotag_action.setEnabled(can_autotag)
        self.browser_lookup_action.setEnabled(can_browser_lookup)
        self.play_file_action.setEnabled(have_files)
        self.open_folder_action.setEnabled(have_files)
        self.cut_action.setEnabled(bool(self.selected_objects))
        files = self.get_selected_or_unmatched_files()
        self.tags_from_filenames_action.setEnabled(bool(files))

    def update_selection(self, objects=None):
        if self.ignore_selection_changes:
            return

        if objects is not None:
            self.selected_objects = objects
        else:
            objects = self.selected_objects

        self.update_actions()

        metadata = None
        obj = None

        if len(objects) == 1:
            obj = list(objects)[0]
            if isinstance(obj, File):
                metadata = obj.metadata
                if obj.state == obj.ERROR:
                    msg = N_("%(filename)s (error: %(error)s)")
                    mparms = {'filename': obj.filename, 'error': obj.error}
                else:
                    msg = N_("%(filename)s")
                    mparms = {
                        'filename': obj.filename,
                    }
                self.set_statusbar_message(msg,
                                           mparms,
                                           echo=None,
                                           history=None)
            elif isinstance(obj, Track):
                metadata = obj.metadata
                if obj.num_linked_files == 1:
                    file = obj.linked_files[0]
                    if file.state == File.ERROR:
                        msg = N_(
                            "%(filename)s (%(similarity)d%%) (error: %(error)s)"
                        )
                        mparms = {
                            'filename': file.filename,
                            'similarity': file.similarity * 100,
                            'error': file.error
                        }
                    else:
                        msg = N_("%(filename)s (%(similarity)d%%)")
                        mparms = {
                            'filename': file.filename,
                            'similarity': file.similarity * 100,
                        }
                    self.set_statusbar_message(msg,
                                               mparms,
                                               echo=None,
                                               history=None)
            elif obj.can_edit_tags():
                metadata = obj.metadata

        self.metadata_box.selection_dirty = True
        self.metadata_box.update()
        self.cover_art_box.set_metadata(metadata, obj)
        self.selection_updated.emit(objects)

    def show_cover_art(self):
        """Show/hide the cover art box."""
        if self.show_cover_art_action.isChecked():
            self.cover_art_box.show()
            self.metadata_box.resize_columns()
        else:
            self.cover_art_box.hide()

    def show_file_browser(self):
        """Show/hide the file browser."""
        if self.show_file_browser_action.isChecked():
            sizes = self.panel.sizes()
            if sizes[0] == 0:
                sizes[0] = sum(sizes) / 4
                self.panel.setSizes(sizes)
            self.file_browser.show()
        else:
            self.file_browser.hide()

    def show_password_dialog(self, reply, authenticator):
        if reply.url().host() == config.setting['server_host']:
            ret = QtGui.QMessageBox.question(
                self, _(u"Authentication Required"),
                _(u"Picard needs authorization to access your personal data on the MusicBrainz server. Would you like to log in now?"
                  ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                QtGui.QMessageBox.Yes)
            if ret == QtGui.QMessageBox.Yes:
                pass
        else:
            dialog = PasswordDialog(authenticator, reply, parent=self)
            dialog.exec_()

    def show_proxy_dialog(self, proxy, authenticator):
        dialog = ProxyDialog(authenticator, proxy, parent=self)
        dialog.exec_()

    def autotag(self):
        self.tagger.autotag(self.selected_objects)

    def cut(self):
        self._clipboard = self.selected_objects
        self.paste_action.setEnabled(bool(self._clipboard))

    def paste(self):
        selected_objects = self.selected_objects
        if not selected_objects:
            target = self.tagger.unmatched_files
        else:
            target = selected_objects[0]
        self.tagger.move_files(
            self.tagger.get_files_from_objects(self._clipboard), target)
        self._clipboard = []
        self.paste_action.setEnabled(False)
예제 #18
0
class MetadataOptionsPage(OptionsPage):

    NAME = "metadata"
    TITLE = N_("Metadata")
    PARENT = None
    SORT_ORDER = 20
    ACTIVE = True

    options = [
        config.TextOption("setting", "va_name", u"Various Artists"),
        config.TextOption("setting", "nat_name", u"[non-album tracks]"),
        config.TextOption("setting", "artist_locale", u"en"),
        config.BoolOption("setting", "translate_artist_names", False),
        config.BoolOption("setting", "release_ars", True),
        config.BoolOption("setting", "track_ars", False),
        config.BoolOption("setting", "folksonomy_tags", False),
        config.BoolOption("setting", "convert_punctuation", True),
        config.BoolOption("setting", "standardize_artists", False),
    ]

    def __init__(self, parent=None):
        super(MetadataOptionsPage, self).__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)

    def load(self):
        self.ui.translate_artist_names.setChecked(
            config.setting["translate_artist_names"])

        combo_box = self.ui.artist_locale
        locales = sorted(ALIAS_LOCALES.keys())
        for i, loc in enumerate(locales):
            name = ALIAS_LOCALES[loc]
            if "_" in loc:
                name = "    " + name
            combo_box.addItem(name, loc)
            if loc == config.setting["artist_locale"]:
                combo_box.setCurrentIndex(i)

        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.folksonomy_tags.setChecked(config.setting["folksonomy_tags"])
        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"])

    def save(self):
        config.setting[
            "translate_artist_names"] = self.ui.translate_artist_names.isChecked(
            )
        config.setting["artist_locale"] = self.ui.artist_locale.itemData(
            self.ui.artist_locale.currentIndex())
        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["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked()
        config.setting["va_name"] = self.ui.va_name.text()
        nat_name = unicode(self.ui.nat_name.text())
        if nat_name != config.setting["nat_name"]:
            config.setting["nat_name"] = nat_name
            self.tagger.nats.update()
        config.setting[
            "standardize_artists"] = self.ui.standardize_artists.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)
예제 #19
0
class MetadataBox(QtGui.QTableWidget):

    options = (
        config.Option("persist", "metadatabox_header_state", QtCore.QByteArray()),
        config.BoolOption("persist", "show_changes_first", False)
    )

    def __init__(self, parent):
        QtGui.QTableWidget.__init__(self, parent)
        self.parent = parent
        self.setAccessibleName(_("metadata view"))
        self.setAccessibleDescription(_("Displays original and new tags for the selected files"))
        self.setColumnCount(3)
        self.setHorizontalHeaderLabels((_("Tag"), _("Original Value"), _("New Value")))
        self.horizontalHeader().setStretchLastSection(True)
        self.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
        self.horizontalHeader().setClickable(False)
        self.verticalHeader().setDefaultSectionSize(21)
        self.verticalHeader().setVisible(False)
        self.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setTabKeyNavigation(False)
        self.setStyleSheet("QTableWidget {border: none;}")
        self.setAttribute(QtCore.Qt.WA_MacShowFocusRect, 1)
        self.colors = {
            TagStatus.NoChange: self.palette().color(QtGui.QPalette.Text),
            TagStatus.Removed: QtGui.QBrush(QtGui.QColor("red")),
            TagStatus.Added: QtGui.QBrush(QtGui.QColor("green")),
            TagStatus.Changed: QtGui.QBrush(QtGui.QColor("darkgoldenrod"))
        }
        self.files = set()
        self.tracks = set()
        self.objects = set()
        self.selection_mutex = QtCore.QMutex()
        self.selection_dirty = False
        self.editing = None  # the QTableWidgetItem being edited
        self.clipboard = [""]
        self.add_tag_action = QtGui.QAction(_(u"Add New Tag..."), parent)
        self.add_tag_action.triggered.connect(partial(self.edit_tag, ""))
        self.changes_first_action = QtGui.QAction(_(u"Show Changes First"), parent)
        self.changes_first_action.setCheckable(True)
        self.changes_first_action.setChecked(config.persist["show_changes_first"])
        self.changes_first_action.toggled.connect(self.toggle_changes_first)
        self.browser_integration = BrowserIntegration()
        # TR: Keyboard shortcut for "Add New Tag..."
        self.add_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+A")), self, partial(self.edit_tag, ""))
        self.add_tag_action.setShortcut(self.add_tag_shortcut.key())
        # TR: Keyboard shortcut for "Edit..." (tag)
        self.edit_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+E")), self, partial(self.edit_selected_tag))
        # TR: Keyboard shortcut for "Remove" (tag)
        self.remove_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+R")), self, self.remove_selected_tags)

    def get_file_lookup(self):
        """Return a FileLookup object."""
        return FileLookup(self, config.setting["server_host"],
                          config.setting["server_port"],
                          self.browser_integration.port)

    def lookup_tags(self):
        lookup = self.get_file_lookup()
        LOOKUP_TAGS = {
            "musicbrainz_recordingid": lookup.recordingLookup,
            "musicbrainz_trackid": lookup.trackLookup,
            "musicbrainz_albumid": lookup.albumLookup,
            "musicbrainz_workid": lookup.workLookup,
            "musicbrainz_artistid": lookup.artistLookup,
            "musicbrainz_albumartistid": lookup.artistLookup,
            "musicbrainz_releasegroupid": lookup.releaseGroupLookup,
            "acoustid_id": lookup.acoustLookup
        }
        return LOOKUP_TAGS

    def open_link(self, values, tag):
        lookup = self.lookup_tags()
        lookup_func = lookup[tag]
        for v in values:
            lookup_func(v)

    def edit(self, index, trigger, event):
        if index.column() != 2:
            return False
        item = self.itemFromIndex(index)
        if item.flags() & QtCore.Qt.ItemIsEditable and \
           trigger in (QtGui.QAbstractItemView.DoubleClicked,
                       QtGui.QAbstractItemView.EditKeyPressed,
                       QtGui.QAbstractItemView.AnyKeyPressed):
            tag = self.tag_diff.tag_names[item.row()]
            values = self.tag_diff.new[tag]
            if len(values) > 1:
                self.edit_tag(tag)
                return False
            else:
                self.editing = item
                item.setText(values[0])
                return QtGui.QTableWidget.edit(self, index, trigger, event)
        return False

    def event(self, e):
        item = self.currentItem()
        if (item and e.type() == QtCore.QEvent.KeyPress and e.modifiers() == QtCore.Qt.ControlModifier):
            column = item.column()
            tag = self.tag_diff.tag_names[item.row()]
            if e.key() == QtCore.Qt.Key_C:
                if column == 1:
                    self.clipboard = list(self.tag_diff.orig[tag])
                elif column == 2:
                    self.clipboard = list(self.tag_diff.new[tag])
            elif e.key() == QtCore.Qt.Key_V and column == 2 and tag != "~length":
                self.set_tag_values(tag, list(self.clipboard))
        return QtGui.QTableWidget.event(self, e)

    def closeEditor(self, editor, hint):
        QtGui.QTableWidget.closeEditor(self, editor, hint)
        tag = self.tag_diff.tag_names[self.editing.row()]
        old = self.tag_diff.new[tag]
        new = [unicode(editor.text())]
        if old == new:
            self.editing.setText(old[0])
        else:
            self.set_tag_values(tag, new)
        self.editing = None
        self.update()

    def contextMenuEvent(self, event):
        menu = QtGui.QMenu(self)
        if self.objects:
            tags = self.selected_tags()
            if len(tags) == 1:
                edit_tag_action = QtGui.QAction(_(u"Edit..."), self.parent)
                edit_tag_action.triggered.connect(partial(self.edit_tag, list(tags)[0]))
                edit_tag_action.setShortcut(self.edit_tag_shortcut.key())
                menu.addAction(edit_tag_action)
            removals = []
            useorigs = []
            item = self.currentItem()
            column = item.column()
            for tag in tags:
                if tag in self.lookup_tags().keys():
                    if (column == 1 or column == 2) and len(tags) == 1 and item.text():
                        if column == 1:
                            values = self.tag_diff.orig[tag]
                        else:
                            values = self.tag_diff.new[tag]
                        lookup_action = QtGui.QAction(_(u"Lookup in &Browser"), self.parent)
                        lookup_action.triggered.connect(partial(self.open_link, values, tag))
                        menu.addAction(lookup_action)
                if self.tag_is_removable(tag):
                    removals.append(partial(self.remove_tag, tag))
                status = self.tag_diff.status[tag] & TagStatus.Changed
                if status == TagStatus.Changed or status == TagStatus.Removed:
                    for file in self.files:
                        objects = [file]
                        if file.parent in self.tracks and len(self.files & set(file.parent.linked_files)) == 1:
                            objects.append(file.parent)
                        orig_values = list(file.orig_metadata.getall(tag)) or [""]
                        useorigs.append(partial(self.set_tag_values, tag, orig_values, objects))
            if removals:
                remove_tag_action = QtGui.QAction(_(u"Remove"), self.parent)
                remove_tag_action.triggered.connect(lambda: [f() for f in removals])
                remove_tag_action.setShortcut(self.remove_tag_shortcut.key())
                menu.addAction(remove_tag_action)
            if useorigs:
                name = ungettext("Use Original Value", "Use Original Values", len(useorigs))
                use_orig_value_action = QtGui.QAction(name, self.parent)
                use_orig_value_action.triggered.connect(lambda: [f() for f in useorigs])
                menu.addAction(use_orig_value_action)
                menu.addSeparator()
            if len(tags) == 1 or removals or useorigs:
                menu.addSeparator()
            menu.addAction(self.add_tag_action)
            menu.addSeparator()
        menu.addAction(self.changes_first_action)
        menu.exec_(event.globalPos())
        event.accept()

    def edit_tag(self, tag):
        EditTagDialog(self.parent, tag).exec_()

    def edit_selected_tag(self):
        tags = self.selected_tags()
        if len(tags) == 1:
            self.edit_tag(list(tags)[0])

    def toggle_changes_first(self, checked):
        config.persist["show_changes_first"] = checked
        self.update()

    def set_tag_values(self, tag, values, objects=None):
        if objects is None:
            objects = self.objects
        self.parent.ignore_selection_changes = True
        if values != [""] or self.tag_is_removable(tag):
            for obj in objects:
                obj.metadata[tag] = values
                obj.update()
        self.update()
        self.parent.ignore_selection_changes = False

    def remove_tag(self, tag):
        self.set_tag_values(tag, [""])

    def remove_selected_tags(self):
        for tag in self.selected_tags():
            if self.tag_is_removable(tag):
                self.remove_tag(tag)

    def tag_is_removable(self, tag):
        return self.tag_diff.status[tag] & TagStatus.NotRemovable == 0

    def selected_tags(self):
        tags = set(self.tag_diff.tag_names[item.row()]
                   for item in self.selectedItems())
        tags.discard("~length")
        return tags

    def _update_selection(self):
        files = set()
        tracks = set()
        objects = set()
        for obj in self.parent.selected_objects:
            if isinstance(obj, File):
                files.add(obj)
            elif isinstance(obj, Track):
                tracks.add(obj)
                files.update(obj.linked_files)
            elif isinstance(obj, Cluster) and obj.can_edit_tags():
                objects.add(obj)
                files.update(obj.files)
            elif isinstance(obj, Album):
                objects.add(obj)
                tracks.update(obj.tracks)
                for track in obj.tracks:
                    files.update(track.linked_files)
        objects.update(files)
        objects.update(tracks)
        self.selection_dirty = False

        self.selection_mutex.lock()
        self.files = files
        self.tracks = tracks
        self.objects = objects
        self.selection_mutex.unlock()

    @throttle(100)
    def update(self):
        if self.editing:
            return
        if self.selection_dirty:
            self._update_selection()
        thread.run_task(self._update_tags, self._update_items)

    def _update_tags(self):
        self.selection_mutex.lock()
        files = self.files
        tracks = self.tracks
        self.selection_mutex.unlock()

        if not (files or tracks):
            return None

        tag_diff = TagDiff()
        orig_tags = tag_diff.orig
        new_tags = tag_diff.new
        # existing_tags are orig_tags that would not be overwritten by
        # any new_tags, assuming clear_existing_tags is disabled.
        existing_tags = set()
        tag_diff.objects = len(files)

        clear_existing_tags = config.setting["clear_existing_tags"]

        for file in files:
            new_metadata = file.new_metadata
            orig_metadata = file.orig_metadata
            tags = set(new_metadata.keys() + orig_metadata.keys())

            for name in filter(lambda x: not x.startswith("~") and file.supports_tag(x), tags):
                new_values = new_metadata.getall(name)
                orig_values = orig_metadata.getall(name)

                if not ((new_values and name not in existing_tags) or clear_existing_tags):
                    new_values = list(orig_values or [""])
                    existing_tags.add(name)

                removed = name in new_metadata.deleted_tags
                tag_diff.add(name, orig_values, new_values, True, removed)

            tag_diff.add("~length",
                         str(orig_metadata.length), str(new_metadata.length), False)

        for track in tracks:
            if track.num_linked_files == 0:
                for name, values in dict.iteritems(track.metadata):
                    if not name.startswith("~"):
                        tag_diff.add(name, values, values, True)

                length = str(track.metadata.length)
                tag_diff.add("~length", length, length, False)

                tag_diff.objects += 1

        all_tags = set(orig_tags.keys() + new_tags.keys())
        tag_names = COMMON_TAGS + \
                    sorted(all_tags.difference(COMMON_TAGS),
                           key=lambda x: display_tag_name(x).lower())

        if config.persist["show_changes_first"]:
            tags_by_status = {}

            for tag in tag_names:
                tags_by_status.setdefault(tag_diff.tag_status(tag), []).append(tag)

            for status in (TagStatus.Changed, TagStatus.Added,
                           TagStatus.Removed, TagStatus.NoChange):
                tag_diff.tag_names += tags_by_status.pop(status, [])
        else:
            tag_diff.tag_names = [
                tag for tag in tag_names if
                tag_diff.status[tag] != TagStatus.Empty]

        return tag_diff

    def _update_items(self, result=None, error=None):
        if self.editing:
            return

        if not (self.files or self.tracks):
            result = None

        self.tag_diff = result

        if result is None:
            self.setRowCount(0)
            return

        self.setRowCount(len(result.tag_names))

        orig_flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
        new_flags = orig_flags | QtCore.Qt.ItemIsEditable

        for i, name in enumerate(result.tag_names):
            length = name == "~length"
            tag_item = self.item(i, 0)
            orig_item = self.item(i, 1)
            new_item = self.item(i, 2)
            if not tag_item:
                tag_item = QtGui.QTableWidgetItem()
                tag_item.setFlags(orig_flags)
                font = tag_item.font()
                font.setBold(True)
                tag_item.setFont(font)
                self.setItem(i, 0, tag_item)
            if not orig_item:
                orig_item = QtGui.QTableWidgetItem()
                orig_item.setFlags(orig_flags)
                self.setItem(i, 1, orig_item)
            if not new_item:
                new_item = QtGui.QTableWidgetItem()
                self.setItem(i, 2, new_item)
            tag_item.setText(display_tag_name(name))
            self.set_item_value(orig_item, self.tag_diff.orig, name)
            new_item.setFlags(orig_flags if length else new_flags)
            self.set_item_value(new_item, self.tag_diff.new, name)

            font = new_item.font()
            if result.tag_status(name) == TagStatus.Removed:
                font.setStrikeOut(True)
            else:
                font.setStrikeOut(False)

            new_item.setFont(font)

            color = self.colors.get(result.tag_status(name),
                                    self.colors[TagStatus.NoChange])
            orig_item.setForeground(color)
            new_item.setForeground(color)

    def set_item_value(self, item, tags, name):
        text, italic = tags.display_value(name)
        item.setText(text)
        font = item.font()
        font.setItalic(italic)
        item.setFont(font)

    def restore_state(self):
        state = config.persist["metadatabox_header_state"]
        header = self.horizontalHeader()
        header.restoreState(state)
        header.setResizeMode(QtGui.QHeaderView.Interactive)

    def save_state(self):
        header = self.horizontalHeader()
        state = header.saveState()
        config.persist["metadatabox_header_state"] = state
예제 #20
0
class TagsCompatibilityOptionsPage(OptionsPage):

    NAME = "tags_compatibility"
    TITLE = N_("Tag Compatibility")
    PARENT = "tags"
    SORT_ORDER = 30
    ACTIVE = True

    options = [
        config.BoolOption("setting", "write_id3v1", True),
        config.BoolOption("setting", "write_id3v23", True),
        config.TextOption("setting", "id3v2_encoding", "utf-16"),
        config.TextOption("setting", "id3v23_join_with", "/"),
        config.BoolOption("setting", "itunes_compatible_grouping", False),
        config.BoolOption("setting", "aac_save_ape", True),
        config.BoolOption("setting", "remove_ape_from_aac", False),
        config.BoolOption("setting", "ac3_save_ape", True),
        config.BoolOption("setting", "remove_ape_from_ac3", False),
        config.BoolOption("setting", "write_wave_riff_info", True),
        config.BoolOption("setting", "remove_wave_riff_info", False),
        config.TextOption("setting", "wave_riff_info_encoding",
                          "windows-1252"),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_TagsCompatibilityOptionsPage()
        self.ui.setupUi(self)
        self.ui.write_id3v23.clicked.connect(self.update_encodings)
        self.ui.write_id3v24.clicked.connect(
            partial(self.update_encodings, force_utf8=True))
        self.ui.aac_no_tags.toggled.connect(
            self.ui.remove_ape_from_aac.setEnabled)
        self.ui.ac3_no_tags.toggled.connect(
            self.ui.remove_ape_from_ac3.setEnabled)

    def load(self):
        self.ui.write_id3v1.setChecked(config.setting["write_id3v1"])
        if config.setting["write_id3v23"]:
            self.ui.write_id3v23.setChecked(True)
        else:
            self.ui.write_id3v24.setChecked(True)
        if config.setting["id3v2_encoding"] == "iso-8859-1":
            self.ui.enc_iso88591.setChecked(True)
        elif config.setting["id3v2_encoding"] == "utf-16":
            self.ui.enc_utf16.setChecked(True)
        else:
            self.ui.enc_utf8.setChecked(True)
        self.ui.id3v23_join_with.setEditText(
            config.setting["id3v23_join_with"])
        self.ui.itunes_compatible_grouping.setChecked(
            config.setting["itunes_compatible_grouping"])
        if config.setting["aac_save_ape"]:
            self.ui.aac_save_ape.setChecked(True)
        else:
            self.ui.aac_no_tags.setChecked(True)
        self.ui.remove_ape_from_aac.setChecked(
            config.setting["remove_ape_from_aac"])
        self.ui.remove_ape_from_aac.setEnabled(
            not config.setting["aac_save_ape"])
        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"])
        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)
        self.update_encodings()

    def save(self):
        config.setting["write_id3v1"] = self.ui.write_id3v1.isChecked()
        config.setting["write_id3v23"] = self.ui.write_id3v23.isChecked()
        config.setting[
            "id3v23_join_with"] = self.ui.id3v23_join_with.currentText()
        if self.ui.enc_iso88591.isChecked():
            config.setting["id3v2_encoding"] = "iso-8859-1"
        elif self.ui.enc_utf16.isChecked():
            config.setting["id3v2_encoding"] = "utf-16"
        else:
            config.setting["id3v2_encoding"] = "utf-8"
        config.setting[
            "itunes_compatible_grouping"] = self.ui.itunes_compatible_grouping.isChecked(
            )
        config.setting["aac_save_ape"] = self.ui.aac_save_ape.isChecked()
        config.setting[
            "remove_ape_from_aac"] = self.ui.remove_ape_from_aac.isChecked()
        config.setting["ac3_save_ape"] = self.ui.ac3_save_ape.isChecked()
        config.setting[
            "remove_ape_from_ac3"] = self.ui.remove_ape_from_ac3.isChecked()
        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"

    def update_encodings(self, force_utf8=False):
        if self.ui.write_id3v23.isChecked():
            if self.ui.enc_utf8.isChecked():
                self.ui.enc_utf16.setChecked(True)
            self.ui.enc_utf8.setEnabled(False)
            self.ui.label_id3v23_join_with.setEnabled(True)
            self.ui.id3v23_join_with.setEnabled(True)
        else:
            self.ui.enc_utf8.setEnabled(True)
            if force_utf8:
                self.ui.enc_utf8.setChecked(True)
            self.ui.label_id3v23_join_with.setEnabled(False)
            self.ui.id3v23_join_with.setEnabled(False)
예제 #21
0
class ScriptingOptionsPage(OptionsPage):

    NAME = "scripting"
    TITLE = N_("Scripting")
    PARENT = None
    SORT_ORDER = 85
    ACTIVE = True
    HELP_URL = '/config/options_scripting.html'

    options = [
        config.BoolOption("setting", "enable_tagger_scripts", False),
        config.ListOption("setting", "list_of_scripts", []),
        config.IntOption("persist", "last_selected_script_pos", 0),
        config.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):
        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
        self.ui.splitter.restoreState(config.persist["scripting_splitter"])

    def save(self):
        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)
예제 #22
0
class FileBrowser(QtGui.QTreeView):

    options = [
        config.TextOption("persist", "current_browser_path", ""),
        config.BoolOption("persist", "show_hidden_files", False),
    ]

    def __init__(self, parent):
        QtGui.QTreeView.__init__(self, parent)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setDragEnabled(True)
        self.move_files_here_action = QtGui.QAction(_("&Move Tagged Files Here"), self)
        self.move_files_here_action.triggered.connect(self.move_files_here)
        self.addAction(self.move_files_here_action)
        self.toggle_hidden_action = QtGui.QAction(_("Show &Hidden Files"), self)
        self.toggle_hidden_action.setCheckable(True)
        self.toggle_hidden_action.setChecked(config.persist["show_hidden_files"])
        self.toggle_hidden_action.toggled.connect(self.show_hidden)
        self.addAction(self.toggle_hidden_action)
        self.set_as_starting_directory_action = QtGui.QAction(_("&Set as starting directory"), self)
        self.set_as_starting_directory_action.triggered.connect(self.set_as_starting_directory)
        self.addAction(self.set_as_starting_directory_action)
        self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
        self.focused = False
        self._set_model()

    def _set_model(self):
        self.model = QtGui.QFileSystemModel()
        self.model.layoutChanged.connect(self._layout_changed)
        self.model.setRootPath("")
        self._set_model_filter()
        filters = []
        for exts, name in supported_formats():
            filters.extend("*" + e for e in exts)
        self.model.setNameFilters(filters)
        # Hide unsupported files completely
        self.model.setNameFilterDisables(False)
        self.model.sort(0, QtCore.Qt.AscendingOrder)
        self.setModel(self.model)
        if sys.platform == "darwin":
            self.setRootIndex(self.model.index("/Volumes"))
        header = self.header()
        header.hideSection(1)
        header.hideSection(2)
        header.hideSection(3)
        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
        header.setStretchLastSection(False)
        header.setVisible(False)

    def _set_model_filter(self):
        filter = QtCore.QDir.AllDirs | QtCore.QDir.Files | QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot
        if config.persist["show_hidden_files"]:
            filter |= QtCore.QDir.Hidden
        self.model.setFilter(filter)

    def _layout_changed(self):
        def scroll():
            # XXX The currentIndex seems to change while QFileSystemModel is
            # populating itself (so setCurrentIndex in __init__ won't last).
            # The time it takes to load varies and there are no signals to find
            # out when it's done. As a workaround, keep restoring the state as
            # long as the layout is updating, and the user hasn't focused yet.
            if not self.focused:
                self._restore_state()
            self.scrollTo(self.currentIndex())
        QtCore.QTimer.singleShot(0, scroll)

    def mousePressEvent(self, event):
        index = self.indexAt(event.pos())
        if index.isValid():
            self.selectionModel().setCurrentIndex(index, QtGui.QItemSelectionModel.NoUpdate)
        QtGui.QTreeView.mousePressEvent(self, event)

    def focusInEvent(self, event):
        self.focused = True
        QtGui.QTreeView.focusInEvent(self, event)

    def show_hidden(self, state):
        config.persist["show_hidden_files"] = state
        self._set_model_filter()

    def save_state(self):
        indexes = self.selectedIndexes()
        if indexes:
            path = self.model.filePath(indexes[0])
            config.persist["current_browser_path"] = os.path.normpath(unicode(path))

    def restore_state(self):
        pass

    def _restore_state(self):
        if config.setting["starting_directory"]:
            path = config.setting["starting_directory_path"]
            scrolltype = QtGui.QAbstractItemView.PositionAtTop
        else:
            path = config.persist["current_browser_path"]
            scrolltype = QtGui.QAbstractItemView.PositionAtCenter
        if path:
            index = self.model.index(find_existing_path(unicode(path)))
            self.setCurrentIndex(index)
            self.expand(index)
            self.scrollTo(index, scrolltype)

    def move_files_here(self):
        indexes = self.selectedIndexes()
        if not indexes:
            return
        path = self.model.filePath(indexes[0])
        config.setting["move_files_to"] = os.path.normpath(unicode(path))

    def set_as_starting_directory(self):
        indexes = self.selectedIndexes()
        if indexes:
            path = self.model.filePath(indexes[0])
            config.setting["starting_directory_path"] = os.path.normpath(unicode(path))
예제 #23
0
파일: cover.py 프로젝트: xlotlu/picard
class CoverOptionsPage(OptionsPage):

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

    options = [
        config.BoolOption("setting", "save_images_to_tags", True),
        config.BoolOption("setting", "save_only_front_images_to_tags", True),
        config.BoolOption("setting", "save_images_to_files", False),
        config.TextOption("setting", "cover_image_filename", "cover"),
        config.BoolOption("setting", "save_images_overwrite", False),
        config.BoolOption("setting", "ca_provider_use_amazon", True),
        config.BoolOption("setting", "ca_provider_use_caa", True),
        config.BoolOption("setting", "ca_provider_use_whitelist", True),
        config.BoolOption("setting", "caa_approved_only", True),
        config.BoolOption("setting", "caa_image_type_as_filename", False),
        config.IntOption("setting", "caa_image_size", 1),
        config.ListOption("setting", "caa_image_types", [u"front"]),
    ]

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

    def load(self):
        self.ui.save_images_to_tags.setChecked(
            config.setting["save_images_to_tags"])
        self.ui.cb_embed_front_only.setChecked(
            config.setting["save_only_front_images_to_tags"])
        self.ui.save_images_to_files.setChecked(
            config.setting["save_images_to_files"])
        self.ui.cover_image_filename.setText(
            config.setting["cover_image_filename"])
        self.ui.save_images_overwrite.setChecked(
            config.setting["save_images_overwrite"])
        self.update_filename()
        self.ui.caprovider_amazon.setChecked(
            config.setting["ca_provider_use_amazon"])
        self.ui.caprovider_caa.setChecked(
            config.setting["ca_provider_use_caa"])
        self.ui.caprovider_whitelist.setChecked(
            config.setting["ca_provider_use_whitelist"])
        self.ui.gb_caa.setEnabled(config.setting["ca_provider_use_caa"])

        self.ui.cb_image_size.setCurrentIndex(config.setting["caa_image_size"])
        widget = self.ui.caa_types_selector_1
        self._selector = CAATypesSelector(widget,
                                          config.setting["caa_image_types"])
        config.setting["caa_image_types"] = self._selector.get_selected_types()
        self.ui.cb_approved_only.setChecked(
            config.setting["caa_approved_only"])
        self.ui.cb_type_as_filename.setChecked(
            config.setting["caa_image_type_as_filename"])
        self.connect(self.ui.caprovider_caa, QtCore.SIGNAL("toggled(bool)"),
                     self.ui.gb_caa.setEnabled)

    def save(self):
        config.setting[
            "save_images_to_tags"] = self.ui.save_images_to_tags.isChecked()
        config.setting[
            "save_only_front_images_to_tags"] = self.ui.cb_embed_front_only.isChecked(
            )
        config.setting[
            "save_images_to_files"] = self.ui.save_images_to_files.isChecked()
        config.setting["cover_image_filename"] = unicode(
            self.ui.cover_image_filename.text())
        config.setting["ca_provider_use_amazon"] =\
            self.ui.caprovider_amazon.isChecked()
        config.setting["ca_provider_use_caa"] =\
            self.ui.caprovider_caa.isChecked()
        config.setting["ca_provider_use_whitelist"] =\
            self.ui.caprovider_whitelist.isChecked()
        config.setting["caa_image_size"] =\
            self.ui.cb_image_size.currentIndex()
        config.setting["caa_image_types"] = self._selector.get_selected_types()
        config.setting["caa_approved_only"] =\
            self.ui.cb_approved_only.isChecked()
        config.setting["caa_image_type_as_filename"] = \
            self.ui.cb_type_as_filename.isChecked()

        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)
예제 #24
0
class CoverOptionsPage(OptionsPage):

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

    options = [
        config.BoolOption("setting", "save_images_to_tags", True),
        config.BoolOption("setting", "embed_only_one_front_image", True),
        config.BoolOption("setting", "save_images_to_files", False),
        config.TextOption("setting", "cover_image_filename", "cover"),
        config.BoolOption("setting", "save_images_overwrite", False),
        config.ListOption("setting", "ca_providers", [
            ('Cover Art Archive', True),
            ('Amazon', True),
            ('Whitelist', True),
            ('CaaReleaseGroup', False),
            ('Local', False),
        ]),
    ]

    def __init__(self, parent=None):
        super(CoverOptionsPage, self).__init__(parent)
        self.ui = Ui_CoverOptionsPage()
        self.ui.setupUi(self)
        self.ui.save_images_to_files.clicked.connect(self.update_filename)
        self.ui.save_images_to_tags.clicked.connect(self.update_save_images_to_tags)
        self.provider_list_widget = SortableCheckboxListWidget()
        self.ui.ca_providers_list.insertWidget(0, self.provider_list_widget)
        self.ca_providers = []

    def load_cover_art_providers(self):
        """Load available providers, initialize provider-specific options, restore state of each
        """
        providers = cover_art_providers()
        for provider in providers:
            try:
                title = _(provider.TITLE)
            except AttributeError:
                title = provider.NAME
            checked = is_provider_enabled(provider.NAME)
            self.provider_list_widget.addItem(SortableCheckboxListItem(title, checked=checked, data=provider.NAME))

        def update_providers_options(items):
            self.ca_providers = [(item.data, item.checked) for item in items]
        self.provider_list_widget.changed.connect(update_providers_options)

    def restore_defaults(self):
        # Remove previous entries
        self.provider_list_widget.clear()
        super(CoverOptionsPage, self).restore_defaults()

    def load(self):
        self.ui.save_images_to_tags.setChecked(config.setting["save_images_to_tags"])
        self.ui.cb_embed_front_only.setChecked(config.setting["embed_only_one_front_image"])
        self.ui.save_images_to_files.setChecked(config.setting["save_images_to_files"])
        self.ui.cover_image_filename.setText(config.setting["cover_image_filename"])
        self.ui.save_images_overwrite.setChecked(config.setting["save_images_overwrite"])
        self.ca_providers = config.setting["ca_providers"]
        self.load_cover_art_providers()
        self.update_all()

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

    def update_all(self):
        self.update_filename()
        self.update_save_images_to_tags()

    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)

    def update_save_images_to_tags(self):
        enabled = self.ui.save_images_to_tags.isChecked()
        self.ui.cb_embed_front_only.setEnabled(enabled)
예제 #25
0
파일: general.py 프로젝트: renosbest/picard
class GeneralOptionsPage(OptionsPage):

    NAME = "general"
    TITLE = N_("General")
    PARENT = None
    SORT_ORDER = 1
    ACTIVE = True

    options = [
        config.TextOption("setting", "server_host", MUSICBRAINZ_SERVERS[0]),
        config.IntOption("setting", "server_port", 443),
        config.TextOption("persist", "oauth_refresh_token", ""),
        config.BoolOption("setting", "analyze_new_files", False),
        config.BoolOption("setting", "ignore_file_mbids", False),
        config.TextOption("persist", "oauth_refresh_token", ""),
        config.TextOption("persist", "oauth_refresh_token_scopes", ""),
        config.TextOption("persist", "oauth_access_token", ""),
        config.IntOption("persist", "oauth_access_token_expires", 0),
        config.TextOption("persist", "oauth_username", ""),
        config.BoolOption("setting", "check_for_updates", True),
        config.IntOption("setting", "update_check_days", 7),
        config.IntOption("setting", "update_level", 0),
        config.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):
        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.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():
            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()

    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()
예제 #26
0
class TagsOptionsPage(OptionsPage):

    NAME = "tags"
    TITLE = N_("Tags")
    PARENT = None
    SORT_ORDER = 30
    ACTIVE = True

    options = [
        config.BoolOption("setting", "clear_existing_tags", False),
        config.TextOption("setting", "preserved_tags", ""),
        config.BoolOption("setting", "write_id3v1", True),
        config.BoolOption("setting", "write_id3v23", True),
        config.TextOption("setting", "id3v2_encoding", "utf-16"),
        config.TextOption("setting", "id3v23_join_with", "/"),
        config.BoolOption("setting", "remove_id3_from_flac", False),
        config.BoolOption("setting", "remove_ape_from_mp3", False),
        config.BoolOption("setting", "tpe2_albumartist", False),
        config.BoolOption("setting", "dont_write_tags", False),
        config.BoolOption("setting", "preserve_timestamps", False),
    ]

    def __init__(self, parent=None):
        super(TagsOptionsPage, self).__init__(parent)
        self.ui = Ui_TagsOptionsPage()
        self.ui.setupUi(self)
        self.ui.write_id3v23.clicked.connect(self.update_encodings)
        self.ui.write_id3v24.clicked.connect(self.update_encodings)
        self.completer = QtWidgets.QCompleter(sorted(TAG_NAMES.keys()), self)
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.completer.setWidget(self.ui.preserved_tags)
        self.ui.preserved_tags.textEdited.connect(self.preserved_tags_edited)
        self.completer.activated.connect(self.completer_activated)

    def load(self):
        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.write_id3v1.setChecked(config.setting["write_id3v1"])
        if config.setting["write_id3v23"]:
            self.ui.write_id3v23.setChecked(True)
        else:
            self.ui.write_id3v24.setChecked(True)
        if config.setting["id3v2_encoding"] == "iso-8859-1":
            self.ui.enc_iso88591.setChecked(True)
        elif config.setting["id3v2_encoding"] == "utf-16":
            self.ui.enc_utf16.setChecked(True)
        else:
            self.ui.enc_utf8.setChecked(True)
        self.ui.id3v23_join_with.setEditText(
            config.setting["id3v23_join_with"])
        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.setText(config.setting["preserved_tags"])
        self.update_encodings()

    def save(self):
        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["write_id3v1"] = self.ui.write_id3v1.isChecked()
        config.setting["write_id3v23"] = self.ui.write_id3v23.isChecked()
        config.setting[
            "id3v23_join_with"] = self.ui.id3v23_join_with.currentText()
        if self.ui.enc_iso88591.isChecked():
            config.setting["id3v2_encoding"] = "iso-8859-1"
        elif self.ui.enc_utf16.isChecked():
            config.setting["id3v2_encoding"] = "utf-16"
        else:
            config.setting["id3v2_encoding"] = "utf-8"
        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"] = self.ui.preserved_tags.text()
        self.tagger.window.enable_tag_saving_action.setChecked(
            not config.setting["dont_write_tags"])

    def update_encodings(self):
        if self.ui.write_id3v23.isChecked():
            if self.ui.enc_utf8.isChecked():
                self.ui.enc_utf16.setChecked(True)
            self.ui.enc_utf8.setEnabled(False)
            self.ui.label_id3v23_join_with.setEnabled(True)
            self.ui.id3v23_join_with.setEnabled(True)
        else:
            self.ui.enc_utf8.setEnabled(True)
            self.ui.enc_utf8.setChecked(True)
            self.ui.label_id3v23_join_with.setEnabled(False)
            self.ui.id3v23_join_with.setEnabled(False)

    def preserved_tags_edited(self, text):
        prefix = text[:self.ui.preserved_tags.cursorPosition()].split(",")[-1]
        self.completer.setCompletionPrefix(prefix)
        if prefix:
            self.completer.complete()
        else:
            self.completer.popup().hide()

    def completer_activated(self, text):
        input = self.ui.preserved_tags
        current = input.text()
        i = input.cursorPosition()
        p = len(self.completer.completionPrefix())
        input.setText("%s%s %s" % (current[:i - p], text, current[i:]))
        input.setCursorPosition(i - p + len(text) + 1)
예제 #27
0
파일: caa.py 프로젝트: skyme5/picard
class ProviderOptionsCaa(ProviderOptions):
    """
        Options for Cover Art Archive cover art provider
    """

    options = [
        config.BoolOption("setting", "caa_save_single_front_image", False),
        config.BoolOption("setting", "caa_approved_only", False),
        config.BoolOption("setting", "caa_image_type_as_filename", False),
        config.IntOption("setting", "caa_image_size", _CAA_IMAGE_SIZE_DEFAULT),
        config.ListOption("setting", "caa_image_types", _CAA_IMAGE_TYPE_DEFAULT_INCLUDE),
        config.BoolOption("setting", "caa_restrict_image_types", True),
        config.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)

        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_save_single_front_image.setChecked(config.setting["caa_save_single_front_image"])
        self.ui.cb_approved_only.setChecked(config.setting["caa_approved_only"])
        self.ui.cb_type_as_filename.setChecked(config.setting["caa_image_type_as_filename"])
        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):
        size = self.ui.cb_image_size.currentData()
        config.setting["caa_image_size"] = size
        config.setting["caa_save_single_front_image"] = \
            self.ui.cb_save_single_front_image.isChecked()
        config.setting["caa_approved_only"] = \
            self.ui.cb_approved_only.isChecked()
        config.setting["caa_image_type_as_filename"] = \
            self.ui.cb_type_as_filename.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
예제 #28
0
class CoverOptionsPage(OptionsPage):

    NAME = "cover"
    TITLE = N_("Cover Art")
    PARENT = None
    SORT_ORDER = 35
    ACTIVE = True
    HELP_URL = '/config/options_cover.html'

    options = [
        config.BoolOption("setting", "save_images_to_tags", True),
        config.BoolOption("setting", "embed_only_one_front_image", True),
        config.BoolOption("setting", "save_images_to_files", False),
        config.TextOption("setting", "cover_image_filename", "cover"),
        config.BoolOption("setting", "save_images_overwrite", False),
        config.ListOption("setting", "ca_providers", [
            ('Cover Art Archive', True),
            ('Amazon', True),
            ('Whitelist', True),
            ('CaaReleaseGroup', False),
            ('Local', False),
        ]),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_CoverOptionsPage()
        self.ui.setupUi(self)
        self.ui.save_images_to_files.clicked.connect(self.update_filename)
        self.ui.save_images_to_tags.clicked.connect(self.update_save_images_to_tags)
        self.move_view = MoveableListView(self.ui.ca_providers_list, self.ui.up_button,
                                          self.ui.down_button)

    def load_cover_art_providers(self):
        """Load available providers, initialize provider-specific options, restore state of each
        """
        for p in cover_art_providers():
            self.ui.ca_providers_list.addItem(CheckboxListItem(_(p.title), checked=p.enabled, data=p.name))

    def restore_defaults(self):
        # Remove previous entries
        self.ui.ca_providers_list.clear()
        super().restore_defaults()

    def ca_providers(self):
        items = []
        for i in range(self.ui.ca_providers_list.count()):
            item = self.ui.ca_providers_list.item(i)
            items.append((item.data, item.checked))
        return items

    def load(self):
        self.ui.save_images_to_tags.setChecked(config.setting["save_images_to_tags"])
        self.ui.cb_embed_front_only.setChecked(config.setting["embed_only_one_front_image"])
        self.ui.save_images_to_files.setChecked(config.setting["save_images_to_files"])
        self.ui.cover_image_filename.setText(config.setting["cover_image_filename"])
        self.ui.save_images_overwrite.setChecked(config.setting["save_images_overwrite"])
        self.load_cover_art_providers()
        self.ui.ca_providers_list.setCurrentRow(0)
        self.update_all()

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

    def update_all(self):
        self.update_filename()
        self.update_save_images_to_tags()

    def update_ca_providers_groupbox_state(self):
        files_enabled = self.ui.save_images_to_files.isChecked()
        tags_enabled = self.ui.save_images_to_tags.isChecked()
        self.ui.ca_providers_groupbox.setEnabled(files_enabled or tags_enabled)

    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)
        self.update_ca_providers_groupbox_state()

    def update_save_images_to_tags(self):
        enabled = self.ui.save_images_to_tags.isChecked()
        self.ui.cb_embed_front_only.setEnabled(enabled)
        self.update_ca_providers_groupbox_state()
예제 #29
0
class RenamingOptionsPage(OptionsPage):

    NAME = "filerenaming"
    TITLE = N_("File Naming")
    PARENT = None
    SORT_ORDER = 40
    ACTIVE = True

    options = [
        config.BoolOption("setting", "windows_compatibility", True),
        config.BoolOption("setting", "ascii_filenames", False),
        config.BoolOption("setting", "rename_files", False),
        config.TextOption(
            "setting",
            "file_naming_format",
            _DEFAULT_FILE_NAMING_FORMAT,
        ),
        config.BoolOption("setting", "move_files", False),
        config.TextOption("setting", "move_files_to", ""),
        config.BoolOption("setting", "move_additional_files", False),
        config.TextOption("setting", "move_additional_files_pattern",
                          "*.jpg *.png"),
        config.BoolOption("setting", "delete_empty_dirs", True),
    ]

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

        self.ui.ascii_filenames.clicked.connect(self.update_examples)
        self.ui.windows_compatibility.clicked.connect(self.update_examples)
        self.ui.rename_files.clicked.connect(self.update_examples)
        self.ui.move_files.clicked.connect(self.update_examples)
        self.ui.move_files_to.editingFinished.connect(self.update_examples)

        self.ui.move_files.toggled.connect(
            partial(enabledSlot, self.toggle_file_moving))
        self.ui.rename_files.toggled.connect(
            partial(enabledSlot, self.toggle_file_renaming))
        self.ui.file_naming_format.textChanged.connect(self.check_formats)
        self.ui.file_naming_format_default.clicked.connect(
            self.set_file_naming_format_default)
        self.highlighter = TaggerScriptSyntaxHighlighter(
            self.ui.file_naming_format.document())
        self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse)

        textEdit = self.ui.file_naming_format
        self.textEditPaletteNormal = textEdit.palette()
        self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal)
        disabled_color = self.textEditPaletteNormal.color(
            QPalette.Inactive, QPalette.Window)
        self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base,
                                              disabled_color)

    def toggle_file_moving(self, state):
        self.ui.delete_empty_dirs.setEnabled(state)
        self.ui.move_files_to.setEnabled(state)
        self.ui.move_files_to_browse.setEnabled(state)
        self.ui.move_additional_files.setEnabled(state)
        self.ui.move_additional_files_pattern.setEnabled(state)

    def toggle_file_renaming(self, state):

        self.ui.file_naming_format.setEnabled(state)
        self.ui.file_naming_format_default.setEnabled(state)
        self.ui.ascii_filenames.setEnabled(state)
        self.ui.file_naming_format_group.setEnabled(state)
        if not sys.platform == "win32":
            self.ui.windows_compatibility.setEnabled(state)

        if self.ui.file_naming_format.isEnabled():
            self.ui.file_naming_format.setPalette(self.textEditPaletteNormal)
        else:
            self.ui.file_naming_format.setPalette(self.textEditPaletteReadOnly)

    def check_formats(self):
        self.test()
        self.update_examples()

    def _example_to_filename(self, file):
        settings = {
            'windows_compatibility':
            self.ui.windows_compatibility.isChecked(),
            'ascii_filenames':
            self.ui.ascii_filenames.isChecked(),
            'rename_files':
            self.ui.rename_files.isChecked(),
            'move_files':
            self.ui.move_files.isChecked(),
            'use_va_format':
            False,  # TODO remove
            'file_naming_format':
            unicode(self.ui.file_naming_format.toPlainText()),
            'move_files_to':
            os.path.normpath(unicode(self.ui.move_files_to.text()))
        }
        try:
            if config.setting["enable_tagger_script"]:
                script = config.setting["tagger_script"]
                parser = ScriptParser()
                parser.eval(script, file.metadata)
            filename = file._make_filename(file.filename, file.metadata,
                                           settings)
            if not settings["move_files"]:
                return os.path.basename(filename)
            return filename
        except SyntaxError:
            return ""
        except ScriptError:
            return ""
        except TypeError:
            return ""

    def update_examples(self):
        # TODO: Here should be more examples etc.
        # TODO: Would be nice to show diffs too....
        example1 = self._example_to_filename(self.example_1())
        example2 = self._example_to_filename(self.example_2())
        self.ui.example_filename.setText(example1)
        self.ui.example_filename_va.setText(example2)

    def load(self):
        if sys.platform == "win32":
            self.ui.windows_compatibility.setChecked(True)
            self.ui.windows_compatibility.setEnabled(False)
        else:
            self.ui.windows_compatibility.setChecked(
                config.setting["windows_compatibility"])
        self.ui.rename_files.setChecked(config.setting["rename_files"])
        self.ui.move_files.setChecked(config.setting["move_files"])
        self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"])
        self.ui.file_naming_format.setPlainText(
            config.setting["file_naming_format"])
        args = {
            "picard-doc-scripting-url": PICARD_URLS['doc_scripting'],
        }
        text = _(u'<a href="%(picard-doc-scripting-url)s">Open Scripting'
                 ' Documentation in your browser</a>') % args
        self.ui.file_naming_format_documentation.setText(text)
        self.ui.move_files_to.setText(config.setting["move_files_to"])
        self.ui.move_files_to.setCursorPosition(0)
        self.ui.move_additional_files.setChecked(
            config.setting["move_additional_files"])
        self.ui.move_additional_files_pattern.setText(
            config.setting["move_additional_files_pattern"])
        self.ui.delete_empty_dirs.setChecked(
            config.setting["delete_empty_dirs"])
        self.update_examples()

    def check(self):
        self.check_format()
        if self.ui.move_files.isChecked() and not unicode(
                self.ui.move_files_to.text()).strip():
            raise OptionsCheckError(
                _("Error"),
                _("The location to move files to must not be empty."))

    def check_format(self):
        parser = ScriptParser()
        try:
            parser.eval(unicode(self.ui.file_naming_format.toPlainText()))
        except Exception as e:
            raise OptionsCheckError("", str(e))
        if self.ui.rename_files.isChecked():
            if not unicode(self.ui.file_naming_format.toPlainText()).strip():
                raise OptionsCheckError(
                    "", _("The file naming format must not be empty."))

    def save(self):
        config.setting[
            "windows_compatibility"] = self.ui.windows_compatibility.isChecked(
            )
        config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked()
        config.setting["rename_files"] = self.ui.rename_files.isChecked()
        config.setting["file_naming_format"] = unicode(
            self.ui.file_naming_format.toPlainText())
        self.tagger.window.enable_renaming_action.setChecked(
            config.setting["rename_files"])
        config.setting["move_files"] = self.ui.move_files.isChecked()
        config.setting["move_files_to"] = os.path.normpath(
            unicode(self.ui.move_files_to.text()))
        config.setting[
            "move_additional_files"] = self.ui.move_additional_files.isChecked(
            )
        config.setting["move_additional_files_pattern"] = unicode(
            self.ui.move_additional_files_pattern.text())
        config.setting[
            "delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked()
        self.tagger.window.enable_moving_action.setChecked(
            config.setting["move_files"])

    def display_error(self, error):
        pass

    def set_file_naming_format_default(self):
        self.ui.file_naming_format.setText(self.options[3].default)


#        self.ui.file_naming_format.setCursorPosition(0)

    def example_1(self):
        file = File("ticket_to_ride.mp3")
        file.state = File.NORMAL
        file.metadata['album'] = 'Help!'
        file.metadata['title'] = 'Ticket to Ride'
        file.metadata['artist'] = 'The Beatles'
        file.metadata['artistsort'] = 'Beatles, The'
        file.metadata['albumartist'] = 'The Beatles'
        file.metadata['albumartistsort'] = 'Beatles, The'
        file.metadata['tracknumber'] = '7'
        file.metadata['totaltracks'] = '14'
        file.metadata['discnumber'] = '1'
        file.metadata['totaldiscs'] = '1'
        file.metadata['date'] = '1965-08-06'
        file.metadata['releasetype'] = ['album', 'soundtrack']
        file.metadata['~primaryreleasetype'] = ['album']
        file.metadata['~secondaryreleasetype'] = ['soundtrack']
        file.metadata['releasestatus'] = 'official'
        file.metadata['releasecountry'] = 'US'
        file.metadata['~extension'] = 'mp3'
        file.metadata[
            'musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028'
        file.metadata[
            'musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d'
        file.metadata[
            'musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d'
        file.metadata[
            'musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c'
        file.metadata[
            'musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c'
        return file

    def example_2(self):
        file = File("track05.mp3")
        file.state = File.NORMAL
        file.metadata['album'] = u"Coup d'État, Volume 1: Ku De Ta / Prologue"
        file.metadata['title'] = u"I've Got to Learn the Mambo"
        file.metadata['artist'] = u"Snowboy feat. James Hunter"
        file.metadata['artistsort'] = u"Snowboy feat. Hunter, James"
        file.metadata['albumartist'] = config.setting['va_name']
        file.metadata['albumartistsort'] = config.setting['va_name']
        file.metadata['tracknumber'] = '5'
        file.metadata['totaltracks'] = '13'
        file.metadata['discnumber'] = '2'
        file.metadata['totaldiscs'] = '2'
        file.metadata['discsubtitle'] = u"Beat Up"
        file.metadata['date'] = u'2005-07-04'
        file.metadata['releasetype'] = [u'album', u'compilation']
        file.metadata['~primaryreleasetype'] = u'album'
        file.metadata['~secondaryreleasetype'] = u'compilation'
        file.metadata['releasestatus'] = u'official'
        file.metadata['releasecountry'] = u'AU'
        file.metadata['compilation'] = '1'
        file.metadata['~multiartist'] = '1'
        file.metadata['~extension'] = 'mp3'
        file.metadata[
            'musicbrainz_albumid'] = u'4b50c71e-0a07-46ac-82e4-cb85dc0c9bdd'
        file.metadata[
            'musicbrainz_recordingid'] = u'b3c487cb-0e55-477d-8df3-01ec6590f099'
        file.metadata[
            'musicbrainz_releasetrackid'] = u'f8649a05-da39-39ba-957c-7abf8f9012be'
        file.metadata[
            'musicbrainz_albumartistid'] = u'89ad4ac3-39f7-470e-963a-56509c546377'
        file.metadata['musicbrainz_artistid'] = [
            u'7b593455-d207-482c-8c6f-19ce22c94679',
            u'9e082466-2390-40d1-891e-4803531f43fd'
        ]
        return file

    def move_files_to_browse(self):
        path = QtGui.QFileDialog.getExistingDirectory(
            self, "", self.ui.move_files_to.text())
        if path:
            path = os.path.normpath(unicode(path))
            self.ui.move_files_to.setText(path)

    def test(self):
        self.ui.renaming_error.setStyleSheet("")
        self.ui.renaming_error.setText("")
        try:
            self.check_format()
        except OptionsCheckError as e:
            self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR)
            self.ui.renaming_error.setText(e.info)
            return
예제 #30
0
파일: interface.py 프로젝트: webiis/picard
class InterfaceOptionsPage(OptionsPage):

    NAME = "interface"
    TITLE = N_("User Interface")
    PARENT = None
    SORT_ORDER = 80
    ACTIVE = True
    SEPARATOR = '—' * 5
    TOOLBAR_BUTTONS = {
        'add_directory_action': {
            'label': N_('Add Folder'),
            'icon': 'folder'
        },
        'add_files_action': {
            'label': N_('Add Files'),
            'icon': 'document-open'
        },
        'cluster_action': {
            'label': N_('Cluster'),
            'icon': 'picard-cluster'
        },
        'autotag_action': {
            'label': N_('Lookup'),
            'icon': 'picard-auto-tag'
        },
        'analyze_action': {
            'label': N_('Scan'),
            'icon': 'picard-analyze'
        },
        'browser_lookup_action': {
            'label': N_('Lookup in Browser'),
            'icon': 'lookup-musicbrainz'
        },
        'save_action': {
            'label': N_('Save'),
            'icon': 'document-save'
        },
        'view_info_action': {
            'label': N_('Info'),
            'icon': 'picard-edit-tags'
        },
        'remove_action': {
            'label': N_('Remove'),
            'icon': 'list-remove'
        },
        'submit_acoustid_action': {
            'label': N_('Submit AcoustIDs'),
            'icon': 'acoustid-fingerprinter'
        },
        'play_file_action': {
            'label': N_('Open in Player'),
            'icon': 'play-music'
        },
        'cd_lookup_action': {
            'label': N_('Lookup CD...'),
            'icon': 'media-optical'
        },
    }
    ACTION_NAMES = set(TOOLBAR_BUTTONS.keys())
    options = [
        config.BoolOption("setting", "toolbar_show_labels", True),
        config.BoolOption("setting", "toolbar_multiselect", False),
        config.BoolOption("setting", "builtin_search", False),
        config.BoolOption("setting", "use_adv_search_syntax", False),
        config.BoolOption("setting", "quit_confirmation", True),
        config.TextOption("setting", "ui_language", ""),
        config.BoolOption("setting", "starting_directory", False),
        config.TextOption("setting", "starting_directory_path",
                          _default_starting_dir),
        config.TextOption("setting", "load_image_behavior", "append"),
        config.ListOption("setting", "toolbar_layout", [
            'add_directory_action',
            'add_files_action',
            'separator',
            'cluster_action',
            'separator',
            'autotag_action',
            'analyze_action',
            'browser_lookup_action',
            'separator',
            'save_action',
            'view_info_action',
            'remove_action',
            'separator',
            'cd_lookup_action',
            'separator',
            'submit_acoustid_action',
        ]),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_InterfaceOptionsPage()
        self.ui.setupUi(self)
        self.ui.ui_language.addItem(_('System default'), '')
        language_list = [(l[0], l[1], _(l[2])) for l in UI_LANGUAGES]

        def fcmp(x):
            return locale.strxfrm(x[2])

        for lang_code, native, translation in sorted(language_list, key=fcmp):
            if native and native != translation:
                name = '%s (%s)' % (translation, native)
            else:
                name = translation
            self.ui.ui_language.addItem(name, lang_code)
        self.ui.starting_directory.stateChanged.connect(
            partial(enabledSlot, self.ui.starting_directory_path.setEnabled))
        self.ui.starting_directory.stateChanged.connect(
            partial(enabledSlot, self.ui.starting_directory_browse.setEnabled))
        self.ui.starting_directory_browse.clicked.connect(
            self.starting_directory_browse)
        self.ui.add_button.clicked.connect(self.add_to_toolbar)
        self.ui.insert_separator_button.clicked.connect(self.insert_separator)
        self.ui.remove_button.clicked.connect(self.remove_action)
        self.move_view = MoveableListView(self.ui.toolbar_layout_list,
                                          self.ui.up_button,
                                          self.ui.down_button,
                                          self.update_action_buttons)
        self.update_buttons = self.move_view.update_buttons

    def load(self):
        self.ui.toolbar_show_labels.setChecked(
            config.setting["toolbar_show_labels"])
        self.ui.toolbar_multiselect.setChecked(
            config.setting["toolbar_multiselect"])
        self.ui.builtin_search.setChecked(config.setting["builtin_search"])
        self.ui.use_adv_search_syntax.setChecked(
            config.setting["use_adv_search_syntax"])
        self.ui.quit_confirmation.setChecked(
            config.setting["quit_confirmation"])
        current_ui_language = config.setting["ui_language"]
        self.ui.ui_language.setCurrentIndex(
            self.ui.ui_language.findData(current_ui_language))
        self.ui.starting_directory.setChecked(
            config.setting["starting_directory"])
        self.ui.starting_directory_path.setText(
            config.setting["starting_directory_path"])
        self.populate_action_list()
        self.ui.toolbar_layout_list.setCurrentRow(0)
        self.update_buttons()

    def save(self):
        config.setting[
            "toolbar_show_labels"] = self.ui.toolbar_show_labels.isChecked()
        config.setting[
            "toolbar_multiselect"] = self.ui.toolbar_multiselect.isChecked()
        config.setting["builtin_search"] = self.ui.builtin_search.isChecked()
        config.setting[
            "use_adv_search_syntax"] = self.ui.use_adv_search_syntax.isChecked(
            )
        config.setting[
            "quit_confirmation"] = self.ui.quit_confirmation.isChecked()
        self.tagger.window.update_toolbar_style()
        new_language = self.ui.ui_language.itemData(
            self.ui.ui_language.currentIndex())
        if new_language != config.setting["ui_language"]:
            config.setting["ui_language"] = self.ui.ui_language.itemData(
                self.ui.ui_language.currentIndex())
            dialog = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Information, _('Language changed'),
                _('You have changed the interface language. You have to restart Picard in order for the change to take effect.'
                  ), QtWidgets.QMessageBox.Ok, self)
            dialog.exec_()
        config.setting[
            "starting_directory"] = self.ui.starting_directory.isChecked()
        config.setting["starting_directory_path"] = os.path.normpath(
            self.ui.starting_directory_path.text())
        self.update_layout_config()

    def restore_defaults(self):
        super().restore_defaults()
        self.update_buttons()

    def starting_directory_browse(self):
        item = self.ui.starting_directory_path
        path = QtWidgets.QFileDialog.getExistingDirectory(
            self, "", item.text())
        if path:
            path = os.path.normpath(path)
            item.setText(path)

    def _get_icon_from_name(self, name):
        return self.TOOLBAR_BUTTONS[name]['icon']

    def _insert_item(self, action, index=None):
        list_item = ToolbarListItem(action)
        list_item.setToolTip(_('Drag and Drop to re-order'))
        if action in self.TOOLBAR_BUTTONS:
            # TODO: Remove temporary workaround once https://github.com/python-babel/babel/issues/415 has been resolved.
            babel_415_workaround = self.TOOLBAR_BUTTONS[action]['label']
            list_item.setText(_(babel_415_workaround))
            list_item.setIcon(
                icontheme.lookup(self._get_icon_from_name(action),
                                 icontheme.ICON_SIZE_MENU))
        else:
            list_item.setText(self.SEPARATOR)
        if index is not None:
            self.ui.toolbar_layout_list.insertItem(index, list_item)
        else:
            self.ui.toolbar_layout_list.addItem(list_item)
        return list_item

    def _all_list_items(self):
        return [
            self.ui.toolbar_layout_list.item(i).action_name
            for i in range(self.ui.toolbar_layout_list.count())
        ]

    def _added_actions(self):
        actions = self._all_list_items()
        return set(action for action in actions if action != 'separator')

    def populate_action_list(self):
        self.ui.toolbar_layout_list.clear()
        for name in config.setting['toolbar_layout']:
            if name in self.ACTION_NAMES or name == 'separator':
                self._insert_item(name)

    def update_action_buttons(self):
        self.ui.add_button.setEnabled(
            self._added_actions() != self.ACTION_NAMES)

    def add_to_toolbar(self):
        display_list = set.difference(self.ACTION_NAMES, self._added_actions())
        selected_action, ok = AddActionDialog.get_selected_action(
            display_list, self)
        if ok:
            list_item = self._insert_item(
                selected_action,
                self.ui.toolbar_layout_list.currentRow() + 1)
            self.ui.toolbar_layout_list.setCurrentItem(list_item)
        self.update_buttons()

    def insert_separator(self):
        insert_index = self.ui.toolbar_layout_list.currentRow() + 1
        self._insert_item('separator', insert_index)

    def remove_action(self):
        item = self.ui.toolbar_layout_list.takeItem(
            self.ui.toolbar_layout_list.currentRow())
        del item
        self.update_buttons()

    def update_layout_config(self):
        config.setting['toolbar_layout'] = self._all_list_items()
        self._update_toolbar()

    def _update_toolbar(self):
        widget = self.parent()
        while not isinstance(widget, QtWidgets.QMainWindow):
            widget = widget.parent()
        # Call the main window's create toolbar method
        widget.create_action_toolbar()
        widget.set_tab_order()