Example #1
0
class ScriptWindow(QDialog):
    def __init__(self, parent=None):
        super(ScriptWindow, self).__init__(parent)

        self.setGeometry(100, 100, 800, 600)
        self.parent = parent
        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        font.setFixedPitch(True)
        font.setPointSize(10)

        self.editor = QPlainTextEdit()
        self.editor.setFont(font)
        self.editor.setTabStopWidth(self.editor.fontMetrics().width(" ") * 4)
        self.highlight = PythonHighlighter(self.editor.document())
        self.editor.setPlainText("# self.parent is the main GUI handle")
        self.runScriptButton = QPushButton("Run script")

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.runScriptButton)

        self.setLayout(self.layout)
        self.setWindowTitle("Script editor")

        self.setup_button_behavior()

        self.show()

    def setup_button_behavior(self):
        self.runScriptButton.clicked.connect(self.runscript)

    def runscript(self):
        print("Script is executed.")
        print("Testing parent data accessibility")

        text2execute = self.editor.toPlainText()

        exec(text2execute)
class DefaultWidget(QWidget):
    """
    The default widget for a repeating element.
    It's a simple line edit with a label and an option required astrix.
    """
    def __init__(
        self,
        label="",
        line_name="na",
        required=False,
        placeholder_text=None,
        spellings=True,
        parent=None,
    ):
        """

        Parameters
        ----------
        label : str
            The label that appears next to the text edit
        line_name : str
            The name to use for the text edit
        required : boolean
            Whethere to display the little blue required asterix
        parent : QWidget

        """
        QWidget.__init__(self, parent=parent)
        self.layout = QHBoxLayout()
        self.qlbl = QLabel(label, self)
        self.added_line = QPlainTextEdit()
        max_line_height = self.added_line.fontMetrics().height() + 10
        self.added_line.setMaximumHeight(max_line_height)
        self.added_line.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.added_line.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.added_line.mouseMoveEvent = self.mouse_move
        self.added_line.setLineWrapMode(QPlainTextEdit.NoWrap)
        self.added_line.textChanged.connect(self.remove_returns)
        if spellings:
            self.highlighter = Highlighter(self.added_line.document())

        if not placeholder_text is None:
            self.added_line.setPlaceholderText(placeholder_text)
        self.added_line.setObjectName(line_name)
        self.layout.addWidget(self.qlbl)
        self.layout.addWidget(self.added_line)

        if required:
            self.required_label = QLabel(self)
            font = QFont()
            font.setFamily("Arial")
            font.setPointSize(9)
            font.setBold(False)
            font.setItalic(False)
            font.setWeight(50)
            self.required_label.setFont(font)
            self.required_label.setScaledContents(True)
            self.required_label.setAlignment(QtCore.Qt.AlignHCenter
                                             | QtCore.Qt.AlignVCenter)
            self.required_label.setIndent(0)

            self.required_label.setText(
                QtCore.QCoreApplication.translate(
                    "",
                    '<html><head/><body><p align="center">'
                    '<span style=" font-size:18pt; color:#55aaff;">*'
                    "</span></p></body></html>",
                ))
            self.layout.addWidget(self.required_label)

        self.layout.setContentsMargins(1, 1, 1, 1)
        self.layout.setSpacing(10)
        self.setLayout(self.layout)

    def mouse_move(self, e):
        """
        over ride the mouse move event to ignore mouse movement (scrolling),
        but only in the vertical dimension.  Makes this plain text edit look and
        feel like a line edit.

        Parameters
        ----------
        e : pyqt event

        Returns
        -------
        None
        """
        if e.y() < self.added_line.height() - 3 and e.x(
        ) < self.added_line.width() - 3:
            QPlainTextEdit.mouseMoveEvent(self.added_line, e)
        self.added_line.verticalScrollBar().setValue(
            self.added_line.verticalScrollBar().minimum())

    def remove_returns(self):
        """
        After changes to the line, we need to make sure that the user has not entered any line returns, since the
        single line is not intended to be a multiline string.  Any line returns "\n" will be replace with a ' '

        Returns
        -------
        None
        """
        self.added_line.textChanged.disconnect()
        old_position = self.added_line.textCursor().position()
        curtext = self.added_line.toPlainText()
        newtext = curtext.replace("\n", " ")
        self.added_line.setPlainText(newtext)
        cursor = self.added_line.textCursor()
        cursor.setPosition(old_position)
        self.added_line.setTextCursor(cursor)
        self.added_line.textChanged.connect(self.remove_returns)

    def setText(self, text):
        utils.set_text(self.added_line, text)
        cursor = self.added_line.textCursor()
        cursor.setPosition(0)
        self.added_line.setTextCursor(cursor)

    def text(self):
        return self.added_line.toPlainText()
