Exemple #1
0
    def remove_file(self, file):
        if file not in self.linked_files:
            return
        self.linked_files.remove(file)
        self.num_linked_files -= 1
        file.copy_metadata(file.orig_metadata)
        self.album._remove_file(self, file)
        file.metadata_images_changed.disconnect(
            self.update_orig_metadata_images)

        if self.num_linked_files > 0:
            self.metadata.copy(self.linked_files[-1].orig_metadata)
        else:
            self.metadata.copy(self.orig_metadata)

        # Restore to non-associated state
        for s_name, s_text in enabled_tagger_scripts_texts():
            parser = ScriptParser()
            try:
                parser.eval(s_text, self.metadata)
            except:
                log.exception("Failed to run tagger script %s on track",
                              s_name)
            self.metadata.strip_whitespace()

        self.update()
Exemple #2
0
 def accept(self):
     profile_id = self.ui.save_to_profile.currentData()
     if profile_id != USER_SETTINGS_PROFILE_ID:
         profile_name = self._get_profile_title_from_id(profile_id)
         message_box = QtWidgets.QMessageBox(self)
         message_box.setIcon(QtWidgets.QMessageBox.Warning)
         message_box.setWindowModality(QtCore.Qt.WindowModal)
         message_box.setWindowTitle(_("Save to Profile"))
         message_box.setText(
             _("Changes will only be saved to the profile: \"%s\"\n\nDo you want to continue?"
               ) % profile_name)
         message_box.setStandardButtons(QtWidgets.QMessageBox.Yes
                                        | QtWidgets.QMessageBox.Cancel)
         if message_box.exec_() != QtWidgets.QMessageBox.Yes:
             return
     for page in self.pages:
         try:
             page.check()
         except OptionsCheckError as e:
             self._show_page_error(page, e)
             return
         except Exception as e:
             log.exception('Failed checking options page %r', page)
             self._show_page_error(page, e)
             return
     for page in self.pages:
         try:
             page.save()
         except Exception as e:
             log.exception('Failed saving options page %r', page)
             self._show_page_error(page, e)
             return
     self.reset_profile()
     super().accept()
Exemple #3
0
 def _save(self, filename, metadata):
     if config.setting['aac_save_ape']:
         super()._save(filename, metadata)
     elif config.setting['remove_ape_from_aac']:
         try:
             mutagen.apev2.delete(encode_filename(filename))
         except BaseException:
             log.exception('Error removing APEv2 tags from %s', filename)
Exemple #4
0
 def run_scripts(metadata):
     for s_name, s_text in enabled_tagger_scripts_texts():
         parser = ScriptParser()
         try:
             parser.eval(s_text, metadata)
         except ScriptError:
             log.exception("Failed to run tagger script %s on track", s_name)
         metadata.strip_whitespace()
Exemple #5
0
    def _finalize_loading_album(self):
        self.enable_update_metadata_images(False)
        for track in self._new_tracks:
            track.orig_metadata.copy(track.metadata)
            track.metadata_images_changed.connect(self.update_metadata_images)

        # Prepare parser for user's script
        for s_name, s_text in enabled_tagger_scripts_texts():
            parser = ScriptParser()
            for track in self._new_tracks:
                # Run tagger script for each track
                try:
                    parser.eval(s_text, track.metadata)
                except ScriptError:
                    log.exception("Failed to run tagger script %s on track",
                                  s_name)
                track.metadata.strip_whitespace()
                track.scripted_metadata.update(track.metadata)
            # Run tagger script for the album itself
            try:
                parser.eval(s_text, self._new_metadata)
            except ScriptError:
                log.exception("Failed to run tagger script %s on album",
                              s_name)
            self._new_metadata.strip_whitespace()

        unmatched_files = [
            file for track in self.tracks for file in track.files
        ]
        self.metadata = self._new_metadata
        self.orig_metadata.copy(self.metadata)
        self.orig_metadata.images.clear()
        self.tracks = self._new_tracks
        del self._new_metadata
        del self._new_tracks
        self.loaded = True
        self.status = AlbumStatus.LOADED
        self.match_files(unmatched_files + self.unmatched_files.files)
        self.enable_update_metadata_images(True)
        self.update_metadata_images()
        self.update()
        self.tagger.window.set_statusbar_message(
            N_('Album %(id)s loaded: %(artist)s - %(album)s'), {
                'id': self.id,
                'artist': self.metadata['albumartist'],
                'album': self.metadata['album']
            },
            timeout=3000)
        for func, always in self._after_load_callbacks:
            func()
        self._after_load_callbacks = []
        if self.item.isSelected():
            self.tagger.window.refresh_metadatabox()
