Example #1
0
def test_preferencesdialog():
    app = QtWidgets.QApplication(sys.argv)
    d = PreferencesDialog()
    d.show()
    app.exec_()
Example #2
0
    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0), **kwargs):
        loglevel = kwargs.pop('loglevel', logging.NOTSET)

        _log.debug('Main window base classes initialization ...')
        QtWidgets.QApplication.setWindowIcon(
            qtsupport.geticon('GSDView.png', __name__))

        super(GSDView, self).__init__(parent, flags, **kwargs)
        title = self.tr('GSDView Open Source Edition v. %s') % info.version
        self.setWindowTitle(title)
        self.setObjectName('gsdview-mainwin')

        # Dialogs
        _log.debug('Setting up file dialog ...')

        #: application global file dialog instance
        self.filedialog = QtWidgets.QFileDialog(self)
        self.filedialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
        self.filedialog.setViewMode(QtWidgets.QFileDialog.Detail)

        _log.debug('Setting up the about dialog ...')

        #: application global about dialog instance
        self.aboutdialog = AboutDialog(self)

        _log.debug('Setting up the preferences dialog ...')

        #: prefernces dialog instance
        self.preferencesdialog = PreferencesDialog(self,
                                                   apply=self.applySettings)

        # Stop button
        _log.debug('Setting up the stop button ...')
        qstyle = QtWidgets.QApplication.style()
        icon = qstyle.standardIcon(QtWidgets.QStyle.SP_BrowserStop)

        #: stop button for external tools
        self.stopbutton = QtWidgets.QPushButton(icon, self.tr('Stop'), self)
        self.statusBar().addPermanentWidget(self.stopbutton)
        self.stopbutton.hide()

        # Progressbar
        _log.debug('Setting up the progress bar ...')

        #: application progress bar
        self.progressbar = QtWidgets.QProgressBar(self)
        self.progressbar.setTextVisible(True)
        self.statusBar().addPermanentWidget(self.progressbar)
        self.progressbar.hide()

        # Miscellanea
        _log.debug('Miscellanea setup ...')

        #: cache directory path
        self.cachedir = None

        # GraphicsViewMonitor and mouse manager
        _log.debug('Setting up "monitor" components ...')

        #: graphics scenes/views monitor
        self.monitor = graphicsview.GraphicsViewMonitor()

        #: mouse manager for graphics scenes/views
        self.mousemanager = mousemanager.MouseManager(self)
        self.mousemanager.mode = 'hand'

        # Plugin Manager

        #: backends list
        self.backends = []

        #: plugin manager instance
        self.pluginmanager = pluginmanager.PluginManager(self, SYSPLUGINSDIR)
        self.preferencesdialog.addPage(
            pluginmanager.PluginManagerGui(self.pluginmanager, self),
            qtsupport.geticon('plugin.svg', __name__),
            label='Plugins')

        # Settings
        if not os.path.isdir(USERCONFIGDIR):
            os.makedirs(USERCONFIGDIR)

        # @TODO: fix filename
        _log.debug('Read application settings ...')
        #self.settings = QtCore.QSettings('gsdview-soft', 'gsdview', self)
        #self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
        #                                 QtCore.QSettings.UserScope,
        #                                 'gsdview', 'gsdview', self)
        cfgfile = os.path.join(USERCONFIGDIR, 'gsdview.ini')
        _log.info('Configuration file: "%s".', cfgfile)

        #: application settings
        self.settings = QtCore.QSettings(cfgfile,
                                         QtCore.QSettings.IniFormat,
                                         self)

        # Setup the log system and the external tools controller
        _log.debug('Complete logging setup...')
        # @TODO: logevel could be set from command line
        #: application standard logger
        self.logger = self.setupLogging(loglevel=loglevel)

        _log.debug('Setting up external tool controller ...')

        #: external tool controller
        self.controller = self.setupController(self.logger, self.statusBar(),
                                               self.progressbar)

        # Actions
        _log.debug('Setting up actions ...')

        #: actions associated to file menu
        self.fileActions = None

        #: settings actions
        self.settingsActions = None

        #: help actions
        self.helpActions = None

        self.setupActions()

        # File menu end toolbar
        self._addMenuFromActions(self.fileActions, self.tr('&File'))
        self._addToolBarFromActions(self.fileActions, self.tr('File toolbar'))

        # Image menu and toolbar
        self.imagemenu = self._addMenuFromActions(self.mousemanager.actions,
                                                  self.tr('&Image'))
        self._addToolBarFromActions(self.mousemanager.actions,
                                    self.tr('Mouse toolbar'))

        # Tools menu
        self.toolsmenu = QtWidgets.QMenu(self.tr('&Tools'), self)
        self.menuBar().addMenu(self.toolsmenu)
        self.toolsmenu.hide()

        # Setup plugins
        _log.debug(self.tr('Setup plugins ...'))
        self.setupPlugins()

        # Settings menu end toolbar
        _log.debug(self.tr('Settings menu setup ...'))
        menu = self._addMenuFromActions(self.settingsActions,
                                        self.tr('&Settings'))
        self._addToolBarFromActions(self.settingsActions,
                                    self.tr('Settings toolbar'))

        #: settings sub-menu
        self.settings_submenu = QtWidgets.QMenu(
            self.tr('&View'), aboutToShow=self.updateSettingsMenu)
        menu.addSeparator()
        menu.addMenu(self.settings_submenu)

        _log.debug(self.tr('Window menu setup ...'))
        self.menuBar().addMenu(self.windowmenu)

        # Help menu end toolbar
        _log.debug('Help menu setup ...')
        self._addMenuFromActions(self.helpActions, self.tr('&Help'))
        self._addToolBarFromActions(self.helpActions, self.tr('Help toolbar'))

        # @NOTE: the window state setup must happen after the plugins loading
        _log.info('Load settings ...')
        self.loadSettings(loglevel=loglevel)  # @TODO: pass cachedir

        self.treeview.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.treeview.customContextMenuRequested.connect(self.itemContextMenu)

        self.statusBar().showMessage('Ready')
