Ejemplo n.º 1
0
    def introVideo():
        M = QWidget()
        image_v = ImageViewer()
        # noinspection PyUnresolvedReferences
        vid2.VideoSignal2.connect(image_v.setImage)

        horizontal = QHBoxLayout()
        horizontal.setContentsMargins(0, 0, 0, 0)
        horizontal.addWidget(image_v)

        M.setLayout(horizontal)
        M.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
                         | Qt.Window | Qt.CustomizeWindowHint)
        M.activateWindow()

        ag = QDesktopWidget().availableGeometry()
        sg = QDesktopWidget().screenGeometry()

        widget = QWidget.geometry(QWidget())
        x = ag.width() - widget.width()
        y = 2 * ag.height() - sg.height() - widget.height()
        M.move(x - 725, y - 240)

        M.show()
        M.raise_()
        push_button2.click()
        M.close()
Ejemplo n.º 2
0
    def show_widget(self):  # pragma: no cover - not testable
        """
        Show and center AppQWidget

        """

        self.center()
        self.show()
        QWidget.activateWindow(self)
Ejemplo n.º 3
0
class DURRAExt(EXTENSION, DURRAExtBase, Ui_durraDialog):
    def __init__(self, parent):
        super(DURRAExt, self).__init__(parent)

        self.backend = DURRABackendExt()

    def setup(self):
        self.backend.setup()

        self.ui = QWidget()
        self.setupUi(self.ui)

        # connect signals
        self.setupConnectionButtons()
        self.buttonBox.rejected.connect(self.onBtnCancel)
        self.btnSave.clicked.connect(self.onBtnSave)
        self.btnInitGit.clicked.connect(self.onBtnInitGit)

        self.disableButtons()

    def createActions(self, window):
        if CONTEXT_KRITA:
            action = window.createAction(MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY)
            # parameter 1 =  the name that Krita uses to identify the action
            # parameter 2 = the text to be added to the menu entry for this script
            action.triggered.connect(self.action_triggered)

    def action_triggered(self):
        self.backend.output = ""
        self.initDocument()
        self.ui.show()
        self.ui.activateWindow()

    def onBtnCancel(self):
        self.ui.close()

    def initDocument(self):
        self.txtLog.clear()
        super().initDocument()

        if self.backend.durradocument.hasKritaDocument():
            self.initUIDocumentInfo()

            if TESTING:
                docInfo = self.backend.durradocument.getKritaDocumentInfo()
                self.backend.output = self.backend.output + "\n\n" + docInfo

            self.txtLog.setPlainText(self.backend.output)

        else:
            self.lblFilename.setText('document not open')
            self.txtTitle.clear()
            self.lblEditingTime.clear()
            self.txtAuthorFullname.clear()
            self.txtAuthorEmail.clear()
            self.txtLicense.clear()
            self.txtRevision.clear()
            self.txtKeyword.clear()
            self.lblVersion.clear()
            self.txtDescription.clear()
            self.txtLog.clear()

    def initUIDocumentInfo(self):
        self.lblFilename.setText(self.backend.durradocument.getFilenameKra())
        if self.backend.durradocument.hasKritaDocument():
            self.txtTitle.setText(self.backend.durradocument.title)
            self.lblEditingTime.setText(
                self.backend.durradocument.getDurationText())
            self.txtAuthorFullname.setText(
                self.backend.durradocument.authorname)
            self.txtAuthorEmail.setText(self.backend.durradocument.authoremail)
            self.txtLicense.setText(self.backend.durradocument.license)
            self.txtRevision.setText(self.backend.durradocument.revisionstr)
            self.txtKeyword.setText(
                self.backend.durradocument.getKeywordsStr())
            self.lblVersion.setText(self.backend.durradocument.versionstr)
            self.txtDescription.setText(self.backend.durradocument.description)

    def onBtnSave(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            self.initUIDocumentInfo()
            output = super().onBtnSave()

            self.txtLog.setText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnGenFiles(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            output = super().onBtnGenFiles()

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnCommitMetaFiles(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            extramsg = self.txtMessage.toPlainText()

            output = super().onBtnCommitMetaFiles(extramsg)

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnCommitFiles(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            extramsg = self.txtMessage.toPlainText()

            output = super().onBtnCommitFiles(extramsg)

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnNewMajorVersion(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            extramsg = self.txtMessage.toPlainText()

            output = super().onBtnNewMajorVersion(extramsg)

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnNewMinjorVersion(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            extramsg = self.txtMessage.toPlainText()

            output = super().onBtnNewMinjorVersion(extramsg)

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnNewPatchedVersion(self):
        self.txtLog.clear()
        if self.backend.durradocument.hasKritaDocument():
            extramsg = self.txtMessage.toPlainText()

            output = super().onBtnNewPatchedVersion(extramsg)

            self.txtLog.setPlainText(output)
            self.txtLog.moveCursor(QtGui.QTextCursor.End)

    def onBtnInitGit(self):
        if self.backend.durradocument.hasKritaDocument():
            self.disableButtons()

            workdir = self.backend.workdir

            initgit_dir = QFileDialog.getExistingDirectory(
                self.ui, "Select a Directory to init git...", workdir,
                QFileDialog.ShowDirsOnly)

            if initgit_dir:
                cmds = self.backend.getGitInitCmds(initgit_dir)

                btnMsg = "Are you sure you want to `init git` in " + initgit_dir + "\n\n" + "Commands to run:\n"
                for cmd in cmds:
                    btnMsg = btnMsg + '$ ' + ' '.join(cmd) + "\n"

                buttonReply = QMessageBox.question(
                    self.ui, 'Select a Directory to init git...', btnMsg,
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                if buttonReply == QMessageBox.Yes:
                    self.txtLog.clear()

                    output = self.backend.runGitInit(initgit_dir)

                    self.txtLog.setPlainText(output)
                    self.txtLog.moveCursor(QtGui.QTextCursor.End)
                else:
                    pass

            if TESTING:
                docInfo = self.backend.durradocument.getKritaDocumentInfo()
                self.backend.output = self.backend.output + "\n\n" + docInfo
                self.txtLog.setToolTip(self.backend.output)

            self.enableButtons()

    def disableButtons(self):
        super().disableButtons()
        self.btnSave.setEnabled(False)
        self.btnInitGit.setEnabled(False)

    def enableButtons(self):
        super().enableButtons()

        self.btnSave.setEnabled(True)
        self.btnInitGit.setEnabled(True)

        if self.backend.durradocument.hasKritaDocument():
            if not self.backend.gitIsRepo(self.backend.workdir):
                self.btnInitGit.setEnabled(True)
            else:
                self.btnInitGit.setEnabled(False)
        else:
            self.btnSave.setEnabled(False)
Ejemplo n.º 4
0
class RingWindow(QMainWindow):
    image: RingImageQLabel
    statusbar: QStatusBar

    def __init__(self):
        super(RingWindow, self).__init__()
        path = os.path.join(sys.path[0], __package__)

        uic.loadUi(os.path.join(path, 'gui_ring.ui'), self)

        self.ctrl = QWidget()
        uic.loadUi(os.path.join(path, 'gui_ring_controls.ui'), self.ctrl)

        self.ctrl.zSpin.valueChanged.connect(self.onZValueChange)
        self.ctrl.openButton.pressed.connect(self.onOpenButton)
        self.ctrl.addButton.pressed.connect(self.onAddButton)
        self.ctrl.plotButton.pressed.connect(self.onPlotButton)
        self.ctrl.measureButton.pressed.connect(self.onMeasureButton)
        self.ctrl.dnaSpin.valueChanged.connect(self.onDnaValChange)
        self.ctrl.actSpin.valueChanged.connect(self.onActValChange)
        self.ctrl.dnaChk.toggled.connect(self.onImgToggle)
        self.ctrl.actChk.toggled.connect(self.onImgToggle)
        self.ctrl.renderChk.stateChanged.connect(self.onRenderChk)

        self.image.clicked.connect(self.onImgUpdate)
        self.image.lineUpdated.connect(self.onImgUpdate)
        self.image.linePicked.connect(self.onLinePickedFromImage)
        self.image.nucleusPicked.connect(self.onNucleusPickedFromImage)
        self.image.dnaChannel = self.ctrl.dnaSpin.value()
        self.image.rngChannel = self.ctrl.actSpin.value()

        self.grph = GraphWidget()
        self.grphtimer = QTimer()
        self.grphtimer.setSingleShot(True)

        # self.stk = StkRingWidget(self.image, linePicked=self.onLinePickedFromStackGraph)

        self.grph.linePicked.connect(self.onLinePickedFromGraph)
        # self.stk.linePicked.connect(self.onLinePickedFromStackGraph)
        self.grphtimer.timeout.connect(self._graph)

        self.image.dnaChannel = self.ctrl.dnaSpin.value()
        self.image.rngChannel = self.ctrl.actSpin.value()

        self.measure_n = 0
        self.selectedLine = None
        self.line_length = 4

        self.currMeasurement = None
        self.currN = None
        self.currZ = None

        self.df = pd.DataFrame()
        self.file = "/Users/Fabio/data/lab/airyscan/nil.czi"

        self.show()
        self.grph.show()
        # self.stk.show()
        self.ctrl.show()
        self.move(0, 0)
        self.resizeEvent(None)
        self.moveEvent(None)

    def resizeEvent(self, event):
        # this is a hack to resize everything when the user resizes the main window
        self.grph.setFixedWidth(self.width())
        self.image.setFixedWidth(self.width())
        self.image.setFixedHeight(self.height())
        self.image.resizeEvent(None)
        self.moveEvent(None)

    def moveEvent(self, QMoveEvent):
        self.ctrl.move(self.frameGeometry().topRight())
        self.grph.move(self.geometry().bottomLeft())
        # self.stk.move(self.ctrl.frameGeometry().topRight())

    def closeEvent(self, event):
        self._saveCurrentFileMeasurements()
        # if not self.df.empty:
        #     self.df.loc[:, "condition"] = self.ctrl.experimentLineEdit.text()
        #     self.df.loc[:, "l"] = self.df.loc[:, "l"].apply(lambda v: np.array2string(v, separator=','))
        #     self.df.to_csv(os.path.join(os.path.dirname(self.image.file), "ringlines.csv"))
        self.grph.close()
        self.ctrl.close()
        # self.stk.close()

    def focusInEvent(self, QFocusEvent):
        logger.debug('focusInEvent')
        self.ctrl.activateWindow()
        self.grph.activateWindow()
        # self.stk.focusInEvent(None)

    def showEvent(self, event):
        self.setFocus()

    def _saveCurrentFileMeasurements(self):
        if not self.df.empty:
            fname = os.path.basename(self.image.file)
            df = self.df[self.df["file"] == fname]
            df.loc[:, "condition"] = self.ctrl.experimentLineEdit.text()
            df.loc[:, "l"] = self.df.loc[:, "l"].apply(
                lambda v: np.array2string(v, separator=','))
            df.to_csv(
                os.path.join(os.path.dirname(self.image.file), f"{fname}.csv"))

    def _graphTendency(self):
        df = pd.DataFrame(self.image.measurements).drop(
            ['x', 'y', 'c', 'ls0', 'ls1', 'd', 'sum'], axis=1)
        df.loc[:, "xx"] = df.loc[:, "l"].apply(lambda v: np.arange(
            start=0, stop=len(v) * self.image.dl, step=self.image.dl))
        df = m.vector_column_to_long_fmt(df, val_col="l", ix_col="xx")
        sns.lineplot(x="xx",
                     y="l",
                     data=df,
                     ax=self.grph.ax,
                     color='k',
                     ci="sd",
                     zorder=20)
        self.grph.ax.set_ylabel('')
        self.grph.ax.set_xlabel('')
        self.grph.canvas.draw()

    def _graph(self, alpha=1.0):
        self.grph.clear()
        lines = self.image.lines(self.image.currNucleusId)
        if lines.empty:
            return

        for ix, me in lines.iterrows():
            x = np.arange(start=0,
                          stop=len(me['value']) * self.image.dl,
                          step=self.image.dl)
            lw = 0.1 if me['li'] != self.image.selectedLine else 0.5
            self.grph.ax.plot(x,
                              me['value'],
                              linewidth=lw,
                              linestyle='-',
                              color=me['c'],
                              alpha=alpha,
                              zorder=10,
                              picker=5,
                              label=int(
                                  me['li']))  # , marker='o', markersize=1)
        self.grph.format_ax()
        # self.statusbar.showMessage("ptp: %s" % ["%d " % me['d'] for me in self.image.lines().iterrows()])
        self.grph.canvas.draw()

    @QtCore.pyqtSlot()
    def onImgToggle(self):
        logger.debug('onImgToggle')
        if self.ctrl.dnaChk.isChecked():
            self.image.activeCh = "dna"
        if self.ctrl.actChk.isChecked():
            self.image.activeCh = "act"

    @QtCore.pyqtSlot()
    def onRenderChk(self):
        logger.debug('onRenderChk')
        self.image.renderMeasurements = self.ctrl.renderChk.isChecked()
        self.stk.renderMeasurements = self.ctrl.renderChk.isChecked()

    @QtCore.pyqtSlot()
    def onOpenButton(self):
        logger.debug('onOpenButton')

        # save current file measurements as a backup
        self._saveCurrentFileMeasurements()

        qfd = QFileDialog()
        path = os.path.dirname(self.file)
        if self.image.file is not None:
            self.statusbar.showMessage("current file: %s" %
                                       os.path.basename(self.image.file))
        flt = "Tiff files(*.tif *.tiff);;Zeiss(*.czi)"
        f = QFileDialog.getOpenFileName(qfd, "Open File", path, flt)
        if len(f) > 0:
            self._open(f[0])

    def _open(self, fname):
        assert type(fname) is str and len(fname) > 0, "No filename given!"
        self.file = fname
        self.image.file = fname
        self.image.zstack = self.ctrl.zSpin.value()
        self.image.dnaChannel = self.ctrl.dnaSpin.value()
        self.ctrl.nchLbl.setText("%d channels" % self.image.nChannels)
        self.ctrl.nzsLbl.setText("%d z-stacks" % self.image.nZstack)
        self.ctrl.nfrLbl.setText(
            "%d %s" % (self.image.nFrames,
                       "frames" if self.image.nFrames > 1 else "frame"))
        self.currMeasurement = None
        self.currN = None
        self.currZ = None

        # self.stk.close()
        # self.stk = StkRingWidget(self.image,
        #                          nucleus_id=self.image.currNucleusId,
        #                          linePicked=self.onLinePickedFromStackGraph,
        #                          line_length=self.line_length,
        #                          dl=self.image.dl,
        #                          lines_to_measure=self.image._nlin
        #                          )
        # # self.stk.linePicked.connect(self.onLinePickedFromStackGraph)
        # self.stk.loadImages(self.image.images, xy=(100, 100), wh=(200, 200))
        # self.stk.hide()
        # self.stk.show()
        self.moveEvent(None)

    @QtCore.pyqtSlot()
    def onImgUpdate(self):
        # logger.debug(f"onImgUpdate")
        self.ctrl.renderChk.setChecked(True)
        self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
        logger.debug(
            f"onImgUpdate. Selected line is {self.stk.selectedLineId}")
        # self.stk.drawMeasurements(erase_bkg=True)
        self.grphtimer.start(1000)

    @QtCore.pyqtSlot()
    def onNucleusPickedFromImage(self):
        logger.debug('onNucleusPickedFromImage')
        # self.stk.dnaChannel = self.image.dnaChannel
        # self.stk.rngChannel = self.image.rngChannel
        # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
        # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0

        # # test rectification code
        # dl = 4
        # ndl = 10
        # nth = 100
        # ppdl = 1
        # ppth = 1
        #
        # tsplaprx = TestSplineApproximation(self.image.currNucleus, self.image)
        # tsplaprx.test_fit()
        # tsplaprx.plot_grid()
        #
        # trct = TestPiecewiseLinearRectification(tsplaprx,
        #                                         dl=dl, n_dl=ndl, n_theta=nth, pix_per_dl=ppdl, pix_per_theta=ppth)
        # trct.plot_rectification()
        #
        # tfnrect = TestFunctionRectification(tsplaprx, dl=dl, n_dl=ndl, n_theta=nth, pix_per_dl=ppdl, pix_per_theta=ppth)
        # tfnrect.plot_rectification()
        #
        # minx, miny, maxx, maxy = self.image.currNucleus.bounds
        # r = int(max(maxx - minx, maxy - miny) / 2)
        # self.stk.loadImages(self.image.images, xy=[n[0] for n in self.image.currNucleus.centroid.xy],
        #                     wh=(r * self.image.pix_per_um, r * self.image.pix_per_um))
        # self.stk.measure()
        # self.stk.drawMeasurements(erase_bkg=True)

    @QtCore.pyqtSlot()
    def onMeasureButton(self):
        logger.debug('onMeasureButton')
        self.image.paint_measures()
        self._graph(alpha=0.2)
        self._graphTendency()

    @QtCore.pyqtSlot()
    def onZValueChange(self):
        logger.debug('onZValueChange')
        self.image.zstack = self.ctrl.zSpin.value() % self.image.nZstack
        self.ctrl.zSpin.setValue(self.image.zstack)
        self._graph()

    @QtCore.pyqtSlot()
    def onDnaValChange(self):
        logger.debug('onDnaValChange')
        val = self.ctrl.dnaSpin.value() % self.image.nChannels
        self.ctrl.dnaSpin.setValue(val)
        self.image.dnaChannel = val
        if self.ctrl.dnaChk.isChecked():
            self.image.activeCh = "dna"
        self.ctrl.dnaChk.setChecked(True)

    @QtCore.pyqtSlot()
    def onActValChange(self):
        logger.debug('onActValChange')
        val = self.ctrl.actSpin.value() % self.image.nChannels
        self.ctrl.actSpin.setValue(val)
        self.image.rngChannel = val
        if self.ctrl.actChk.isChecked():
            self.image.activeCh = "act"
        self.ctrl.actChk.setChecked(True)

    @QtCore.pyqtSlot()
    def onAddButton(self):
        logger.debug('onAddButton')
        if self.currMeasurement is not None and self.currN is not None and self.currZ is not None:
            new = pd.DataFrame(self.currMeasurement)
            new = new.loc[(new["n"] == self.currN) & (new["z"] == self.currZ)]
            new.loc[:, "m"] = self.measure_n
            new.loc[:, "file"] = os.path.basename(self.image.file)
            # new.loc[:, "x"] = new.loc[:, "l"].apply(lambda v: np.arange(start=0, stop=len(v), step=self.image.dl))
            self.df = self.df.append(new, ignore_index=True, sort=False)
            self.measure_n += 1
            self.currMeasurement = None
            self.currN = None
            self.currZ = None

            print(self.df)

    @QtCore.pyqtSlot()
    def onPlotButton(self):
        logger.debug('onPlotButton')
        if self.image.measurements is None: return
        import matplotlib.pyplot as plt
        import seaborn as sns
        from matplotlib.gridspec import GridSpec

        plt.style.use('bmh')
        pal = sns.color_palette("Blues", n_colors=len(self.image.measurements))
        fig = plt.figure(figsize=(2, 2 * 4), dpi=300)
        gs = GridSpec(nrows=2, ncols=1, height_ratios=[4, 0.5])
        ax1 = plt.subplot(gs[0, 0])
        ax2 = plt.subplot(gs[1, 0])
        self.image.drawMeasurements(ax1, pal)

        lw = 1
        for me, c in zip(self.image.measurements, pal):
            x = np.arange(start=0,
                          stop=len(me['l']) * self.image.dl,
                          step=self.image.dl)
            ax2.plot(x,
                     me['l'],
                     linewidth=lw,
                     linestyle='-',
                     color=c,
                     alpha=1,
                     zorder=10)

        ax1.xaxis.set_major_locator(ticker.MultipleLocator(20))
        ax1.xaxis.set_minor_locator(ticker.MultipleLocator(10))
        ax1.yaxis.set_major_locator(ticker.MultipleLocator(20))
        ax1.yaxis.set_minor_locator(ticker.MultipleLocator(10))

        ax2.xaxis.set_major_locator(ticker.MultipleLocator(1))
        ax2.xaxis.set_minor_locator(ticker.MultipleLocator(0.5))
        ax2.yaxis.set_major_locator(ticker.MultipleLocator(1e4))
        ax2.yaxis.set_minor_locator(ticker.MultipleLocator(5e3))
        ax2.yaxis.set_major_formatter(EngFormatter(unit=''))

        fig.savefig(os.path.basename(self.image.file) + ".pdf")

    @QtCore.pyqtSlot()
    def onLinePickedFromGraph(self):
        logger.debug('onLinePickedFromGraph')
        self.selectedLine = self.grph.selectedLine if self.grph.selectedLine is not None else None
        if self.selectedLine is not None:
            self.image.selectedLine = self.selectedLine

            self.currMeasurement = self.image.measurements
            self.currN = self.selectedLine
            self.currZ = self.image.zstack
            # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            # self.stk.selectedZ = self.currZ

            try:
                self.stk.drawMeasurements(erase_bkg=True)
            except Exception as e:
                logger.error(e)

            self.statusbar.showMessage("line %d selected" % self.selectedLine)

    @QtCore.pyqtSlot()
    def onLinePickedFromStackGraph(self):
        logger.debug('onLinePickedFromStackGraph')
        self.selectedLine = self.stk.selectedLineId if self.stk.selectedLineId is not None else None
        if self.selectedLine is not None:
            self.currN = self.stk.selectedLineId
            # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            self.currZ = self.stk.selectedZ

            self.statusbar.showMessage(
                f"Line {self.currN} of z-stack {self.currZ} selected.")
            logger.info(f"Line {self.currN} of z-stack {self.currZ} selected.")

    @QtCore.pyqtSlot()
    def onLinePickedFromImage(self):
        logger.debug('onLinePickedFromImage')
        self.selectedLine = self.image.selectedLine if self.image.selectedLine is not None else None
        if self.selectedLine is not None:
            self.currN = self.selectedLine
            self.currZ = self.image.zstack
            # self.stk.selectedLineId = self.image.selectedLine if self.image.selectedLine is not None else 0
            # self.stk.selectedNucId = self.image.currNucleusId if self.image.currNucleusId is not None else 0
            # self.stk.selectedZ = self.currZ

            self.statusbar.showMessage("Line %d selected" % self.selectedLine)
Ejemplo n.º 5
0
 def show(self):
     # For non-modal dialogs, show() is not enough to bring the window at the forefront, we have
     # to call raise() as well
     QWidget.showNormal(self)
     QWidget.raise_(self)
     QWidget.activateWindow(self)
Ejemplo n.º 6
0
 def show(self):
     # For non-modal dialogs, show() is not enough to bring the window at the forefront, we have
     # to call raise() as well
     QWidget.showNormal(self)
     QWidget.raise_(self)
     QWidget.activateWindow(self)
Ejemplo n.º 7
0
class BashActionsDialog(QDialog):

    def __init__(self):
        super().__init__()
        self.mainDialog, self.wid0, self.wid1 = QWidget(), QWidget(), QWidget()
        self.mainDialog.setWindowModality(Qt.NonModal)
        self.mainLayout = QVBoxLayout(self.mainDialog)
        self.box0, self.box1 = QHBoxLayout(self.wid0), QHBoxLayout(self.wid1)
        QLabel(self.mainDialog).setPixmap(
            QIcon.fromTheme("applications-graphics").pixmap(64))
        self.formLayout = QFormLayout()
        self.refreshButton = QPushButton("Refresh")
        self.loadButton = QPushButton("Load")
        self.mode = QComboBox()
        self.mode.addItems(("Full", "Simple"))
        self.widgetDocuments = QListWidget()
        self.widgetDocuments.setSortingEnabled(True)
        self.widgetDocuments.setToolTip("Choose 1 or more files")
        self.widgetDocuments.setSelectionMode(QAbstractItemView.MultiSelection)
        self.widgetDocuments.setSizeAdjustPolicy(
            QAbstractScrollArea.AdjustToContents)
        self.bashscript, self.preview = QPlainTextEdit(), QPlainTextEdit()
        self.bashscript.setPlaceholderText(_MSG0)
        self.bashscript.setToolTip(_MSG0)
        self.bashscript.setPlainText(Path(__file__ + ".txt").read_text())
        self.preview.setPlaceholderText(
            "Read-Only Preview of your Bash Commands before execution.")
        self.preview.setToolTip(
            "This will run exactly as seen here, line-by-line 1 per line.")
        self.preview.setReadOnly(True)
        self.log = QPlainTextEdit()
        self.log.setPlaceholderText(
            "Read-Only Log of your Bash Commands after execution.")
        self.log.setToolTip("Standard output, standard error and extra info.")
        self.log.setReadOnly(True)
        self.chrt = QCheckBox("Low CPU priority")
        self.autoquote = QCheckBox(
            "Auto add quotes if the filename or path has white spaces")
        self.asave = QCheckBox("Auto save")
        self.autoquote.setChecked(True)
        self.asave.setChecked(True)
        self.mini = QCheckBox("Minimize during execution")
        self.qq = QCheckBox("Close after execution")
        self.repeats, self.delay = QSpinBox(), QSpinBox()
        self.backoff, self.timeout = QSpinBox(), QSpinBox()
        self.repeats.setRange(1, 99)
        self.repeats.setPrefix("Repeat ")
        self.repeats.setSuffix(" times")
        self.delay.setRange(0, 99)
        self.delay.setPrefix("Delay ")
        self.delay.setSuffix(" seconds")
        self.backoff.setRange(1, 9)
        self.backoff.setPrefix("Backoff ")
        self.backoff.setSuffix(" seconds")
        self.timeout.setRange(0, 999)
        self.timeout.setValue(999)
        self.timeout.setPrefix("Timeout at ")
        self.timeout.setSuffix(" seconds")
        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok |
                                          QDialogButtonBox.Close |
                                          QDialogButtonBox.Help)
        # self.misteryButton = QPushButton("?", self.buttonBox)
        # self.misteryButton.setFlat(True)

        self.refreshButton.clicked.connect(self.loadDocuments)
        self.loadButton.clicked.connect(self.load_script)
        self.buttonBox.accepted.connect(self.confirmButton)
        self.buttonBox.rejected.connect(self.mainDialog.close)
        self.buttonBox.helpRequested.connect(lambda: open_new_tab(__url__))

        self.mode.currentIndexChanged.connect(self.change_mode)
        self.bashscript.textChanged.connect(self.update_preview)

        self.kritaInstance = krita.Krita.instance()
        self.documentsList, self.selected = [], []
        self.process = QProcess(self)
        # self.process.error.connect(
        #     lambda: self.statusBar().showMessage("Info: Process Killed", 5000))

    def change_mode(self, index):
        for item in (self.preview, self.log, self.wid0, self.wid1, self.autoquote):
            if index == 1:
                item.hide()
                self.formLayout.labelForField(item).hide()
                self.mainDialog.resize(400, 400)
            else:
                item.show()
                self.formLayout.labelForField(item).show()
                self.mainDialog.resize(800, 800)

    def load_script(self):
        self.bashscript.setPlainText(str(Path(QFileDialog.getOpenFileName(
            self, "Open 1 Bash Script Template", "Bash_Commands_Template.sh",
            "Bash Script to use as Template (*.sh);;Plain text files (*.txt)"
            )[0]).read_text()))

    def update_preview(self):
        script = str(self.bashscript.toPlainText()).strip()
        self.selected = [a.text() for a in self.widgetDocuments.selectedItems()]
        if not len(script):
            return                             # Nothing to do yet.
        elif not len(self.selected):
            self.preview.setPlainText(script)  # We got script but no files yet.
        else:
            preview_script = script
            for index, selected_file in enumerate(self.selected):
                index, seleted_file = index + 1, str(selected_file)
                need_quote = self.autoquote.isChecked() and " " in seleted_file
                preview_script = preview_script.replace(
                    f"FILE{index}",
                    f'"{seleted_file}"' if need_quote else seleted_file)
            self.preview.setPlainText(preview_script)

    def initialize(self):
        self.loadDocuments()

        self.formLayout.addRow(QLabel("<center><h1>Bash Actions for Krita"))
        self.formLayout.addRow("Mode", self.mode)
        self.formLayout.addRow("Opened image files", self.widgetDocuments)
        self.formLayout.addRow(" ", self.refreshButton)
        self.formLayout.addRow("Commands template", self.bashscript)
        self.formLayout.addRow(" ", self.loadButton)
        self.formLayout.addRow("Preview", self.preview)
        self.formLayout.addRow("Log", self.log)

        self.box1.addWidget(self.repeats)
        self.box1.addWidget(self.delay)
        self.box1.addWidget(self.backoff)
        self.box1.addWidget(self.timeout)
        self.formLayout.addRow("Execution repeats", self.wid1)

        self.box0.addWidget(self.asave)
        self.box0.addWidget(self.chrt)
        self.box0.addWidget(self.mini)
        self.box0.addWidget(self.qq)
        self.formLayout.addRow("Execution details", self.wid0)

        self.formLayout.addRow("Filename correction", self.autoquote)
        self.mainLayout.addLayout(self.formLayout)
        self.mainLayout.addWidget(self.buttonBox)

        self.mainDialog.resize(800, 800)
        self.mainDialog.setWindowTitle("Bash Actions")
        self.mainDialog.show()
        self.mainDialog.activateWindow()
        self.bashscript.setFocus()

    def loadDocuments(self):
        self.widgetDocuments.clear()
        self.documentsList = [
            document for document in self.kritaInstance.documents()
            if document.fileName()]
        for document in self.documentsList:
            self.widgetDocuments.addItem(document.fileName())

    def confirmButton(self):
        start_time, repeat = datetime.now(), int(self.repeats.value())
        end_time = int(time.time() + int(self.timeout.value()))
        delay, backoff = int(self.delay.value()), int(self.backoff.value())
        if self.asave.isChecked():
            Path(__file__ + ".txt").write_text(str(self.bashscript.toPlainText()))
        chrt = which("chrt") if self.chrt.isChecked() else None
        commands = tuple(str(self.preview.toPlainText()).strip().splitlines())
        if not len(commands):
            return QMessageBox.warning(self, __doc__, "Nothing to execute!.")
        if self.mini.isChecked() and self.mainDialog.isVisible():
            self.mainDialog.hide()
        self.log.clear()
        self.log.setPlainText(f"""STARTED: {start_time} by user {getuser()}.
        LINES OF BASH TO EXECUTE: {len(commands)}.\nTIMEOUT: {end_time} Secs.
        SELECTED FILES: {len(self.selected)} files.\nDELAY: {delay} Secs.
        BACKOFF: {backoff} Secs.\nREPEATS: {repeat} Times.\nPRIORITY: {chrt}""")

        while repeat:
            self.log.appendPlainText(f"REPETITION: {repeat} loop.")
            for i, cmd in enumerate(commands):
                cmd = f"""{chrt} -i 0 {cmd.strip()}""" if chrt else cmd.strip()
                self.log.appendPlainText(f"{i} EXECUTING: {cmd}.")
                self.process.start(cmd)
                self.process.waitForFinished(self.timeout.value())
                self.log.appendPlainText(
                    bytes(self.process.readAllStandardError()).decode("utf-8"))
                self.log.appendPlainText(
                    bytes(self.process.readAllStandardOutput()).decode("utf-8"))
            sleep(delay)
            repeat -= 1
            delay *= backoff
            if end_time and time.time() > end_time:
                self.log.appendPlainText(f"TIMEOUT: {time.time()} > {end_time}")
                return
        else:
            self.log.appendPlainText(f"FINISHED: {datetime.now()}.")
            if self.mini.isChecked() and not self.mainDialog.isVisible():
                self.mainDialog.show()
            if self.qq.isChecked():
                self.mainDialog.close()
                self.close()