Exemple #6
0
    def _run_script(self, script):
        s_name = script[1]
        s_text = script[3]
        parser = ScriptParser()

        for obj in self._get_unique_metadata_objects():
            try:
                parser.eval(s_text, obj.metadata)
                obj.update()
            except ScriptError as e:
                log.exception('Error running tagger script "%s" on object %r', s_name, obj)
                msg = N_('Script error in "%(script)s": %(message)s')
                mparms = {
                    'script': s_name,
                    'message': string_(e),
                }
                self.tagger.window.set_statusbar_message(msg, mparms)
Exemple #7
0
    def _run_script(self, script):
        s_name = script[1]
        s_text = script[3]
        parser = ScriptParser()

        for obj in self._get_unique_metadata_objects():
            try:
                parser.eval(s_text, obj.metadata)
                obj.update()
            except ScriptError as e:
                log.exception('Error running tagger script "%s" on object %r', s_name, obj)
                msg = N_('Script error in "%(script)s": %(message)s')
                mparms = {
                    'script': s_name,
                    'message': str(e),
                }
                self.tagger.window.set_statusbar_message(msg, mparms)
Exemple #8
0
    def _parse_recording(self, recording):
        m = self.metadata
        recording_to_metadata(recording, m, self)
        self._customize_metadata()
        run_track_metadata_processors(self.album, m, None, recording)
        for s_name, s_text in enabled_tagger_scripts_texts():
            parser = ScriptParser()
            try:
                parser.eval(s_text, m)
            except ScriptError:
                log.exception("Failed to run tagger script %s on track",
                              s_name)
            m.strip_whitespace()

        self.loaded = True
        if self.callback:
            self.callback()
            self.callback = None
        self.tagger.nats.update(True)
Exemple #9
0
 def accept(self):
     for page in self.pages:
         try:
             page.check()
         except OptionsCheckError as e:
             self._show_page_error(page, e)
             return
         except Exception as e:
             log.exception('Failed checking options page %r', page)
             self._show_page_error(page, e)
             return
     for page in self.pages:
         try:
             page.save()
         except Exception as e:
             log.exception('Failed saving options page %r', page)
             self._show_page_error(page, e)
             return
     super().accept()
Exemple #10
0
    def _parse_recording(self, recording):
        m = self.metadata
        recording_to_metadata(recording, m, self)
        self.orig_metadata.copy(m)
        self._customize_metadata()
        run_track_metadata_processors(self.album, m, None, recording)
        for s_name, s_text in enabled_tagger_scripts_texts():
            parser = ScriptParser()
            try:
                parser.eval(s_text, m)
            except ScriptError:
                log.exception("Failed to run tagger script %s on track", s_name)
            m.strip_whitespace()

        self.loaded = True
        self.status = None
        if self.callback:
            self.callback()
            self.callback = None
        self.album.update(True)
Exemple #11
0
    def update_file_metadata(self, file):
        if file not in self.linked_files:
            return
        file.copy_metadata(self.orig_metadata)
        file.metadata['~extension'] = file.orig_metadata['~extension']
        self.metadata.copy(file.metadata)

        # Re-run tagger scripts with updated metadata
        for s_name, s_text in enabled_tagger_scripts_texts():
            parser = ScriptParser()
            try:
                parser.eval(s_text, file.metadata)
                parser.eval(s_text, self.metadata)
            except:
                log.exception("Failed to run tagger script %s on file", s_name)
            file.metadata.strip_whitespace()
            self.metadata.strip_whitespace()

        file.metadata.changed = True
        file.update(signal=False)
        self.update()