Example #3
0
def test_preferencesdialog():
    app = QtWidgets.QApplication(sys.argv)
    d = PreferencesDialog()
    d.show()
    app.exec_()
Example #4
0
class GSDView(ItemModelMainWindow):
    # @TODO:
    #   * cache browser, cache cleanup
    #   * open internal product
    #   * disable actions when the external tool is running
    #   * /usr/share/doc/python-qt4-doc/examples/mainwindows/recentfiles.py

    '''Main window class for GSDView application.'''

    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0), **kwargs):
        loglevel = kwargs.pop('loglevel', logging.NOTSET)

        _log.debug('Main window base classes initialization ...')
        QtWidgets.QApplication.setWindowIcon(
            qtsupport.geticon('GSDView.png', __name__))

        super(GSDView, self).__init__(parent, flags, **kwargs)
        title = self.tr('GSDView Open Source Edition v. %s') % info.version
        self.setWindowTitle(title)
        self.setObjectName('gsdview-mainwin')

        # Dialogs
        _log.debug('Setting up file dialog ...')

        #: application global file dialog instance
        self.filedialog = QtWidgets.QFileDialog(self)
        self.filedialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
        self.filedialog.setViewMode(QtWidgets.QFileDialog.Detail)

        _log.debug('Setting up the about dialog ...')

        #: application global about dialog instance
        self.aboutdialog = AboutDialog(self)

        _log.debug('Setting up the preferences dialog ...')

        #: prefernces dialog instance
        self.preferencesdialog = PreferencesDialog(self,
                                                   apply=self.applySettings)

        # Stop button
        _log.debug('Setting up the stop button ...')
        qstyle = QtWidgets.QApplication.style()
        icon = qstyle.standardIcon(QtWidgets.QStyle.SP_BrowserStop)

        #: stop button for external tools
        self.stopbutton = QtWidgets.QPushButton(icon, self.tr('Stop'), self)
        self.statusBar().addPermanentWidget(self.stopbutton)
        self.stopbutton.hide()

        # Progressbar
        _log.debug('Setting up the progress bar ...')

        #: application progress bar
        self.progressbar = QtWidgets.QProgressBar(self)
        self.progressbar.setTextVisible(True)
        self.statusBar().addPermanentWidget(self.progressbar)
        self.progressbar.hide()

        # Miscellanea
        _log.debug('Miscellanea setup ...')

        #: cache directory path
        self.cachedir = None

        # GraphicsViewMonitor and mouse manager
        _log.debug('Setting up "monitor" components ...')

        #: graphics scenes/views monitor
        self.monitor = graphicsview.GraphicsViewMonitor()

        #: mouse manager for graphics scenes/views
        self.mousemanager = mousemanager.MouseManager(self)
        self.mousemanager.mode = 'hand'

        # Plugin Manager

        #: backends list
        self.backends = []

        #: plugin manager instance
        self.pluginmanager = pluginmanager.PluginManager(self, SYSPLUGINSDIR)
        self.preferencesdialog.addPage(
            pluginmanager.PluginManagerGui(self.pluginmanager, self),
            qtsupport.geticon('plugin.svg', __name__),
            label='Plugins')

        # Settings
        if not os.path.isdir(USERCONFIGDIR):
            os.makedirs(USERCONFIGDIR)

        # @TODO: fix filename
        _log.debug('Read application settings ...')
        #self.settings = QtCore.QSettings('gsdview-soft', 'gsdview', self)
        #self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
        #                                 QtCore.QSettings.UserScope,
        #                                 'gsdview', 'gsdview', self)
        cfgfile = os.path.join(USERCONFIGDIR, 'gsdview.ini')
        _log.info('Configuration file: "%s".', cfgfile)

        #: application settings
        self.settings = QtCore.QSettings(cfgfile,
                                         QtCore.QSettings.IniFormat,
                                         self)

        # Setup the log system and the external tools controller
        _log.debug('Complete logging setup...')
        # @TODO: logevel could be set from command line
        #: application standard logger
        self.logger = self.setupLogging(loglevel=loglevel)

        _log.debug('Setting up external tool controller ...')

        #: external tool controller
        self.controller = self.setupController(self.logger, self.statusBar(),
                                               self.progressbar)

        # Actions
        _log.debug('Setting up actions ...')

        #: actions associated to file menu
        self.fileActions = None

        #: settings actions
        self.settingsActions = None

        #: help actions
        self.helpActions = None

        self.setupActions()

        # File menu end toolbar
        self._addMenuFromActions(self.fileActions, self.tr('&File'))
        self._addToolBarFromActions(self.fileActions, self.tr('File toolbar'))

        # Image menu and toolbar
        self.imagemenu = self._addMenuFromActions(self.mousemanager.actions,
                                                  self.tr('&Image'))
        self._addToolBarFromActions(self.mousemanager.actions,
                                    self.tr('Mouse toolbar'))

        # Tools menu
        self.toolsmenu = QtWidgets.QMenu(self.tr('&Tools'), self)
        self.menuBar().addMenu(self.toolsmenu)
        self.toolsmenu.hide()

        # Setup plugins
        _log.debug(self.tr('Setup plugins ...'))
        self.setupPlugins()

        # Settings menu end toolbar
        _log.debug(self.tr('Settings menu setup ...'))
        menu = self._addMenuFromActions(self.settingsActions,
                                        self.tr('&Settings'))
        self._addToolBarFromActions(self.settingsActions,
                                    self.tr('Settings toolbar'))

        #: settings sub-menu
        self.settings_submenu = QtWidgets.QMenu(
            self.tr('&View'), aboutToShow=self.updateSettingsMenu)
        menu.addSeparator()
        menu.addMenu(self.settings_submenu)

        _log.debug(self.tr('Window menu setup ...'))
        self.menuBar().addMenu(self.windowmenu)

        # Help menu end toolbar
        _log.debug('Help menu setup ...')
        self._addMenuFromActions(self.helpActions, self.tr('&Help'))
        self._addToolBarFromActions(self.helpActions, self.tr('Help toolbar'))

        # @NOTE: the window state setup must happen after the plugins loading
        _log.info('Load settings ...')
        self.loadSettings(loglevel=loglevel)  # @TODO: pass cachedir

        self.treeview.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.treeview.customContextMenuRequested.connect(self.itemContextMenu)

        self.statusBar().showMessage('Ready')

    # Model/View utils ######################################################
    def currentGraphicsView(self):
        window = self.mdiarea.activeSubWindow()
        if window:
            widget = window.widget()
            if isinstance(widget, QtWidgets.QGraphicsView):
                return widget
        return None

    @QtCore.Slot(QtCore.QPoint)
    def itemContextMenu(self, pos):
        modelindex = self.treeview.indexAt(pos)
        if not modelindex.isValid():
            return
        # @TODO: check
        # @NOTE: set the current index so that action callback can retrieve
        #        the cottect item
        self.treeview.setCurrentIndex(modelindex)
        item = self.datamodel.itemFromIndex(modelindex)
        backend = self.pluginmanager.plugins[item.backend]
        menu = backend.itemContextMenu(item)
        if menu:
            menu.exec_(self.treeview.mapToGlobal(pos))

    # Event handlers ########################################################
    def closeEvent(self, event):
        self.controller.stop_tool()
        # @TODO: whait for finished (??)
        # @TODO: save opened datasets (??)
        self.saveSettings()
        self.pluginmanager.save_settings(self.settings)
        self.closeAll()
        self.pluginmanager.reset()
        _log.info('Closing application')
        #event.accept()
        super(GSDView, self).closeEvent(event)

    def changeEvent(self, event):
        try:
            if event.oldState() == QtCore.Qt.WindowNoState:
                # save window size and position
                self.settings.beginGroup('mainwindow')
                self.settings.setValue('geometry', self.saveGeometry())
                # @TODO: clean
                #self.settings.setValue('position', self.pos())
                #self.settings.setValue('size', self.size())
                self.settings.endGroup()
                event.accept()
        except AttributeError:
            pass

    # Custom exception hook #################################################
    def excepthook(self, exctype, excvalue, tracebackobj):
        '''Global function to catch unhandled exceptions.

        :param exctype:
            exception class
        :param excvalue:
            exception instance
        :param tracebackobj:
            traceback object

        '''

        sys.__excepthook__(exctype, excvalue, tracebackobj)
        # No messages for keyboard interruts
        if not issubclass(exctype, Exception):
        #~ if issubclass(exctype, KeyboardInterrupt):
            msg = str(excvalue)
            if not msg:
                msg = excvalue.__class__.__name__
            _log.info(msg)
            self.close()
            return

        # @TODO: check
        # Guard for avoiding multiple dialog opening
        if hasattr(self, '_busy'):
            return
        self._busy = True

        # @TODO: sometimes a RuntimeError is raised claiming that the
        #        "underlying C/C++ object has been deleted".
        #        Try to build the dialog without parent (self) and check
        #        modality.
        dialog = ExceptionDialog(exctype, excvalue, tracebackobj, self)
        #dialog.show()
        ret = dialog.exec_()
        if ret == QtWidgets.QDialog.Rejected:
            self.close()
        else:
            _log.warning('ignoring an unhandled exception may cause '
                         'program malfunctions.')

    # Setup helpers #########################################################
    def _setupFileActions(self):
        # @TODO: add a "close all" (items) action
        actionsgroup = QtWidgets.QActionGroup(self)

        # Open
        icon = qtsupport.geticon('open.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&Open'), actionsgroup,
            objectName='openAction',
            shortcut=self.tr('Ctrl+O'),
            toolTip=self.tr('Open an existing file'),
            statusTip=self.tr('Open an existing file'),
            triggered=self.openFile)

        # Close
        icon = qtsupport.geticon('close.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&Close'), actionsgroup,
            objectName='closeAction',
            # 'Ctrl+W' shortcu is used for closing windows
            #shortcut=self.tr('Ctrl+W'),
            toolTip=self.tr('Close the current file'),
            statusTip=self.tr('Close the current file'),
            triggered=self.closeItem)

        # Separator
        QtWidgets.QAction(actionsgroup).setSeparator(True)
        # objectName='separator')

        # Exit
        icon = qtsupport.geticon('quit.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&Exit'), actionsgroup,
            objectName='exitAction',
            shortcut=self.tr('Ctrl+X'),
            toolTip=self.tr('Exit the program'),
            statusTip=self.tr('Exit the program'),
            triggered=self.close)

        return actionsgroup

    def _setupSettingsActions(self):
        actionsgroup = QtWidgets.QActionGroup(self)

        # Preferences
        icon = qtsupport.geticon('preferences.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&Preferences'), actionsgroup,
            objectName='preferencesAction',
            toolTip=self.tr('Open the program preferences dialog'),
            statusTip=self.tr('Open the program preferences dialog'),
            triggered=self.showPreferencesDialog)

        icon = qtsupport.geticon('full-screen.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&Full Screen'), actionsgroup,
            objectName='fullScreenAction',
            shortcut='Ctrl+Meta+F',
            toolTip=self.tr('Toggle full screen mode'),
            statusTip=self.tr('Toggle full screen mode'),
            triggered=self.toggleFullScreenMode)

        return actionsgroup

    def _setupHelpActions(self):
        actionsgroup = QtWidgets.QActionGroup(self)

        # About
        icon = qtsupport.geticon('about.svg', __name__)
        QtWidgets.QAction(
            icon, self.tr('&About'), actionsgroup,
            objectName='aboutAction',
            toolTip=self.tr('Show program information'),
            statusTip=self.tr('Show program information'),
            triggered=lambda: self.aboutdialog.exec_())

        # AboutQt
        icon = QtGui.QIcon(':/qt-project.org/qmessagebox/images/qtlogo-64.png')
        # @COMPATIBILITY: Qt4 --> Qt5
        if not icon.availableSizes():
            icon = QtGui.QIcon(':/trolltech/qmessagebox/images/qtlogo-64.png')
        if not icon.availableSizes():
            icon = QtGui.QIcon.fromTheme('qtlogo-64')
        QtWidgets.QAction(
            icon, self.tr('About &Qt'), actionsgroup,
            objectName='aboutQtAction',
            toolTip=self.tr('Show information about Qt'),
            statusTip=self.tr('Show information about Qt'),
            triggered=lambda: QtWidgets.QMessageBox.aboutQt(self))

        return actionsgroup

    def setupActions(self):
        self.fileActions = self._setupFileActions()
        self.settingsActions = self._setupSettingsActions()
        self.helpActions = self._setupHelpActions()
        # @TODO: tree view actions: expand/collapse all, expand/collapse
        #        subtree
        # @TODO: stop action

    def _addMenuFromActions(self, actions, name):
        menu = qtsupport.actionGroupToMenu(actions, name, self)
        self.menuBar().addMenu(menu)
        return menu

    def _addToolBarFromActions(self, actions, name):
        toolbar = qtsupport.actionGroupToToolbar(actions, name)
        self.addToolBar(toolbar)

        # @COMPATIBILITY: pyside 1.2.2
        #                 without the call to toolbar.parent() the toolbar is
        #                 not actually added
        assert toolbar.parent()

        return toolbar

    def setupPlugins(self):
        # load backends
        # @WARNING: (pychecker) Function (__import__) doesn't support **kwArgs
        module = __import__('gsdview.gdalbackend', fromlist=['gsdview'])
        self.pluginmanager.load_module(module, 'gdalbackend')

        # load settings
        self.pluginmanager.load_settings(self.settings)

        # save initial state
        self.pluginmanager.save_settings(self.settings)

    def setupLogging(self, loglevel=None):
        logger = logging.getLogger('gsdview')

        # move this to launch.py
        fmt = ('%(levelname)s: %(asctime)s %(filename)s line %(lineno)d in '
               '%(funcName)s: %(message)s')
        formatter = logging.Formatter(fmt)
        logfile = os.path.join(USERCONFIGDIR, 'gsdview.log')
        handler = logging.FileHandler(logfile, 'w')
        handler.setLevel(logging.DEBUG)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        formatter = logging.Formatter('%(message)s')
        handler = QtDialogLoggingHandler(parent=self, dialog=None)
        handler.setLevel(logging.WARNING)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        # set log level
        if loglevel in (None, logging.NOTSET, 'NOTSET'):
            # @WARNING: duplicate loadSettings
            level = self.settings.value('preferences/loglevel', 'INFO')
            levelno = logging.getLevelName(str(level))
            if isinstance(levelno, int):
                logger.setLevel(levelno)
                _log.info('"%s" loglevel set', level)
            else:
                _log.debug('invalid log level: "%s"', level)

        return logger

    def setupController(self, logger, statusbar, progressbar):
        controller = QtToolController(logger, parent=self)
        controller.subprocess.started.connect(self.processingStarted)
        controller.finished.connect(self.processingDone)
        self.stopbutton.clicked.connect(controller.stop_tool)

        return controller

    # Settings ##############################################################
    def _restoreWindowState(self, settings=None):
        if settings is None:
            settings = self.settings

        settings.beginGroup('mainwindow')
        try:
            # @TODO: clean
            #position = settings.value('position')
            #if position is not None:
            #    self.move(position)
            #size = settings.value('size')
            #if size is not None:
            #    self.resize(size)
            #else:
            #    # default size
            #    self.resize(800, 600)

            geometry = settings.value('geometry')
            if (not geometry or
                    (geometry and not self.restoreGeometry(geometry))):
                # default size
                self.resize(800, 600)

            state = settings.value('state')
            if state:
                self.restoreState(state)

            # @TODO: clean
            #try:
            #    winstate = settings.value('winstate', QtCore.Qt.WindowNoState)
            #    winstate = int(winstate)
            #    if winstate and winstate != QtCore.Qt.WindowNoState:
            #        # @COMPATIBILITY: presumably a bug in PyQt4 4.7.2
            #        winstate = qtsupport.intToWinState[winstate]
            #        self.setWindowState(winstate)
            #except (KeyError, ValueError) as e:
            #    _log.info('unable to restore the window state')
            #    _log.debug('', exc_info=True)

            # State of toolbars ad docks
            state = settings.value('state')
            if state is not None:
                self.restoreState(state)
        finally:
            settings.endGroup()

    def _restoreFileDialogState(self, settings=None):
        if settings is None:
            settings = self.settings

        settings.beginGroup('filedialog')
        try:
            # state
            state = settings.value('state')
            if state is not None:
                try:
                    # QFileDialog.restoreState is new in Qt 4.3
                    self.filedialog.restoreState(state)
                except AttributeError:
                    _log.debug('unable to restore the file dialog state')

            # workdir
            workdir = settings.value('workdir', utils.default_workdir())
            workdir = os.path.expanduser(os.path.expandvars(workdir))
            self.filedialog.setDirectory(workdir)

            # history
            #history = settings.value('history')
            #if history:
            #    self.filedialog.setHistory(history)

            # sidebar urls
            try:
                # QFileDialog.setSidebarUrls is new in Qt 4.3
                sidebarurls = settings.value('sidebarurls')
                if sidebarurls:
                    sidebarurls = [QtCore.QUrl(item) for item in sidebarurls]
                    self.filedialog.setSidebarUrls(sidebarurls)
            except AttributeError:
                _log.debug('unable to restore sidebar URLs of the file dialog')
        finally:
            settings.endGroup()

    def loadSettings(self, settings=None, loglevel=None):
        # @TODO: split app saveSettings frlm plugins one
        if settings is None:
            settings = self.settings

        # general
        self._restoreWindowState(settings)
        self._restoreFileDialogState(settings)

        settings.beginGroup('preferences')
        try:
            # log level
            if loglevel in (None, logging.NOTSET, 'NOTSET'):
                level = settings.value('loglevel', 'INFO')
                levelno = logging.getLevelName(level)
                if isinstance(levelno, int):
                    self.logger.setLevel(levelno)
                    _log.debug('"%s" loglevel set', level)
                else:
                    _log.debug('invalid log level: "%s"', level)

            # cache location
            default = os.path.join(USERCONFIGDIR, 'cache')
            cachedir = settings.value('cachedir', default)
            self.cachedir = os.path.expanduser(os.path.expandvars(cachedir))
        finally:
            settings.endGroup()

        # cache
        # @TODO

        # plugins
        for plugin in self.pluginmanager.plugins.values():
            plugin.loadSettings(settings)

    def _saveWindowState(self, settings=None):
        if settings is None:
            settings = self.settings

        settings.beginGroup('mainwindow')
        try:
            settings.setValue('winstate', self.windowState())
            # @TODO: clean
            #if self.windowState() == QtCore.Qt.WindowNoState:
            #    settings.setValue('position', self.pos())
            #    settings.setValue('size', self.size())

            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('state', self.saveState())
        finally:
            settings.endGroup()

    def _saveFileDialogState(self, settings=None):
        if settings is None:
            settings = self.settings

        settings.beginGroup('filedialog')
        try:
            # state
            try:
                # QFileDialog.saveState is new in Qt 4.3
                settings.setValue('state', self.filedialog.saveState())
            except AttributeError:
                _log.debug('unable to save the file dialog state')

            # workdir
            # @NOTE: uncomment to preserve the session value
            #workdir = settings.setValue('workdir',
            #                            self.filedialog.directory())

            # history
            #settings.setValue('history', self.filedialog.history())

            # sidebar urls
            try:
                # QFileDialog.sidebarUrls is new in Qt 4.3
                sidebarurls = self.filedialog.sidebarUrls()
                if sidebarurls:
                    settings.setValue('sidebarurls', sidebarurls)
            except AttributeError:
                _log.debug('unable to save sidebar URLs of the file dialog')
        finally:
            settings.endGroup()

    def saveSettings(self, settings=None):
        if settings is None:
            settings = self.settings

        # general
        self._saveWindowState(settings)
        self._saveFileDialogState(settings)

        settings.beginGroup('preferences')
        try:
            level = logging.getLevelName(self.logger.level)
            settings.setValue('loglevel', level)

            # only changed via preferences
            #settings.setValue('cachedir', self.cachedir)
        finally:
            settings.endGroup()

        # @NOTE: cache preferences are only modified via preferences dialog

        for plugin in self.pluginmanager.plugins.values():
            #logging.debug('save %s plugin preferences' % plugin.name)
            plugin.saveSettings(settings)

    @QtCore.Slot()
    def toggleFullScreenMode(self):
        self.setWindowState(self.windowState() ^ QtCore.Qt.WindowFullScreen)

    @QtCore.Slot()
    def updateSettingsMenu(self):
        # @TODO: rewrite; it should not be needed to copy the menu into a
        #        new one
        self.settings_submenu.clear()
        menu = self.createPopupMenu()

        for action in menu.actions():
            if self.tr('toolbar') in action.text():
                self.settings_submenu.addAction(action)
        self.settings_submenu.addSeparator()
        for action in menu.actions():
            if self.tr('toolbar') not in action.text():
                self.settings_submenu.addAction(action)

    @QtCore.Slot()
    def applySettings(self):
        self.preferencesdialog.save(self.settings)
        self.loadSettings()

    @QtCore.Slot()
    def showPreferencesDialog(self):
        # @TODO: complete
        self.saveSettings()
        self.preferencesdialog.load(self.settings)
        if self.preferencesdialog.exec_():
            self.applySettings()

    # File actions ##########################################################
    @QtCore.Slot()
    def openFile(self):
        # @TODO: remove; this is a temporary workaround for a Qt bug in
        #        Cocoa version
        self.filedialog.selectNameFilter(self.filedialog.selectedNameFilter())

        # @TODO: allow multiple file selection
        if self.filedialog.exec_():
            filename = str(self.filedialog.selectedFiles()[0])
            if filename:
                for backendname in self.backends:
                    backend = self.pluginmanager.plugins[backendname]
                    try:
                        item = backend.openFile(filename)
                        if item:
                            self.datamodel.appendRow(item)
                            self.treeview.expand(item.index())
                            _log.debug('File "%s" opened with backend "%s"',
                                       filename, backendname)
                        else:
                            _log.info('file %s" already open', filename)
                        break
                    except errors.OpenError:
                        #_log.exception('exception caught')
                        _log.debug('Backend "%s" failed to open file "%s"',
                                   backendname, filename)
                else:
                    _log.error('Unable to open file "%s"', filename)

    @QtCore.Slot()
    def closeItem(self):
        # @TODO: extend for multiple datasets
        #~ self.closeGdalDataset.emit()

        item = self.currentItem()
        if item:
            # find the toplevel item
            while item.parent():
                item = item.parent()

            try:
                #~ backend = self.pluginmanager.plugins[item.backend]
                #~ backend.closeFile(item)
                item.close()
            except AttributeError:
                self.datamodel.invisibleRootItem().removeRow(item.row())

        self.statusBar().showMessage('Ready.')

    @QtCore.Slot()
    def closeAll(self):
        root = self.datamodel.invisibleRootItem()
        while root.hasChildren():
            item = root.child(0)
            try:
                #~ backend = self.pluginmanager.plugins[item.backend]
                #~ backend.closeFile(item)
                item.close()
            except AttributeError:
                root.removeRow(item.row())

    # Auxiliary methods ####################################################
    @QtCore.Slot()
    @QtCore.Slot(str)
    def processingStarted(self, msg=None):
        if msg:
            self.statusBar().showMessage(msg)
        self.progressbar.show()
        self.stopbutton.setEnabled(True)
        self.stopbutton.show()

    @QtCore.Slot(int)
    def updateProgressBar(self, fract):
        self.progressbar.show()
        self.progressbar.setValue(int(100. * fract))

    @QtCore.Slot(int)
    def processingDone(self, returncode=0):
        #self.controller.reset() # @TODO: remove
        try:
            if returncode != 0:
                msg = ('An error occurred during the quicklook generation.\n'
                       'Now close the dataset.')
                QtWidgets.QMessageBox.warning(self, '', msg)
                self.closeItem()   # @TODO: check
        finally:
            self.progressbar.hide()
            self.stopbutton.setEnabled(False)
            self.stopbutton.hide()
            self.statusBar().showMessage('Ready.')