예제 #1
0
class Automation(QtWidgets.QWidget):
    '''
    Widget for easy automation within dataArtist, providing:

    * On/Of switch
    * 'Collect' button to grab the address of tool buttons and it's parameters
    * 'Import' list to load saved scripts
    * Multiple script editors listed as Tabs
    * 'Run on new input'
        - Activate active script as soon as the input has changed
    * 'Run' - run active script now
    '''
    def __init__(self, display, splitter):
        QtWidgets.QWidget.__init__(self)

        self.display = display
        display.sigLayerChanged.connect(self.toggleDataChanged)
        display.sigNewLayer.connect(self.toggleNewData)
        self.setMinimumHeight(30)

        self.splitter = splitter

        self._collect = False
        self._activeWidgets = []

        # LAYOUT
        layout = QtWidgets.QVBoxLayout()
        layout.setAlignment(QtCore.Qt.AlignTop)
        # setMargin removed. obsolete, doesn't do anything, not even in PyQt4
        self.setLayout(layout)
        self._hl = QtWidgets.QHBoxLayout()
        layout.addLayout(self._hl)

        # BUTTON: show/hide 'Scripts'
        self.btn_scripts = QtWidgets.QRadioButton('Scripts')
        f = self.btn_scripts.font()
        f.setBold(True)
        self.btn_scripts.setFont(f)
        self.btn_scripts.clicked.connect(self._uncheckConsole)
        self.btn_scripts.clicked.connect(self._toggleScriptsFirstTime)
        self.btn_scripts.clicked.connect(self.updateSize)
        self._hl.addWidget(self.btn_scripts)

        # BUTTON: show/hide 'Console'
        self.btn_console = QtWidgets.QRadioButton('Console')
        f = self.btn_console.font()
        f.setBold(True)
        self.btn_console.setFont(f)
        self.btn_console.clicked.connect(self._uncheckScripts)
        self.btn_console.clicked.connect(self._toggleConsoleFirstTime)
        self.btn_console.clicked.connect(self.updateSize)

        self._hl.addWidget(self.btn_console)

        g = QtWidgets.QButtonGroup(self)
        g.setExclusive(False)
        g.addButton(self.btn_scripts)
        g.addButton(self.btn_console)

        self.splitter.setStretchFactor(0, 0)

        self.cb_run_on = QtWidgets.QComboBox()
        self.cb_run_on.addItems(['-', 'New Data', 'Data Changed'])

        self.tabs = FwTabWidget()
        self.tabs.hide()
        self.tabs.setTabsAddable(True)
        self.tabs.setTabsClosable(True)
        self.tabs.setTabsRenamable(True)

    def _uncheckScripts(self, show):
        if show and self.btn_scripts.isChecked():
            self._toggleScripts(False)
            self.btn_scripts.setChecked(False)

    def _uncheckConsole(self, show):
        if show and self.btn_console.isChecked():
            self._toggleConsole(False)
            self.btn_console.setChecked(False)

    def _toggleConsoleFirstTime(self):

        txt = '''This is a Python {} console.
        it accepts ...
        * all built-in functions, like 'dir'
        * already imported modules, like 'np', for numpy
        * special dataArtist functions, like d', d.l, d.l0,...
        '''.format(platform.python_version())

        namespace = _ExecGlobalsDict(self.display)

        self.console = console.ConsoleWidget(namespace=namespace, text=txt)
        self.console.ui.exceptionBtn.hide()
        self.console.input.sigExecuteCmd.connect(
            self.display.widget.updateView)
        self.layout().addWidget(self.console)

        # update connections:
        self.btn_console.clicked.disconnect(self._toggleConsoleFirstTime)
        self.btn_console.clicked.connect(self._toggleConsole)

    def _toggleConsole(self, show):
        self.console.setVisible(show)

    def _toggleScriptsFirstTime(self):
        # build scripts layout
        refreshR = 20

        # COMBOBOX: IMPORT
        self.combo_import = QtWidgets.QComboBox()
        self.combo_import.addItems(('<import>', 'from file'))
        self.combo_import.addItems(
            # don't show '.py' and hide __init__.py
            [
                x[:-3] for x in SPRIPT_PATH.listdir()
                if (x[0] != '_' and x.endswith('.py'))
            ])
        self.combo_import.currentIndexChanged.connect(self._importScript)
        # BUTTON: COLLECT
        self.btn_collect = QtWidgets.QPushButton('Collect')
        self.btn_collect.setToolTip(
            'click on all tool parameters you want to change during the batch process'
        )
        self.btn_collect.setCheckable(True)
        self.btn_collect.clicked.connect(self.collectTools)
        # TABWIDGET: SCRIPT
        self.tabs.defaultTabWidget = lambda: ScriptTab(self, refreshR)
        self.tabs.addEmptyTab('New')
        # BUTTON: RUN AT NEW INPUT
        self.label_run_on = QtWidgets.QLabel('Activate on')

        # SPINBOX REFRESHRATE
        self.label_refresh = QtWidgets.QLabel('Refresh rate:')
        self.sb_refreshrate = QtWidgets.QSpinBox()
        self.sb_refreshrate.setSuffix(" Hz")
        self.sb_refreshrate.setMinimum(0)
        self.sb_refreshrate.setMaximum(100)
        self.sb_refreshrate.setValue(refreshR)
        self.sb_refreshrate.valueChanged.connect(
            lambda hz: self.tabs.currentWidget().thread.setRefreshrate(hz))
        # BUTTON: RUN
        self.btn_run_now = QtWidgets.QPushButton('Run')
        self.btn_run_now.setCheckable(True)
        self.btn_run_now.clicked.connect(self.toggle)

        self._hl.addWidget(self.btn_collect)
        l = self.layout()
        l.addWidget(self.combo_import)
        l.addWidget(self.tabs)

        self.tabs.show()

        hl2 = QtWidgets.QHBoxLayout()
        hl2.addWidget(self.label_run_on)
        hl2.addWidget(self.cb_run_on)
        hl2.addWidget(self.label_refresh)
        hl2.addWidget(self.sb_refreshrate)
        hl2.insertStretch(1, 0)
        hl2.insertStretch(2, 0)
        l.addLayout(hl2)
        l.addWidget(self.btn_run_now)

        # update connections:
        self.btn_scripts.clicked.disconnect(self._toggleScriptsFirstTime)
        self.btn_scripts.clicked.connect(self._toggleScripts)

    def checkWidgetIsActive(self, widget):
        # bring widget into list of updated widgets
        # if not there already
        a = self._activeWidgets
        if widget not in a:
            a.append(widget)

    def saveState(self):
        state = {
            'scriptOn': self.btn_scripts.isChecked(),
            'consoleOn': self.btn_console.isChecked(),
            'runOn': str(self.cb_run_on.currentText()),
            'tabTitles': [str(self.tabs.tabText(tab)) for tab in self.tabs],
            'curTab': self.tabs.currentIndex()
        }
        # SCRIPTS
        ss = state['scripts'] = []
        for tab in self.tabs:
            ss.append(tab.editor.toPlainText())
        return state

    def restoreState(self, state):
        # BUTTONS
        self.btn_scripts.setChecked(state['scriptOn'])
        #         self._toggleScripts(state['active'])
        self.btn_console.setChecked(state['consoleOn'])
        # self.btn_run_new.setChecked(l['runOnNewInput'])
        self.cb_run_on.setCurrentIndex([
            self.cb_run_on.itemText(i) for i in range(self.cb_run_on.count())
        ].index(state['runOn']))
        # SCRIPTS
        ss = state['scripts']
        if ss and not hasattr(self, 'combo_import'):
            self._toggleScriptsFirstTime()
        self.tabs.clear()
        for n, title in enumerate(state['tabTitles']):
            tab = self.tabs.addEmptyTab(title)
            tab.editor.setPlainText(ss[n])
        self.tabs.setCurrentIndex(state['curTab'])

    def collectTools(self):
        '''
        Get a Tool button or a parameter within the tools menu
        and add it to the active script
        '''
        # TOGGLE BETWEEN NORMAL- AND PointingHandCursor
        self._collect = not self._collect
        if self._collect:
            QtWidgets.QApplication.setOverrideCursor(
                QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        else:
            QtWidgets.QApplication.restoreOverrideCursor()
        # TOGGLE BUTTON
        self.btn_collect.setChecked(self._collect)

        # add chosen widget to active script:
        def fn(tool):
            return self.tabs.currentWidget().addTool(tool)

        for tool in self.display.widget.tools.values():
            # TOOLS:
            tool.returnToolOnClick(self._collect, fn)
            # TOOL-PARAMETERS:
            if isinstance(tool.menu(), ParameterMenu):
                tool.menu().pTree.returnParameterOnClick(self._collect, fn)

    def _importScript(self, index):
        '''
        Open a file, read it's content and add it to the active script tab
        '''
        self.combo_import.setCurrentIndex(0)
        if index == 0:  # '<import>' placeholder within the comboBox
            return
        if index == 1:  # import from file
            f = self.display.workspace.gui.dialogs.getOpenFileName()
            if f is not None and f.isfile():
                tab = self.tabs.addEmptyTab('file')
                with open(f, 'r') as r:
                    tab.editor.appendPlainText(r.read())
        else:
            # import one of the examples scripts
            tab = self.tabs.addEmptyTab('ex%s' % str(index - 1))
            tab.editor.appendPlainText(
                open(
                    SPRIPT_PATH.join(str(self.combo_import.itemText(index))) +
                    '.py', 'r').read())

    def _updateWidget(self):
        '''
        update all active display widgets
        '''
        for widget in self._activeWidgets:
            try:
                widget.updateView(force=True)
            except Exception:
                traceback.print_exc()

    def minimumHeight(self):
        return QtWidgets.QWidget.minimumHeight(self) + 10

    def updateSize(self):
        s = self.splitter
        l = s.sizes()
        if self.btn_scripts.isChecked() or self.btn_console.isChecked():
            # resize to 50%
            s.setSizes(np.ones(len(l)) * np.mean(l))
            self.splitter.setStretchFactor(0, 1)
        else:
            minSize = self.minimumHeight()
            l[1] = max(0, np.sum(l) - minSize)
            l[0] = minSize

            s.setSizes(l)
            self.splitter.setStretchFactor(0, 0)

    def _toggleScripts(self, show):
        '''
        Show/hide all widgets within Automation except of the on/off switch
        And move the horizontal splitter according to the space needed
        '''
        if show:
            self.tabs.show()
            self.btn_run_now.show()
            self.btn_collect.show()
            self.label_run_on.show()
            self.cb_run_on.show()
            self.combo_import.show()
            self.label_refresh.show()
            self.sb_refreshrate.show()
        else:
            self.tabs.hide()
            self.btn_run_now.hide()
            self.btn_collect.hide()
            self.label_run_on.hide()
            self.cb_run_on.hide()
            self.combo_import.hide()
            self.label_refresh.hide()
            self.sb_refreshrate.hide()

    def _setRunning(self):
        '''update button'''
        self.btn_run_now.setChecked(True)
        self.btn_run_now.setText('Stop')
        self.display.workspace.gui.undoRedo.is_active = False
        # TODO: remove try... if .display if not a weakref.proxy any more
        try:
            d = self.display.__repr__.__self__
        except:
            d = self.display
        # SHOW RUN INDICATOR
        self._runIndicator = CircleWidget(d)
        self._runIndicator.move(2, 2)
        self._runIndicator.show()

    def _setDone(self):
        '''update button'''
        self.btn_run_now.setChecked(False)
        self.btn_run_now.setText('Start')
        self.display.workspace.gui.undoRedo.is_active = True
        if hasattr(self, '_runIndicator'):
            self._runIndicator.close()
            del self._runIndicator

    def toggle(self):
        '''
        Start/Stop the active script
        '''
        tab = self.tabs.currentWidget()
        # STOP
        if self.btn_run_now.text() == 'Stop':
            tab.thread.kill()
            return
        # START
        self.display.backupChangedLayer(changes='Script <%s>' %
                                        self.tabs.tabText(tab))
        self._activeWidgets = [self.display.widget]

        tab.thread.start()

    def toggleNewData(self):
        '''
        toggle run settings allow running for new data
        '''
        if self.btn_scripts.isChecked() and self.cb_run_on.currentIndex() == 1:
            self.toggle()

    def toggleDataChanged(self):
        '''
        toggle run settings allow running for data changed
        '''
        if self.btn_scripts.isChecked() and self.cb_run_on.currentIndex() == 2:
            self.toggle()