Exemple #12
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            totalalbumtracks = 0
            absolutetracknumber = 0
            va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node['media']:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if 'discs' in medium_node:
                    discids = [disc.get('id') for disc in medium_node['discs']]
                    mm['~musicbrainz_discids'] = discids
                    mm['musicbrainz_discid'] = list(self._discids.intersection(discids))

                if "pregap" in medium_node:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(medium_node['pregap'], mm, artists, va, absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                track_count = medium_node['track-count']
                if track_count:
                    tracklist_node = medium_node['tracks']
                    for track_node in tracklist_node:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)

                if "data-tracks" in medium_node:
                    for track_node in medium_node['data-tracks']:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = str(absolutetracknumber)

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            del self._release_artist_nodes
            self._tracks_loaded = True

        if not self._requests:
            self.enable_update_metadata_images(False)
            # Prepare parser for user's script
            for s_name, s_text in enabled_tagger_scripts_texts():
                parser = ScriptParser()
                for track in self._new_tracks:
                    # Run tagger script for each track
                    try:
                        parser.eval(s_text, track.metadata)
                    except ScriptError:
                        log.exception("Failed to run tagger script %s on track", s_name)
                    track.metadata.strip_whitespace()
                # Run tagger script for the album itself
                try:
                    parser.eval(s_text, self._new_metadata)
                except ScriptError:
                    log.exception("Failed to run tagger script %s on album", s_name)
                self._new_metadata.strip_whitespace()

            for track in self.tracks:
                track.metadata_images_changed.connect(self.update_metadata_images)
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.enable_update_metadata_images(True)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'),
                {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000
            )
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Exemple #13
0
    def _finalize_loading(self, error):
        if error:
            self.metadata.clear()
            self.status = _("[could not load album %s]") % self.id
            del self._new_metadata
            del self._new_tracks
            self.update()
            return

        if self._requests > 0:
            return

        if not self._tracks_loaded:
            artists = set()
            all_media = []
            absolutetracknumber = 0

            va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID

            djmix_ars = {}
            if hasattr(self._new_metadata, "_djmix_ars"):
                djmix_ars = self._new_metadata._djmix_ars

            for medium_node in self._release_node['media']:
                mm = Metadata()
                mm.copy(self._new_metadata)
                medium_to_metadata(medium_node, mm)
                discpregap = False
                format = medium_node.get('format')
                if format:
                    all_media.append(format)

                for dj in djmix_ars.get(mm["discnumber"], []):
                    mm.add("djmixer", dj)

                if 'discs' in medium_node:
                    discids = [disc.get('id') for disc in medium_node['discs']]
                    mm['~musicbrainz_discids'] = discids
                    mm['musicbrainz_discid'] = list(self._discids.intersection(discids))

                if "pregap" in medium_node:
                    discpregap = True
                    absolutetracknumber += 1
                    track = self._finalize_loading_track(medium_node['pregap'], mm, artists, va, absolutetracknumber, discpregap)
                    track.metadata['~pregap'] = "1"

                track_count = medium_node['track-count']
                if track_count:
                    tracklist_node = medium_node['tracks']
                    for track_node in tracklist_node:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)

                if "data-tracks" in medium_node:
                    for track_node in medium_node['data-tracks']:
                        absolutetracknumber += 1
                        track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap)
                        track.metadata['~datatrack'] = "1"

            totalalbumtracks = absolutetracknumber
            self._new_metadata['~totalalbumtracks'] = totalalbumtracks
            # Generate a list of unique media, but keep order of first appearance
            self._new_metadata['media'] = " / ".join(list(OrderedDict.fromkeys(all_media)))

            for track in self._new_tracks:
                track.metadata["~totalalbumtracks"] = totalalbumtracks
                if len(artists) > 1:
                    track.metadata["~multiartist"] = "1"
            del self._release_node
            del self._release_artist_nodes
            self._tracks_loaded = True

        if not self._requests:
            self.enable_update_metadata_images(False)
            # Prepare parser for user's script
            for s_name, s_text in enabled_tagger_scripts_texts():
                parser = ScriptParser()
                for track in self._new_tracks:
                    # Run tagger script for each track
                    try:
                        parser.eval(s_text, track.metadata)
                    except ScriptError:
                        log.exception("Failed to run tagger script %s on track", s_name)
                    track.metadata.strip_whitespace()
                # Run tagger script for the album itself
                try:
                    parser.eval(s_text, self._new_metadata)
                except ScriptError:
                    log.exception("Failed to run tagger script %s on album", s_name)
                self._new_metadata.strip_whitespace()

            for track in self.tracks:
                track.metadata_images_changed.connect(self.update_metadata_images)
                for file in list(track.linked_files):
                    file.move(self.unmatched_files)
            self.metadata = self._new_metadata
            self.tracks = self._new_tracks
            del self._new_metadata
            del self._new_tracks
            self.loaded = True
            self.status = None
            self.match_files(self.unmatched_files.files)
            self.enable_update_metadata_images(True)
            self.update()
            self.tagger.window.set_statusbar_message(
                N_('Album %(id)s loaded: %(artist)s - %(album)s'),
                {
                    'id': self.id,
                    'artist': self.metadata['albumartist'],
                    'album': self.metadata['album']
                },
                timeout=3000
            )
            for func in self._after_load_callbacks:
                func()
            self._after_load_callbacks = []
