Example #1
0
class Eddy(QtWidgets.QApplication):
    """
    This class implements the main QtCore.Qt application.
    """
    RestartCode = 8
    sgnCreateSession = QtCore.pyqtSignal(str)
    sgnSessionCreated = QtCore.pyqtSignal('QMainWindow')
    sgnSessionClosed = QtCore.pyqtSignal('QMainWindow')

    def __init__(self, argv):
        """
        Initialize Eddy.
        :type argv: list
        """
        super().__init__(argv)

        self.openFilePath = None
        self.server = None
        self.sessions = DistinctList()
        self.started = False
        self.welcome = None

        # APPLICATION INFO
        self.setDesktopFileName('{}.{}'.format(ORGANIZATION_REVERSE_DOMAIN, APPNAME))
        self.setOrganizationName(ORGANIZATION)
        self.setOrganizationDomain(ORGANIZATION_DOMAIN)
        self.setApplicationName(APPNAME)
        self.setApplicationDisplayName(APPNAME)
        self.setApplicationVersion(VERSION)

        # PARSE COMMAND LINE ARGUMENTS
        self.options = CommandLineParser()
        self.options.process(argv)

        # CHECK FOR A RUNNING INSTANCE
        self.socket = QtNetwork.QLocalSocket()
        self.socket.connectToServer(APPID)
        self.running = self.socket.waitForConnected()
        if not self.isRunning():
            QtNetwork.QLocalServer.removeServer(APPID)
            self.server = QtNetwork.QLocalServer()
            self.server.listen(APPID)
            self.socket = None
            connect(self.sgnCreateSession, self.doCreateSession)

        connect(self.aboutToQuit, self.onAboutToQuit)

    #############################################
    #   EVENTS
    #################################

    def event(self, event):
        """
        Executed when an event is received.
        :type event: T <= QEvent | QFileOpenEvent
        :rtype: bool
        """
        # HANDLE FILEOPEN EVENT (TRIGGERED BY MACOS WHEN DOUBLE CLICKING A FILE)
        #if event.type() == QtCore.QEvent.FileOpen and not __debug__:
        if event.type() == QtCore.QEvent.FileOpen:
            path = expandPath(event.file())
            #fileName,fileExtension = os.path.splitext(path)
            type = File.forPath(path)
            if type and type is File.Graphol:
                if fexists(path):
                    if self.started:
                        self.sgnCreateSession.emit(path)
                    else:
                        # CACHE PATH UNTIL APPLICATION STARTUP HAS COMPLETED
                        self.openFilePath = path

        return super().event(event)

    #############################################
    #   INTERFACE
    #################################

    def configure(self):
        """
        Perform initial configuration tasks for Eddy to work properly.
        """
        #############################################
        # CONFIGURE FONTS
        #################################

        fontDB = QtGui.QFontDatabase()
        fonts = QtCore.QDirIterator(':/fonts/')
        while fonts.hasNext():
            fontDB.addApplicationFont(fonts.next())

        # FONT SUBSTITUTIONS
        QtGui.QFont.insertSubstitution('Sans Serif', 'Roboto')
        QtGui.QFont.insertSubstitution('Monospace', 'Roboto Mono')

        # APPLICATION DEFAULT FONT
        self.setFont(Font('Roboto', pixelSize=12))

        #############################################
        # CONFIGURE LAYOUT
        #################################

        style = EddyProxyStyle('Fusion')
        self.setStyle(style)
        self.setStyleSheet(style.stylesheet)

        #############################################
        # DRAW THE SPLASH SCREEN
        #################################

        splash = None
        if not self.options.isSet(CommandLineParser.NO_SPLASH):
            splash = Splash(mtime=2)
            splash.show()

        #############################################
        # CONFIGURE RECENT PROJECTS
        #################################

        settings = QtCore.QSettings()

        if not settings.contains('project/recent'):
            # From PyQt5 documentation: if the value of the setting is a container (corresponding
            # to either QVariantList, QVariantMap or QVariantHash) then the type is applied to the
            # contents of the container. So we can't use an empty list as default value because
            # PyQt5 needs to know the type of the contents added to the collection: we avoid
            # this problem by placing the list of example projects as recent project list.
            examples = list(filter(lambda path: fexists(path) and faccess(path), [
                expandPath('@examples/Animals{}'.format(File.Graphol.extension)),
                expandPath('@examples/Diet{}'.format(File.Graphol.extension)),
                expandPath('@examples/Family{}'.format(File.Graphol.extension)),
                expandPath('@examples/LUBM{}'.format(File.Graphol.extension)),
                expandPath('@examples/Pizza{}'.format(File.Graphol.extension)),
            ]))
            settings.setValue('project/recent', examples)
        else:
            # If we have some projects in our recent list, check whether they exists on the
            # filesystem. If they do not exists we remove them from our recent list.
            projects = []
            for path in map(expandPath, settings.value('project/recent', None, str) or []):
                if fexists(path) and path not in projects:
                    projects.append(path)
            settings.setValue('project/recent', projects)
            settings.sync()

        #############################################
        # LOOKUP PLUGINS
        #################################

        PluginManager.scan('@plugins/', '@home/plugins/')

        #############################################
        # CLOSE THE SPLASH SCREEN
        #################################

        if splash and not self.options.isSet(CommandLineParser.NO_SPLASH):
            splash.sleep()
            splash.close()

    def isRunning(self):
        """
        Returns True if there is already another instance of Eddy which is running, False otherwise.
        :rtype: bool
        """
        return self.running

    def start(self):
        """
        Run the application by showing the welcome dialog.
        """
        # CONFIGURE THE WORKSPACE
        #settings = QtCore.QSettings()
        #workspace = expandPath(settings.value('workspace/home', WORKSPACE, str))
        '''
        if not isdir(workspace):
            window = WorkspaceDialog()
            if window.exec_() == WorkspaceDialog.Rejected:
                raise SystemExit
        '''

        # PROCESS COMMAND LINE ARGUMENTS
        args = self.options.positionalArguments()

        if self.openFilePath:
            args.append(self.openFilePath)
            self.openFilePath = None
        # SHOW WELCOME DIALOG
        self.welcome = Welcome(self)
        self.welcome.show()
        # PROCESS ADDITIONAL COMMAND LINE OPTIONS
        if self.options.isSet(CommandLineParser.OPEN):
            value = self.options.value(CommandLineParser.OPEN)
            if value:
                project = os.path.join(workspace, value)
                if project and isdir(os.path.join(workspace, project)):
                    self.sgnCreateSession.emit(project)
                else:
                    LOGGER.warning('Unable to open project: %s', project)
        # POSITIONAL ARGUMENTS
        elif args:
            fname = expandPath(args[0])
            if fexists(fname):
                #project = os.path.dirname(fname)
                project = fname
                self.sgnCreateSession.emit(project)
            else:
                LOGGER.warning('Unable to open file: %s', fname)
        # COMPLETE STARTUP
        self.started = True

    #############################################
    #   SLOTS
    #################################

    @QtCore.pyqtSlot(str)
    def doCreateSession(self, path):
        """
        Create a session using the given project path.
        :type path: str
        """
        for session in self.sessions:
            # Look among the active sessions and see if we already have
            # a session loaded for the given project: if so, focus it.
            if session.project.path == path:
                session.show()
                break
        else:
            # If we do not have a session for the given project we'll create one.
            with BusyProgressDialog('Loading project: {0}'.format(path)):
                try:
                    session = Session(self, path)
                except ProjectStopLoadingError:
                    pass
                except (ProjectNotFoundError, ProjectNotValidError, ProjectVersionError) as e:
                    LOGGER.warning('Failed to create session for project %s: %s', path, e)
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setIconPixmap(QtGui.QIcon(':/icons/48/ic_error_outline_black').pixmap(48))
                    msgbox.setText('Failed to create session for project: <b>{0}</b>!'.format(path))
                    msgbox.setTextFormat(QtCore.Qt.RichText)
                    msgbox.setDetailedText(format_exception(e))
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Close)
                    msgbox.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy'))
                    msgbox.setWindowTitle('Project Error!')
                    msgbox.exec_()
                except Exception as e:
                    raise e
                else:

                    #############################################
                    # UPDATE RECENT PROJECTS
                    #################################

                    settings = QtCore.QSettings()
                    projects = settings.value('project/recent', None, str) or []

                    try:
                        projects.remove(path)
                    except ValueError:
                        pass
                    finally:
                        projects.insert(0, path)
                        projects = projects[:8]
                        settings.setValue('project/recent', projects)
                        settings.sync()

                    #############################################
                    # CLOSE THE WELCOME SCREEN IF NECESSARY
                    #################################

                    try:
                        self.welcome.close()
                    except (AttributeError, RuntimeError):
                        pass

                    #############################################
                    # STARTUP THE SESSION
                    #################################

                    connect(session.sgnQuit, self.doQuit)
                    connect(session.sgnClosed, self.onSessionClosed)
                    self.sessions.append(session)
                    self.sgnSessionCreated.emit(session)
                    session.show()

    @QtCore.pyqtSlot(str, str, str)
    def doCreateSessionFromScratch(self, projName, ontIri, ontPrefix):
        """
        Create a session for a new brand project.
        """
        for session in self.sessions:
            # Look among the active sessions and see if we already have
            # a session loaded for the given project: if so, focus it.
            if not session.project.path and session.project.name == projName and session.project.ontologyIRI == ontIri:
                session.show()
                break
        else:
            # If we do not have a session for the given project we'll create one.
            with BusyProgressDialog('Creating new project with name: {0}'.format(projName)):
                try:
                    session = Session(self, path=None, projName=projName, ontIri=ontIri, ontPrefix=ontPrefix)
                except ProjectStopLoadingError:
                    pass
                except Exception as e:
                    LOGGER.warning('Failed to create session for new project : %s',  e)
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setIconPixmap(QtGui.QIcon(':/icons/48/ic_error_outline_black').pixmap(48))
                    msgbox.setText('Failed to create session for new project')
                    msgbox.setTextFormat(QtCore.Qt.RichText)
                    msgbox.setDetailedText(format_exception(e))
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Close)
                    msgbox.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy'))
                    msgbox.setWindowTitle('Project Error!')
                    msgbox.exec_()
                else:

                    #############################################
                    # CLOSE THE WELCOME SCREEN IF NECESSARY
                    #################################

                    try:
                        self.welcome.close()
                    except (AttributeError, RuntimeError):
                        pass

                    #############################################
                    # STARTUP THE SESSION
                    #################################

                    connect(session.sgnQuit, self.doQuit)
                    connect(session.sgnClosed, self.onSessionClosed)
                    self.sessions.append(session)
                    self.sgnSessionCreated.emit(session)
                    session.show()

    @QtCore.pyqtSlot()
    def doQuit(self):
        """
        Quit Eddy.
        """
        for session in self.sessions:
            if not session.close():
                return
        self.quit()

    @QtCore.pyqtSlot()
    def doRestart(self):
        """
        Restart Eddy.
        """
        for session in self.sessions:
            if not session.close():
                return
        self.exit(Eddy.RestartCode)

    @QtCore.pyqtSlot()
    def doFocusSession(self):
        """
        Make the session specified in the action data the application active window.
        """
        action = self.sender()
        if isinstance(action, QtWidgets.QAction):
            session = action.data()
            if session in self.sessions:
                self.setActiveWindow(session)

    @QtCore.pyqtSlot()
    def doFocusNextSession(self):
        """
        Make the next session the application active window.
        """
        session = self.activeWindow()
        if session and session in self.sessions:
            nextSession = self.sessions[(self.sessions.index(session) + 1) % len(self.sessions)]
            self.setActiveWindow(nextSession)

    @QtCore.pyqtSlot()
    def doFocusPreviousSession(self):
        """
        Make the previous session the application active window.
        """
        session = self.activeWindow()
        if session and session in self.sessions:
            prevSession = self.sessions[(self.sessions.index(session) - 1) % len(self.sessions)]
            self.setActiveWindow(prevSession)

    @QtCore.pyqtSlot()
    def onAboutToQuit(self):
        """
        Executed when the application is about to quit.
        """
        if self.server:
            self.server.close()

    @QtCore.pyqtSlot()
    def onSessionClosed(self):
        """
        Quit Eddy.
        """
        # SAVE SESSION STATE
        session = self.sender()
        if session:
            # noinspection PyUnresolvedReferences
            session.save()
            self.sessions.remove(session)
            self.sgnSessionClosed.emit(session)
        # CLEANUP POSSIBLE LEFTOVERS
        self.sessions = DistinctList(filter(None, self.sessions))
        # SWITCH TO AN ACTIVE WINDOW OR WELCOME PANEL
        if self.sessions:
            session = self.sessions[-1]
            session.show()
        else:
            self.welcome = Welcome(self)
            self.welcome.show()
