class LastfmOptionsPage(OptionsPage): NAME = "lastfm" TITLE = "Last.fm" PARENT = "plugins" options = [ BoolOption("setting", "lastfm_use_track_tags", False), BoolOption("setting", "lastfm_use_artist_tags", False), IntOption("setting", "lastfm_min_tag_usage", 15), TextOption("setting", "lastfm_ignore_tags", "seen live,favorites"), TextOption("setting", "lastfm_join_tags", ""), ] def __init__(self, parent=None): super(LastfmOptionsPage, self).__init__(parent) self.ui = Ui_LastfmOptionsPage() self.ui.setupUi(self) def load(self): self.ui.use_track_tags.setChecked(self.config.setting["lastfm_use_track_tags"]) self.ui.use_artist_tags.setChecked(self.config.setting["lastfm_use_artist_tags"]) self.ui.min_tag_usage.setValue(self.config.setting["lastfm_min_tag_usage"]) self.ui.ignore_tags.setText(self.config.setting["lastfm_ignore_tags"]) self.ui.join_tags.setEditText(self.config.setting["lastfm_join_tags"]) def save(self): self.config.setting["lastfm_use_track_tags"] = self.ui.use_track_tags.isChecked() self.config.setting["lastfm_use_artist_tags"] = self.ui.use_artist_tags.isChecked() self.config.setting["lastfm_min_tag_usage"] = self.ui.min_tag_usage.value() self.config.setting["lastfm_ignore_tags"] = unicode(self.ui.ignore_tags.text()) self.config.setting["lastfm_join_tags"] = unicode(self.ui.join_tags.currentText())
class RatingsOptionsPage(OptionsPage): NAME = "ratings" TITLE = N_("Ratings") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True HELP_URL = '/config/options_ratings.html' options = [ BoolOption("setting", "enable_ratings", False), TextOption("setting", "rating_user_email", "*****@*****.**"), BoolOption("setting", "submit_ratings", True), IntOption("setting", "rating_steps", 6), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RatingsOptionsPage() self.ui.setupUi(self) def load(self): config = get_config() self.ui.enable_ratings.setChecked(config.setting["enable_ratings"]) self.ui.rating_user_email.setText(config.setting["rating_user_email"]) self.ui.submit_ratings.setChecked(config.setting["submit_ratings"]) def save(self): config = get_config() config.setting["enable_ratings"] = self.ui.enable_ratings.isChecked() config.setting["rating_user_email"] = self.ui.rating_user_email.text() config.setting["submit_ratings"] = self.ui.submit_ratings.isChecked()
def test_upgrade_to_v2_1_0_dev_1(self): BoolOption("setting", "use_genres", False) IntOption("setting", "max_genres", 5) IntOption("setting", "min_genre_usage", 90) TextOption("setting", "ignore_genres", "seen live, favorites, fixme, owned") TextOption("setting", "join_genres", "") BoolOption("setting", "only_my_genres", False) BoolOption("setting", "artists_genres", False) BoolOption("setting", "folksonomy_tags", False) self.config.setting['folksonomy_tags'] = True self.config.setting['max_tags'] = 6 self.config.setting['min_tag_usage'] = 85 self.config.setting['ignore_tags'] = "abc" self.config.setting['join_tags'] = "abc" self.config.setting['only_my_tags'] = True self.config.setting['artists_tags'] = True upgrade_to_v2_1_0_dev_1(self.config) self.assertEqual(self.config.setting['use_genres'], True) self.assertEqual(self.config.setting['max_genres'], 6) self.assertEqual(self.config.setting['min_genre_usage'], 85) self.assertEqual(self.config.setting['ignore_genres'], "abc") self.assertEqual(self.config.setting['join_genres'], "abc") self.assertEqual(self.config.setting['only_my_genres'], True) self.assertEqual(self.config.setting['artists_genres'], True) self.assertIn('folksonomy_tags', self.config.setting) self.assertNotIn('max_tags', self.config.setting) self.assertNotIn('min_tag_usage', self.config.setting) self.assertNotIn('ignore_tags', self.config.setting) self.assertNotIn('join_tags', self.config.setting) self.assertNotIn('only_my_tags', self.config.setting) self.assertNotIn('artists_tags', self.config.setting)
def test_upgrade_to_v2_6_0_beta_2(self): BoolOption('setting', 'image_type_as_filename', False) BoolOption('setting', 'save_only_one_front_image', False) self.config.setting['caa_image_type_as_filename'] = True self.config.setting['caa_save_single_front_image'] = True upgrade_to_v2_6_0_beta_2(self.config) self.assertNotIn('caa_image_type_as_filename', self.config.setting) self.assertTrue(self.config.setting['image_type_as_filename']) self.assertNotIn('caa_save_single_front_image', self.config.setting) self.assertTrue(self.config.setting['save_only_one_front_image'])
class TagsOptionsPage(OptionsPage): NAME = "tags" TITLE = N_("Tags") PARENT = None SORT_ORDER = 30 ACTIVE = True HELP_URL = '/config/options_tags.html' options = [ BoolOption("setting", "dont_write_tags", False), BoolOption("setting", "preserve_timestamps", False), BoolOption("setting", "clear_existing_tags", False), BoolOption("setting", "remove_id3_from_flac", False), BoolOption("setting", "remove_ape_from_mp3", False), ListOption("setting", "preserved_tags", []), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_TagsOptionsPage() self.ui.setupUi(self) def load(self): config = get_config() self.ui.write_tags.setChecked(not config.setting["dont_write_tags"]) self.ui.preserve_timestamps.setChecked( config.setting["preserve_timestamps"]) self.ui.clear_existing_tags.setChecked( config.setting["clear_existing_tags"]) self.ui.remove_ape_from_mp3.setChecked( config.setting["remove_ape_from_mp3"]) self.ui.remove_id3_from_flac.setChecked( config.setting["remove_id3_from_flac"]) self.ui.preserved_tags.update(config.setting["preserved_tags"]) self.ui.preserved_tags.set_user_sortable(False) def save(self): config = get_config() config.setting["dont_write_tags"] = not self.ui.write_tags.isChecked() config.setting[ "preserve_timestamps"] = self.ui.preserve_timestamps.isChecked() clear_existing_tags = self.ui.clear_existing_tags.isChecked() if clear_existing_tags != config.setting["clear_existing_tags"]: config.setting["clear_existing_tags"] = clear_existing_tags self.tagger.window.metadata_box.update() config.setting[ "remove_ape_from_mp3"] = self.ui.remove_ape_from_mp3.isChecked() config.setting[ "remove_id3_from_flac"] = self.ui.remove_id3_from_flac.isChecked() config.setting["preserved_tags"] = list(self.ui.preserved_tags.tags) self.tagger.window.enable_tag_saving_action.setChecked( not config.setting["dont_write_tags"])
class ProxyDialog(QtGui.QDialog): options = [ BoolOption("persist", "save_authentication", True), ] def __init__(self, authenticator, proxy, parent=None): QtGui.QDialog.__init__(self, parent) self._authenticator = authenticator self._proxy = proxy self.ui = Ui_PasswordDialog() self.ui.setupUi(self) self.ui.info_text.setText( _("The proxy %s requires you to login. Please enter your username and password." ) % self.config.setting["proxy_server_host"]) self.ui.save_authentication.setChecked( self.config.persist["save_authentication"]) self.ui.username.setText(self.config.setting["proxy_username"]) self.ui.password.setText(self.config.setting["proxy_password"]) self.ui.save_authentication.hide() self.connect(self.ui.buttonbox, QtCore.SIGNAL('accepted()'), self.set_proxy_password) def set_proxy_password(self): self.config.setting["proxy_username"] = unicode( self.ui.username.text()) self.config.setting["proxy_password"] = unicode( self.ui.password.text()) self._authenticator.setUser(unicode(self.ui.username.text())) self._authenticator.setPassword(unicode(self.ui.password.text())) self.accept()
class NoReleaseOptionsPage(OptionsPage): NAME = 'norelease' TITLE = 'No release' PARENT = 'plugins' options = [ BoolOption('setting', 'norelease_enable', False), TextOption( 'setting', 'norelease_strip_tags', 'asin,barcode,catalognumber,date,label,media,releasecountry,releasestatus' ), ] def __init__(self, parent=None): super(NoReleaseOptionsPage, self).__init__(parent) self.ui = Ui_NoReleaseOptionsPage() self.ui.setupUi(self) def load(self): self.ui.norelease_strip_tags.setText( config.setting['norelease_strip_tags']) self.ui.norelease_enable.setChecked(config.setting['norelease_enable']) def save(self): config.setting['norelease_strip_tags'] = str( self.ui.norelease_strip_tags.text()) config.setting[ 'norelease_enable'] = self.ui.norelease_enable.isChecked()
class TheAudioDbOptionsPage(ProviderOptions): _options_ui = Ui_TheAudioDbOptionsPage options = [ TextOption("setting", "theaudiodb_use_cdart", OPTION_CDART_NOALBUMART), BoolOption("setting", "theaudiodb_use_high_quality", False), ] def load(self): if config.setting["theaudiodb_use_cdart"] == OPTION_CDART_ALWAYS: self.ui.theaudiodb_cdart_use_always.setChecked(True) elif config.setting["theaudiodb_use_cdart"] == OPTION_CDART_NEVER: self.ui.theaudiodb_cdart_use_never.setChecked(True) elif config.setting["theaudiodb_use_cdart"] == OPTION_CDART_NOALBUMART: self.ui.theaudiodb_cdart_use_if_no_albumcover.setChecked(True) self.ui.theaudiodb_use_high_quality.setChecked( config.setting['theaudiodb_use_high_quality']) def save(self): if self.ui.theaudiodb_cdart_use_always.isChecked(): config.setting["theaudiodb_use_cdart"] = OPTION_CDART_ALWAYS elif self.ui.theaudiodb_cdart_use_never.isChecked(): config.setting["theaudiodb_use_cdart"] = OPTION_CDART_NEVER elif self.ui.theaudiodb_cdart_use_if_no_albumcover.isChecked(): config.setting["theaudiodb_use_cdart"] = OPTION_CDART_NOALBUMART config.setting['theaudiodb_use_high_quality'] = self.ui.theaudiodb_use_high_quality.isChecked()
class ScriptingOptionsPage(OptionsPage): NAME = "scripting" TITLE = N_("Scripting") PARENT = "advanced" SORT_ORDER = 30 ACTIVE = True options = [ BoolOption("setting", "enable_tagger_script", False), TextOption("setting", "tagger_script", ""), ] STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" def __init__(self, parent=None): super(ScriptingOptionsPage, self).__init__(parent) self.ui = Ui_ScriptingOptionsPage() self.ui.setupUi(self) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.tagger_script.document()) self.connect(self.ui.tagger_script, QtCore.SIGNAL("textChanged()"), self.live_checker) def live_checker(self): self.ui.script_error.setStyleSheet("") self.ui.script_error.setText("") try: self.check() except OptionsCheckError, e: self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.script_error.setText(e.info) return
def setUp(self): super().setUp() self.tmp_directory = self.mktmpdir() self.configpath = os.path.join(self.tmp_directory, 'test.ini') shutil.copy(os.path.join('test', 'data', 'test.ini'), self.configpath) self.addCleanup(os.remove, self.configpath) self.config = Config.from_file(None, self.configpath) self.addCleanup(self.cleanup_config_obj) self.config.application["version"] = "testing" logging.disable(logging.ERROR) Option.registry = {} ListOption('profiles', self.PROFILES_KEY, []) Option('profiles', self.SETTINGS_KEY, {}) # Get valid profile option settings for testing option_settings = list(UserProfileGroups.get_all_settings_list()) self.test_setting_0 = option_settings[0] self.test_setting_1 = option_settings[1] self.test_setting_2 = option_settings[2] self.test_setting_3 = option_settings[3] TextOption("setting", self.test_setting_0, "abc") BoolOption("setting", self.test_setting_1, True) IntOption("setting", self.test_setting_2, 42) TextOption("setting", self.test_setting_3, "xyz")
class FileTreeView(BaseTreeView): header_state = Option("persist", "file_view_header_state", QtCore.QByteArray()) header_locked = BoolOption("persist", "file_view_header_locked", False) def __init__(self, window, parent=None): super().__init__(window, parent) self.setAccessibleName(_("file view")) self.setAccessibleDescription( _("Contains unmatched files and clusters")) self.unmatched_files = ClusterItem(self.tagger.unclustered_files, False, self) self.unmatched_files.update() self.unmatched_files.setExpanded(True) self.clusters = ClusterItem(self.tagger.clusters, False, self) self.set_clusters_text() self.clusters.setExpanded(True) self.tagger.cluster_added.connect(self.add_file_cluster) self.tagger.cluster_removed.connect(self.remove_file_cluster) def add_file_cluster(self, cluster, parent_item=None): self.add_cluster(cluster, parent_item) self.set_clusters_text() def remove_file_cluster(self, cluster): cluster.item.setSelected(False) self.clusters.removeChild(cluster.item) self.set_clusters_text() def set_clusters_text(self): self.clusters.setText( MainPanel.TITLE_COLUMN, '%s (%d)' % (_("Clusters"), len(self.tagger.clusters)))
def test_upgrade_to_v1_4_0_dev_7(self): BoolOption('setting', 'embed_only_one_front_image', False) self.config.setting['save_only_front_images_to_tags'] = True upgrade_to_v1_4_0_dev_7(self.config) self.assertNotIn('save_only_front_images_to_tags', self.config.setting) self.assertTrue(self.config.setting['embed_only_one_front_image'])
def test_bool_opt_set_read_back(self): BoolOption("setting", "bool_option", True) # set option and read back self.config.setting["bool_option"] = False self.assertEqual(self.config.setting["bool_option"], False) self.assertIs(type(self.config.setting["bool_option"]), bool)
def test_upgrade_to_v1_3_0_dev_1(self): BoolOption('setting', 'windows_compatibility', False) self.config.setting['windows_compatible_filenames'] = True upgrade_to_v1_3_0_dev_1(self.config) self.assertNotIn('windows_compatible_filenames', self.config.setting) self.assertTrue(self.config.setting['windows_compatibility'])
class FolksonomyOptionsPage(OptionsPage): NAME = "folsonomy" TITLE = N_("Folksonomy Tags") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True options = [ IntOption("setting", "max_tags", 5), IntOption("setting", "min_tag_usage", 90), TextOption("setting", "ignore_tags", "seen live,favorites,fixme,owned"), TextOption("setting", "join_tags", ""), BoolOption("setting", "only_my_tags", False), ] def __init__(self, parent=None): super(FolksonomyOptionsPage, self).__init__(parent) self.ui = Ui_FolksonomyOptionsPage() self.ui.setupUi(self) def load(self): self.ui.max_tags.setValue(self.config.setting["max_tags"]) self.ui.min_tag_usage.setValue(self.config.setting["min_tag_usage"]) self.ui.join_tags.setEditText(self.config.setting["join_tags"]) self.ui.ignore_tags.setText(self.config.setting["ignore_tags"]) self.ui.only_my_tags.setChecked(self.config.setting["only_my_tags"]) def save(self): self.config.setting["max_tags"] = self.ui.max_tags.value() self.config.setting["min_tag_usage"] = self.ui.min_tag_usage.value() self.config.setting["join_tags"] = self.ui.join_tags.currentText() self.config.setting["ignore_tags"] = self.ui.ignore_tags.text() self.config.setting["only_my_tags"] = self.ui.only_my_tags.isChecked()
class AutoMapperOptionsPage(OptionsPage): NAME = PLUGIN_NAME.casefold() TITLE = r'Non-standard Tags Mapping' PARENT = r'tags' # r'plugins' ? options = [BoolOption(r'setting', r'purgeUnmapped', False)] def __init__(self, parent=None): super().__init__(parent) self.box = QtWidgets.QVBoxLayout(self) self.purgeUnmapped = QtWidgets.QCheckBox(self) self.purgeUnmapped.setCheckable(True) self.purgeUnmapped.setChecked(False) self.purgeUnmapped.setText(r'Purge non-standard tags left unmapped') self.box.addWidget(self.purgeUnmapped) self.spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.box.addItem(self.spacer) def load(self): self.purgeUnmapped.setChecked(config.setting[r'purgeUnmapped']) def save(self): config.setting[r'purgeUnmapped'] = self.purgeUnmapped.isChecked()
class AlbumTreeView(BaseTreeView): header_state = Option("persist", "album_view_header_state", QtCore.QByteArray()) header_locked = BoolOption("persist", "album_view_header_locked", False) def __init__(self, window, parent=None): super().__init__(window, parent) self.setAccessibleName(_("album view")) self.setAccessibleDescription(_("Contains albums and matched files")) self.tagger.album_added.connect(self.add_album) self.tagger.album_removed.connect(self.remove_album) def add_album(self, album): if isinstance(album, NatAlbum): item = NatAlbumItem(album, True) self.insertTopLevelItem(0, item) else: item = AlbumItem(album, True, self) item.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd) for i, column in enumerate(MainPanel.columns): font = item.font(i) font.setBold(True) item.setFont(i, font) item.setText(i, album.column(column[1])) self.add_cluster(album.unmatched_files, item) def remove_album(self, album): album.item.setSelected(False) self.takeTopLevelItem(self.indexOfTopLevelItem(album.item))
def test_upgrade_to_v2_6_0_beta_3(self): from picard.ui.theme import UiTheme BoolOption('setting', 'use_system_theme', False) self.config.setting['use_system_theme'] = True upgrade_to_v2_6_0_beta_3(self.config) self.assertNotIn('use_system_theme', self.config.setting) self.assertIn('ui_theme', self.config.setting) self.assertEqual(str(UiTheme.SYSTEM), self.config.setting['ui_theme'])
class GeneralOptionsPage(OptionsPage): NAME = "general" TITLE = N_("General") PARENT = None SORT_ORDER = 1 ACTIVE = True options = [ TextOption("setting", "server_host", "musicbrainz.org"), IntOption("setting", "server_port", 80), TextOption("setting", "username", ""), PasswordOption("setting", "password", ""), BoolOption("setting", "analyze_new_files", False), BoolOption("setting", "ignore_file_mbids", False), ] def __init__(self, parent=None): super(GeneralOptionsPage, self).__init__(parent) self.ui = Ui_GeneralOptionsPage() self.ui.setupUi(self) mirror_servers = [ "musicbrainz.org", ] self.ui.server_host.addItems(sorted(mirror_servers)) def load(self): self.ui.server_host.setEditText(self.config.setting["server_host"]) self.ui.server_port.setValue(self.config.setting["server_port"]) self.ui.username.setText(self.config.setting["username"]) self.ui.password.setText(self.config.setting["password"]) self.ui.analyze_new_files.setChecked(self.config.setting["analyze_new_files"]) self.ui.ignore_file_mbids.setChecked(self.config.setting["ignore_file_mbids"]) def save(self): self.config.setting["server_host"] = unicode(self.ui.server_host.currentText()).strip() self.config.setting["server_port"] = self.ui.server_port.value() self.config.setting["username"] = unicode(self.ui.username.text()) # trivially encode the password, just to not make it so apparent self.config.setting["password"] = rot13(unicode(self.ui.password.text())) self.config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() self.config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked()
class TagsCompatibilityWaveOptionsPage(OptionsPage): NAME = "tags_compatibility_wave" TITLE = N_("WAVE") PARENT = "tags" SORT_ORDER = 60 ACTIVE = True HELP_URL = '/config/options_tags_compatibility_wave.html' options = [ BoolOption("setting", "write_wave_riff_info", True), BoolOption("setting", "remove_wave_riff_info", False), TextOption("setting", "wave_riff_info_encoding", "windows-1252"), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_TagsCompatibilityOptionsPage() self.ui.setupUi(self) def load(self): config = get_config() self.ui.write_wave_riff_info.setChecked( config.setting["write_wave_riff_info"]) self.ui.remove_wave_riff_info.setChecked( config.setting["remove_wave_riff_info"]) if config.setting["wave_riff_info_encoding"] == "utf-8": self.ui.wave_riff_info_enc_utf8.setChecked(True) else: self.ui.wave_riff_info_enc_cp1252.setChecked(True) def save(self): config = get_config() config.setting[ "write_wave_riff_info"] = self.ui.write_wave_riff_info.isChecked() config.setting[ "remove_wave_riff_info"] = self.ui.remove_wave_riff_info.isChecked( ) if self.ui.wave_riff_info_enc_utf8.isChecked(): config.setting["wave_riff_info_encoding"] = "utf-8" else: config.setting["wave_riff_info_encoding"] = "windows-1252"
def test_upgrade_to_v1_4_0_dev_6(self): BoolOption('setting', 'enable_tagger_scripts', False) ListOption('setting', 'list_of_scripts', []) self.config.setting['enable_tagger_script'] = True self.config.setting['tagger_script'] = "abc" upgrade_to_v1_4_0_dev_6(self.config) self.assertNotIn('enable_tagger_script', self.config.setting) self.assertNotIn('tagger_script', self.config.setting) self.assertTrue(self.config.setting['enable_tagger_scripts']) self.assertEqual([(0, DEFAULT_NUMBERED_SCRIPT_NAME % 1, True, 'abc')], self.config.setting['list_of_scripts'])
class CoverOptionsPage(OptionsPage): NAME = "cover" TITLE = N_("Cover Art") PARENT = None SORT_ORDER = 35 ACTIVE = True options = [ BoolOption("setting", "save_images_to_tags", True), BoolOption("setting", "save_images_to_files", False), TextOption("setting", "cover_image_filename", "cover"), BoolOption("setting", "save_images_overwrite", False), ] def __init__(self, parent=None): super(CoverOptionsPage, self).__init__(parent) self.ui = Ui_CoverOptionsPage() self.ui.setupUi(self) self.connect(self.ui.save_images_to_files, QtCore.SIGNAL("clicked()"), self.update_filename) def load(self): self.ui.save_images_to_tags.setChecked(self.config.setting["save_images_to_tags"]) self.ui.save_images_to_files.setChecked(self.config.setting["save_images_to_files"]) self.ui.cover_image_filename.setText(self.config.setting["cover_image_filename"]) self.ui.save_images_overwrite.setChecked(self.config.setting["save_images_overwrite"]) self.update_filename() def save(self): self.config.setting["save_images_to_tags"] = self.ui.save_images_to_tags.isChecked() self.config.setting["save_images_to_files"] = self.ui.save_images_to_files.isChecked() self.config.setting["cover_image_filename"] = unicode(self.ui.cover_image_filename.text()) self.config.setting["save_images_overwrite"] = self.ui.save_images_overwrite.isChecked() def update_filename(self): enabled = self.ui.save_images_to_files.isChecked() self.ui.cover_image_filename.setEnabled(enabled) self.ui.save_images_overwrite.setEnabled(enabled)
def test_as_dict(self): TextOption("setting", "text_option", "abc") BoolOption("setting", "bool_option", True) IntOption("setting", "int_option", 42) self.config.setting["int_option"] = 123 expected = { "text_option": "abc", "bool_option": True, "int_option": 123, } self.assertEqual(expected, self.config.setting.as_dict())
class AdvancedOptionsPage(OptionsPage): NAME = "advanced" TITLE = N_("Advanced") PARENT = None SORT_ORDER = 90 ACTIVE = True HELP_URL = '/config/options_advanced.html' options = [ TextOption("setting", "ignore_regex", ""), BoolOption("setting", "ignore_hidden_files", False), BoolOption("setting", "recursively_add_files", True), IntOption("setting", "ignore_track_duration_difference_under", 2), BoolOption("setting", "completeness_ignore_videos", False), BoolOption("setting", "completeness_ignore_pregap", False), BoolOption("setting", "completeness_ignore_data", False), BoolOption("setting", "completeness_ignore_silence", False), ListOption("setting", "compare_ignore_tags", []), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_AdvancedOptionsPage() self.ui.setupUi(self) self.init_regex_checker(self.ui.ignore_regex, self.ui.regex_error) def load(self): config = get_config() self.ui.ignore_regex.setText(config.setting["ignore_regex"]) self.ui.ignore_hidden_files.setChecked(config.setting["ignore_hidden_files"]) self.ui.recursively_add_files.setChecked(config.setting["recursively_add_files"]) self.ui.ignore_track_duration_difference_under.setValue(config.setting["ignore_track_duration_difference_under"]) self.ui.completeness_ignore_videos.setChecked(config.setting["completeness_ignore_videos"]) self.ui.completeness_ignore_pregap.setChecked(config.setting["completeness_ignore_pregap"]) self.ui.completeness_ignore_data.setChecked(config.setting["completeness_ignore_data"]) self.ui.completeness_ignore_silence.setChecked(config.setting["completeness_ignore_silence"]) self.ui.compare_ignore_tags.update(config.setting["compare_ignore_tags"]) self.ui.compare_ignore_tags.set_user_sortable(False) def save(self): config = get_config() config.setting["ignore_regex"] = self.ui.ignore_regex.text() config.setting["ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked() config.setting["recursively_add_files"] = self.ui.recursively_add_files.isChecked() config.setting["ignore_track_duration_difference_under"] = self.ui.ignore_track_duration_difference_under.value() config.setting["completeness_ignore_videos"] = self.ui.completeness_ignore_videos.isChecked() config.setting["completeness_ignore_pregap"] = self.ui.completeness_ignore_pregap.isChecked() config.setting["completeness_ignore_data"] = self.ui.completeness_ignore_data.isChecked() config.setting["completeness_ignore_silence"] = self.ui.completeness_ignore_silence.isChecked() tags = list(self.ui.compare_ignore_tags.tags) if tags != config.setting["compare_ignore_tags"]: config.setting["compare_ignore_tags"] = tags def restore_defaults(self): self.ui.compare_ignore_tags.clear() super().restore_defaults()
class TagsCompatibilityAC3OptionsPage(OptionsPage): NAME = "tags_compatibility_ac3" TITLE = N_("AC3") PARENT = "tags" SORT_ORDER = 50 ACTIVE = True HELP_URL = '/config/options_tags_compatibility_ac3.html' options = [ BoolOption("setting", "ac3_save_ape", True), BoolOption("setting", "remove_ape_from_ac3", False), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_TagsCompatibilityOptionsPage() self.ui.setupUi(self) self.ui.ac3_no_tags.toggled.connect( self.ui.remove_ape_from_ac3.setEnabled) def load(self): config = get_config() if config.setting["ac3_save_ape"]: self.ui.ac3_save_ape.setChecked(True) else: self.ui.ac3_no_tags.setChecked(True) self.ui.remove_ape_from_ac3.setChecked( config.setting["remove_ape_from_ac3"]) self.ui.remove_ape_from_ac3.setEnabled( not config.setting["ac3_save_ape"]) def save(self): config = get_config() config.setting["ac3_save_ape"] = self.ui.ac3_save_ape.isChecked() config.setting[ "remove_ape_from_ac3"] = self.ui.remove_ape_from_ac3.isChecked()
class EchoNestOptionsPage(OptionsPage): NAME = "echonest" TITLE = "echonest" PARENT = "plugins" options = [ BoolOption("setting", "echonest_upload", False), BoolOption("setting", "echonest_artist_title_lookup", True), FloatOption("setting", "echonest_duration_diff", 5.0), ] def __init__(self, parent=None): super(EchoNestOptionsPage, self).__init__(parent) self.ui = Ui_EchoNestOptionsPage() self.ui.setupUi(self) def load(self): #self.ui.echonest_upload.setChecked(self.config.setting["echonest_upload"]) self.ui.echonest_artist_title_lookup.setChecked(self.config.setting["echonest_artist_title_lookup"]) #self.ui.echonest_duration_diff.setChecked(self.config.setting["echonest_duration_diff"]) def save(self): #self.config.setting["echonest_upload"] = self.ui.echonest_upload.isChecked() self.config.setting["echonest_artist_title_lookup"] = self.ui.echonest_artist_title_lookup.isChecked()
class PasswordDialog(QtGui.QDialog): options = [ BoolOption("persist", "save_authentication", True), ] def __init__(self, authenticator, reply, parent=None): QtGui.QDialog.__init__(self, parent) self._authenticator = authenticator self.ui = Ui_PasswordDialog() self.ui.setupUi(self) self.ui.info_text.setText( _("The server %s requires you to login. Please enter your username and password." ) % reply.url().host()) # TODO: Implement proper password storage for arbitrary servers if self._is_musicbrainz_server(reply.url().host(), reply.url().port()): self.ui.save_authentication.setChecked( self.config.persist["save_authentication"]) self.ui.username.setText(self.config.setting["username"]) self.ui.password.setText(self.config.setting["password"]) else: self.ui.username.setText(reply.url().userName()) self.ui.password.setText(reply.url().password()) self.ui.save_authentication.setChecked(False) self.ui.save_authentication.hide() self.connect(self.ui.buttonbox, QtCore.SIGNAL('accepted()'), self.set_new_password) def set_new_password(self): self.config.persist[ "save_authentication"] = self.ui.save_authentication.isChecked() if self.config.persist["save_authentication"]: self.config.setting["username"] = unicode(self.ui.username.text()) self.config.setting["password"] = rot13( unicode(self.ui.password.text())) self._authenticator.setUser(unicode(self.ui.username.text())) self._authenticator.setPassword(unicode(self.ui.password.text())) self.accept() def _is_musicbrainz_server(self, host, port): return host == self.config.setting["server_host"] and port == self.config.setting["server_port"] \ or host == PUID_SUBMIT_HOST and port == PUID_SUBMIT_PORT
class ProxyOptionsPage(OptionsPage): NAME = "proxy" TITLE = N_("Web Proxy") PARENT = "advanced" SORT_ORDER = 10 ACTIVE = True options = [ BoolOption("setting", "use_proxy", False), TextOption("setting", "proxy_server_host", ""), IntOption("setting", "proxy_server_port", 80), TextOption("setting", "proxy_username", ""), TextOption("setting", "proxy_password", ""), ] def __init__(self, parent=None): super(ProxyOptionsPage, self).__init__(parent) self.ui = Ui_ProxyOptionsPage() self.ui.setupUi(self) def load(self): self.ui.web_proxy.setChecked(self.config.setting["use_proxy"]) self.ui.server_host.setText(self.config.setting["proxy_server_host"]) self.ui.server_port.setValue(self.config.setting["proxy_server_port"]) self.ui.username.setText(self.config.setting["proxy_username"]) self.ui.password.setText(self.config.setting["proxy_password"]) def save(self): self.config.setting["use_proxy"] = self.ui.web_proxy.isChecked() self.config.setting["proxy_server_host"] = unicode( self.ui.server_host.text()) self.config.setting["proxy_server_port"] = self.ui.server_port.value() self.config.setting["proxy_username"] = unicode( self.ui.username.text()) self.config.setting["proxy_password"] = unicode( self.ui.password.text()) self.tagger.xmlws.setup_proxy()
class ProviderOptionsCaa(ProviderOptions): """ Options for Cover Art Archive cover art provider """ HELP_URL = '/config/options_cover_art_archive.html' options = [ BoolOption("setting", "caa_approved_only", False), IntOption("setting", "caa_image_size", _CAA_IMAGE_SIZE_DEFAULT), ListOption("setting", "caa_image_types", _CAA_IMAGE_TYPE_DEFAULT_INCLUDE), BoolOption("setting", "caa_restrict_image_types", True), ListOption("setting", "caa_image_types_to_omit", _CAA_IMAGE_TYPE_DEFAULT_EXCLUDE), ] _options_ui = Ui_CaaOptions def __init__(self, parent=None): super().__init__(parent) self.ui.restrict_images_types.clicked.connect(self.update_caa_types) self.ui.select_caa_types.clicked.connect(self.select_caa_types) def restore_defaults(self): self.caa_image_types = _CAA_IMAGE_TYPE_DEFAULT_INCLUDE self.caa_image_types_to_omit = _CAA_IMAGE_TYPE_DEFAULT_EXCLUDE super().restore_defaults() def load(self): self.ui.cb_image_size.clear() for item_id, item in _CAA_THUMBNAIL_SIZE_MAP.items(): self.ui.cb_image_size.addItem(_(item.label), userData=item_id) config = get_config() size = config.setting["caa_image_size"] index = self.ui.cb_image_size.findData(size) if index < 0: index = self.ui.cb_image_size.findData(_CAA_IMAGE_SIZE_DEFAULT) self.ui.cb_image_size.setCurrentIndex(index) self.ui.cb_approved_only.setChecked(config.setting["caa_approved_only"]) self.ui.restrict_images_types.setChecked( config.setting["caa_restrict_image_types"]) self.caa_image_types = config.setting["caa_image_types"] self.caa_image_types_to_omit = config.setting["caa_image_types_to_omit"] self.update_caa_types() def save(self): config = get_config() size = self.ui.cb_image_size.currentData() config.setting["caa_image_size"] = size config.setting["caa_approved_only"] = \ self.ui.cb_approved_only.isChecked() config.setting["caa_restrict_image_types"] = \ self.ui.restrict_images_types.isChecked() config.setting["caa_image_types"] = self.caa_image_types config.setting["caa_image_types_to_omit"] = self.caa_image_types_to_omit def update_caa_types(self): enabled = self.ui.restrict_images_types.isChecked() self.ui.select_caa_types.setEnabled(enabled) def select_caa_types(self): (types, types_to_omit, ok) = CAATypesSelectorDialog.run( self, self.caa_image_types, self.caa_image_types_to_omit) if ok: self.caa_image_types = types self.caa_image_types_to_omit = types_to_omit
def test_bool_opt_convert(self): opt = BoolOption("setting", "bool_option", False) self.assertEqual(opt.convert(1), True)
class ScriptEditorDialog(PicardDialog): """File Naming Script Editor Page """ TITLE = N_("File naming script editor") STYLESHEET_ERROR = OptionsPage.STYLESHEET_ERROR help_url = '/config/options_filerenaming_editor.html' options = [ TextOption("setting", "file_naming_format", DEFAULT_FILE_NAMING_FORMAT), ListOption("setting", "file_naming_scripts", []), TextOption("setting", "selected_file_naming_script_id", ""), BoolOption('persist', 'script_editor_show_documentation', False), ] signal_save = QtCore.pyqtSignal() signal_update = QtCore.pyqtSignal() signal_selection_changed = QtCore.pyqtSignal() default_script_directory = os.path.normpath( QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.DocumentsLocation)) default_script_filename = "picard_naming_script.ptsp" def __init__(self, parent=None, examples=None): """Stand-alone file naming script editor. Args: parent (QMainWindow or OptionsPage, optional): Parent object. Defaults to None. examples (ScriptEditorExamples, required): Object containing examples to display. Defaults to None. """ super().__init__(parent) self.examples = examples self.FILE_TYPE_ALL = _("All Files") + " (*)" self.FILE_TYPE_SCRIPT = _("Picard Script Files") + " (*.pts *.txt)" self.FILE_TYPE_PACKAGE = _( "Picard Naming Script Package") + " (*.ptsp *.yaml)" self.SCRIPT_TITLE_SYSTEM = _("System: %s") self.SCRIPT_TITLE_USER = _("User: %s") # TODO: Make this work properly so that it can be accessed from both the main window and the options window. # self.setWindowFlags(QtCore.Qt.Window) self.setWindowModality(QtCore.Qt.WindowModal) self.setWindowTitle(self.TITLE) self.displaying = False self.loading = True self.ui = Ui_ScriptEditor() self.ui.setupUi(self) self.make_menu() self.ui.label.setWordWrap(False) self.installEventFilter(self) # Dialog buttons self.reset_button = QtWidgets.QPushButton(_('Revert')) self.reset_button.setToolTip(self.reset_action.toolTip()) self.reset_button.clicked.connect(self.reset_script) self.ui.buttonbox.addButton(self.reset_button, QtWidgets.QDialogButtonBox.ActionRole) self.save_button = self.ui.buttonbox.addButton( QtWidgets.QDialogButtonBox.Save) self.save_button.setToolTip(self.save_action.toolTip()) self.ui.buttonbox.accepted.connect(self.save_script) self.close_button = self.ui.buttonbox.addButton( QtWidgets.QDialogButtonBox.Close) self.close_button.setToolTip(self.close_action.toolTip()) self.ui.buttonbox.rejected.connect(self.close_window) self.ui.buttonbox.addButton(QtWidgets.QDialogButtonBox.Help) self.ui.buttonbox.helpRequested.connect(self.show_help) self.ui.file_naming_format.setEnabled(True) # Add scripting documentation to parent frame. doc_widget = ScriptingDocumentationWidget(self, include_link=False) self.ui.documentation_frame_layout.addWidget(doc_widget) self.ui.file_naming_format.textChanged.connect(self.check_formats) self._sampled_example_files = [] self.ui.example_filename_after.itemSelectionChanged.connect( self.match_before_to_after) self.ui.example_filename_before.itemSelectionChanged.connect( self.match_after_to_before) self.ui.preset_naming_scripts.currentIndexChanged.connect( partial(self.select_script, skip_check=False)) self.synchronize_vertical_scrollbars( (self.ui.example_filename_before, self.ui.example_filename_after)) self.toggle_documentation() # Force update to display self.examples_current_row = -1 self.script_metadata_changed = False # self.select_script() self.load() self.loading = False def make_menu(self): """Build the menu bar. """ config = get_config() main_menu = QtWidgets.QMenuBar() # File menu settings file_menu = main_menu.addMenu(_('&File')) file_menu.setToolTipsVisible(True) self.import_action = QtWidgets.QAction(_("&Import a script file"), self) self.import_action.setToolTip(_("Import a file as a new script")) self.import_action.setIcon(icontheme.lookup('document-open')) self.import_action.triggered.connect(self.import_script) file_menu.addAction(self.import_action) self.export_action = QtWidgets.QAction(_("&Export a script file"), self) self.export_action.setToolTip(_("Export the script to a file")) self.export_action.setIcon(icontheme.lookup('document-save')) self.export_action.triggered.connect(self.export_script) file_menu.addAction(self.export_action) self.close_action = QtWidgets.QAction(_("E&xit / Close editor"), self) self.close_action.setToolTip(_("Close the script editor")) self.close_action.triggered.connect(self.close_window) file_menu.addAction(self.close_action) # Script menu settings script_menu = main_menu.addMenu(_('&Script')) script_menu.setToolTipsVisible(True) self.details_action = QtWidgets.QAction(_("Edit Script &Metadata"), self) self.details_action.setToolTip(_("Display the details for the script")) self.details_action.triggered.connect(self.view_script_details) self.details_action.setShortcut(QtGui.QKeySequence(_("Ctrl+M"))) script_menu.addAction(self.details_action) self.add_action = QtWidgets.QAction(_("Add a &new script"), self) self.add_action.setToolTip(_("Create a new file naming script")) self.add_action.setIcon(icontheme.lookup('add-item')) self.add_action.triggered.connect(self.new_script) script_menu.addAction(self.add_action) self.copy_action = QtWidgets.QAction(_("&Copy the current script"), self) self.copy_action.setToolTip( _("Save a copy of the script as a new script")) self.copy_action.setIcon(icontheme.lookup('edit-copy')) self.copy_action.triggered.connect(self.copy_script) script_menu.addAction(self.copy_action) self.delete_action = QtWidgets.QAction(_("&Delete the current script"), self) self.delete_action.setToolTip(_("Delete the script")) self.delete_action.setIcon(icontheme.lookup('list-remove')) self.delete_action.triggered.connect(self.delete_script) script_menu.addAction(self.delete_action) self.reset_action = QtWidgets.QAction(_("&Revert the current script"), self) self.reset_action.setToolTip( _("Revert the script to the last saved value")) self.reset_action.setIcon(icontheme.lookup('view-refresh')) self.reset_action.triggered.connect(self.reset_script) script_menu.addAction(self.reset_action) self.save_action = QtWidgets.QAction(_("&Save the current script"), self) self.save_action.setToolTip(_("Save changes to the script")) self.save_action.setIcon(icontheme.lookup('document-save')) self.save_action.setShortcut(QtGui.QKeySequence(_("Ctrl+S"))) self.save_action.triggered.connect(self.save_script) script_menu.addAction(self.save_action) # Display menu settings display_menu = main_menu.addMenu(_('&View')) display_menu.setToolTipsVisible(True) self.examples_action = QtWidgets.QAction( _("&Reload random example files"), self) self.examples_action.setToolTip( _(self.examples.tooltip_text) % self.examples.max_samples) self.examples_action.setIcon(icontheme.lookup('view-refresh')) self.examples_action.triggered.connect(self.update_example_files) display_menu.addAction(self.examples_action) display_menu.addAction(self.ui.file_naming_format.wordwrap_action) display_menu.addAction(self.ui.file_naming_format.show_tooltips_action) self.docs_action = QtWidgets.QAction(_("&Show documentation"), self) self.docs_action.setToolTip( _("View the scripting documentation in a sidebar")) self.docs_action.triggered.connect(self.toggle_documentation) self.docs_action.setShortcut(QtGui.QKeySequence(_("Ctrl+H"))) self.docs_action.setCheckable(True) self.docs_action.setChecked( config.persist["script_editor_show_documentation"]) display_menu.addAction(self.docs_action) # Help menu settings help_menu = main_menu.addMenu(_('&Help')) help_menu.setToolTipsVisible(True) self.help_action = QtWidgets.QAction(_("&Help..."), self) self.help_action.setShortcut(QtGui.QKeySequence.HelpContents) self.help_action.triggered.connect(self.show_help) help_menu.addAction(self.help_action) self.scripting_docs_action = QtWidgets.QAction( _("&Scripting documentation..."), self) self.scripting_docs_action.setToolTip( _("Open the scripting documentation in your browser")) self.scripting_docs_action.triggered.connect(self.docs_browser) help_menu.addAction(self.scripting_docs_action) self.ui.layout_for_menubar.addWidget(main_menu) def load(self): """Load initial configuration. """ config = get_config() self.examples.settings = config.setting self.naming_scripts = config.setting["file_naming_scripts"] self.selected_script_id = config.setting[ "selected_file_naming_script_id"] self.selected_script_index = 0 idx = self.populate_script_selector() self.ui.preset_naming_scripts.blockSignals(True) self.ui.preset_naming_scripts.setCurrentIndex(idx) self.ui.preset_naming_scripts.blockSignals(False) self.select_script(skip_check=True) def docs_browser(self): """Open the scriping documentation in a browser. """ webbrowser2.open('doc_scripting') @staticmethod def synchronize_vertical_scrollbars(widgets): """Synchronize position of vertical scrollbars and selections for listed widgets. """ # Set highlight colors for selected list items example_style = widgets[0].palette() highlight_bg = example_style.color(QPalette.Active, QPalette.Highlight) highlight_fg = example_style.color(QPalette.Active, QPalette.HighlightedText) stylesheet = "QListView::item:selected { color: " + highlight_fg.name( ) + "; background-color: " + highlight_bg.name() + "; }" def _sync_scrollbar_vert(widget, value): widget.blockSignals(True) widget.verticalScrollBar().setValue(value) widget.blockSignals(False) widgets = set(widgets) for widget in widgets: for other in widgets - {widget}: widget.verticalScrollBar().valueChanged.connect( partial(_sync_scrollbar_vert, other)) widget.setStyleSheet(stylesheet) def eventFilter(self, object, event): """Process selected events. """ evtype = event.type() if evtype in {QtCore.QEvent.WindowActivate, QtCore.QEvent.FocusIn}: self.update_examples() return False def close_window(self): """Close the window. """ self.close() def closeEvent(self, event): """Custom close event handler to check for unsaved changes. """ if self.unsaved_changes_confirmation(): if self.has_changed(): self.select_script(skip_check=True) super().closeEvent(event) else: event.ignore() def populate_script_selector(self): """Populate the script selection combo box. Returns: int: The index of the selected script in the combo box. """ if not self.selected_script_id: script_item = FileNamingScript( script=get_config().setting["file_naming_format"], title=_("Primary file naming script"), readonly=False, deletable=True, ) self.naming_scripts.insert(0, script_item.to_yaml()) self.selected_script_id = script_item['id'] self.ui.preset_naming_scripts.blockSignals(True) self.ui.preset_naming_scripts.clear() def _add_and_check(idx, count, title, item): self.ui.preset_naming_scripts.addItem(title, item) if item['id'] == self.selected_script_id: idx = count count += 1 return idx, count idx = 0 count = 0 # Use separate counter rather than `i` in case ScriptImportError triggers, resulting in an incorrect index count. for i in range(len(self.naming_scripts)): try: script_item = FileNamingScript().create_from_yaml( self.naming_scripts[i], create_new_id=False) except ScriptImportError: pass else: self.naming_scripts[i] = script_item.to_yaml( ) # Ensure scripts are stored with id codes idx, count = _add_and_check( idx, count, self.SCRIPT_TITLE_USER % script_item["title"], script_item) for script_item in get_file_naming_script_presets(): idx, count = _add_and_check(idx, count, script_item['title'], script_item) self.ui.preset_naming_scripts.blockSignals(False) self.update_scripts_list() return idx def toggle_documentation(self): """Toggle the display of the scripting documentation sidebar. """ checked = self.docs_action.isChecked() config = get_config() config.persist["script_editor_show_documentation"] = checked self.ui.documentation_frame.setVisible(checked) def view_script_details(self): """View and edit (if not readonly) the metadata associated with the script. """ selected_item = self.get_selected_item() details_page = ScriptDetailsEditor(self, selected_item) details_page.signal_save.connect(self.update_from_details) details_page.show() details_page.raise_() details_page.activateWindow() def has_changed(self): """Check if the current script has pending edits to the title or script that have not been saved. Returns: bool: True if there are unsaved changes, otherwise false. """ script_item = self.ui.preset_naming_scripts.itemData( self.selected_script_index) return self.ui.script_title.text().strip() != script_item['title'] or \ self.get_script() != script_item['script'] or \ self.script_metadata_changed def update_from_details(self): """Update the script selection combo box and script list after updates from the script details dialog. """ selected_item = self.get_selected_item() self.update_combo_box_item( self.ui.preset_naming_scripts.currentIndex(), selected_item) self.ui.script_title.setText(selected_item['title']) self.script_metadata_changed = True def _insert_item(self, script_item): """Insert a new item into the script selection combo box and update the script list in the settings. Args: script_item (FileNamingScript): File naming scrip to insert. """ self.ui.preset_naming_scripts.blockSignals(True) idx = len(self.naming_scripts) self.ui.preset_naming_scripts.insertItem( idx, self.SCRIPT_TITLE_USER % script_item['title'], script_item) self.ui.preset_naming_scripts.setCurrentIndex(idx) self.ui.preset_naming_scripts.blockSignals(False) self.update_scripts_list() self.select_script(skip_check=True) def new_script(self): """Add a new (empty) script to the script selection combo box and script list. """ if self.unsaved_changes_confirmation(): script_item = FileNamingScript(script='$noop()') self._insert_item(script_item) def copy_script(self): """Add a copy of the script as a new editable script to the script selection combo box and script list. """ if self.unsaved_changes_confirmation(): selected = self.ui.preset_naming_scripts.currentIndex() script_item = self.ui.preset_naming_scripts.itemData(selected) new_item = script_item.copy() self._insert_item(new_item) def update_script_in_settings(self, script_item): self.signal_save.emit() def update_scripts_list(self): """Refresh the script list in the settings based on the contents of the script selection combo box. """ self.naming_scripts = [] for idx in range(self.ui.preset_naming_scripts.count()): script_item = self.ui.preset_naming_scripts.itemData(idx) # Only add items that can be removed -- no presets if script_item.deletable: self.naming_scripts.append(script_item.to_yaml()) def get_selected_item(self): """Get the selected item from the script selection combo box. Returns: FileNamingScript: The selected script. """ selected = self.ui.preset_naming_scripts.currentIndex() return self.ui.preset_naming_scripts.itemData(selected) def unsaved_changes_confirmation(self): """Check if there are unsaved changes and as the user to confirm the action resulting in their loss. Returns: bool: True if no unsaved changes or user confirms the action, otherwise False. """ if not self.loading and self.has_changed() and not confirmation_dialog( self, _("There are unsaved changes to the current script. Do you want to continue and lose these changes?" )): self.ui.preset_naming_scripts.blockSignals(True) self.ui.preset_naming_scripts.setCurrentIndex( self.selected_script_index) self.ui.preset_naming_scripts.blockSignals(False) return False return True def select_script(self, skip_check=False): """Load the current script from the combo box into the editor. Args: skip_check (bool): Skip the check for unsaved edits. Defaults to False. """ if skip_check or self.unsaved_changes_confirmation(): script_item = self.get_selected_item() self.ui.script_title.setText(script_item['title']) self.set_script(script_item['script']) self.selected_script_id = script_item['id'] self.selected_script_index = self.ui.preset_naming_scripts.currentIndex( ) self.script_metadata_changed = False self.update_script_in_settings(script_item) self.set_button_states() self.update_examples() self.signal_selection_changed.emit() def update_combo_box_item(self, idx, script_item): """Update the title and item data for the specified script selection combo box item. Args: idx (int): Index of the item to update script_item (FileNamingScript): Updated script information """ self.ui.preset_naming_scripts.setItemData(idx, script_item) self.ui.preset_naming_scripts.setItemText( idx, self.SCRIPT_TITLE_USER % script_item['title']) self.update_script_in_settings(script_item) self.update_scripts_list() def set_button_states(self, save_enabled=True): """Set the button states based on the readonly and deletable attributes of the currently selected item in the script selection combo box. Args: save_enabled (bool, optional): Allow updates to be saved to this item. Defaults to True. """ selected = self.ui.preset_naming_scripts.currentIndex() if selected < 0: return script_item = self.get_selected_item() readonly = script_item['readonly'] self.ui.script_title.setReadOnly(readonly or selected < 1) # Buttons self.ui.file_naming_format.setReadOnly(readonly) self.save_button.setEnabled(save_enabled and not readonly) self.reset_button.setEnabled(not readonly) # Menu items self.save_action.setEnabled(save_enabled and not readonly) self.reset_action.setEnabled(not readonly) self.add_action.setEnabled(save_enabled) self.copy_action.setEnabled(save_enabled) self.delete_action.setEnabled(script_item['deletable'] and save_enabled) self.import_action.setEnabled(save_enabled) self.export_action.setEnabled(save_enabled) @staticmethod def synchronize_selected_example_lines(current_row, source, target): """Matches selected item in target to source""" if source.currentRow() != current_row: current_row = source.currentRow() target.blockSignals(True) target.setCurrentRow(current_row) target.blockSignals(False) def match_after_to_before(self): """Sets the selected item in the 'after' list to the corresponding item in the 'before' list. """ self.synchronize_selected_example_lines( self.examples_current_row, self.ui.example_filename_before, self.ui.example_filename_after) def match_before_to_after(self): """Sets the selected item in the 'before' list to the corresponding item in the 'after' list. """ self.synchronize_selected_example_lines( self.examples_current_row, self.ui.example_filename_after, self.ui.example_filename_before) def delete_script(self): """Removes the currently selected script from the script selection combo box and script list. """ if confirmation_dialog( self, _('Are you sure that you want to delete the script?')): idx = self.ui.preset_naming_scripts.currentIndex() self.ui.preset_naming_scripts.blockSignals(True) self.ui.preset_naming_scripts.removeItem(idx) if idx >= self.ui.preset_naming_scripts.count(): idx = self.ui.preset_naming_scripts.count() - 1 self.ui.preset_naming_scripts.setCurrentIndex(idx) self.ui.preset_naming_scripts.blockSignals(False) self.update_scripts_list() self.select_script(skip_check=True) def save_script(self): """Saves changes to the current script to the script list and combo box item. """ selected = self.ui.preset_naming_scripts.currentIndex() self.signal_save.emit() title = str(self.ui.script_title.text()).strip() if title: script_item = self.ui.preset_naming_scripts.itemData(selected) script_item.title = title script_item.script = self.get_script() self.update_combo_box_item(selected, script_item) dialog = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, _("Save Script"), _("Changes to the script have been saved."), QtWidgets.QMessageBox.Ok, self) dialog.exec_() else: self.display_error( OptionsCheckError(_("Error"), _("The script title must not be empty."))) def get_script(self): """Provides the text of the file naming script currently loaded into the editor. Returns: str: File naming script """ return str(self.ui.file_naming_format.toPlainText()).strip() def set_script(self, script_text): """Sets the text of the file naming script into the editor and settings. Args: script_text (str): File naming script text to set in the editor. """ self.ui.file_naming_format.setPlainText(str(script_text).strip()) def update_example_files(self): """Update the before and after file naming examples list. """ self.examples.update_sample_example_files() self.display_examples() def update_examples(self): """Update the before and after file naming examples using the current file naming script in the editor. """ override = {'file_naming_format': self.get_script()} self.examples.update_examples(override) self.display_examples() @staticmethod def update_example_listboxes(before_listbox, after_listbox, examples): """Update the contents of the file naming examples before and after listboxes. Args: before_listbox (QListBox): The before listbox after_listbox (QListBox): The after listbox examples (ScriptEditorExamples): The object to use for the examples """ before_listbox.clear() after_listbox.clear() for before, after in sorted(examples, key=lambda x: x[1]): before_listbox.addItem(before) after_listbox.addItem(after) def display_examples(self): """Update the display of the before and after file naming examples. """ self.examples_current_row = -1 examples = self.examples.get_examples() self.update_example_listboxes(self.ui.example_filename_before, self.ui.example_filename_after, examples) self.signal_update.emit() def output_error(self, title, fmt, filename, msg): """Log error and display error message dialog. Args: title (str): Title to display on the error dialog box fmt (str): Format for the error type being displayed filename (str): Name of the file being imported or exported msg (str): Error message to display """ log.error(fmt, filename, msg) error_message = _(fmt) % (filename, _(msg)) self.display_error(ScriptFileError(_(title), error_message)) def output_file_error(self, fmt, filename, msg): """Log file error and display error message dialog. Args: fmt (str): Format for the error type being displayed filename (str): Name of the file being imported or exported msg (str): Error message to display """ self.output_error(_("File Error"), fmt, filename, msg) def import_script(self): """Import from an external text file to a new script. Import can be either a plain text script or a naming script package. """ FILE_ERROR_IMPORT = N_('Error importing "%s". %s.') FILE_ERROR_DECODE = N_('Error decoding "%s". %s.') if not self.unsaved_changes_confirmation(): return dialog_title = _("Import Script File") dialog_file_types = self._get_dialog_filetypes() options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog filename, file_type = QtWidgets.QFileDialog.getOpenFileName( self, dialog_title, self.default_script_directory, dialog_file_types, options=options) if filename: log.debug('Importing naming script file: %s' % filename) try: with open(filename, 'r', encoding='utf8') as i_file: file_content = i_file.read() except OSError as error: self.output_file_error(FILE_ERROR_IMPORT, filename, error.strerror) return if not file_content.strip(): self.output_file_error(FILE_ERROR_IMPORT, filename, _('The file was empty')) return if file_type == self.FILE_TYPE_PACKAGE: try: script_item = FileNamingScript().create_from_yaml( file_content) except ScriptImportError as error: self.output_file_error(FILE_ERROR_DECODE, filename, error) return else: script_item = FileNamingScript(title=_("Imported from %s") % filename, script=file_content.strip()) self._insert_item(script_item) def export_script(self): """Export the current script to an external file. Export can be either as a plain text script or a naming script package. """ FILE_ERROR_EXPORT = N_('Error exporting file "%s". %s.') script_item = self.get_selected_item() script_text = self.get_script() if script_text: default_path = os.path.normpath( os.path.join(self.default_script_directory, self.default_script_filename)) dialog_title = _("Export Script File") dialog_file_types = self._get_dialog_filetypes() options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog filename, file_type = QtWidgets.QFileDialog.getSaveFileName( self, dialog_title, default_path, dialog_file_types, options=options) if filename: # Fix issue where Qt may set the extension twice (name, ext) = os.path.splitext(filename) if ext and str(name).endswith('.' + ext): filename = name log.debug('Exporting naming script file: %s' % filename) if file_type == self.FILE_TYPE_PACKAGE: script_text = script_item.to_yaml() try: with open(filename, 'w', encoding='utf8') as o_file: o_file.write(script_text) except OSError as error: self.output_file_error(FILE_ERROR_EXPORT, filename, error.strerror) else: dialog = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, _("Export Script"), _("Script successfully exported to %s") % filename, QtWidgets.QMessageBox.Ok, self) dialog.exec_() def _get_dialog_filetypes(self): return ";;".join(( self.FILE_TYPE_PACKAGE, self.FILE_TYPE_SCRIPT, self.FILE_TYPE_ALL, )) def reset_script(self): """Reset the script to the last saved value. """ if self.has_changed(): if confirmation_dialog( self, _("Are you sure that you want to reset the script to its last saved value?" )): self.select_script(skip_check=True) else: dialog = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, _("Revert Script"), _("There have been no changes made since the last time the script was saved." ), QtWidgets.QMessageBox.Ok, self) dialog.exec_() def check_formats(self): """Checks for valid file naming script and settings, and updates the examples. """ self.test() self.update_examples() def check_format(self): """Parse the file naming script and check for errors. """ config = get_config() parser = ScriptParser() script_text = self.get_script() try: parser.eval(script_text) except Exception as e: raise ScriptCheckError("", str(e)) if config.setting["rename_files"]: if not self.get_script(): raise ScriptCheckError( "", _("The file naming format must not be empty.")) def display_error(self, error): """Display an error message for the specified error. Args: error (Exception): The exception to display. """ # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): dialog = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, error.title, error.info, QtWidgets.QMessageBox.Ok, self) dialog.exec_() def test(self): """Parse the script and display any errors. """ self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") save_enabled = True try: self.check_format() except ScriptCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) save_enabled = False self.set_button_states(save_enabled=save_enabled)