Beispiel #1
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.model = Project()
        self.view = QTreeView(self)
        self.view.setModel(self.model)
        self.view.setDragEnabled(True)
        self.view.setDragDropMode(QAbstractItemView.InternalMove)
        self.setCentralWidget(self.view)

    def mousePressEvent(self, e):
        print(e.x(), e.y())
        return QMainWindow.mousePressEvent(self, e)
Beispiel #2
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.model = Project()
        self.view = QTreeView(self)
        self.view.setModel(self.model)
        self.view.setDragEnabled(True)
        self.view.setDragDropMode(QAbstractItemView.InternalMove)
        self.setCentralWidget(self.view)

    def mousePressEvent(self, e):
        print(e.x(), e.y())
        return QMainWindow.mousePressEvent(self, e)
Beispiel #3
0
class MVCConfigurationGUI(MVCConfigurationBase):
    """
    GUI implementation of MVCConfigurationBase
    """
    def __init__(self, configuration):
        super().__init__(configuration)
        assertMainThread()
        srv = Services.getService("MainWindow")
        srv.aboutToClose.connect(self._aboutToClose)
        confMenu = srv.menuBar().addMenu("&Configuration")
        toolBar = srv.getToolBar()

        configuration.configNameChanged.connect(self._configNameChanged)
        configuration.dirtyChanged.connect(self._dirtyChanged)

        style = QApplication.style()
        self.actLoad = QAction(
            QIcon.fromTheme("document-open",
                            style.standardIcon(QStyle.SP_DialogOpenButton)),
            "Open config", self)
        self.actLoad.triggered.connect(self._execLoad)
        self.actSave = QAction(
            QIcon.fromTheme("document-save",
                            style.standardIcon(QStyle.SP_DialogSaveButton)),
            "Save config", self)
        self.actSave.triggered.connect(self._execSaveConfig)
        self.actSaveWithGuiState = QAction(
            QIcon.fromTheme("document-save",
                            style.standardIcon(QStyle.SP_DialogSaveButton)),
            "Save config sync gui state", self)
        self.actSaveWithGuiState.triggered.connect(
            self._execSaveConfigWithGuiState)
        self.actNew = QAction(
            QIcon.fromTheme("document-new",
                            style.standardIcon(QStyle.SP_FileIcon)),
            "New config", self)
        self.actNew.triggered.connect(self._execNew)

        self.actActivate = QAction(
            QIcon.fromTheme("arrow-up", style.standardIcon(QStyle.SP_ArrowUp)),
            "Initialize", self)
        self.actActivate.triggered.connect(self.activate)
        self.actDeactivate = QAction(
            QIcon.fromTheme("arrow-down",
                            style.standardIcon(QStyle.SP_ArrowDown)),
            "Deinitialize", self)
        self.actDeactivate.triggered.connect(self.deactivate)

        confMenu.addAction(self.actLoad)
        confMenu.addAction(self.actSave)
        confMenu.addAction(self.actSaveWithGuiState)
        confMenu.addAction(self.actNew)
        confMenu.addAction(self.actActivate)
        confMenu.addAction(self.actDeactivate)
        toolBar.addAction(self.actLoad)
        toolBar.addAction(self.actSave)
        toolBar.addAction(self.actNew)
        toolBar.addAction(self.actActivate)
        toolBar.addAction(self.actDeactivate)

        self.recentConfigs = [QAction() for i in range(10)]
        self.recentConfigs[0].setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R))
        confMenu.addSeparator()
        recentMenu = confMenu.addMenu("Recent")
        for a in self.recentConfigs:
            a.setVisible(False)
            a.triggered.connect(self._openRecent)
            recentMenu.addAction(a)

        self.mainWidget = srv.newDockWidget("Configuration", None,
                                            Qt.LeftDockWidgetArea)
        self.treeView = QTreeView(self.mainWidget)
        self.treeView.setHeaderHidden(False)
        self.treeView.setSelectionMode(QAbstractItemView.NoSelection)
        self.treeView.setEditTriggers(self.treeView.EditKeyPressed
                                      | self.treeView.AnyKeyPressed)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setExpandsOnDoubleClick(False)
        self.treeView.setDragEnabled(True)
        self.treeView.setDropIndicatorShown(True)
        self.treeView.setDragDropMode(QAbstractItemView.DragOnly)
        self.mainWidget.setWidget(self.treeView)
        self.treeView.setModel(self.model)
        self.treeView.header().setStretchLastSection(True)
        self.treeView.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self.treeView.doubleClicked.connect(self._onItemDoubleClicked)
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(
            self._execTreeViewContextMenu)
        # expand applications by default
        self.treeView.setExpanded(self.model.index(1, 0), True)
        self.delegate = PropertyDelegate(self.model, ITEM_ROLE,
                                         ConfigurationModel.PropertyContent,
                                         self)
        self.treeView.setItemDelegate(self.delegate)

        self.restoreState()
        srv.aboutToClose.connect(self.saveState)
        # a list of dock widgets displaying subgraphs
        self._graphViews = []
        # make sure that the graph views are closed when the config is closed
        self._configuration.subConfigRemoved.connect(self._subConfigRemoved)
        self._waitForActivated = None
        self._waitForOpenState = None

    def _execLoad(self):
        assertMainThread()
        if self._checkDirty():
            return
        fn, _ = QFileDialog.getOpenFileName(self.mainWidget,
                                            "Load configuration",
                                            self.cfgfile,
                                            filter="*.json")
        if fn is not None and fn != "":
            logger.debug("Loading config file %s", fn)
            try:
                self.loadConfig(fn)
            except Exception as e:  # pylint: disable=broad-except
                logger.exception("Error while loading configuration %s: %s",
                                 fn, str(e))
                QMessageBox.warning(self.mainWidget,
                                    "Error while loading configuration",
                                    str(e))

    def _openRecent(self):
        """
        Called when the user clicks on a recent config.

        :return:
        """
        if self._checkDirty():
            return
        action = self.sender()
        fn = action.data()
        try:
            self.loadConfig(fn)
        except Exception as e:  # pylint: disable=broad-except
            # catching general exception is wanted here.
            logger.exception("Error while loading configuration %s: %s", fn,
                             str(e))
            QMessageBox.warning(self.mainWidget,
                                "Error while loading configuration", str(e))

    def _checkDirty(self):
        if self._configuration.dirty():
            ans = QMessageBox.question(
                None,
                "Save changes?",
                "There are unsaved changes. Do you want to save them?",
                buttons=QMessageBox.Save | QMessageBox.Discard
                | QMessageBox.Cancel,
                defaultButton=QMessageBox.Save)
            if ans == QMessageBox.Save:
                self.saveConfig()
                return False
            if ans == QMessageBox.Cancel:
                return True
        return False

    def _aboutToClose(self, mainWindow):
        if self._checkDirty():
            mainWindow.ignoreCloseEvent()

    def _execNew(self):
        assertMainThread()
        if self._checkDirty():
            return
        fn, _ = QFileDialog.getSaveFileName(self.mainWidget,
                                            "New configuration",
                                            ".",
                                            filter="*.json")
        if fn is not None and fn != "":
            logger.debug("Creating config file %s", fn)
            self.newConfig(fn)

    def _execSaveConfig(self):
        if self.configuration().filename() is None:
            self._execSaveConfigAs()
        else:
            self.saveConfig()

    def _execSaveConfigWithGuiState(self):
        if self.configuration().filename() is None:
            self._execSaveConfigAs()
        else:
            self.saveConfigWithGuiState()

    def _execSaveConfigAs(self):
        """
        Opens a file dialog to get the save file name and calls saveConfig.

        :return:
        """
        assertMainThread()
        fn, _ = QFileDialog.getSaveFileName(self.mainWidget,
                                            "Save configuration as", ".",
                                            "*.json")
        if fn is not None and fn != "":
            self.saveConfigAs(fn)

    def _addGraphView(self, subConfig):
        g = subConfig.getGraph()
        # remove already deleted graph views from internal list
        valid_graphViews = []
        for gv in self._graphViews:
            if shiboken2.isValid(gv):  # pylint: disable=no-member
                valid_graphViews.append(gv)
        self._graphViews = valid_graphViews
        # check if graph view is already there
        for gv in self._graphViews:
            if gv.widget().scene().graph == g:
                logger.info("Graph view already exists.")
                return
        # create new graph view
        srv = Services.getService("MainWindow")
        graphDw = srv.newDockWidget("Graph (%s)" % (subConfig.getName()),
                                    parent=None,
                                    defaultArea=Qt.RightDockWidgetArea,
                                    allowedArea=Qt.RightDockWidgetArea
                                    | Qt.BottomDockWidgetArea)
        graphDw.setAttribute(Qt.WA_DeleteOnClose, True)
        assert isinstance(graphDw, QDockWidget)
        graphView = GraphEditorView(graphDw)
        graphView.setScene(GraphScene(subConfig.getGraph(), graphDw))
        graphDw.setWidget(graphView)
        self._graphViews.append(graphDw)
        graphDw.visibleChanged.connect(self._removeGraphViewFromList)

    def _subConfigRemoved(self, subConfigName, configType):
        g = self._configuration.subConfigByNameAndTye(subConfigName,
                                                      configType).getGraph()
        for gv in self._graphViews:
            if gv.widget().scene().graph == g:
                logger.debug("deleting graph view for subconfig %s",
                             subConfigName)
                gv.deleteLater()

    def _removeGraphViewFromList(self, visible):
        if visible:
            return
        gv = self.sender()
        try:
            self._graphViews.remove(gv)
            logger.debug("removed graphview from list")
        except ValueError:
            logger.debug("graphview not in list, ignored")

    def _execTreeViewContextMenu(self, point):
        index = self.treeView.indexAt(point)
        item = self.model.data(index, ITEM_ROLE)
        if isinstance(item, ConfigurationModel.SubConfigContent):
            m = QMenu()
            a1 = QAction("Edit graph")
            m.addAction(a1)
            a1.triggered.connect(lambda: self._addGraphView(item.subConfig))
            if self.model.isApplication(index):
                a2 = QAction("Select Application")
                a2.triggered.connect(lambda: self.changeActiveApp(
                    self.model.data(index, Qt.DisplayRole)))
                a3 = QAction("Init Application")
                a3.triggered.connect(lambda: self._changeActiveAppAndInit(
                    self.model.data(index, Qt.DisplayRole)))
                m.addActions([a2, a3])
                pbsrv = Services.getService("PlaybackControl")
                m2 = m.addMenu("Init and load sequence")
                m3 = m.addMenu("Init, load and play")
                s1 = []
                s2 = []
                for a in pbsrv.recentSeqs:
                    assert isinstance(a, QAction)
                    if a.isVisible():
                        # pylint: disable=cell-var-from-loop
                        # the below statements are tested and work
                        aseq = QAction(a.text())
                        aseq.triggered.connect(lambda arg1=a.data(
                        ), seq=a.data(): self._changeActiveAppInitAndLoad(
                            self.model.data(index, Qt.DisplayRole), seq, False)
                                               )
                        s1.append(aseq)
                        aseq = QAction(a.text())
                        aseq.triggered.connect(lambda arg1=a.data(
                        ), seq=a.data(): self._changeActiveAppInitAndLoad(
                            self.model.data(index, Qt.DisplayRole), seq, True))
                        # pylint: enable=cell-var-from-loop
                        s2.append(aseq)
                m2.addActions(s1)
                m3.addActions(s2)
            m.exec_(self.treeView.mapToGlobal(point))
            return
        if self.model.isSubConfigParent(
                index) == Configuration.CONFIG_TYPE_APPLICATION:
            m = QMenu()
            a = QAction("Add application")
            m.addAction(a)
            a = m.exec_(self.treeView.mapToGlobal(point))
            if a is not None:
                self._configuration.addNewApplication()
            return
        if self.model.isSubConfigParent(
                index) == Configuration.CONFIG_TYPE_COMPOSITE:
            m = QMenu()
            a = QAction("Add composite filter")
            m.addAction(a)
            a = m.exec_(self.treeView.mapToGlobal(point))
            if a is not None:
                self._configuration.addNewCompositeFilter()
            return

    def _configNameChanged(self, cfgfile):
        logger.debug("_configNameChanged: %s", cfgfile)
        assertMainThread()
        self.cfgfile = cfgfile
        self._dirtyChanged(self._configuration.dirty())
        foundIdx = None
        for i, a in enumerate(self.recentConfigs):
            if a.data() == cfgfile:
                foundIdx = i
        if foundIdx is None:
            foundIdx = len(self.recentConfigs) - 1
        for i in range(foundIdx, 0, -1):
            self.recentConfigs[i].setText(self.recentConfigs[i - 1].text())
            self.recentConfigs[i].setData(self.recentConfigs[i - 1].data())
            self.recentConfigs[i].setVisible(
                self.recentConfigs[i - 1].data() is not None)
        self.recentConfigs[0].setText(cfgfile)
        self.recentConfigs[0].setData(cfgfile)
        self.recentConfigs[0].setVisible(True)

    def _dirtyChanged(self, dirty):
        srv = Services.getService("MainWindow")
        if self.cfgfile is None:
            title = "nexxT: <unnamed>"
        else:
            title = "nexxT: " + self.cfgfile
        if dirty:
            title += " *"
        srv.setWindowTitle(title)

    def _onItemDoubleClicked(self, index):
        assertMainThread()
        if self.model.isApplication(index):
            app = self.model.data(index, Qt.DisplayRole)
            self.changeActiveApp(app)
        else:
            self.treeView.edit(index)

    def _changeActiveAppAndInit(self, app):
        """
        Call this slot to activate and init an application

        :param app: can be either an Application instance or the name of an application
        :return:
        """
        assertMainThread()
        if isinstance(app, str):
            app = self.configuration().applicationByName(app)
        currentApp = Application.activeApplication
        if currentApp is not None:
            currentApp = currentApp.getApplication()
        self._waitForActivated = app
        self.changeActiveApp(app.getName())

    def _changeActiveAppInitAndLoad(self, app, sequence, startPlay):
        self._waitForOpenState = (app, sequence, startPlay)
        self._changeActiveAppAndInit(app)

    def appActivated(self, name, app):  # pylint: disable=unused-argument
        """
        Called when the application is activated.

        :param name: the application name
        :param app: An ActiveApplication instance.
        :return:
        """
        assertMainThread()
        if app is not None:
            self.activeAppStateChange(app.getState())
            app.stateChanged.connect(self.activeAppStateChange)
            if self._waitForActivated == app.getApplication():
                MethodInvoker(self.activate, Qt.QueuedConnection)
        else:
            self.actActivate.setEnabled(False)
            self.actDeactivate.setEnabled(False)
        self._waitForActivated = None

    def _disconnectSingleShotPlay(self):
        assertMainThread()
        pbsrv = Services.getService("PlaybackControl")
        try:
            pbsrv.playbackPaused.disconnect(self._singleShotPlay)
        except RuntimeError:
            # we are already disconnected.
            pass

    def _singleShotPlay(self):
        assertMainThread()
        pbsrv = Services.getService("PlaybackControl")
        MethodInvoker(pbsrv.startPlayback, Qt.QueuedConnection)
        self._disconnectSingleShotPlay()

    def activeAppStateChange(self, newState):
        """
        Called when the active application changes its state.

        :param newState: the new application's state (see FilterState)
        :return:
        """
        assertMainThread()
        if newState == FilterState.CONSTRUCTED:
            self.actActivate.setEnabled(True)
        else:
            self.actActivate.setEnabled(False)
        if newState == FilterState.ACTIVE:
            if self._waitForOpenState is not None:
                app, pbfile, startPlay = self._waitForOpenState
                self._waitForOpenState = None
                if app == Application.activeApplication.getApplication(
                ).getName():
                    pbsrv = Services.getService("PlaybackControl")
                    if startPlay:
                        pbsrv.playbackPaused.connect(self._singleShotPlay)
                        QTimer.singleShot(2000, self._disconnectSingleShotPlay)
                    MethodInvoker(pbsrv.browser.setActive, Qt.QueuedConnection,
                                  pbfile)
            self.actDeactivate.setEnabled(True)
            self.actSaveWithGuiState.setEnabled(False)
        else:
            self.actDeactivate.setEnabled(False)
            self.actSaveWithGuiState.setEnabled(True)

    def restoreState(self):
        """
        Restore the state of the configuration gui service (namely the recently
        open config files). This is saved in QSettings because it is used
        across config files.

        :return:
        """
        logger.debug("restoring config state ...")
        settings = QSettings()
        v = settings.value("ConfigurationRecentFiles")
        if v is not None and isinstance(v, QByteArray):
            ds = QDataStream(v)
            recentFiles = ds.readQStringList()
            idx = 0
            for f in recentFiles:
                if f != "" and f is not None:
                    self.recentConfigs[idx].setData(f)
                    self.recentConfigs[idx].setText(f)
                    self.recentConfigs[idx].setVisible(True)
                    idx += 1
                    if idx >= len(self.recentConfigs):
                        break
        logger.debug("restoring config state done")

    def saveState(self):
        """
        Save the state of the configuration gui service (namely the recently
        open config files). This is saved in QSettings because it is used
        across config files.

        :return:
        """
        logger.debug("saving config state ...")
        settings = QSettings()
        b = QByteArray()
        ds = QDataStream(b, QIODevice.WriteOnly)
        l = [
            rc.data() for rc in self.recentConfigs
            if rc.isVisible() and rc.data() is not None and rc.data() != ""
        ]
        ds.writeQStringList(l)
        settings.setValue("ConfigurationRecentFiles", b)
        logger.debug("saving config state done (%s)", l)