Exemple #14
0
    def __init__(self, default_page=None, parent=None):
        super().__init__(parent)
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        from picard.ui.ui_options import Ui_Dialog
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.ui.reset_all_button = QtWidgets.QPushButton(
            _("&Restore all Defaults"))
        self.ui.reset_all_button.setToolTip(
            _("Reset all of Picard's settings"))
        self.ui.reset_button = QtWidgets.QPushButton(_("Restore &Defaults"))
        self.ui.reset_button.setToolTip(
            _("Reset all settings for current option page"))

        ok = StandardButton(StandardButton.OK)
        ok.setText(_("Make It So!"))
        self.ui.buttonbox.addButton(ok, QtWidgets.QDialogButtonBox.AcceptRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.CANCEL),
                                    QtWidgets.QDialogButtonBox.RejectRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.HELP),
                                    QtWidgets.QDialogButtonBox.HelpRole)
        self.ui.buttonbox.addButton(self.ui.reset_all_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)
        self.ui.buttonbox.addButton(self.ui.reset_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)

        self.ui.buttonbox.accepted.connect(self.accept)
        self.ui.buttonbox.rejected.connect(self.reject)
        self.ui.reset_all_button.clicked.connect(self.confirm_reset_all)
        self.ui.reset_button.clicked.connect(self.confirm_reset)
        self.ui.buttonbox.helpRequested.connect(self.show_help)

        self.ui.attached_profiles_button = QtWidgets.QPushButton(
            _("Attached Profiles"))
        self.ui.attached_profiles_button.setToolTip(
            _("Show which profiles are attached to the options on this page"))
        self.ui.buttonbox.addButton(self.ui.attached_profiles_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)
        self.ui.attached_profiles_button.clicked.connect(
            self.show_attached_profiles_dialog)

        config = get_config()

        self.pages = []
        for Page in page_classes:
            try:
                page = Page(self.ui.pages_stack)
                self.pages.append(page)
            except Exception:
                log.exception('Failed initializing options page %r', page)
        self.item_to_page = {}
        self.page_to_item = {}
        self.default_item = None
        if not default_page:
            default_page = config.persist["options_last_active_page"]
        self.add_pages(None, default_page, self.ui.pages_tree)

        # work-around to set optimal option pane width
        self.ui.pages_tree.expandAll()
        max_page_name = self.ui.pages_tree.sizeHintForColumn(
            0) + 2 * self.ui.pages_tree.frameWidth()
        self.ui.dialog_splitter.setSizes(
            [max_page_name,
             self.geometry().width() - max_page_name])

        self.ui.pages_tree.setHeaderLabels([""])
        self.ui.pages_tree.header().hide()
        self.ui.pages_tree.itemSelectionChanged.connect(self.switch_page)

        self.restoreWindowState()
        self.finished.connect(self.saveWindowState)

        for page in self.pages:
            try:
                page.load()
            except Exception:
                log.exception('Failed loading options page %r', page)
                self.disable_page(page.NAME)
        self.ui.pages_tree.setCurrentItem(self.default_item)

        self.profile_page = self.get_page('profiles')
        self.profile_page.signal_refresh.connect(
            self.update_from_profile_changes)

        self.first_enter = True
        self.installEventFilter(self)

        self.highlight_enabled_profile_options()
        current_page = self.item_to_page[self.ui.pages_tree.currentItem()]
        self.set_profiles_button_and_highlight(current_page)
