예제 #1
0
def test_open_file():
    manager = RecentFilesManager('pyQode', 'test')
    manager.clear()
    assert manager.last_file() is None
    manager.open_file(__file__)
    assert len(manager.get_recent_files()) == 1
    assert manager.last_file() == __file__
예제 #2
0
 def __init__(self, files, verbose):
     logger.setup(verbose=verbose)
     _logger().info('QIdle v%s', self.version_str)
     self.windows = []
     self.qapp = QtWidgets.QApplication(sys.argv)
     icons.init()
     self._init_libraries()
     self.recent_files_manager = RecentFilesManager(*Preferences.names)
     self._current = None
     self.qapp.focusChanged.connect(self.on_focus_changed)
     self._setup_custom_mimetypes()
예제 #3
0
def test_menu_recent_files():
    manager = RecentFilesManager('pyQode', 'test')
    manager.clear()
    manager.open_file(__file__)
    manager.open_file(pyqode.core.__file__)
    mnu = MenuRecentFiles(None, recent_files_manager=manager, title='Recents',
                          icon_provider=None, clear_icon=None)
    mnu.show()
예제 #4
0
def test_normalized_path():
    manager = RecentFilesManager('pyQode', 'test')
    manager.clear()
    manager.open_file(r'c:\Test/test.cbl')
    manager.open_file(r'c:\Test\test.cbl')
    assert len(manager.get_value('list', [])) == 1
예제 #5
0
def test_max_files():
    manager = RecentFilesManager('pyQode', 'test')
    manager.clear()
    manager.max_recent_files = 1
    manager.open_file(__file__)
    assert manager.last_file() == __file__
    manager.open_file(pyqode.core.__file__)
    assert manager.last_file() == pyqode.core.__file__
    assert len(manager.get_recent_files()) == 1
예제 #6
0
def test_remove_file():
    manager = RecentFilesManager('pyQode', 'test')
    manager.max_recent_files = 10
    manager.clear()
    manager.open_file(__file__)
    test_path = pyqode.core.__file__
    manager.open_file(test_path)
    assert len(manager.get_recent_files()) == 2
    assert manager.last_file() == test_path
    manager.remove(test_path)
    assert len(manager.get_recent_files()) == 1
    assert manager.last_file() == __file__
