예제 #1
0
class MainWindow(QtGui.QMainWindow):

    options = [
        Option("persist", "window_state", QtCore.QByteArray(),
               QtCore.QVariant.toByteArray),
        Option("persist", "window_position", QtCore.QPoint(),
               QtCore.QVariant.toPoint),
        Option("persist", "window_size", QtCore.QSize(780, 560),
               QtCore.QVariant.toSize),
        Option("persist", "bottom_splitter_state", QtCore.QByteArray(),
               QtCore.QVariant.toByteArray),
        BoolOption("persist", "window_maximized", False),
        BoolOption("persist", "view_cover_art", False),
        BoolOption("persist", "view_file_browser", False),
        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)

        # 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 self.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):
        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()
            self.config.persist["window_position"] = geom.topLeft()
            self.config.persist["window_size"] = geom.size()
        else:
            pos = self.pos()
            if not pos.isNull():
                self.config.persist["window_position"] = pos
            self.config.persist["window_size"] = self.size()
        self.config.persist["window_maximized"] = isMaximized
        self.config.persist["view_cover_art"] = self.show_cover_art_action.isChecked()
        self.config.persist["view_file_browser"] = self.show_file_browser_action.isChecked()
        self.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(self.config.persist["window_state"])
        pos = self.config.persist["window_position"]
        size = self.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 self.config.persist["window_maximized"]:
            self.setWindowState(QtCore.Qt.WindowMaximized)
        bottom_splitter_state = self.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.file_counts_label = QtGui.QLabel()
        self.listening_label = QtGui.QLabel()
        self.listening_label.setVisible(False)
        self.listening_label.setToolTip(_("Picard listens on a port to integrate with your browser and downloads release"
                                          " information when you click the \"Tagger\" buttons on the MusicBrainz website"))
        self.statusBar().addPermanentWidget(self.file_counts_label)
        self.statusBar().addPermanentWidget(self.listening_label)
        self.connect(self.tagger, QtCore.SIGNAL("file_state_changed"), self.update_statusbar_files)
        self.connect(self.tagger, QtCore.SIGNAL("listen_port_changed"), self.update_statusbar_listen_port)
        self.update_statusbar_files(0)

    def update_statusbar_files(self, num_pending_files):
        """Updates the status bar information."""
        self.file_counts_label.setText(_(" Files: %(files)d, Pending Files: %(pending)d ")
            % {"files": self.tagger.num_files(), "pending": num_pending_files})

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

    def set_statusbar_message(self, message, *args, **kwargs):
        """Set the status bar message."""
        try:
            if message:
                self.log.debug(repr(message.replace('%%s', '%%r')), *args)
        except:
            pass
        self.tagger.thread_pool.call_from_thread(
            self._set_statusbar_message, message, *args, **kwargs)

    def _set_statusbar_message(self, message, *args, **kwargs):
        if message:
            if args:
                message = _(message) % args
            else:
                message = _(message)
        self.statusBar().showMessage(message, kwargs.get('timeout', 0))

    def clear_statusbar_message(self):
        """Set the status bar message."""
        self.statusBar().clearMessage()

    def _on_submit(self):
        if self.tagger.use_acoustid:
            if not self.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()
        else:
            self.tagger.puidmanager.submit()

    def create_actions(self):
        self.options_action = QtGui.QAction(icontheme.lookup('preferences-desktop'), _("&Options..."), self)
        self.connect(self.options_action, QtCore.SIGNAL("triggered()"), 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.connect(self.cut_action, QtCore.SIGNAL("triggered()"), 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.connect(self.paste_action, QtCore.SIGNAL("triggered()"), self.paste)

        self.help_action = QtGui.QAction(_("&Help..."), self)

        self.help_action.setShortcut(QtGui.QKeySequence.HelpContents)
        self.connect(self.help_action, QtCore.SIGNAL("triggered()"), self.show_help)

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

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

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

        self.support_forum_action = QtGui.QAction(_("&Support Forum..."), self)
        self.connect(self.support_forum_action, QtCore.SIGNAL("triggered()"), 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.connect(self.add_files_action, QtCore.SIGNAL("triggered()"), 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.connect(self.add_directory_action, QtCore.SIGNAL("triggered()"),
                     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.connect(self.save_action, QtCore.SIGNAL("triggered()"), self.save)

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

        self.exit_action = QtGui.QAction(_(u"E&xit"), self)
        # TR: Keyboard shortcut for "Exit"
        self.exit_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Q")))
        self.connect(self.exit_action, QtCore.SIGNAL("triggered()"), 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.connect(self.remove_action, QtCore.SIGNAL("triggered()"), 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 self.config.persist["view_file_browser"]:
            self.show_file_browser_action.setChecked(True)
        self.show_file_browser_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+B")))
        self.connect(self.show_file_browser_action, QtCore.SIGNAL("triggered()"), self.show_file_browser)

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

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

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

        self.analyze_action = QtGui.QAction(icontheme.lookup('picard-analyze'), _(u"&Scan"), self)
        self.analyze_action.setEnabled(False)
        # TR: Keyboard shortcut for "Analyze"
        self.analyze_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Y")))
        self.connect(self.analyze_action, QtCore.SIGNAL("triggered()"), self.analyze)

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

        self.autotag_action = QtGui.QAction(icontheme.lookup('picard-auto-tag'), _(u"&Lookup"), self)
        self.autotag_action.setToolTip(_(u"Lookup metadata"))
        self.autotag_action.setStatusTip(_(u"Lookup metadata"))
        self.autotag_action.setEnabled(False)
        # TR: Keyboard shortcut for "Lookup"
        self.autotag_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+L")))
        self.connect(self.autotag_action, QtCore.SIGNAL("triggered()"), 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.connect(self.view_info_action, QtCore.SIGNAL("triggered()"), 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.connect(self.refresh_action, QtCore.SIGNAL("triggered()"), self.refresh)

        self.enable_renaming_action = QtGui.QAction(_(u"&Rename Files"), self)
        self.enable_renaming_action.setCheckable(True)
        self.enable_renaming_action.setChecked(self.config.setting["rename_files"])
        self.connect(self.enable_renaming_action, QtCore.SIGNAL("triggered(bool)"), 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(self.config.setting["move_files"])
        self.connect(self.enable_moving_action, QtCore.SIGNAL("triggered(bool)"), 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 self.config.setting["dont_write_tags"])
        self.connect(self.enable_tag_saving_action, QtCore.SIGNAL("triggered(bool)"), self.toggle_tag_saving)

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

        self.view_log_action = QtGui.QAction(_(u"View &Log..."), self)
        self.connect(self.view_log_action, QtCore.SIGNAL("triggered()"), self.show_log)

        self.connect(self.tagger.xmlws, QtCore.SIGNAL("authentication_required"), self.show_password_dialog)
        self.connect(self.tagger.xmlws, QtCore.SIGNAL("proxyAuthentication_required"), self.show_proxy_dialog)

        self.open_file_action = QtGui.QAction(_(u"&Open..."), self)
        self.open_file_action.setStatusTip(_(u"Open the file"))
        self.connect(self.open_file_action, QtCore.SIGNAL("triggered()"), self.open_file)

        self.open_folder_action = QtGui.QAction(_(u"Open &Folder..."), self)
        self.open_folder_action.setStatusTip(_(u"Open the containing folder"))
        self.connect(self.open_folder_action, QtCore.SIGNAL("triggered()"), self.open_folder)

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

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

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

    def open_tags_from_filenames(self):
        files = self.tagger.get_files_from_objects(self.selected_objects)
        if not files:
            files = self.tagger.unmatched_files.files
        if files:
            dialog = TagsFromFileNamesDialog(files, self)
            dialog.exec_()

    def create_menus(self):
        menu = self.menuBar().addMenu(_(u"&File"))
        menu.addAction(self.add_files_action)
        menu.addAction(self.add_directory_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.toggleViewAction())
        menu.addAction(self.search_toolbar.toggleViewAction())
        menu.addSeparator()
        menu.addAction(self.refresh_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.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)
        self.menuBar().addSeparator()
        menu = self.menuBar().addMenu(_(u"&Help"))
        menu.addAction(self.help_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 self.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)

    def create_toolbar(self):
        self.toolbar = toolbar = self.addToolBar(_(u"&Toolbar"))
        self.update_toolbar_style()
        toolbar.setObjectName("main_toolbar")
        toolbar.addAction(self.add_files_action)
        toolbar.addAction(self.add_directory_action)
        toolbar.addSeparator()
        toolbar.addAction(self.save_action)
        toolbar.addAction(self.submit_action)
        toolbar.addSeparator()

        toolbar.addAction(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.connect(self.cd_lookup_menu, QtCore.SIGNAL("triggered(QAction*)"), self.tagger.lookup_cd)
            button = toolbar.widgetForAction(self.cd_lookup_action)
            button.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
            button.setMenu(self.cd_lookup_menu)

        toolbar.addAction(self.cluster_action)
        toolbar.addAction(self.autotag_action)
        toolbar.addAction(self.analyze_action)
        toolbar.addAction(self.view_info_action)
        toolbar.addAction(self.remove_action)
        toolbar.addAction(self.browser_lookup_action)
        self.search_toolbar = toolbar = self.addToolBar(_(u"&Search Bar"))
        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"), QtCore.QVariant("album"))
        self.search_combo.addItem(_(u"Artist"), QtCore.QVariant("artist"))
        self.search_combo.addItem(_(u"Track"), QtCore.QVariant("track"))
        hbox.addWidget(self.search_combo, 0)
        self.search_edit = QtGui.QLineEdit(search_panel)
        self.connect(self.search_edit, QtCore.SIGNAL("returnPressed()"), 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))
        hbox.addWidget(self.search_button)
        toolbar.addWidget(search_panel)

    def enable_submit(self, enabled):
        """Enable/disable the 'Submit PUIDs' 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 = unicode(self.search_edit.text())
        type = unicode(self.search_combo.itemData(
                       self.search_combo.currentIndex()).toString())
        self.tagger.search(text, type, self.config.setting["use_adv_search_syntax"])

    def add_files(self):
        """Add files to the tagger."""
        current_directory = self.config.persist["current_directory"] or QtCore.QDir.homePath()
        current_directory = find_existing_path(unicode(current_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)
            self.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 = self.config.persist["current_directory"] or QtCore.QDir.homePath()
        current_directory = find_existing_path(unicode(current_directory))

        dir_list = []
        if not self.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:
            self.config.persist["current_directory"] = dir_list[0]
        elif len(dir_list) > 1:
            (parent, dir) = os.path.split(str(dir_list[0]))
            self.config.persist["current_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.open("http://musicbrainz.org/doc/Picard_Documentation")

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

    def confirm_va_removal(self):
        return QtGui.QMessageBox.question(self,
                _("Various Artists file naming scheme removal"),
_("""The separate file naming scheme for various artists albums has been
removed in this version of Picard. You currently do not use the this option,
but have a separate file naming scheme defined. Do you want to remove it or
merge it with your file naming scheme for single artist albums?"""),
            _("Merge"), _("Remove"))

    def show_va_removal_notice(self):
        QtGui.QMessageBox.information(self,
            _("Various Artists file naming scheme removal"),
_("""The separate file naming scheme for various artists albums has been
removed in this version of Picard. Your file naming scheme has automatically
been merged with that of single artist albums."""),
            QtGui.QMessageBox.Ok)


    def open_bug_report(self):
        webbrowser2.open("http://musicbrainz.org/doc/Picard_Troubleshooting")

    def open_support_forum(self):
        webbrowser2.open("http://forums.musicbrainz.org/viewforum.php?id=2")

    def open_donation_page(self):
        webbrowser2.open('http://metabrainz.org/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._ignore_selection_changes = True
        self.tagger.remove(self.selected_objects)
        self.panel._ignore_selection_changes = False
        self.panel.update_current_view()

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

    def open_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):
        file = self.tagger.get_files_from_objects(self.selected_objects)[0]
        dialog = InfoDialog(file, self)
        dialog.exec_()

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

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

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

    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())
        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
            if 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.cut_action.setEnabled(bool(self.selected_objects))

    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
        statusbar = u""
        obj = None

        if len(objects) == 1:
            obj = list(objects)[0]
            if isinstance(obj, File):
                metadata = obj.metadata
                statusbar = obj.filename
                if obj.state == obj.ERROR:
                    statusbar += _(" (Error: %s)") % obj.error
            elif isinstance(obj, Track):
                metadata = obj.metadata
                if obj.num_linked_files == 1:
                    file = obj.linked_files[0]
                    statusbar = "%s (%d%%)" % (file.filename, file.similarity * 100)
                    if file.state == File.ERROR:
                        statusbar += _(" (Error: %s)") % file.error
            elif obj.can_edit_tags():
                metadata = obj.metadata

        self.metadata_box.update_selection()
        self.metadata_box.update()
        self.cover_art_box.set_metadata(metadata, obj)
        self.set_statusbar_message(statusbar)

    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.shrink_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):
        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.panel.views[0].drop_files(self.tagger.get_files_from_objects(self._clipboard), target)
        self._clipboard = []
        self.paste_action.setEnabled(False)