Exemple #15
0
    def __init__(self, default_page=None, parent=None):
        super().__init__(parent)
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        from picard.ui.ui_options import Ui_Dialog
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.ui.reset_all_button = QtWidgets.QPushButton(
            _("&Restore all Defaults"))
        self.ui.reset_all_button.setToolTip(
            _("Reset all of Picard's settings"))
        self.ui.reset_button = QtWidgets.QPushButton(_("Restore &Defaults"))
        self.ui.reset_button.setToolTip(
            _("Reset all settings for current option page"))

        ok = StandardButton(StandardButton.OK)
        ok.setText(_("Make It So!"))
        self.ui.buttonbox.addButton(ok, QtWidgets.QDialogButtonBox.AcceptRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.CANCEL),
                                    QtWidgets.QDialogButtonBox.RejectRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.HELP),
                                    QtWidgets.QDialogButtonBox.HelpRole)
        self.ui.buttonbox.addButton(self.ui.reset_all_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)
        self.ui.buttonbox.addButton(self.ui.reset_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)

        self.ui.buttonbox.accepted.connect(self.accept)
        self.ui.buttonbox.rejected.connect(self.reject)
        self.ui.reset_all_button.clicked.connect(self.confirm_reset_all)
        self.ui.reset_button.clicked.connect(self.confirm_reset)
        self.ui.buttonbox.helpRequested.connect(self.help)

        self.pages = []
        for Page in page_classes:
            try:
                page = Page(self.ui.pages_stack)
                self.pages.append(page)
            except Exception:
                log.exception('Failed initializing options page %r', page)
        self.item_to_page = {}
        self.page_to_item = {}
        self.default_item = None
        self.add_pages(None, default_page, self.ui.pages_tree)

        # work-around to set optimal option pane width
        self.ui.pages_tree.expandAll()
        max_page_name = self.ui.pages_tree.sizeHintForColumn(
            0) + 2 * self.ui.pages_tree.frameWidth()
        self.ui.pages_tree.collapseAll()
        self.ui.splitter.setSizes(
            [max_page_name,
             self.geometry().width() - max_page_name])

        self.ui.pages_tree.setHeaderLabels([""])
        self.ui.pages_tree.header().hide()
        self.ui.pages_tree.itemSelectionChanged.connect(self.switch_page)

        self.restoreWindowState()
        self.finished.connect(self.saveWindowState)

        for page in self.pages:
            try:
                page.load()
            except Exception:
                log.exception('Failed loading options page %r', page)
                self.disable_page(page.NAME)
        self.ui.pages_tree.setCurrentItem(self.default_item)