class DefaultWidget(QWidget):
    """
    The default widget for a repeating element.
    It's a simple line edit with a label and an option required astrix.
    """
    def __init__(self,
                 label='',
                 line_name='na',
                 required=False,
                 placeholder_text=None,
                 spellings=True,
                 parent=None):
        """

        Parameters
        ----------
        label : str
            The label that appears next to the text edit
        line_name : str
            The name to use for the text edit
        required : boolean
            Whethere to display the little blue required asterix
        parent : QWidget

        """
        QWidget.__init__(self, parent=parent)
        self.layout = QHBoxLayout()
        self.qlbl = QLabel(label, self)
        self.added_line = QPlainTextEdit()
        max_line_height = self.added_line.fontMetrics().height() + 10
        self.added_line.setMaximumHeight(max_line_height)
        self.added_line.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.added_line.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.added_line.setLineWrapMode(QPlainTextEdit.NoWrap)
        self.added_line.textChanged.connect(self.remove_returns)
        if spellings:
            self.highlighter = Highlighter(self.added_line.document())

        if not placeholder_text is None:
            self.added_line.setPlaceholderText(placeholder_text)
        self.added_line.setObjectName(line_name)
        self.layout.addWidget(self.qlbl)
        self.layout.addWidget(self.added_line)

        if required:
            self.required_label = QLabel(self)
            font = QFont()
            font.setFamily("Arial")
            font.setPointSize(9)
            font.setBold(False)
            font.setItalic(False)
            font.setWeight(50)
            self.required_label.setFont(font)
            self.required_label.setScaledContents(True)
            self.required_label.setAlignment(QtCore.Qt.AlignHCenter
                                             | QtCore.Qt.AlignVCenter)
            self.required_label.setIndent(0)

            self.required_label.setText(
                QtCore.QCoreApplication.translate(
                    "", "<html><head/><body><p align=\"center\">"
                    "<span style=\" font-size:18pt; color:#55aaff;\">*"
                    "</span></p></body></html>"))
            self.layout.addWidget(self.required_label)

        self.layout.setContentsMargins(1, 1, 1, 1)
        self.layout.setSpacing(10)
        self.setLayout(self.layout)

    def remove_returns(self):
        self.added_line.textChanged.disconnect()
        cursor = self.added_line.textCursor()
        curtext = self.added_line.toPlainText()
        newtext = curtext.replace('\n', ' ')
        self.added_line.setPlainText(newtext)
        self.added_line.setTextCursor(cursor)
        self.added_line.textChanged.connect(self.remove_returns)

    def setText(self, text):
        utils.set_text(self.added_line, text)
        cursor = self.added_line.textCursor()
        cursor.setPosition(0)
        self.added_line.setTextCursor(cursor)

    def text(self):
        return self.added_line.toPlainText()