Beispiel #4
0
class ApplicationPage(QWidget):
    host: "Host"

    def __init__(self, host: "Host", **kwargs):
        super().__init__(**kwargs)
        self.host = host
        self.model = AppStoreModel(self, self.host.app_store)
        self._layout()

    def _layout(self):
        layout = QHBoxLayout(self)
        self.setLayout(layout)

        self.tree = QTreeView(self)
        self.tree.setModel(self.model)
        self.tree.setUniformRowHeights(True)
        self.tree.setColumnWidth(0, 200)
        self.tree.setDragEnabled(True)
        self.tree.setDragDropMode(QAbstractItemView.InternalMove)
        self.tree.viewport().setAcceptDrops(True)
        layout.addWidget(self.tree, 1)

        buttons = QVBoxLayout()
        buttons.setAlignment(Qt.AlignTop)

        add_button = QPushButton(QIcon.fromTheme("list-add"), "", self)
        add_button.setToolTip("Add application")
        add_button.clicked.connect(self.on_add)
        buttons.addWidget(add_button)

        mkdir_button = QPushButton(QIcon.fromTheme("folder-new"), "", self)
        mkdir_button.setToolTip("Make directory")
        mkdir_button.clicked.connect(self.on_mkdir)
        buttons.addWidget(mkdir_button)

        delete_button = QPushButton(QIcon.fromTheme("list-remove"), "", self)
        delete_button.setToolTip("Remove selected item")
        delete_button.clicked.connect(self.on_delete)
        buttons.addWidget(delete_button)

        layout.addLayout(buttons)

    def on_add(self):
        dialog = QInputDialog(self)
        dialog.setLabelText("Enter appconfig.json URL")

        if dialog.exec_() == QInputDialog.Rejected:
            return

        app_url = dialog.textValue().strip()
        self.host.app_store.add_app_ui([app_url])

    def on_mkdir(self):
        dialog = QInputDialog(self)
        dialog.setLabelText("Directory name")

        if dialog.exec_() == QInputDialog.Rejected:
            return

        dirname = dialog.textValue().strip()

        if not dirname or "/" in dirname:
            QMessageBox.critical(self, "Invalid input",
                                 "This directory name cannot be used")
            return

        self.host.app_store.mkdir(dirname)

    def on_delete(self):
        data = self.model.mimeData(self.tree.selectedIndexes())
        if not data:
            return

        data = json.loads(
            data.data("application/x-qabstractitemmodeldatalist").data())
        if data["type"] == "dir":
            confirm = QMessageBox.question(
                self,
                "Remove folder",
                f"Remove {data['id']}?\n\nAll applications will be moved to the top level",
            )
            if confirm == QMessageBox.StandardButton.No:
                return

            self.host.app_store.rmdir(data["id"])
        else:
            app = self.host.app_store[data["id"]]
            confirm = QMessageBox.question(
                self,
                "Remove application",
                f"Remove {app['appName']}?",
            )
            if confirm == QMessageBox.StandardButton.No:
                return

            self.host.app_store.remove_app(data["id"])