Exemple #16
0
    def __init__(self, default_page=None, parent=None):
        super().__init__(parent)
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        from picard.ui.ui_options import Ui_Dialog
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.ui.reset_all_button = QtWidgets.QPushButton(
            _("&Restore all Defaults"))
        self.ui.reset_all_button.setToolTip(
            _("Reset all of Picard's settings"))
        self.ui.reset_button = QtWidgets.QPushButton(_("Restore &Defaults"))
        self.ui.reset_button.setToolTip(
            _("Reset all settings for current option page"))

        ok = StandardButton(StandardButton.OK)
        ok.setText(_("Make It So!"))
        self.ui.buttonbox.addButton(ok, QtWidgets.QDialogButtonBox.AcceptRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.CANCEL),
                                    QtWidgets.QDialogButtonBox.RejectRole)
        self.ui.buttonbox.addButton(StandardButton(StandardButton.HELP),
                                    QtWidgets.QDialogButtonBox.HelpRole)
        self.ui.buttonbox.addButton(self.ui.reset_all_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)
        self.ui.buttonbox.addButton(self.ui.reset_button,
                                    QtWidgets.QDialogButtonBox.ActionRole)

        self.ui.buttonbox.accepted.connect(self.accept)
        self.ui.buttonbox.rejected.connect(self.reject)
        self.ui.reset_all_button.clicked.connect(self.confirm_reset_all)
        self.ui.reset_button.clicked.connect(self.confirm_reset)
        self.ui.buttonbox.helpRequested.connect(self.show_help)

        profile_help = StandardButton(StandardButton.HELP)
        profile_help.setText(_("Profile Help"))
        self.ui.profiles_buttonbox.addButton(
            profile_help, QtWidgets.QDialogButtonBox.HelpRole)
        self.ui.profiles_buttonbox.helpRequested.connect(
            self.show_profile_help)

        self.ui.attached_profiles_button = QtWidgets.QPushButton(
            _("Attached Profiles"))
        self.ui.attached_profiles_button.setToolTip(
            _("Show which profiles are attached to the options on this page"))
        self.ui.profiles_buttonbox.addButton(
            self.ui.attached_profiles_button,
            QtWidgets.QDialogButtonBox.ActionRole)
        self.ui.attached_profiles_button.clicked.connect(
            self.show_attached_profiles_dialog)

        config = get_config()

        self.pages = []
        for Page in page_classes:
            try:
                page = Page(self.ui.pages_stack)
                self.pages.append(page)
            except Exception:
                log.exception('Failed initializing options page %r', page)
        self.item_to_page = {}
        self.page_to_item = {}
        self.default_item = None
        if not default_page:
            default_page = config.persist["options_last_active_page"]
        self.add_pages(None, default_page, self.ui.pages_tree)

        # work-around to set optimal option pane width
        self.ui.pages_tree.expandAll()
        max_page_name = self.ui.pages_tree.sizeHintForColumn(
            0) + 2 * self.ui.pages_tree.frameWidth()
        self.ui.dialog_splitter.setSizes(
            [max_page_name,
             self.geometry().width() - max_page_name])

        self.ui.pages_tree.setHeaderLabels([""])
        self.ui.pages_tree.header().hide()
        self.ui.pages_tree.itemSelectionChanged.connect(self.switch_page)

        self.restoreWindowState()
        self.finished.connect(self.saveWindowState)

        for page in self.pages:
            try:
                page.load()
            except Exception:
                log.exception('Failed loading options page %r', page)
                self.disable_page(page.NAME)
        self.ui.pages_tree.setCurrentItem(self.default_item)

        self.first_enter = True
        self.installEventFilter(self)

        self.USER_SETTINGS_TITLE = _("User base settings")

        if config.profiles[SettingConfigSection.PROFILES_KEY]:
            self.ui.profile_frame.show()
            self.ui.save_to_profile.clear()
            self.ui.save_to_profile.addItem(self.USER_SETTINGS_TITLE,
                                            USER_SETTINGS_PROFILE_ID)
            index = 0
            for idx, item in enumerate(
                    config.profiles[SettingConfigSection.PROFILES_KEY],
                    start=1):
                self.ui.save_to_profile.addItem(item["title"], item["id"])
                if not index and item["enabled"]:
                    index = idx
            self.ui.save_to_profile.currentIndexChanged.connect(
                self.switch_profile)
            self.ui.save_to_profile.setCurrentIndex(index)
        else:
            self.ui.profile_frame.hide()