class WeatherInput(QWidget):
    """
    Weather parameter input for GUI. Contains selector on whether to scrape, import, or ignore with corresponding
    windows for each based on current selection e.g. parameter input fields if scraping.
    """

    def __init__(self):
        super().__init__()

        self.__init_weather()
        self.__init_design()

    def __init_weather(self):
        self.layout = QVBoxLayout()

        self.__scrape()
        self.__import()
        self.__none()
        self.__revolver()
        self.__selector()

        self.layout.addWidget(self.selector)
        self.layout.addWidget(self.revolver)

        self.layout.addWidget(QLabel("Powered by the <a href=\"https://www.weatherbit.io/\">Weatherbit API</a>"))

        self.setLayout(self.layout)

    def __scrape(self):
        input_fields = [("City", "text"), ("State", "text"), ("Country", "text"), ("Days", "text"),
                        ("weatherbit.io Key", "text")]
        input_defaults = {"City": "Cambridge", "State": "MA", "Country": "US", "Days": "14"}
        self.scrape_layout, self.scrape_fields = basic_form_creator(input_fields, input_defaults)

        # adding "Imperial Units" and "Save Weather" options as checkboxes in same row.
        imperial = QCheckBox("Imperial Units")
        imperial.setChecked(True)  # default is checked
        save = QCheckBox("Save")
        self.scrape_fields["Imperial Units"] = imperial
        self.scrape_fields["Save Weather"] = save

        self.checkbox_layout = QHBoxLayout()
        self.checkbox_layout.addWidget(imperial)
        self.checkbox_layout.addWidget(save)
        self.checkbox_layout.addStretch()

        checkbox = QWidget()
        checkbox.setLayout(self.checkbox_layout)
        self.scrape_layout.addWidget(checkbox)

        self.scrape = QWidget(self)
        self.scrape.setLayout(self.scrape_layout)

    def __import(self):
        self.import_layout = QVBoxLayout()

        button = QPushButton("Browse")

        self.import_path = QPlainTextEdit()
        font_height = self.import_path.fontMetrics().height()
        num_rows = 5  # size of browse window, plenty of space to display full path
        self.import_path.setFixedHeight(int(num_rows * font_height))

        self.import_layout.addWidget(button, 0, Qt.AlignTop | Qt.AlignLeft)  # align top-left corner
        self.import_layout.addWidget(self.import_path, 0, Qt.AlignTop | Qt.AlignLeft)

        button.clicked.connect(lambda: self.import_path.setPlainText(QFileDialog.getOpenFileName()[0]))

        self.import_ = QWidget(self)
        self.import_.setLayout(self.import_layout)

    def __selector(self):
        self.selector_layout = QHBoxLayout()
        self.selector_button_group = QButtonGroup()
        self.selector_button_group.setExclusive(True)

        self.none_button = QRadioButton("None")
        self.scrape_button = QRadioButton("Scrape")
        self.import_button = QRadioButton("Import")

        self.selector_button_group.addButton(self.none_button, 1)
        self.selector_button_group.addButton(self.scrape_button, 2)  # need 1-index because 0 means not found
        self.selector_button_group.addButton(self.import_button, 3)
        self.selector_layout.addWidget(self.none_button)
        self.selector_layout.addWidget(self.scrape_button)
        self.selector_layout.addWidget(self.import_button)
        self.selector_layout.addStretch()
        self.scrape_button.setChecked(True)

        self.selector = QWidget(self)
        self.selector.setLayout(self.selector_layout)

    def __none(self):
        self.none_ = QWidget()

    def __revolver(self):
        self.revolver = QStackedWidget()
        self.revolver.addWidget(self.none_)
        self.revolver.addWidget(self.scrape)
        self.revolver.addWidget(self.import_)
        self.revolver.setCurrentIndex(1)  # default setting is scrape

    def switch_window(self, button):
        """
        Switch input fields based on current selection.
        :param button: radio button of current selection
        :return: nothing, mutates object
        """
        self.revolver.setCurrentIndex(self.selector_button_group.id(button) - 1)  # make up for 1-index

    def __init_design(self):
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.scrape_layout.setContentsMargins(0, 3, 11, 0)  # effort to line up to TwilightInput
        self.scrape_layout.setSpacing(5)
        self.import_layout.setSpacing(10)
        self.import_layout.addStretch(1)
        self.selector_layout.setContentsMargins(0, 0, 0, 11)
        self.checkbox_layout.setContentsMargins(0, 0, 0, 0)
        self.scrape_layout.addStretch(25)  # stretch large enough to move checkboxes close to other fields

        self.default_border = self.scrape_fields["City"].styleSheet()  # just get from any existing line edit
        self.error_border = "border: 1px solid red;"
