Exemplo n.º 1
0
class ServerPlugin(Plugin):
    __icon__ = "fa.qrcode"
    __pname__ = "server"
    __views__ = ["slice_viewer"]
    __tab__ = "server"

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        run_config = {
            "server_ip": "127.0.0.1",
            "server_port": "8134",
            "workspace_name": "test_hunt_d4b",
            "use_ssh": False,
            "ssh_host": "ws168.diamond.ac.uk",
            "ssh_port": "22",
        }

        workspace_config = {
            "dataset_name": "data",
            "datasets_dir": "/path/to/my/data/dir",
            "vol_fname": "myfile.h5",
            "workspace_name": "my_survos_workspace",
            "downsample_by": "1",
        }

        from survos2.server.config import cfg

        pipeline_config = dict(cfg)

        self.run_config = run_config
        self.workspace_config = workspace_config
        self.pipeline_config = pipeline_config

        self.server_process = None
        self.client_process = None

        self.layout = QVBoxLayout()
        tabwidget = QTabWidget()
        tab1 = QWidget()
        tab2 = QWidget()

        tabwidget.addTab(tab1, "Setup and Start Survos")
        self.create_workspace_button = QPushButton("Create workspace")

        tab1.layout = QVBoxLayout()
        tab1.setLayout(tab1.layout)
        chroot_fields = self.get_chroot_fields()
        tab1.layout.addWidget(chroot_fields)
        workspace_fields = self.get_workspace_fields()
        tab1.layout.addWidget(workspace_fields)

        self.setup_adv_run_fields()
        self.adv_run_fields.hide()

        run_fields = self.get_run_fields()
        tab1.layout.addWidget(run_fields)
        output_config_button = QPushButton("Save config")

        self.create_workspace_button.clicked.connect(
            self.create_workspace_clicked)
        output_config_button.clicked.connect(self.output_config_clicked)
        self.layout.addWidget(tabwidget)

        self.setGeometry(300, 300, 600, 400)
        self.setWindowTitle("SuRVoS Settings Editor")
        current_fpth = os.path.dirname(os.path.abspath(__file__))
        self.setWindowIcon(
            QIcon(os.path.join(current_fpth, "resources", "logo.png")))
        self.setLayout(self.layout)
        self.show()

    def get_chroot_fields(self):
        chroot_fields = QGroupBox("Set Main Directory for Storing Workspaces:")
        chroot_fields.setMaximumHeight(130)
        chroot_layout = QGridLayout()
        self.given_chroot_linedt = QLineEdit(CHROOT)
        chroot_layout.addWidget(self.given_chroot_linedt, 1, 0, 1, 2)
        set_chroot_button = QPushButton("Set Workspaces Root")
        chroot_layout.addWidget(set_chroot_button, 1, 2)
        chroot_fields.setLayout(chroot_layout)
        set_chroot_button.clicked.connect(self.set_chroot)
        return chroot_fields

    def get_workspace_fields(self):
        """Gets the QGroupBox that contains all the fields for setting up the workspace.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with workspace fields.
        """
        select_data_button = QPushButton("Select")
        workspace_fields = QGroupBox("Create New Workspace:")
        wf_layout = QGridLayout()
        wf_layout.addWidget(QLabel("Data File Path:"), 0, 0)
        current_data_path = Path(self.workspace_config["datasets_dir"],
                                 self.workspace_config["vol_fname"])
        self.data_filepth_linedt = QLineEdit(str(current_data_path))
        wf_layout.addWidget(self.data_filepth_linedt, 1, 0, 1, 2)
        wf_layout.addWidget(select_data_button, 1, 2)
        wf_layout.addWidget(QLabel("HDF5 Internal Data Path:"), 2, 0, 1, 1)
        ws_dataset_name = self.workspace_config["dataset_name"]
        internal_h5_path = (ws_dataset_name
                            if str(ws_dataset_name).startswith("/") else "/" +
                            ws_dataset_name)
        self.h5_intpth_linedt = QLineEdit(internal_h5_path)
        wf_layout.addWidget(self.h5_intpth_linedt, 2, 1, 1, 1)
        wf_layout.addWidget(QLabel("Workspace Name:"), 3, 0)
        self.ws_name_linedt_1 = QLineEdit(
            self.workspace_config["workspace_name"])
        wf_layout.addWidget(self.ws_name_linedt_1, 3, 1)
        wf_layout.addWidget(QLabel("Downsample Factor:"), 4, 0)
        self.downsample_spinner = QSpinBox()
        self.downsample_spinner.setRange(1, 10)
        self.downsample_spinner.setSpecialValueText("None")
        self.downsample_spinner.setMaximumWidth(60)
        self.downsample_spinner.setValue(
            int(self.workspace_config["downsample_by"]))
        wf_layout.addWidget(self.downsample_spinner, 4, 1, 1, 1)
        # ROI
        self.setup_roi_fields()
        wf_layout.addWidget(self.roi_fields, 4, 2, 1, 2)
        self.roi_fields.hide()

        wf_layout.addWidget(self.create_workspace_button, 5, 0, 1, 3)
        workspace_fields.setLayout(wf_layout)
        select_data_button.clicked.connect(self.launch_data_loader)
        return workspace_fields

    def setup_roi_fields(self):
        """Sets up the QGroupBox that displays the ROI dimensions, if selected."""
        self.roi_fields = QGroupBox("ROI:")
        roi_fields_layout = QHBoxLayout()
        # z
        roi_fields_layout.addWidget(QLabel("z:"), 0)
        self.zstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zstart_roi_val, 1)
        roi_fields_layout.addWidget(QLabel("-"), 2)
        self.zend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zend_roi_val, 3)
        # y
        roi_fields_layout.addWidget(QLabel("y:"), 4)
        self.ystart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.ystart_roi_val, 5)
        roi_fields_layout.addWidget(QLabel("-"), 6)
        self.yend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.yend_roi_val, 7)
        # x
        roi_fields_layout.addWidget(QLabel("x:"), 8)
        self.xstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xstart_roi_val, 9)
        roi_fields_layout.addWidget(QLabel("-"), 10)
        self.xend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xend_roi_val, 11)

        self.roi_fields.setLayout(roi_fields_layout)

    def setup_adv_run_fields(self):
        """Sets up the QGroupBox that displays the advanced optiona for starting SuRVoS2."""
        self.adv_run_fields = QGroupBox("Advanced Run Settings:")
        adv_run_layout = QGridLayout()
        adv_run_layout.addWidget(QLabel("Server IP Address:"), 0, 0)
        self.server_ip_linedt = QLineEdit(self.run_config["server_ip"])
        adv_run_layout.addWidget(self.server_ip_linedt, 0, 1)
        adv_run_layout.addWidget(QLabel("Server Port:"), 1, 0)
        self.server_port_linedt = QLineEdit(self.run_config["server_port"])
        adv_run_layout.addWidget(self.server_port_linedt, 1, 1)

        # SSH Info
        self.ssh_button = QRadioButton("Use SSH")
        self.ssh_button.setAutoExclusive(False)
        adv_run_layout.addWidget(self.ssh_button, 0, 2)
        ssh_flag = self.run_config.get("use_ssh", False)
        if ssh_flag:
            self.ssh_button.setChecked(True)
        self.ssh_button.toggled.connect(self.toggle_ssh)

        self.adv_ssh_fields = QGroupBox("SSH Settings:")
        adv_ssh_layout = QGridLayout()
        adv_ssh_layout.setColumnStretch(2, 2)
        ssh_host_label = QLabel("Host")
        self.ssh_host_linedt = QLineEdit(self.run_config.get("ssh_host", ""))
        adv_ssh_layout.addWidget(ssh_host_label, 0, 0)
        adv_ssh_layout.addWidget(self.ssh_host_linedt, 0, 1, 1, 2)
        ssh_user_label = QLabel("Username")
        self.ssh_username_linedt = QLineEdit(self.get_login_username())
        adv_ssh_layout.addWidget(ssh_user_label, 1, 0)
        adv_ssh_layout.addWidget(self.ssh_username_linedt, 1, 1, 1, 2)
        ssh_port_label = QLabel("Port")
        self.ssh_port_linedt = QLineEdit(self.run_config.get("ssh_port", ""))
        adv_ssh_layout.addWidget(ssh_port_label, 2, 0)
        adv_ssh_layout.addWidget(self.ssh_port_linedt, 2, 1, 1, 2)
        self.adv_ssh_fields.setLayout(adv_ssh_layout)
        #adv_run_layout.addWidget(self.adv_ssh_fields, 1, 2, 2, 5)

        self.adv_run_fields.setLayout(adv_run_layout)

    def get_run_fields(self):
        """Gets the QGroupBox that contains the fields for starting SuRVoS.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with run fields.
        """
        self.run_button = QPushButton("Start Server")
        self.stop_button = QPushButton("Stop Server")

        self.existing_button = QPushButton("Use Existing Server")

        advanced_button = QRadioButton("Advanced")
        run_fields = QGroupBox("Run SuRVoS:")
        run_layout = QGridLayout()

        workspaces = os.listdir(CHROOT)
        self.workspaces_list = ComboBox()
        for s in workspaces:
            self.workspaces_list.addItem(key=s)

        run_layout.addWidget(QLabel("Workspace Name:"), 0, 0)
        self.ws_name_linedt_2 = QLineEdit(
            self.workspace_config["workspace_name"])
        self.ws_name_linedt_2.setAlignment(Qt.AlignLeft)
        self.workspaces_list.setLineEdit(self.ws_name_linedt_2)

        # run_layout.addWidget(self.ws_name_linedt_2, 0, 1)

        run_layout.addWidget(self.workspaces_list, 0, 1)
        run_layout.addWidget(advanced_button, 1, 0)
        run_layout.addWidget(self.adv_run_fields, 2, 1)
        run_layout.addWidget(self.run_button, 3, 0, 1, 3)
        run_layout.addWidget(self.stop_button, 4, 0, 1, 3)
        run_layout.addWidget(self.existing_button, 5, 0, 1, 3)
        run_fields.setLayout(run_layout)

        advanced_button.toggled.connect(self.toggle_advanced)
        self.run_button.clicked.connect(self.run_clicked)
        self.stop_button.clicked.connect(self.stop_clicked)
        self.existing_button.clicked.connect(self.existing_clicked)

        return run_fields

    def get_login_username(self):
        try:
            user = getpass.getuser()
        except Exception:
            user = ""
        return user

    def refresh_chroot(self):
        workspaces = os.listdir(DataModel.g.CHROOT)
        self.workspaces_list.clear()
        for s in workspaces:
            self.workspaces_list.addItem(key=s)

    @pyqtSlot()
    def set_chroot(self):
        CHROOT = self.given_chroot_linedt.text()
        Config.update({"model": {"chroot": CHROOT}})
        logger.debug(f"Setting CHROOT to {CHROOT}")
        DataModel.g.CHROOT = CHROOT
        self.refresh_chroot()

    @pyqtSlot()
    def launch_data_loader(self):
        """Load the dialog box widget to select data with data preview window and ROI selection."""
        path = None
        int_h5_pth = None
        dialog = LoadDataDialog(self)
        result = dialog.exec_()
        self.roi_limits = None
        if result == QDialog.Accepted:
            path = dialog.winput.path.text()
            int_h5_pth = dialog.int_h5_pth.text()
            down_factor = dialog.downsample_spinner.value()
        if path and int_h5_pth:
            self.data_filepth_linedt.setText(path)
            self.h5_intpth_linedt.setText(int_h5_pth)
            self.downsample_spinner.setValue(down_factor)
            if dialog.roi_changed:
                self.roi_limits = tuple(map(str, dialog.get_roi_limits()))
                self.roi_fields.show()
                self.update_roi_fields_from_dialog()
            else:
                self.roi_fields.hide()

    def update_roi_fields_from_dialog(self):
        """Updates the ROI fields in the main window."""
        x_start, x_end, y_start, y_end, z_start, z_end = self.roi_limits
        self.xstart_roi_val.setText(x_start)
        self.xend_roi_val.setText(x_end)
        self.ystart_roi_val.setText(y_start)
        self.yend_roi_val.setText(y_end)
        self.zstart_roi_val.setText(z_start)
        self.zend_roi_val.setText(z_end)

    @pyqtSlot()
    def toggle_advanced(self):
        """Controls displaying/hiding the advanced run fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_run_fields.show()
        else:
            self.adv_run_fields.hide()

    @pyqtSlot()
    def toggle_ssh(self):
        """Controls displaying/hiding the SSH fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_ssh_fields.show()
        else:
            self.adv_ssh_fields.hide()

    @pyqtSlot()
    def create_workspace_clicked(self):
        """Performs checks and coordinates workspace creation on button press."""
        logger.debug("Creating workspace: ")
        # Set the path to the data file
        vol_path = Path(self.data_filepth_linedt.text())
        if not vol_path.is_file():
            err_str = f"No data file exists at {vol_path}!"
            logger.error(err_str)
            self.button_feedback_response(err_str,
                                          self.create_workspace_button,
                                          "maroon")
        else:
            self.workspace_config["datasets_dir"] = str(vol_path.parent)
            self.workspace_config["vol_fname"] = str(vol_path.name)
            dataset_name = self.h5_intpth_linedt.text()
            self.workspace_config["dataset_name"] = str(dataset_name).strip(
                "/")
            # Set the workspace name
            ws_name = self.ws_name_linedt_1.text()
            self.workspace_config["workspace_name"] = ws_name
            # Set the downsample factor
            ds_factor = self.downsample_spinner.value()
            self.workspace_config["downsample_by"] = ds_factor
            # Set the ROI limits if they exist
            if self.roi_limits:
                self.workspace_config["roi_limits"] = self.roi_limits
            try:
                response = init_ws(self.workspace_config)
                _, error = response
                if not error:
                    self.button_feedback_response(
                        "Workspace created sucessfully",
                        self.create_workspace_button,
                        "green",
                    )
                    # Update the workspace name in the 'Run' section
                    self.ws_name_linedt_2.setText(self.ws_name_linedt_1.text())
            except WorkspaceException as e:
                logger.exception(e)
                self.button_feedback_response(str(e),
                                              self.create_workspace_button,
                                              "maroon")
            self.refresh_chroot()

    def button_feedback_response(self, message, button, colour_str, timeout=2):
        """Changes button colour and displays feedback message for a limited time period.

        Args:
            message (str): Message to display in button.
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            colour_str (str): The standard CSS colour string or hex code describing the colour to change the button to.
        """
        timeout *= 1000
        msg_old = button.text()
        col_old = button.palette().button().color
        txt_col_old = button.palette().buttonText().color
        button.setText(message)
        button.setStyleSheet(f"background-color: {colour_str}; color: white")
        timer = QTimer()
        timer.singleShot(
            timeout,
            lambda: self.reset_button(button, msg_old, col_old, txt_col_old))

    @pyqtSlot()
    def reset_button(self, button, msg_old, col_old, txt_col_old):
        """Sets a button back to its original display settings.

        Args:
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            msg_old (str): Message to display in button.
            col_old (str): The standard CSS colour string or hex code describing the colour to change the button to.
            txt_col_old (str): The standard CSS colour string or hex code describing the colour to change the button text to.
        """
        button.setStyleSheet(f"background-color: {col_old().name()}")
        button.setStyleSheet(f"color: {txt_col_old().name()}")
        button.setText(msg_old)
        button.update()

    @pyqtSlot()
    def output_config_clicked(self):
        """Outputs pipeline config YAML file on button click."""
        out_fname = "pipeline_cfg.yml"
        logger.debug(f"Outputting pipeline config: {out_fname}")
        with open(out_fname, "w") as outfile:
            yaml.dump(self.pipeline_config,
                      outfile,
                      default_flow_style=False,
                      sort_keys=False)

    def get_ssh_params(self):
        ssh_host = self.ssh_host_linedt.text()
        ssh_user = self.ssh_username_linedt.text()
        ssh_port = int(self.ssh_port_linedt.text())
        return ssh_host, ssh_user, ssh_port

    def start_server_over_ssh(self):
        params = self.get_ssh_params()
        if not all(params):
            logger.error(
                "Not all SSH parameters given! Not connecting to SSH.")
            pass
        ssh_host, ssh_user, ssh_port = params
        # Pop up dialog to ask for password
        text, ok = QInputDialog.getText(None, "Login",
                                        f"Password for {ssh_user}@{ssh_host}",
                                        QLineEdit.Password)
        if ok and text:
            self.ssh_worker = SSHWorker(params, text, self.run_config)
            self.ssh_thread = QThread(self)
            self.ssh_worker.moveToThread(self.ssh_thread)
            self.ssh_worker.button_message_signal.connect(
                self.send_msg_to_run_button)
            self.ssh_worker.error_signal.connect(self.on_ssh_error)
            self.ssh_worker.finished.connect(self.start_client)
            self.ssh_worker.update_ip_linedt_signal.connect(
                self.update_ip_linedt)
            self.ssh_thread.started.connect(
                self.ssh_worker.start_server_over_ssh)
            self.ssh_thread.start()

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            "Quit",
            "Are you sure you want to quit? "
            "The server will be stopped.",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    @pyqtSlot()
    def on_ssh_error(self):
        self.ssh_error = True

    @pyqtSlot(str)
    def update_ip_linedt(self, ip):
        self.server_ip_linedt.setText(ip)

    @pyqtSlot(list)
    def send_msg_to_run_button(self, param_list):
        self.button_feedback_response(param_list[0], self.run_button,
                                      param_list[1], param_list[2])

    @pyqtSlot()
    def stop_clicked(self):
        logger.debug("Stopping server")
        if self.server_process is not None:
            self.server_process.kill()

    @pyqtSlot()
    def run_clicked(self):
        """Starts SuRVoS2 server and client as subprocesses when 'Run' button pressed.

        Raises:
            Exception: If survos.py not found.
        """
        with progress(total=3) as pbar:
            pbar.set_description("Starting server...")
            pbar.update(1)

        self.ssh_error = (
            False  # Flag which will be set to True if there is an SSH error
        )
        command_dir = os.path.abspath(os.path.dirname(__file__))  # os.getcwd()

        # Set current dir to survos root
        from pathlib import Path

        command_dir = Path(
            command_dir).absolute().parent.parent.parent.resolve()
        os.chdir(command_dir)

        self.script_fullname = os.path.join(command_dir, "survos.py")
        if not os.path.isfile(self.script_fullname):
            raise Exception("{}: Script not found".format(
                self.script_fullname))
        # Retrieve the parameters from the fields TODO: Put some error checking in
        self.run_config["workspace_name"] = self.ws_name_linedt_2.text()
        self.run_config["server_port"] = self.server_port_linedt.text()
        # Temporary measure to check whether the workspace exists or not
        full_ws_path = os.path.join(Config["model.chroot"],
                                    self.run_config["workspace_name"])
        if not os.path.isdir(full_ws_path):
            logger.error(
                f"No workspace can be found at {full_ws_path}, Not starting SuRVoS."
            )
            self.button_feedback_response(
                f"Workspace {self.run_config['workspace_name']} does not appear to exist!",
                self.run_button,
                "maroon",
            )
            return
        pbar.update(1)
        # Try some fancy SSH stuff here
        if self.ssh_button.isChecked():
            self.start_server_over_ssh()
        else:
            self.server_process = subprocess.Popen([
                "python",
                self.script_fullname,
                "start_server",
                self.run_config["workspace_name"],
                self.run_config["server_port"],
                DataModel.g.CHROOT,
            ])
            try:
                outs, errs = self.server_process.communicate(timeout=10)
                print(f"OUTS: {outs, errs}")
            except subprocess.TimeoutExpired:
                pass

            # self.start_client()
            logger.info(f"setting remote: {self.server_port_linedt.text()}")
            remote_ip_port = "127.0.0.1:" + self.server_port_linedt.text()
            logger.info(f"setting remote: {remote_ip_port}")
            resp = Launcher.g.set_remote(remote_ip_port)
            logger.info(f"Response from server to setting remote: {resp}")

            cfg.ppw.clientEvent.emit({
                "source": "server_tab",
                "data": "set_workspace",
                "workspace": self.ws_name_linedt_2.text(),
            })
            cfg.ppw.clientEvent.emit({
                "source": "panel_gui",
                "data": "refresh",
                "value": None
            })
            #cfg.ppw.clientEvent.emit({'data' : 'view_feature', 'feature_id' : '001_raw'})
        pbar.update(1)

    @pyqtSlot()
    def existing_clicked(self):
        ssh_ip = self.server_ip_linedt.text()
        remote_ip_port = ssh_ip + ":" + self.server_port_linedt.text()
        logger.info(f"setting remote: {remote_ip_port}")
        resp = Launcher.g.set_remote(remote_ip_port)
        logger.info(f"Response from server to setting remote: {resp}")

        cfg.ppw.clientEvent.emit({
            "source": "server_tab",
            "data": "set_workspace",
            "workspace": self.ws_name_linedt_2.text(),
        })
        cfg.ppw.clientEvent.emit({
            "source": "panel_gui",
            "data": "refresh",
            "value": None
        })

    def start_client(self):
        if not self.ssh_error:
            self.button_feedback_response("Starting Client.", self.run_button,
                                          "green", 7)
            self.run_config["server_ip"] = self.server_ip_linedt.text()
            self.client_process = subprocess.Popen([
                "python",
                self.script_fullname,
                "nu_gui",
                self.run_config["workspace_name"],
                str(self.run_config["server_ip"]) + ":" +
                str(self.run_config["server_port"]),
            ])