예제 #7
0
    def __init__(self, qapp, splash, args):
        def show_msg_on_splash(msg):
            if msg:
                _logger().info(msg)
                if splash is not None:
                    splash.showMessage(
                        msg, QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter,
                        QtCore.Qt.white)
                    qapp.processEvents()
        self.show_windows = True  # set to false when running the test suite
        self._closed = False
        self._active_window = None
        self._args = args
        self._qapp = qapp
        self._splash = splash
        super().__init__()
        _shared.APP = self
        self.editor_windows = []
        show_msg_on_splash(_('Setting up except hook...'))
        self._setup_except_hook()
        self.flg_force_indexing = False

        show_msg_on_splash(_('Loading translations...'))
        _logger().info('available locales: %r',
                       api.gettext.get_available_locales())

        show_msg_on_splash(_('Loading environemnt...'))
        environ.apply()

        show_msg_on_splash(_('Loading shortcuts...'))
        shortcuts.load()

        show_msg_on_splash(_('Creating index database...'))
        if not api.index.create_database():
            _logger().warn('indexing is disabled because database creation failed1')
            # failed to create db (missing extension)
            settings.set_indexing_enabled(False)

        show_msg_on_splash(_('Loading font: Hack-Bold.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Bold.ttf')
        show_msg_on_splash(_('Loading font: Hack-BoldItalic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-BoldItalic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Italic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Italic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Regular.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Regular.ttf')

        show_msg_on_splash(_('Setting up mimetypes...'))
        mime_types.load()

        show_msg_on_splash(_('Setting up user interface...'))
        self._qapp.setWindowIcon(QtGui.QIcon.fromTheme(
            'hackedit', QtGui.QIcon(':/icons/hackedit_128.png')))
        self._qapp.lastWindowClosed.connect(self.quit)
        self._qapp.focusWindowChanged.connect(self._on_current_window_changed)
        self._setup_tray_icon()
        self.apply_preferences()

        show_msg_on_splash(_('Loading plugins...'))
        self.plugin_manager = PluginManager()

        show_msg_on_splash(_('Setting up templates...'))
        self.setup_templates()

        show_msg_on_splash(_('Loading recent files...'))
        self._recents = RecentFilesManager(
            qapp.organizationName(), qapp.applicationName())

        show_msg_on_splash(_('Setting up welcome window...'))
        self._welcome_window = WelcomeWindow(self)
        self.last_window = self._welcome_window

        show_msg_on_splash('')
예제 #8
0
class Application(QtCore.QObject):
    """
    Runs the QApplication and manages the list of editor windows.
    """

    #: signal emitted when an unhandled exception occurred (for internal use)
    _report_exception_requested = QtCore.pyqtSignal(object, str)

    # -------------------------------------------------------------------------
    # Public API
    # -------------------------------------------------------------------------
    @property
    def window_count(self):
        return len(self.editor_windows)

    def __init__(self, qapp, splash, args):
        def show_msg_on_splash(msg):
            if msg:
                _logger().info(msg)
                if splash is not None:
                    splash.showMessage(
                        msg, QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter,
                        QtCore.Qt.white)
                    qapp.processEvents()
        self.show_windows = True  # set to false when running the test suite
        self._closed = False
        self._active_window = None
        self._args = args
        self._qapp = qapp
        self._splash = splash
        super().__init__()
        _shared.APP = self
        self.editor_windows = []
        show_msg_on_splash(_('Setting up except hook...'))
        self._setup_except_hook()
        self.flg_force_indexing = False

        show_msg_on_splash(_('Loading translations...'))
        _logger().info('available locales: %r',
                       api.gettext.get_available_locales())

        show_msg_on_splash(_('Loading environemnt...'))
        environ.apply()

        show_msg_on_splash(_('Loading shortcuts...'))
        shortcuts.load()

        show_msg_on_splash(_('Creating index database...'))
        if not api.index.create_database():
            _logger().warn('indexing is disabled because database creation failed1')
            # failed to create db (missing extension)
            settings.set_indexing_enabled(False)

        show_msg_on_splash(_('Loading font: Hack-Bold.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Bold.ttf')
        show_msg_on_splash(_('Loading font: Hack-BoldItalic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-BoldItalic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Italic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Italic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Regular.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Regular.ttf')

        show_msg_on_splash(_('Setting up mimetypes...'))
        mime_types.load()

        show_msg_on_splash(_('Setting up user interface...'))
        self._qapp.setWindowIcon(QtGui.QIcon.fromTheme(
            'hackedit', QtGui.QIcon(':/icons/hackedit_128.png')))
        self._qapp.lastWindowClosed.connect(self.quit)
        self._qapp.focusWindowChanged.connect(self._on_current_window_changed)
        self._setup_tray_icon()
        self.apply_preferences()

        show_msg_on_splash(_('Loading plugins...'))
        self.plugin_manager = PluginManager()

        show_msg_on_splash(_('Setting up templates...'))
        self.setup_templates()

        show_msg_on_splash(_('Loading recent files...'))
        self._recents = RecentFilesManager(
            qapp.organizationName(), qapp.applicationName())

        show_msg_on_splash(_('Setting up welcome window...'))
        self._welcome_window = WelcomeWindow(self)
        self.last_window = self._welcome_window

        show_msg_on_splash('')

    def setup_templates(self):
        for template_provider in self.plugin_manager.template_providers:
            try:
                url = template_provider.get_url()
                label = template_provider.get_label()
            except TypeError:
                _logger().exception('failed to get url and label from template'
                                    ' provider plugin...')
            else:
                if url and label:
                    exists = False
                    sources = templates.get_sources()
                    if not sources:
                        exists = False
                    else:
                        for src in sources:
                            if src['label'] == label:
                                exists = True
                                break
                    if not exists:
                        print('add source', label, url)
                        templates.add_source(label, url)

    def check_for_update(self):
        common.check_for_update(
            self._qapp.activeWindow(), show_up_to_date_msg=False)

    def restart(self):
        """
        Restarts the IDE.
        """
        QtCore.QProcess.startDetached(sys.executable, sys.argv)
        self.quit()

    def open_path(self, path, sender=None, force=False):
        """
        Open a new window with `path`.

        :param path: folder or file to open
        """
        path = os.path.normpath(path.strip())
        if path.endswith(('/', '\\')):
            path = path[:-1]
        ret = self._open(path, sender, force)
        shortcuts.save()
        return ret

    def get_recent_files_manager(self):
        """
        Gets the recent files manager
        """
        return self._recents

    def get_open_windows(self):
        """
        Gets the list of open windows.
        """
        ret_val = []
        for w in self.editor_windows:
            ret_val.append(w)
        return ret_val

    def quit(self):
        """
        Quits the application.

        :param force: True to force exit, bypassing the exit question.
        """
        if self._closed:
            return
        self.tray_icon.hide()
        self._closed = True
        self._qapp.exit(0)
        self._qapp = None

    @property
    def active_window(self):
        """
        Gets/Sets the active editor window
        """
        return self._active_window

    @active_window.setter
    def active_window(self, window):
        """
        Sets `window` as the active window.

        :param window: Window to activate.
        """
        self._set_active_window(window)

    @staticmethod
    def get_workspaces():
        """
        Returns the list of available workspaces names.

        :return: list of str
        """
        WorkspaceManager().get_names()

    def run(self):
        """
        Runs the application'main loop.
        """
        if self._splash is not None:
            self._splash.close()
            self._splash = None
            delattr(self, '_splash')

        _logger().debug('running')
        nb_window = 0
        for path in self._args.paths:
            if self.open_path(path):
                nb_window += 1
        if settings.restore_last_window():
            try:
                path = self.get_recent_files_manager().get_recent_files()[0]
            except IndexError:
                _logger().warn('failed to reopen last window, recent files is '
                               'empty')
            else:
                if self.open_path(path):
                    nb_window += 1
        if nb_window == 0:
            self._welcome_window.show()
        if self._args.autoquit:
            QtCore.QTimer.singleShot(5000, self.quit)
        if settings.automatically_check_for_updates():
            QtCore.QTimer.singleShot(2000, self.check_for_update)
        self._qapp.exec_()

    def apply_preferences(self):
        """
        Apply preferences on all open windows
        """
        if settings.dark_theme():
            self._qapp.setStyleSheet(load_stylesheet_pyqt5())
        else:
            self._qapp.setStyleSheet('')
        if self._args.dev:
            self._qapp.setStyleSheet(
                self._qapp.styleSheet() +
                '\nQToolBar{background-color: #80AA80;color: white;}')
        self.tray_icon.setVisible(settings.show_tray_icon())
        mime_types.load()
        icons.init()
        FileSystemContextMenu.set_file_explorer_command(
            settings.file_manager_cmd())
        for w in self.editor_windows:
            w.apply_preferences()
        self.flg_force_indexing = False

    # -------------------------------------------------------------------------
    # Private API (+ overridden methods)
    # -------------------------------------------------------------------------
    def _setup_except_hook(self):
        qcrash.get_system_information = versions.get_system_infos
        qcrash.get_application_log = logger.get_application_log
        qcrash.install_backend(
            qcrash.backends.GithubBackend(QCRASH_GH_OWNER, QCRASH_GH_REPO),
            qcrash.backends.EmailBackend(QCRASH_EMAIL, 'HackEdit'))
        qcrash.set_qsettings(QtCore.QSettings())

        qcrash.install_except_hook(except_hook=self._report_exception)

    def _setup_tray_icon(self):
        self.tray_icon = QtWidgets.QSystemTrayIcon(self._qapp)
        self.tray_icon.setIcon(self._qapp.windowIcon())
        self.tray_icon.messageClicked.connect(self._restore_last_msg_window)
        self.tray_icon.activated.connect(self._restore_last_active_window)
        tray_icon_menu = QtWidgets.QMenu(None)
        self.tray_icon_menu_windows = tray_icon_menu.addMenu(
            _('Restore window'))
        self.tray_icon_menu_windows.setEnabled(False)
        self.tray_icon_menu_windows.setIcon(QtGui.QIcon.fromTheme(
            'view-restore'))
        tray_icon_menu.addSeparator()
        if not system.PLASMA_DESKTOP:
            # plasma desktop already adds a "quit" action automatically
            action = tray_icon_menu.addAction(_('Quit'))
            action.setIcon(QtGui.QIcon.fromTheme('application-exit'))
            action.triggered.connect(self.quit)
        self.tray_icon.setContextMenu(tray_icon_menu)

    def _on_current_window_changed(self, _):
        if self._qapp is None:
            # last window closed (qapp is none when all windows have been
            # closed)
            self.last_window = None
        else:
            w = self._qapp.activeWindow()
            if isinstance(w, MainWindow):
                self.last_window = w

    def _restore_last_msg_window(self):
        try:
            api.window.restore(self.tray_icon.last_window)
        except AttributeError:
            self._restore_last_active_window()

    def _restore_last_active_window(self):
        api.window.restore(self.last_window)

    def _setup_workspace_plugins(self, win, workspace):
        _logger().debug('setting up workspace plugins: %r -> %r',
                        workspace, win)
        win.workspace = workspace
        for plugin_name in workspace['plugins']:
            _logger().debug(
                    'adding plugin to window: %r -> %r', plugin_name, win)
            try:
                plugin_class = self.plugin_manager.workspace_plugins[
                    plugin_name]
            except KeyError:
                # plugin not found, why?
                try:
                    tb, e = self.plugin_manager.failed_to_load[plugin_name]
                except KeyError:
                    # plugin not installed
                    title = _('Failed to find plugin %r') % plugin_name
                    desc = _('Plugin not installed?')
                    event = api.events.Event(
                        title, desc, level=api.events.WARNING)
                else:
                    # plugin found but failed to load
                    title = _('Failed to load plugin %r') % plugin_name
                    desc = _('An error has occured during the loading of the '
                             'plugin...\n\nError=%r') % tb.splitlines()[-1]
                    event = api.events.PluginLoadErrorEvent(
                        title, desc, e, tb=tb)
                    event.level = api.events.WARNING
                win.notifications.add(event, force_show=True)
            else:
                try:
                    plugin = plugin_class(win)
                    plugin.activate()
                except Exception as e:
                    # plugin found but failed to load
                    title = _('Failed to activate plugin %r') % plugin_name
                    tb = traceback.format_exc()
                    desc = _('An error has occured during the activation of '
                             'the plugin...\n\nError=%s') % tb
                    event = api.events.PluginLoadErrorEvent(title, desc, e)
                    event.level = api.events.WARNING
                    win.notifications.add(event, force_show=True)
                else:
                    _logger().debug(
                        'plugin added to window: %r -> %r', plugin, win)
                    win.plugins.append(plugin)

    def _update_windows(self):
        for w in self.editor_windows:
            w.update_title()
        enable = len(self.editor_windows) > 0
        self.tray_icon_menu_windows.setEnabled(enable)
        self.tray_icon_menu_windows.clear()
        for w in self.editor_windows:
            w.update_mnu_view()
            w.update_mnu_recents()
            title = ' + '.join([os.path.split(p)[1] for p in w.projects])
            a = self.tray_icon_menu_windows.addAction(title)
            a.triggered.connect(self._restore_window_from_tray)

    def _restore_window_from_tray(self):
        action = self.sender()
        for w in self.editor_windows:
            title = ' + '.join([os.path.split(p)[1] for p in w.projects])
            if title == action.text():
                api.window.restore(w)
                break

    @staticmethod
    def _has_multiple_projects(path):
        data = load_user_config(path)
        try:
            return len(data['linked_paths'])
        except KeyError:
            return False

    def _open(self, path, sender, force):
        _logger().info('opening path: %s', path)
        if os.path.isfile(path):
            # open a new window on parent directory and open file in a new tab
            tab_to_open = path
            # todo find project root dir, fallback to dirname if not root
            # project dir could be found
            path = os.path.dirname(path)
            _logger().info('file project root dir: %s', path)
        else:
            tab_to_open = None

        # check if the path is not already open in an existing window
        window = None
        if not force:
            for w in self.editor_windows:
                if path in w.projects:
                    # already open
                    window = w
                    self._set_active_window(w)
                    if tab_to_open:
                        w.open_file(tab_to_open)
                    break

        if window is None:
            if sender is None or self._has_multiple_projects(path):
                # from homepage or when project has multiple projects
                open_mode = settings.OpenMode.NEW_WINDOW
            else:
                open_mode = self._ask_open_mode(path)
                if open_mode is None:
                    # user canceled dialog
                    return False

            # open folder in new/current window
            if open_mode == settings.OpenMode.NEW_WINDOW:
                workspace = load_workspace(path)
                if workspace is None:
                    # ask the user to choose a workspace
                    workspace = self._ask_for_workspace()
                    if workspace is None:
                        # dialog canceled by user, cancel the whole operation
                        self._qapp.restoreOverrideCursor()
                        return False
                self._welcome_window.setEnabled(False)
                self._qapp.setOverrideCursor(QtCore.Qt.WaitCursor)
                self._qapp.processEvents()
                # Open in new window
                window = self._add_new_window(path, workspace)
                # remember workspace for next open
                window.workspace = workspace
                # save workspace
                save_workspace(path, workspace['name'])
            else:
                self._welcome_window.setEnabled(False)
                self._qapp.setOverrideCursor(QtCore.Qt.WaitCursor)
                self._qapp.processEvents()
                window = sender
                _logger().debug('opening project in existing window: %r',
                                window)
                window.open_folder(path)

        _logger().debug('project opened: %r' % window)

        # update recent files
        if tab_to_open:
            window.open_file(tab_to_open)
        else:
            self._recents.open_file(window.projects[0])

        # show window
        self._show_window(window)

        # update the windows menu of all opened editor windows
        self._update_windows()

        self._qapp.restoreOverrideCursor()

        # hide welcome window
        self._welcome_window.close()

        return True

    def _show_window(self, window):
        if self.show_windows:
            window.raise_()
            window.show()
        if window.current_tab:
            window.current_tab.setFocus()
        self._qapp.setActiveWindow(window)

    def _add_new_window(self, path, workspace):
        _logger().debug('creating new window')
        window = MainWindow(self, path=path, workspace=workspace)
        window.flg_setup = True
        self.editor_windows.append(window)
        _logger().debug('setting up workspaces plugins')
        self._setup_workspace_plugins(window, workspace)
        _logger().debug('restoring state')
        if not window.restore_state(path) and self.show_windows:
            # show maximised the first time a window is created,
            # afterwards, restore_state will take care of restoring
            # the correct window size/show mode.
            window.showMaximized()
        _logger().debug('applying user preferences')
        window.apply_preferences()
        window.closed.connect(self._on_window_closed)
        window.current_project_changed.connect(self._update_windows)
        window.current_tab_changed.connect(self._update_windows)
        window.setup_status_bar()
        window.flg_setup = False
        window.setup_menu_toolbar()
        return window

    def _ask_open_mode(self, path):
        return DlgOpen.get_open_mode(self._qapp.activeWindow(), path)

    def _ask_for_workspace(self):
        workspaces = WorkspaceManager()
        names = workspaces.get_names()
        lnames = len(names)
        if lnames == 0 or 'HACKEDIT_CORE_TEST_SUITE' in os.environ:
            from hackedit.plugins.workspaces import GenericWorkspace
            workspace = GenericWorkspace().get_data()
        else:
            if lnames == 1:
                name = names[0]
            else:
                name = DlgSelectWorkspace.get_workspace(
                    self._welcome_window, self)
                if name is None:
                    return None
                # reload workspaces, user might have edited the workspaces
                workspaces = WorkspaceManager()
            workspace = workspaces.workspace_by_name(name)
        return workspace

    def _on_window_closed(self, window):
        self.editor_windows.remove(window)
        _logger().debug('window closed: %r' % window)
        self._update_windows()
        if self.editor_windows:
            self.last_window = self.editor_windows[0]

    def _set_active_window(self, window):
        _logger().debug('active window set to %r' % window)
        self._qapp.setActiveWindow(window)
        self._active_window = weakref.proxy(window)
        for w in self.editor_windows:
            if w != window:
                w.update_mnu_view()

    def _except_hook(self, exc_type, exc_val, tb):
        from cement.core.exc import CaughtSignal
        import signal
        if isinstance(exc_val, CaughtSignal) and exc_val.signum in [
                signal.SIGKILL, signal.SIGTERM]:
            os._exit(1)

        tb = '\n'.join([''.join(traceback.format_tb(tb)),
                        '{0}: {1}'.format(exc_type.__name__, exc_val)])
        # exception might come from another thread, use a signal
        # so that we can be sure we will show the bug report dialog from
        # the main gui thread.
        self._report_exception_requested.emit(exc_val, tb)

    def _report_exception(self, exc, tb):
        try:
            title = '[Unhandled exception]  %s: %s' % (
                exc.__class__.__name__, str(exc))
            try:
                w = self.last_window
                if w.notifications is None:
                    raise AttributeError
            except AttributeError:
                msg_box = QtWidgets.QMessageBox()
                msg_box.setWindowTitle(_('Unhandled exception'))
                msg_box.setText(_('An unhandled exception has occured...'))
                msg_box.setInformativeText(
                    _('Would you like to report the bug to the developer?'))
                msg_box.setIcon(msg_box.Critical)
                msg_box.setDetailedText(tb)
                msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok |
                                           QtWidgets.QMessageBox.Cancel)
                msg_box.button(msg_box.Ok).setText(_('Report'))
                msg_box.button(msg_box.Cancel).setText(_('Close'))
                if msg_box.exec_() == msg_box.Ok:
                    common.report_bug(
                        None, title=title, traceback=tb)
            else:
                action = QtWidgets.QAction(None)
                action.setText(_('Restart HackEdit'))
                action.triggered.connect(self.restart)
                ev = api.events.ExceptionEvent(
                    title, _('An unhandled exception has occured: %r\n\n'
                             'Please report!') % exc,
                    exc, tb=tb, custom_actions=[action])
                w.notifications.add(ev, False, True)
        except Exception:
            _logger().exception('exception in excepthook')
예제 #9
0
    def __init__(self, qapp, splash, args):
        def show_msg_on_splash(msg):
            if msg:
                _logger().info(msg)
                if splash is not None:
                    splash.showMessage(
                        msg, QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter,
                        QtCore.Qt.white)
                    qapp.processEvents()
        self.show_windows = True  # set to false when running the test suite
        self._closed = False
        self._active_window = None
        self._args = args
        self._qapp = qapp
        self._splash = splash
        super().__init__()
        _shared.APP = self
        self.editor_windows = []

        show_msg_on_splash(_('Setting up except hook...'))
        self._setup_except_hook()
        self.flg_force_indexing = False

        show_msg_on_splash(_('Loading translations...'))
        _logger().info('available locales: %r',
                       api.gettext.get_available_locales())

        show_msg_on_splash(_('Loading environemnt...'))
        environ.apply()

        show_msg_on_splash(_('Loading shortcuts...'))
        shortcuts.load()

        show_msg_on_splash(_('Creating index database...'))
        if not api.index.create_database():
            _logger().warn('indexing is disabled because database creation failed1')
            # failed to create db (missing extension)
            settings.set_indexing_enabled(False)

        show_msg_on_splash(_('Loading font: Hack-Bold.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Bold.ttf')
        show_msg_on_splash(_('Loading font: Hack-BoldItalic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-BoldItalic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Italic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Italic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Regular.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Regular.ttf')

        show_msg_on_splash(_('Setting up mimetypes...'))
        mime_types.load()

        show_msg_on_splash(_('Setting up user interface...'))
        self._qapp.setWindowIcon(QtGui.QIcon.fromTheme(
            'hackedit', QtGui.QIcon(':/icons/hackedit_128.png')))
        self._qapp.lastWindowClosed.connect(self.quit)
        self._qapp.focusWindowChanged.connect(self._on_current_window_changed)
        self._setup_tray_icon()
        self.apply_preferences()

        show_msg_on_splash(_('Loading plugins...'))
        self.plugin_manager = PluginManager()

        show_msg_on_splash(_('Setting up templates...'))
        self.setup_templates()

        show_msg_on_splash(_('Loading recent files...'))
        self._recents = RecentFilesManager(
            qapp.organizationName(), qapp.applicationName())

        show_msg_on_splash(_('Setting up welcome window...'))
        self._welcome_window = WelcomeWindow(self)
        self.last_window = self._welcome_window

        show_msg_on_splash('')
예제 #10
0
class Application(QtCore.QObject):
    """
    Runs the QApplication and manages the list of editor windows.
    """

    #: signal emitted when an unhandled exception occurred (for internal use)
    _report_exception_requested = QtCore.pyqtSignal(object, str)

    # -------------------------------------------------------------------------
    # Public API
    # -------------------------------------------------------------------------
    @property
    def window_count(self):
        return len(self.editor_windows)

    def __init__(self, qapp, splash, args):
        def show_msg_on_splash(msg):
            if msg:
                _logger().info(msg)
                if splash is not None:
                    splash.showMessage(
                        msg, QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter,
                        QtCore.Qt.white)
                    qapp.processEvents()
        self.show_windows = True  # set to false when running the test suite
        self._closed = False
        self._active_window = None
        self._args = args
        self._qapp = qapp
        self._splash = splash
        super().__init__()
        _shared.APP = self
        self.editor_windows = []

        show_msg_on_splash(_('Setting up except hook...'))
        self._setup_except_hook()
        self.flg_force_indexing = False

        show_msg_on_splash(_('Loading translations...'))
        _logger().info('available locales: %r',
                       api.gettext.get_available_locales())

        show_msg_on_splash(_('Loading environemnt...'))
        environ.apply()

        show_msg_on_splash(_('Loading shortcuts...'))
        shortcuts.load()

        show_msg_on_splash(_('Creating index database...'))
        if not api.index.create_database():
            _logger().warn('indexing is disabled because database creation failed1')
            # failed to create db (missing extension)
            settings.set_indexing_enabled(False)

        show_msg_on_splash(_('Loading font: Hack-Bold.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Bold.ttf')
        show_msg_on_splash(_('Loading font: Hack-BoldItalic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-BoldItalic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Italic.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Italic.ttf')
        show_msg_on_splash(_('Loading font: Hack-Regular.ttf'))
        QtGui.QFontDatabase.addApplicationFont(
            ':/fonts/Hack-Regular.ttf')

        show_msg_on_splash(_('Setting up mimetypes...'))
        mime_types.load()

        show_msg_on_splash(_('Setting up user interface...'))
        self._qapp.setWindowIcon(QtGui.QIcon.fromTheme(
            'hackedit', QtGui.QIcon(':/icons/hackedit_128.png')))
        self._qapp.lastWindowClosed.connect(self.quit)
        self._qapp.focusWindowChanged.connect(self._on_current_window_changed)
        self._setup_tray_icon()
        self.apply_preferences()

        show_msg_on_splash(_('Loading plugins...'))
        self.plugin_manager = PluginManager()

        show_msg_on_splash(_('Setting up templates...'))
        self.setup_templates()

        show_msg_on_splash(_('Loading recent files...'))
        self._recents = RecentFilesManager(
            qapp.organizationName(), qapp.applicationName())

        show_msg_on_splash(_('Setting up welcome window...'))
        self._welcome_window = WelcomeWindow(self)
        self.last_window = self._welcome_window

        show_msg_on_splash('')

    def setup_templates(self):
        for template_provider in self.plugin_manager.template_providers:
            try:
                url = template_provider.get_url()
                label = template_provider.get_label()
            except TypeError:
                _logger().exception('failed to get url and label from template'
                                    ' provider plugin...')
            else:
                if url and label:
                    exists = False
                    sources = templates.get_sources()
                    if not sources:
                        exists = False
                    else:
                        for src in sources:
                            if src['label'] == label:
                                exists = True
                                break
                    if not exists:
                        print('add source', label, url)
                        templates.add_source(label, url)

    def check_for_update(self):
        common.check_for_update(
            self._qapp.activeWindow(), show_up_to_date_msg=False)

    def restart(self):
        """
        Restarts the IDE.
        """
        QtCore.QProcess.startDetached(sys.executable, sys.argv)
        self.quit()

    def open_path(self, path, sender=None, force=False):
        """
        Open a new window with `path`.

        :param path: folder or file to open
        """
        path = os.path.normpath(path.strip())
        if path.endswith(('/', '\\')):
            path = path[:-1]
        ret = self._open(path, sender, force)
        shortcuts.save()
        return ret

    def get_recent_files_manager(self):
        """
        Gets the recent files manager
        """
        return self._recents

    def get_open_windows(self):
        """
        Gets the list of open windows.
        """
        ret_val = []
        for w in self.editor_windows:
            ret_val.append(w)
        return ret_val

    def quit(self):
        """
        Quits the application.

        :param force: True to force exit, bypassing the exit question.
        """
        if self._closed:
            return
        self.tray_icon.hide()
        self._closed = True
        self._qapp.exit(0)
        self._qapp = None

    @property
    def active_window(self):
        """
        Gets/Sets the active editor window
        """
        return self._active_window

    @active_window.setter
    def active_window(self, window):
        """
        Sets `window` as the active window.

        :param window: Window to activate.
        """
        self._set_active_window(window)

    @staticmethod
    def get_workspaces():
        """
        Returns the list of available workspaces names.

        :return: list of str
        """
        WorkspaceManager().get_names()

    def run(self):
        """
        Runs the application'main loop.
        """
        if self._splash is not None:
            self._splash.close()
            self._splash = None
            delattr(self, '_splash')

        nb_window = 0
        for path in self._args.paths:
            if self.open_path(path):
                nb_window += 1
        if settings.restore_last_window():
            try:
                path = self.get_recent_files_manager().get_recent_files()[0]
            except IndexError:
                _logger().warn('failed to reopen last window, recent files is '
                               'empty')
            else:
                if self.open_path(path):
                    nb_window += 1
        if nb_window == 0:
            self._welcome_window.show()
        if self._args.autoquit:
            QtCore.QTimer.singleShot(5000, self.quit)
        if settings.automatically_check_for_updates():
            QtCore.QTimer.singleShot(2000, self.check_for_update)
        self._qapp.exec_()

    def apply_preferences(self):
        """
        Apply preferences on all open windows
        """
        if settings.dark_theme():
            self._qapp.setStyleSheet(load_stylesheet_pyqt5())
        else:
            self._qapp.setStyleSheet('')
        if os.environ.get('HACKEDIT_DEV_MODE') is not None:
            self._qapp.setStyleSheet(
                self._qapp.styleSheet() +
                '\nQToolBar{background-color: #AAAA80;color: white;}')
        self.tray_icon.setVisible(settings.show_tray_icon())
        mime_types.load()
        icons.init()
        FileSystemContextMenu.set_file_explorer_command(
            settings.file_manager_cmd())
        for w in self.editor_windows:
            w.apply_preferences()
        self.flg_force_indexing = False

    # -------------------------------------------------------------------------
    # Private API (+ overridden methods)
    # -------------------------------------------------------------------------
    def _setup_except_hook(self):
        qcrash.get_system_information = versions.get_system_infos
        qcrash.get_application_log = logger.get_application_log
        qcrash.install_backend(
            qcrash.backends.GithubBackend(QCRASH_GH_OWNER, QCRASH_GH_REPO),
            qcrash.backends.EmailBackend(QCRASH_EMAIL, 'HackEdit'))
        qcrash.set_qsettings(QtCore.QSettings())

        qcrash.install_except_hook(except_hook=self._report_exception)

    def _setup_tray_icon(self):
        self.tray_icon = QtWidgets.QSystemTrayIcon(self._qapp)
        self.tray_icon.setIcon(self._qapp.windowIcon())
        self.tray_icon.messageClicked.connect(self._restore_last_msg_window)
        self.tray_icon.activated.connect(self._restore_last_active_window)
        tray_icon_menu = QtWidgets.QMenu(None)
        self.tray_icon_menu_windows = tray_icon_menu.addMenu(
            _('Restore window'))
        self.tray_icon_menu_windows.setEnabled(False)
        self.tray_icon_menu_windows.setIcon(QtGui.QIcon.fromTheme(
            'view-restore'))
        tray_icon_menu.addSeparator()
        if not system.PLASMA_DESKTOP:
            # plasma desktop already adds a "quit" action automatically
            action = tray_icon_menu.addAction(_('Quit'))
            action.setIcon(QtGui.QIcon.fromTheme('application-exit'))
            action.triggered.connect(self.quit)
        self.tray_icon.setContextMenu(tray_icon_menu)

    def _on_current_window_changed(self, _):
        if self._qapp is None:
            # last window closed (qapp is none when all windows have been
            # closed)
            self.last_window = None
        else:
            w = self._qapp.activeWindow()
            if isinstance(w, MainWindow):
                self.last_window = w

    def _restore_last_msg_window(self):
        try:
            api.window.restore(self.tray_icon.last_window)
        except AttributeError:
            self._restore_last_active_window()

    def _restore_last_active_window(self):
        api.window.restore(self.last_window)

    def _setup_workspace_plugins(self, win, workspace):
        _logger().debug('setting up workspace plugins: %r -> %r',
                        workspace, win)
        win.workspace = workspace
        for plugin_name in workspace['plugins']:
            _logger().debug(
                    'adding plugin to window: %r -> %r', plugin_name, win)
            try:
                plugin_class = self.plugin_manager.workspace_plugins[
                    plugin_name]
            except KeyError:
                # plugin not found, why?
                try:
                    tb, e = self.plugin_manager.failed_to_load[plugin_name]
                except KeyError:
                    # plugin not installed
                    title = _('Failed to find plugin %r') % plugin_name
                    desc = _('Plugin not installed?')
                    event = api.events.Event(
                        title, desc, level=api.events.WARNING)
                else:
                    # plugin found but failed to load
                    title = _('Failed to load plugin %r') % plugin_name
                    desc = _('An error has occured during the loading of the '
                             'plugin...\n\nError=%r') % tb.splitlines()[-1]
                    event = api.events.PluginLoadErrorEvent(
                        title, desc, e, tb=tb)
                    event.level = api.events.WARNING
                win.notifications.add(event, force_show=True)
            else:
                try:
                    plugin = plugin_class(win)
                    plugin.activate()
                except Exception as e:
                    # plugin found but failed to load
                    title = _('Failed to activate plugin %r') % plugin_name
                    tb = traceback.format_exc()
                    desc = _('An error has occured during the activation of '
                             'the plugin...\n\nError=%s') % tb
                    event = api.events.PluginLoadErrorEvent(title, desc, e)
                    event.level = api.events.WARNING
                    win.notifications.add(event, force_show=True)
                else:
                    _logger().debug(
                        'plugin added to window: %r -> %r', plugin, win)
                    win.plugins.append(plugin)

    def _update_windows(self):
        for w in self.editor_windows:
            w.update_title()
        enable = len(self.editor_windows) > 0
        self.tray_icon_menu_windows.setEnabled(enable)
        self.tray_icon_menu_windows.clear()
        for w in self.editor_windows:
            w.update_mnu_view()
            w.update_mnu_recents()
            title = ' + '.join([os.path.split(p)[1] for p in w.projects])
            a = self.tray_icon_menu_windows.addAction(title)
            a.triggered.connect(self._restore_window_from_tray)

    def _restore_window_from_tray(self):
        action = self.sender()
        for w in self.editor_windows:
            title = ' + '.join([os.path.split(p)[1] for p in w.projects])
            if title == action.text():
                api.window.restore(w)
                break

    @staticmethod
    def _has_multiple_projects(path):
        data = load_user_config(path)
        try:
            return len(data['linked_paths'])
        except KeyError:
            return False

    def _open(self, path, sender, force):
        _logger().info('opening path: %s', path)
        if os.path.isfile(path):
            # open a new window on parent directory and open file in a new tab
            tab_to_open = path
            # todo find project root dir, fallback to dirname if not root
            # project dir could be found
            path = os.path.dirname(path)
            _logger().info('file project root dir: %s', path)
        else:
            tab_to_open = None

        # check if the path is not already open in an existing window
        window = None
        if not force:
            for w in self.editor_windows:
                if path in w.projects:
                    # already open
                    window = w
                    self._set_active_window(w)
                    if tab_to_open:
                        w.open_file(tab_to_open)
                    break

        if window is None:
            if sender is None or self._has_multiple_projects(path):
                # from homepage or when project has multiple projects
                open_mode = settings.OpenMode.NEW_WINDOW
            else:
                open_mode = self._ask_open_mode(path)
                if open_mode is None:
                    # user canceled dialog
                    return False

            # open folder in new/current window
            if open_mode == settings.OpenMode.NEW_WINDOW:
                workspace = load_workspace(path)
                if workspace is None:
                    # ask the user to choose a workspace
                    workspace = self._ask_for_workspace()
                    if workspace is None:
                        # dialog canceled by user, cancel the whole operation
                        self._qapp.restoreOverrideCursor()
                        return False
                self._welcome_window.setEnabled(False)
                self._qapp.setOverrideCursor(QtCore.Qt.WaitCursor)
                self._qapp.processEvents()
                # Open in new window
                window = self._add_new_window(path, workspace)
                # remember workspace for next open
                window.workspace = workspace
                # save workspace
                save_workspace(path, workspace['name'])
            else:
                self._welcome_window.setEnabled(False)
                self._qapp.setOverrideCursor(QtCore.Qt.WaitCursor)
                self._qapp.processEvents()
                window = sender
                _logger().debug('opening project in existing window: %r',
                                window)
                window.open_folder(path)

        _logger().debug('project opened: %r' % window)

        # update recent files
        if tab_to_open:
            window.open_file(tab_to_open)
        else:
            self._recents.open_file(window.projects[0])

        # show window
        self._show_window(window)

        # update the windows menu of all opened editor windows
        self._update_windows()

        self._qapp.restoreOverrideCursor()

        # hide welcome window
        self._welcome_window.close()

        return True

    def _show_window(self, window):
        if self.show_windows:
            window.raise_()
            window.show()
        if window.current_tab:
            window.current_tab.setFocus()
        self._qapp.setActiveWindow(window)

    def _add_new_window(self, path, workspace):
        _logger().debug('creating new window')
        window = MainWindow(self, path=path, workspace=workspace)
        window.flg_setup = True
        self.editor_windows.append(window)
        _logger().debug('setting up workspaces plugins')
        self._setup_workspace_plugins(window, workspace)
        _logger().debug('restoring state')
        if not window.restore_state(path) and self.show_windows:
            # show maximised the first time a window is created,
            # afterwards, restore_state will take care of restoring
            # the correct window size/show mode.
            window.showMaximized()
        _logger().debug('applying user preferences')
        window.apply_preferences()
        window.closed.connect(self._on_window_closed)
        window.current_project_changed.connect(self._update_windows)
        window.current_tab_changed.connect(self._update_windows)
        window.setup_status_bar()
        window.flg_setup = False
        window.setup_menu_toolbar()
        return window

    def _ask_open_mode(self, path):
        return DlgOpen.get_open_mode(self._qapp.activeWindow(), path)

    def _ask_for_workspace(self):
        workspaces = WorkspaceManager()
        names = workspaces.get_names()
        lnames = len(names)
        if lnames == 0 or 'HACKEDIT_CORE_TEST_SUITE' in os.environ:
            from hackedit.plugins.workspaces import GenericWorkspace
            workspace = GenericWorkspace().get_data()
        else:
            if lnames == 1:
                name = names[0]
            else:
                name = DlgSelectWorkspace.get_workspace(
                    self._welcome_window, self)
                if name is None:
                    return None
                # reload workspaces, user might have edited the workspaces
                workspaces = WorkspaceManager()
            workspace = workspaces.workspace_by_name(name)
        return workspace

    def _on_window_closed(self, window):
        self.editor_windows.remove(window)
        _logger().debug('window closed: %r' % window)
        self._update_windows()
        if self.editor_windows:
            self.last_window = self.editor_windows[0]

    def _set_active_window(self, window):
        _logger().debug('active window set to %r' % window)
        self._qapp.setActiveWindow(window)
        self._active_window = weakref.proxy(window)
        for w in self.editor_windows:
            if w != window:
                w.update_mnu_view()

    def _except_hook(self, exc_type, exc_val, tb):
        from cement.core.exc import CaughtSignal
        import signal
        if isinstance(exc_val, CaughtSignal) and exc_val.signum in [
                signal.SIGKILL, signal.SIGTERM]:
            os._exit(1)

        tb = '\n'.join([''.join(traceback.format_tb(tb)),
                        '{0}: {1}'.format(exc_type.__name__, exc_val)])
        # exception might come from another thread, use a signal
        # so that we can be sure we will show the bug report dialog from
        # the main gui thread.
        self._report_exception_requested.emit(exc_val, tb)

    def _report_exception(self, exc, tb):
        try:
            title = '[Unhandled exception]  %s: %s' % (
                exc.__class__.__name__, str(exc))
            try:
                w = self.last_window
                if w.notifications is None:
                    raise AttributeError
            except AttributeError:
                msg_box = QtWidgets.QMessageBox()
                msg_box.setWindowTitle(_('Unhandled exception'))
                msg_box.setText(_('An unhandled exception has occured...'))
                msg_box.setInformativeText(
                    _('Would you like to report the bug to the developer?'))
                msg_box.setIcon(msg_box.Critical)
                msg_box.setDetailedText(tb)
                msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok |
                                           QtWidgets.QMessageBox.Cancel)
                msg_box.button(msg_box.Ok).setText(_('Report'))
                msg_box.button(msg_box.Cancel).setText(_('Close'))
                if msg_box.exec_() == msg_box.Ok:
                    common.report_bug(
                        None, title=title, traceback=tb)
            else:
                action = QtWidgets.QAction(None)
                action.setText(_('Restart HackEdit'))
                action.triggered.connect(self.restart)
                ev = api.events.ExceptionEvent(
                    title, _('An unhandled exception has occured: %r\n\n'
                             'Please report!') % exc,
                    exc, tb=tb, custom_actions=[action])
                w.notifications.add(ev, False, True)
        except Exception:
            _logger().exception('exception in excepthook')
예제 #11
0
class Application:
    """
    Defines the QIdle applications. This is where we manage the collection of
    open windows.

    """
    @property
    def version_str(self):
        return __version__

    def __init__(self, files, verbose):
        logger.setup(verbose=verbose)
        _logger().info('QIdle v%s', self.version_str)
        self.windows = []
        self.qapp = QtWidgets.QApplication(sys.argv)
        icons.init()
        self._init_libraries()
        self.recent_files_manager = RecentFilesManager(*Preferences.names)
        self._current = None
        self.qapp.focusChanged.connect(self.on_focus_changed)
        self._setup_custom_mimetypes()

    def _setup_custom_mimetypes(self):
        mimetypes.add_type('text/xml', 'ui')

    def on_focus_changed(self, prev, new):
        if new:
            parent = new
            try:
                while (not isinstance(parent, QtWidgets.QMainWindow) and
                        parent.parent() is not None):
                    parent = parent.parent()
            except TypeError:
                # TypeError: 'NoneType' object is not callable
                # happen with the IPythonConsole
                pass
            if self._current != parent and parent is not None:
                self._current = parent
                if self._current is not None:
                    _logger().info('current window changed: %r',
                                   self._current)
                else:
                    _logger().info('current window changed: None')

    def _init_libraries(self):
        if (not '.dev' in self.version_str and
                os.path.exists(get_library_zip_path())):
            _logger().info('libraries.zip is up to date')
            return
        else:
            # dependencies frozen into a zip file on startup:
            import jedi
            import pep8
            import pyqode
            import pyqode.core
            import pyqode.python
            import pyqode.qt
            import qidle
            import frosted
            import pies
            _logger().info('updating libraries.zip')
            embed_package_into_zip(
                [jedi, pep8, pyqode, pyqode.core, pyqode.python, pyqode.qt, qidle,
                 frosted, pies])

    def update_windows_menu(self):
        for w in self.windows:
            w.update_windows_menu(self.windows)

    def activate_window(self, window):
        self.qapp.setActiveWindow(window)
        if isinstance(window, ProjectWindow):
            window.showMaximized()
        else:
            window.show()
        window.raise_()
        window.setFocus()
        self._current = window
        _logger().info('showing window: %s' % window)

    def create_script_window(self, path=None):
        """
        Creates a new script window.

        :param path: Optional file to open in the script window. None to
                     create a new file in memory.

        :return: ScriptWindow
        """
        # first look if the requested path is not already open
        if path:
            for w in self.windows:
                if w.path == path:
                    self.activate_window(w)
                    return w
        active_window = self.qapp.activeWindow()
        if active_window:
            active_window.save_state()

        window = ScriptWindow(self)
        window.closed.connect(self._on_window_closed)
        self.windows.append(window)
        if path and os.path.exists(path):
            window.open(path)
        else:
            window.new()
        self.update_windows_menu()
        self.activate_window(window)
        window.configure_shortcuts()
        return window

    def create_project_window(self, path):
        """
        Creates a new project window.

        :param path: Optional file to open in the script window. None to
                     create a new project.

        :return: ScriptWindow
        """
        # first look if the requested path is not already open
        if path:
            for w in self.windows:
                if w.path == path:
                    self.activate_window(w)
                    return w
        active_window = self.qapp.activeWindow()
        if active_window:
            active_window.save_state()

        window = ProjectWindow(self)
        window.closed.connect(self._on_window_closed)
        self.windows.append(window)
        window.open(path)
        self.update_windows_menu()
        self.activate_window(window)
        window.configure_shortcuts()
        return window

    def _on_window_closed(self, window):
        _logger().info('window closed: %s' % window.path)
        self.remember_path(window.path)
        self.windows.remove(window)
        self.update_windows_menu()

    def remember_path(self, path):
        """
        Adds the path to the list of recent paths.
        """
        _logger().debug('remember path: %s', path)
        self.recent_files_manager.open_file(path)
        for w in self.windows:
            w.update_recents_menu()

    def _open_in_new_window(self, path, script):
        if script:
            self.create_script_window(path)
        else:
            self.create_project_window(path)

    def _open_in_current_window(self, path, script):
        win = self._current
        assert win is not None
        # makes sure window types are corresponding (if we want to open a
        # project from a script window, a new proj window must be created)
        if ((script and isinstance(win, ScriptWindow) or
                (not script and isinstance(win, ProjectWindow)))):
            win.open(path)
        else:
            self._open_in_new_window(path, script)

    def open_recent(self, path):
        _logger().info('open recent file: %s', path)
        script = os.path.isfile(path)
        if script:
            action = Preferences().general.open_scr_action
        else:
            action = Preferences().general.open_project_action
        if action == Preferences().general.OpenActions.NEW:
            self._open_in_new_window(path, script)
        elif action == Preferences().general.OpenActions.CURRENT:
            self._open_in_current_window(path, script)
        else:
            # ask
            val = DlgAskOpenScript.ask(self.qapp.activeWindow())
            if val == Preferences().general.OpenActions.NEW:
                self._open_in_new_window(path, script)
            elif val == Preferences().general.OpenActions.CURRENT:
                self._open_in_current_window(path, script)

    def apply_preferences(self):
        for w in self.windows:
            w.apply_preferences()

    def init(self):
        if Preferences().general.reopen_last_window:
            try:
                path = self.recent_files_manager.last_file()
            except IndexError:
                self.create_script_window()
            else:
                _logger().info('reopen last window: %s' % path)
                if os.path.isfile(path):
                    self.create_script_window(path)
                else:
                    self.create_project_window(path)
        else:
            # create untitled script window
            self.create_script_window()

    def run(self):
        """
        Runs the application.
        """
        self.init()
        self.qapp.exec_()