Example #5
0
class ScriptWriterDialog(QDialog):
    def __init__(self, parent=None):
        super(ScriptWriterDialog, self).__init__(parent)

        self.setGeometry(200, 100, 650, 700)

        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        font.setFixedPitch(True)
        font.setPointSize(11)

        self.editor = QPlainTextEdit()
        self.editor.setFont(font)
        self.editor.setPlainText(templatestr)
        self.editor.setTabStopWidth(self.editor.fontMetrics().width(" ") * 4)
        # access the 'text' of QPlainTextEdit by
        self.highlight = ScriptHighlighter(self.editor.document())

        # replace system stdout with custom object to hijack it
        sys.stdout = Stream(newText=self.onUpdateText)

        self.stdoutbox = QTextEdit()
        self.stdoutbox.moveCursor(QTextCursor.Start)
        self.stdoutbox.setFixedHeight(150)

        self.loadScriptButton = QPushButton("Load script")
        self.checkSpacesButton = QPushButton("Convert tabs")
        self.saveScriptButton = QPushButton("Save script")
        self.runScriptButton = QPushButton("Run Script")

        self.buttonsLayout = QHBoxLayout()
        self.buttonsLayout.addWidget(self.loadScriptButton)
        self.buttonsLayout.addWidget(self.checkSpacesButton)
        self.buttonsLayout.addWidget(self.saveScriptButton)
        self.buttonsLayout.addWidget(self.runScriptButton)

        self.layout = QVBoxLayout()

        self.currentfilelabel = QLabel("No scripts loaded")
        self.layout.addWidget(self.currentfilelabel)
        self.layout.addWidget(self.editor)
        self.layout.addLayout(self.buttonsLayout)
        self.layout.addWidget(QLabel("status message:"))
        self.layout.addWidget(self.stdoutbox)

        self.setLayout(self.layout)

        self.setWindowTitle("easyAMPS Script editor v0.1")

        self.setup_button_behavior()

    def setup_button_behavior(self):
        self.loadScriptButton.clicked.connect(self.loadscript)
        self.checkSpacesButton.clicked.connect(self.converttabs)
        self.saveScriptButton.clicked.connect(self.savescript)
        self.runScriptButton.clicked.connect(self.runscript)

    def loadscript(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, fileType = QFileDialog.getOpenFileName(
            self,
            "Open AMPS yaml configuration script",
            "",
            "YAML file (*.yaml)",
            options=options,
        )

        if fileName:

            self.currentfilepath = Path(fileName)
            self.currentfilelabel.setText(str(self.currentfilepath))

            # raw text is loaded too
            with open(self.currentfilepath, "rt") as fhd:
                scriptbody = "".join(fhd.readlines())

            self.editor.setPlainText(scriptbody)

            print("Script is loaded.")

        else:
            return False
        pass

    def converttabs(self):
        scriptbody = self.editor.toPlainText()
        scriptbody = scriptbody.replace("\t", "    ")

        print("Tabs have been replaced by spaces, check the script")

        # get current cursor position
        cursor = self.editor.textCursor()
        cursor.beginEditBlock()

        # load the replaced script
        self.editor.setPlainText(scriptbody)

        cursor.endEditBlock()

    def savescript(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, fileType = QFileDialog.getSaveFileName(
            self,
            "Save AMPS yaml configuration script",
            "",
            "YAML file (*.yaml)",
            options=options,
        )

        if fileName:
            targetfile = Path(fileName).with_suffix(".yaml")

            # get text body
            scriptbody = self.editor.toPlainText()

            # replace tabs with spaces
            scriptbody = scriptbody.replace("\t", "    ")

            with open(targetfile, "wt") as fhd:
                fhd.write(scriptbody)

            print(f"Saved to {targetfile}")

    @safecall
    def runscript(self):
        """ Execute script from editor """
        scriptbody = self.editor.toPlainText()
        scriptbody = scriptbody.replace("\t", "    ")
        scriptconfig = yaml.load(scriptbody, Loader=yaml.FullLoader)

        print("Running script  ...")

        inputdir = Path(scriptconfig["directories"]["input"])
        outdir = Path(scriptconfig["directories"]["output"])

        print(f"Reading input files from : {inputdir}")
        print(f"Processed files will be saved to : {outdir}")

        if not outdir.exists():
            outdir.mkdir(exist_ok=True, parents=True)

        experiments = {
            name: Path(inputdir / filename)
            for name, filename in scriptconfig["experiments"].items()
        }

        # load blank file
        blank = read_detail(experiments["blank"])

        # go through each experiment
        for expt_key in experiments.keys():
            if expt_key != "blank":
                print(f"Experiment : {expt_key}")

                if expt_key not in scriptconfig.keys():
                    # skip experiment that has no configurations
                    print(
                        f"Could not find configuration for '{expt_key}' experiment. Skipping it.'"
                    )
                    continue
                else:
                    alldata = read_detail(experiments[expt_key],
                                          label_rows=False)
                    timepoints = alldata["Read #"].unique().tolist()
                    lastread = timepoints[-1]

                    print(f"Found {len(timepoints)} read # :")
                    print(timepoints)

                    data = alldata

                    subtract_blank_fluorescence(data, blank)
                    datadict = process_experiment(data,
                                                  scriptconfig,
                                                  expt_key,
                                                  transpose_block=False)

                    for name, dataframe in datadict.items():
                        output_filename = f"{name}.csv"
                        output_target = outdir / output_filename
                        dataframe.to_csv(f"{str(output_target)}", index=False)

    def onUpdateText(self, text):
        cursor = self.stdoutbox.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.stdoutbox.setTextCursor(cursor)
        self.stdoutbox.ensureCursorVisible()

    def __del__(self):
        sys.stdout = sys.__stdout__