Exemplo n.º 2
0
class MorphologyLauncher(QWidget):

    def __init__(self, parent):
        super().__init__()
        self.setWindowTitle(config.thisTranslation["cp7"])
        self.parent = parent
        self.bookList = BibleBooks.getStandardBookAbbreviations()
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()
        subLayout = QHBoxLayout()
        subLayout.addWidget(self.searchFieldWidget())
        button = QPushButton("Search")
        button.clicked.connect(self.searchMorphology)
        subLayout.addWidget(button)
        mainLayout.addLayout(subLayout)

        subLayout = QHBoxLayout()
        subLayout.addWidget(QLabel("Start:"))
        self.startBookCombo = QComboBox()
        subLayout.addWidget(self.startBookCombo)
        self.startBookCombo.addItems(self.bookList)
        self.startBookCombo.setCurrentIndex(0)
        subLayout.addWidget(QLabel("End:"))
        self.endBookCombo = QComboBox()
        subLayout.addWidget(self.endBookCombo)
        self.endBookCombo.addItems(self.bookList)
        self.endBookCombo.setCurrentIndex(65)
        subLayout.addWidget(QLabel(" "))
        button = QPushButton("Entire Bible")
        button.clicked.connect(lambda: self.selectBookCombos(0, 65))
        subLayout.addWidget(button)
        button = QPushButton("Current Book")
        button.clicked.connect(lambda: self.selectBookCombos(config.mainB-1, config.mainB-1))
        subLayout.addWidget(button)
        button = QPushButton("OT")
        button.clicked.connect(lambda: self.selectBookCombos(0, 38))
        subLayout.addWidget(button)
        button = QPushButton("NT")
        button.clicked.connect(lambda: self.selectBookCombos(39, 65))
        subLayout.addWidget(button)
        button = QPushButton("Gospels")
        button.clicked.connect(lambda: self.selectBookCombos(39, 42))
        subLayout.addWidget(button)
        subLayout.addStretch()
        mainLayout.addLayout(subLayout)

        subLayout = QHBoxLayout()
        self.searchTypeBox = QGroupBox("Type")
        layout = QVBoxLayout()
        self.strongsRadioButton = QRadioButton("Lexical")
        self.strongsRadioButton.setToolTip("G2424")
        self.strongsRadioButton.toggled.connect(lambda checked, mode="Lexical": self.searchTypeChanged(checked, mode))
        self.strongsRadioButton.setChecked(True)
        layout.addWidget(self.strongsRadioButton)
        radioButton = QRadioButton("Word")
        radioButton.setToolTip("Ἰησοῦς")
        radioButton.toggled.connect(lambda checked, mode="Word": self.searchTypeChanged(checked, mode))
        layout.addWidget(radioButton)
        radioButton = QRadioButton("Gloss")
        radioButton.setToolTip("Jesus")
        radioButton.toggled.connect(lambda checked, mode="Gloss": self.searchTypeChanged(checked, mode))
        layout.addWidget(radioButton)
        # radioButton = QRadioButton("Transliteration")
        # radioButton.setToolTip("Iēsous")
        # radioButton.toggled.connect(lambda checked, mode="Transliteration": self.searchTypeChanged(checked, mode))
        # layout.addWidget(radioButton)
        self.searchTypeBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.searchTypeBox)

        languageList = ["Greek", "Hebrew"]
        self.languageBox = QGroupBox("Language")
        layout = QVBoxLayout()
        for count, lang in enumerate(languageList):
            button = QRadioButton(lang)
            button.toggled.connect(lambda checked, mode=lang: self.languageChanged(checked, mode))
            layout.addWidget(button)
            if count == 0:
                self.greekButton = button
        self.languageBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.languageBox)

        posList = ["Adjective", "Adverb", "Article", "Conjunction", "Interjection", "Noun", "Participle", "Preposition", "Pronoun", "Verb", ""]
        self.posCheckBoxes = []
        self.partOfSpeechBox = QGroupBox("Part of speech")
        layout = QVBoxLayout()
        for count, pos in enumerate(posList):
            button = QRadioButton(pos)
            button.toggled.connect(lambda checked, mode=pos: self.partOfSpeechChanged(checked, mode))
            layout.addWidget(button)
            if count == 0:
                self.nounButton = button
        self.partOfSpeechBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.partOfSpeechBox)

        greekCaseList = ["Accusative", "Dative", "Genitive", "Nominative", "Vocative"]
        self.greekCaseCheckBoxes = []
        self.greekCaseBox = QGroupBox("Case")
        self.greekCaseBox.hide()
        layout = QVBoxLayout()
        for case in greekCaseList:
            checkbox = QCheckBox(case)
            layout.addWidget(checkbox)
            self.greekCaseCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, case=case: self.checkBoxChanged(checked, case, self.greekCaseCheckBoxes))
        self.greekCaseBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.greekCaseBox)

        greekTenseList = ["Aorist", "Future", "Imperfect", "Perfect", "Pluperfect", "Present"]
        self.greekTenseCheckBoxes = []
        self.greekTenseBox = QGroupBox("Tense")
        self.greekTenseBox.hide()
        layout = QVBoxLayout()
        for tense in greekTenseList:
            checkbox = QCheckBox(tense)
            layout.addWidget(checkbox)
            self.greekTenseCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, tense=tense: self.checkBoxChanged(checked, tense, self.greekTenseCheckBoxes))
        self.greekTenseBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.greekTenseBox)

        voiceList = ["Active", "Middle", "Passive"]
        self.voiceCheckBoxes = []
        self.voiceBox = QGroupBox("Voice")
        self.voiceBox.hide()
        layout = QVBoxLayout()
        for voice in voiceList:
            checkbox = QCheckBox(voice)
            layout.addWidget(checkbox)
            self.voiceCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, voice=voice: self.checkBoxChanged(checked, voice, self.voiceCheckBoxes))
        self.voiceBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.voiceBox)

        greekMoodList = ["Imperative", "Indicative", "Optative", "Subjunctive"]
        self.greekMoodCheckBoxes = []
        self.greekMoodBox = QGroupBox("Mood")
        self.greekMoodBox.hide()
        layout = QVBoxLayout()
        for mood in greekMoodList:
            checkbox = QCheckBox(mood)
            layout.addWidget(checkbox)
            self.greekMoodCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, mood=mood: self.checkBoxChanged(checked, mood, self.greekMoodCheckBoxes))
        self.greekMoodBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.greekMoodBox)

        hebrewStateList = ["Construct", "Absolute"]
        self.hebrewStateCheckBoxes = []
        self.hebrewStateBox = QGroupBox("State")
        self.hebrewStateBox.hide()
        layout = QVBoxLayout()
        for state in hebrewStateList:
            checkbox = QCheckBox(state)
            layout.addWidget(checkbox)
            self.hebrewStateCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, state=state: self.checkBoxChanged(checked, state, self.hebrewStateCheckBoxes))
        self.hebrewStateBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.hebrewStateBox)

        hebrewStateList = ["Construct", "Absolute"]
        self.hebrewStateCheckBoxes = []
        self.hebrewStateBox = QGroupBox("State")
        self.hebrewStateBox.hide()
        layout = QVBoxLayout()
        for stem in hebrewStateList:
            checkbox = QCheckBox(stem)
            layout.addWidget(checkbox)
            self.hebrewStateCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, stem=stem: self.checkBoxChanged(checked, stem, self.hebrewStateCheckBoxes))
        self.hebrewStateBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.hebrewStateBox)

        hebrewPatternList = ["Hif‘il", "Hitpa“el", "Nif‘al", "Pi“el", "Pu“al", "Qal", "Wayyiqtol"]
        self.hebrewPatternCheckBoxes = []
        self.hebrewPatternBox = QGroupBox("State")
        self.hebrewPatternBox.hide()
        layout = QVBoxLayout()
        for stem in hebrewPatternList:
            checkbox = QCheckBox(stem)
            layout.addWidget(checkbox)
            self.hebrewPatternCheckBoxes.append(checkbox)
            # checkbox.stateChanged.connect(lambda checked, stem=stem: self.checkBoxChanged(checked, stem, self.hebrewPatternCheckBoxes))
        self.hebrewPatternBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.hebrewPatternBox)

        personList = ["First", "Second", "Third"]
        self.personCheckBoxes = []
        self.personBox = QGroupBox("Person")
        self.personBox.hide()
        layout = QVBoxLayout()
        for person in personList:
            checkbox = QCheckBox(person)
            layout.addWidget(checkbox)
            self.personCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, person=person: self.checkBoxChanged(checked, person, self.personCheckBoxes))
        self.personBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.personBox)

        numberList = ["Singular", "Plural", "Dual"]
        self.numberCheckBoxes = []
        self.numberBox = QGroupBox("Number")
        self.numberBox.hide()
        layout = QVBoxLayout()
        for number in numberList:
            checkbox = QCheckBox(number)
            layout.addWidget(checkbox)
            self.numberCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, number=number: self.checkBoxChanged(checked, number, self.numberCheckBoxes))
        self.numberBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.numberBox)

        genderList = ["Masculine", "Feminine", "Neuter"]
        self.genderCheckBoxes = []
        self.genderBox = QGroupBox("Gender")
        self.genderBox.hide()
        layout = QVBoxLayout()
        for gender in genderList:
            checkbox = QCheckBox(gender)
            layout.addWidget(checkbox)
            self.genderCheckBoxes.append(checkbox)
            checkbox.stateChanged.connect(lambda checked, gender=gender: self.checkBoxChanged(checked, gender, self.genderCheckBoxes))
        self.genderBox.setLayout(layout)
        layout.addStretch()
        subLayout.addWidget(self.genderBox)

        # TODO:
        hebrewList = ["Absolute", "",  "Infinitive", "Pronominal", ""]

        self.spacerBox1 = QGroupBox(" ")
        layout = QVBoxLayout()
        layout.addStretch()
        self.spacerBox1.setLayout(layout)
        subLayout.addWidget(self.spacerBox1)

        self.spacerBox2 = QGroupBox(" ")
        layout = QVBoxLayout()
        layout.addStretch()
        self.spacerBox2.setLayout(layout)
        subLayout.addWidget(self.spacerBox2)

        self.spacerBox3 = QGroupBox(" ")
        layout = QVBoxLayout()
        layout.addStretch()
        self.spacerBox3.setLayout(layout)
        subLayout.addWidget(self.spacerBox3)

        self.spacerBox4 = QGroupBox(" ")
        layout = QVBoxLayout()
        layout.addStretch()
        self.spacerBox4.setLayout(layout)
        subLayout.addWidget(self.spacerBox4)

        self.spacerBox5 = QGroupBox(" ")
        layout = QVBoxLayout()
        layout.addStretch()
        self.spacerBox5.setLayout(layout)
        subLayout.addWidget(self.spacerBox5)

        mainLayout.addLayout(subLayout)

        mainLayout.addStretch()
        self.setLayout(mainLayout)

        self.greekButton.setChecked(True)
        self.nounButton.setChecked(True)

    def selectBookCombos(self, start, end):
        self.startBookCombo.setCurrentIndex(start)
        self.endBookCombo.setCurrentIndex(end)

    def searchTypeChanged(self, checked, type):
        self.type = type

    def searchFieldWidget(self):
        self.searchField = QLineEdit()
        self.searchField.setClearButtonEnabled(True)
        self.searchField.setToolTip(config.thisTranslation["menu5_searchItems"])
        self.searchField.returnPressed.connect(self.searchMorphology)
        return self.searchField

    def checkBoxChanged(self, state, value, checkboxes):
        if int(state) > 0:
            for checkbox in checkboxes:
                if checkbox.isChecked() and value != checkbox.text():
                    checkbox.setChecked(False)

    def languageChanged(self, checked, language):
        if checked:
            self.language = language
        self.updateAllCheckboxes()

    def partOfSpeechChanged(self, checked, pos):
        if checked:
            self.pos = pos
        self.updateAllCheckboxes()

    def updateAllCheckboxes(self):
        self.genderBox.hide()
        self.numberBox.hide()
        self.greekCaseBox.hide()
        self.personBox.hide()
        self.greekTenseBox.hide()
        self.greekMoodBox.hide()
        self.voiceBox.hide()
        self.hebrewStateBox.hide()
        self.hebrewPatternBox.hide()
        self.spacerBox1.hide()
        self.spacerBox2.hide()
        self.spacerBox3.hide()
        self.spacerBox4.hide()
        self.spacerBox5.hide()
        if self.language == "Greek":
            if self.pos in ("Noun", "Adjective"):
                self.greekCaseBox.show()
                self.numberBox.show()
                self.genderBox.show()
                self.spacerBox4.show()
                self.spacerBox5.show()
            elif self.pos in ("Pronoun", "Article"):
                self.greekCaseBox.show()
                self.numberBox.show()
                self.genderBox.show()
                self.personBox.show()
                self.spacerBox5.show()
            elif self.pos == "Verb":
                self.greekTenseBox.show()
                self.voiceBox.show()
                self.greekMoodBox.show()
                self.personBox.show()
                self.numberBox.show()
            elif self.pos == "Adverb":
                self.greekCaseBox.show()
                self.numberBox.show()
                self.genderBox.show()
                self.spacerBox4.show()
                self.spacerBox5.show()
            else:
                self.spacerBox1.show()
                self.spacerBox2.show()
                self.spacerBox3.show()
                self.spacerBox4.show()
                self.spacerBox5.show()
        else:
            if self.pos in ("Noun", "Adjective", "Preposition", "Pronoun"):
                self.numberBox.show()
                self.genderBox.show()
                self.hebrewStateBox.show()
                self.spacerBox4.show()
                self.spacerBox5.show()
            elif self.pos in ("Verb", "Adverb"):
                self.personBox.show()
                self.genderBox.show()
                self.numberBox.show()
                self.hebrewStateBox.show()
                self.hebrewPatternBox.show()
            else:
                self.spacerBox1.show()
                self.spacerBox2.show()
                self.spacerBox3.show()
                self.spacerBox4.show()
                self.spacerBox5.show()

    def searchMorphology(self):
        searchTerm = self.searchField.text()
        if len(searchTerm) > 1:
            morphologyList = []
            morphologyList.append(self.pos)
            if self.language == "Greek":
                if self.pos in ("Noun", "Adjective"):
                    checkBoxList = [self.greekCaseCheckBoxes, self.numberCheckBoxes, self.genderCheckBoxes]
                elif self.pos == ("Pronoun", "Article"):
                    checkBoxList = [self.greekCaseCheckBoxes, self.numberCheckBoxes, self.genderCheckBoxes, self.personCheckBoxes]
                elif self.pos == "Verb":
                    checkBoxList = [self.greekTenseCheckBoxes, self.voiceCheckBoxes, self.greekMoodCheckBoxes, self.personCheckBoxes, self.numberCheckBoxes]
                elif self.pos == "Adverb":
                    checkBoxList = [self.greekCaseCheckBoxes, self.numberCheckBoxes, self.genderCheckBoxes]
                else:
                    checkBoxList = []
            else:
                if self.pos in ("Noun", "Adjective", "Preposition", "Pronoun"):
                    checkBoxList = [self.numberCheckBoxes, self.genderCheckBoxes, self.hebrewStateCheckBoxes]
                elif self.pos in ("Verb", "Adverb"):
                    checkBoxList = [self.personCheckBoxes, self.genderCheckBoxes, self.numberCheckBoxes, self.hebrewStateCheckBoxes, self.hebrewPatternCheckBoxes]
                else:
                    checkBoxList = []
            for checkBoxes in checkBoxList:
                for checkbox in checkBoxes:
                    if checkbox.isChecked():
                        morphologyList.append(checkbox.text())
            morphology = ",".join(morphologyList)
            startBook = self.startBookCombo.currentIndex() + 1
            endBook = self.endBookCombo.currentIndex() + 1
            if endBook < startBook:
                endBook = startBook
            if self.type == "Lexical":
                command = "SEARCHMORPHOLOGYBYLEX:::{0}:::{1}:::{2}-{3}".format(searchTerm, morphology, startBook, endBook)
            elif self.type == "Word":
                command = "SEARCHMORPHOLOGYBYWORD:::{0}:::{1}:::{2}-{3}".format(searchTerm, morphology, startBook, endBook)
            elif self.type == "Gloss":
                command = "SEARCHMORPHOLOGYBYGLOSS:::{0}:::{1}:::{2}-{3}".format(searchTerm, morphology, startBook, endBook)
            self.parent.runTextCommand(command)