Example #2
0
class Eddy(QtWidgets.QApplication):
    """
    This class implements the main QtCore.Qt application.
    """
    sgnCreateSession = QtCore.pyqtSignal(str)

    def __init__(self, options, argv):
        """
        Initialize Eddy.
        :type options: Namespace
        :type argv: list
        """
        super().__init__(argv)

        self.server = None
        self.socket = QtNetwork.QLocalSocket()
        self.socket.connectToServer(APPID)
        self.running = self.socket.waitForConnected()
        self.sessions = DistinctList()
        self.welcome = None

        if not self.isRunning() or options.tests:
            self.server = QtNetwork.QLocalServer()
            self.server.listen(APPID)
            self.socket = None
            connect(self.sgnCreateSession, self.doCreateSession)

    #############################################
    #   INTERFACE
    #################################

    def configure(self, options):
        """
        Perform initial configuration tasks for Eddy to work properly.
        :type options: Namespace
        """
        #############################################
        # DRAW THE SPLASH SCREEN
        #################################

        splash = None
        if not options.nosplash:
            splash = Splash(mtime=4)
            splash.show()

        #############################################
        # CONFIGURE RECENT PROJECTS
        #################################

        settings = QtCore.QSettings(ORGANIZATION, APPNAME)
        examples = [
            expandPath('@examples/Animals'),
            expandPath('@examples/Diet'),
            expandPath('@examples/Family'),
            expandPath('@examples/LUBM'),
            expandPath('@examples/Pizza'),
        ]

        if not settings.contains('project/recent'):
            # From PyQt5 documentation: if the value of the setting is a container (corresponding
            # to either QVariantList, QVariantMap or QVariantHash) then the type is applied to the
            # contents of the container. So we can't use an empty list as default value because
            # PyQt5 needs to know the type of the contents added to the collection: we avoid
            # this problem by placing the list of example projects as recent project list.
            settings.setValue('project/recent', examples)
        else:
            # If we have some projects in our recent list, check whether they exists on the
            # filesystem. If they do not exists we remove them from our recent list.
            projects = []
            for path in map(expandPath, settings.value('project/recent')):
                if isdir(path) and path not in projects:
                    projects.append(path)
            settings.setValue('project/recent', projects or examples)
            settings.sync()

        #############################################
        # CONFIGURE FONTS
        #################################

        fontDB = QtGui.QFontDatabase()
        fontDB.addApplicationFont(':/fonts/Roboto-Black')
        fontDB.addApplicationFont(':/fonts/Roboto-BlackItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Bold')
        fontDB.addApplicationFont(':/fonts/Roboto-BoldItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Italic')
        fontDB.addApplicationFont(':/fonts/Roboto-Light')
        fontDB.addApplicationFont(':/fonts/Roboto-LightItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Medium')
        fontDB.addApplicationFont(':/fonts/Roboto-MediumItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Regular')
        fontDB.addApplicationFont(':/fonts/Roboto-Thin')
        fontDB.addApplicationFont(':/fonts/Roboto-ThinItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Bold')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-BoldItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Italic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Light')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-LightItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Regular')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Bold')
        fontDB.addApplicationFont(':/fonts/RobotoMono-BoldItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Italic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Light')
        fontDB.addApplicationFont(':/fonts/RobotoMono-LightItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Medium')
        fontDB.addApplicationFont(':/fonts/RobotoMono-MediumItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Regular')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Thin')
        fontDB.addApplicationFont(':/fonts/RobotoMono-ThinItalic')

        self.setFont(Font('Roboto', 12))

        #############################################
        # CONFIGURE LAYOUT
        #################################

        buffer = ''
        resources = expandPath('@resources/styles/')
        for name in os.listdir(resources):
            path = os.path.join(resources, name)
            if fexists(path) and File.forPath(path) is File.Qss:
                buffer += fread(path)
        self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
        self.setStyle(EddyProxyStyle('Fusion'))
        self.setStyleSheet(buffer)

        #############################################
        # LOOKUP PLUGINS
        #################################

        PluginManager.scan('@plugins/', '@home/plugins/')

        #############################################
        # CLOSE THE SPLASH SCREEN
        #################################

        if splash and not options.nosplash:
            splash.sleep()
            splash.close()

        #############################################
        # CONFIGURE THE WORKSPACE
        #################################

        workspace = expandPath(settings.value('workspace/home', WORKSPACE,
                                              str))
        if not isdir(workspace):
            window = WorkspaceDialog()
            if window.exec_() == WorkspaceDialog.Rejected:
                raise SystemExit

    def isRunning(self):
        """
        Returns True if there is already another instance of Eddy which is running, False otherwise.
        :rtype: bool
        """
        return self.running

    def start(self, options):
        """
        Run the application by showing the welcome dialog.
        :type options: Namespace
        """
        self.welcome = Welcome(self)
        self.welcome.show()
        if options.open and isdir(options.open):
            self.sgnCreateSession.emit(expandPath(options.open))

    #############################################
    #   SLOTS
    #################################

    @QtCore.pyqtSlot(str)
    def doCreateSession(self, path):
        """
        Create a session using the given project path.
        :type path: str
        """
        for session in self.sessions:
            # Look among the active sessions and see if we already have
            # a session loaded for the given project: if so, focus it.
            if session.project.path == path:
                session.show()
                break
        else:
            # If we do not have a session for the given project we'll create one.
            with BusyProgressDialog('Loading project: {0}'.format(
                    os.path.basename(path))):

                try:
                    session = Session(self, path)
                except ProjectStopLoadingError:
                    pass
                except (ProjectNotFoundError, ProjectNotValidError,
                        ProjectVersionError) as e:
                    LOGGER.warning(
                        'Failed to create session for project %s: %s', path, e)
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setIconPixmap(
                        QtGui.QIcon(
                            ':/icons/48/ic_error_outline_black').pixmap(48))
                    msgbox.setText(
                        'Failed to create session for project: <b>{0}</b>!'.
                        format(os.path.basename(path)))
                    msgbox.setTextFormat(QtCore.Qt.RichText)
                    msgbox.setDetailedText(format_exception(e))
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Close)
                    msgbox.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy'))
                    msgbox.setWindowTitle('Project Error!')
                    msgbox.exec_()
                except Exception as e:
                    raise e
                else:

                    #############################################
                    # UPDATE RECENT PROJECTS
                    #################################

                    settings = QtCore.QSettings(ORGANIZATION, APPNAME)
                    projects = settings.value('project/recent', None,
                                              str) or []

                    try:
                        projects.remove(path)
                    except ValueError:
                        pass
                    finally:
                        projects.insert(0, path)
                        projects = projects[:8]
                        settings.setValue('project/recent', projects)
                        settings.sync()

                    #############################################
                    # CLOSE THE WELCOME SCREEN IF NECESSARY
                    #################################

                    try:
                        self.welcome.close()
                    except (AttributeError, RuntimeError):
                        pass

                    #############################################
                    # STARTUP THE SESSION
                    #################################

                    connect(session.sgnQuit, self.doQuit)
                    connect(session.sgnClosed, self.onSessionClosed)
                    self.sessions.append(session)
                    session.show()

    @QtCore.pyqtSlot()
    def doQuit(self):
        """
        Quit Eddy.
        """
        for session in self.sessions:
            session.save()
        self.quit()

    @QtCore.pyqtSlot()
    def onSessionClosed(self):
        """
        Quit Eddy.
        """
        ## SAVE SESSION STATE
        session = self.sender()
        if session:
            session.save()
            self.sessions.remove(session)
        ## CLEANUP POSSIBLE LEFTOVERS
        self.sessions = DistinctList(filter(None, self.sessions))
        ## SWITCH TO AN ACTIVE WINDOW OR WELCOME PANEL
        if self.sessions:
            session = self.sessions[-1]
            session.show()
        else:
            self.welcome = Welcome(self)
            self.welcome.show()
Example #3
0
class Eddy(QtWidgets.QApplication):
    """
    This class implements the main QtCore.Qt application.
    """
    sgnCreateSession = QtCore.pyqtSignal(str)

    def __init__(self, options, argv):
        """
        Initialize Eddy.
        :type options: Namespace
        :type argv: list
        """
        super().__init__(argv)

        self.server = None
        self.socket = QtNetwork.QLocalSocket()
        self.socket.connectToServer(APPID)
        self.running = self.socket.waitForConnected()
        self.sessions = DistinctList()
        self.welcome = None

        if not self.isRunning() or options.tests:
            self.server = QtNetwork.QLocalServer()
            self.server.listen(APPID)
            self.socket = None
            connect(self.sgnCreateSession, self.doCreateSession)

    #############################################
    #   INTERFACE
    #################################

    def configure(self, options):
        """
        Perform initial configuration tasks for Eddy to work properly.
        :type options: Namespace
        """
        #############################################
        # DRAW THE SPLASH SCREEN
        #################################

        splash = None
        if not options.nosplash:
            splash = Splash(mtime=4)
            splash.show()

        #############################################
        # CONFIGURE RECENT PROJECTS
        #################################

        settings = QtCore.QSettings(ORGANIZATION, APPNAME)
        examples = [
            expandPath('@examples/Animals'),
            expandPath('@examples/Diet'),
            expandPath('@examples/Family'),
            expandPath('@examples/LUBM'),
            expandPath('@examples/Pizza'),
        ]

        if not settings.contains('project/recent'):
            # From PyQt5 documentation: if the value of the setting is a container (corresponding
            # to either QVariantList, QVariantMap or QVariantHash) then the type is applied to the
            # contents of the container. So we can't use an empty list as default value because
            # PyQt5 needs to know the type of the contents added to the collection: we avoid
            # this problem by placing the list of example projects as recent project list.
            settings.setValue('project/recent', examples)
        else:
            # If we have some projects in our recent list, check whether they exists on the
            # filesystem. If they do not exists we remove them from our recent list.
            projects = []
            for path in map(expandPath, settings.value('project/recent')):
                if isdir(path) and path not in projects:
                    projects.append(path)
            settings.setValue('project/recent', projects or examples)
            settings.sync()

        #############################################
        # CONFIGURE FONTS
        #################################

        fontDB = QtGui.QFontDatabase()
        fontDB.addApplicationFont(':/fonts/Roboto-Black')
        fontDB.addApplicationFont(':/fonts/Roboto-BlackItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Bold')
        fontDB.addApplicationFont(':/fonts/Roboto-BoldItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Italic')
        fontDB.addApplicationFont(':/fonts/Roboto-Light')
        fontDB.addApplicationFont(':/fonts/Roboto-LightItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Medium')
        fontDB.addApplicationFont(':/fonts/Roboto-MediumItalic')
        fontDB.addApplicationFont(':/fonts/Roboto-Regular')
        fontDB.addApplicationFont(':/fonts/Roboto-Thin')
        fontDB.addApplicationFont(':/fonts/Roboto-ThinItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Bold')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-BoldItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Italic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Light')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-LightItalic')
        fontDB.addApplicationFont(':/fonts/RobotoCondensed-Regular')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Bold')
        fontDB.addApplicationFont(':/fonts/RobotoMono-BoldItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Italic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Light')
        fontDB.addApplicationFont(':/fonts/RobotoMono-LightItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Medium')
        fontDB.addApplicationFont(':/fonts/RobotoMono-MediumItalic')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Regular')
        fontDB.addApplicationFont(':/fonts/RobotoMono-Thin')
        fontDB.addApplicationFont(':/fonts/RobotoMono-ThinItalic')

        self.setFont(Font('Roboto', 12))

        #############################################
        # CONFIGURE LAYOUT
        #################################

        buffer = ''
        resources = expandPath('@resources/styles/')
        for name in os.listdir(resources):
            path = os.path.join(resources, name)
            if fexists(path) and File.forPath(path) is File.Qss:
                buffer += fread(path)
        self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
        self.setStyle(EddyProxyStyle('Fusion'))
        self.setStyleSheet(buffer)

        #############################################
        # CLOSE THE SPLASH SCREEN
        #################################

        if splash and not options.nosplash:
            splash.sleep()
            splash.close()

        #############################################
        # CONFIGURE THE WORKSPACE
        #################################

        workspace = expandPath(settings.value('workspace/home', WORKSPACE, str))
        if not isdir(workspace):
            window = WorkspaceDialog()
            if window.exec_() == WorkspaceDialog.Rejected:
                raise SystemExit

    def isRunning(self):
        """
        Returns True if there is already another instance of Eddy which is running, False otherwise.
        :rtype: bool
        """
        return self.running

    def start(self, options):
        """
        Run the application by showing the welcome dialog.
        :type options: Namespace
        """
        self.welcome = Welcome(self)
        self.welcome.show()
        if options.open and isdir(options.open):
            self.sgnCreateSession.emit(expandPath(options.open))

    #############################################
    #   SLOTS
    #################################

    @QtCore.pyqtSlot(str)
    def doCreateSession(self, path):
        """
        Create a session using the given project path.
        :type path: str
        """
        for session in self.sessions:
            # Look among the active sessions and see if we already have
            # a session loaded for the given project: if so, focus it.
            if session.project.path == path:
                session.show()
                break
        else:
            # If we do not have a session for the given project we'll create one.
            with BusyProgressDialog('Loading project: {0}'.format(os.path.basename(path))):
    
                try:
                    session = Session(self, path)
                except ProjectStopLoadingError:
                    pass
                except (ProjectNotFoundError, ProjectNotValidError, ProjectVersionError) as e:
                    LOGGER.warning('Failed to create session for project %s: %s', path, e)
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setIconPixmap(QtGui.QIcon(':/icons/48/ic_error_outline_black').pixmap(48))
                    msgbox.setText('Failed to create session for project: <b>{0}</b>!'.format(os.path.basename(path)))
                    msgbox.setTextFormat(QtCore.Qt.RichText)
                    msgbox.setDetailedText(format_exception(e))
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Close)
                    msgbox.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy'))
                    msgbox.setWindowTitle('Project Error!')
                    msgbox.exec_()
                except Exception as e:
                    raise e
                else:
                    
                    #############################################
                    # UPDATE RECENT PROJECTS
                    #################################
    
                    settings = QtCore.QSettings(ORGANIZATION, APPNAME)
                    projects = settings.value('project/recent', None, str) or []
    
                    try:
                        projects.remove(path)
                    except ValueError:
                        pass
                    finally:
                        projects.insert(0, path)
                        projects = projects[:8]
                        settings.setValue('project/recent', projects)
                        settings.sync()
    
                    #############################################
                    # CLOSE THE WELCOME SCREEN IF NECESSARY
                    #################################
    
                    try:
                        self.welcome.close()
                    except (AttributeError, RuntimeError):
                        pass
    
                    #############################################
                    # STARTUP THE SESSION
                    #################################
                    
                    connect(session.sgnQuit, self.doQuit)
                    connect(session.sgnClosed, self.onSessionClosed)
                    self.sessions.append(session)
                    session.show()
    
    @QtCore.pyqtSlot()
    def doQuit(self):
        """
        Quit Eddy.
        """
        for session in self.sessions:
            session.save()
        self.quit()

    @QtCore.pyqtSlot()
    def onSessionClosed(self):
        """
        Quit Eddy.
        """
        ## SAVE SESSION STATE
        session = self.sender()
        if session:
            session.save()
            self.sessions.remove(session)
        ## CLEANUP POSSIBLE LEFTOVERS
        self.sessions = DistinctList(filter(None, self.sessions))
        ## SWITCH TO AN ACTIVE WINDOW OR WELCOME PANEL
        if self.sessions:
            session = self.sessions[-1]
            session.show()
        else:
            self.welcome = Welcome(self)
            self.welcome.show()