Ejemplo n.º 1
0
 def load_pass_file(self, pass_file_object):
     """
     populates the window with pass_file data
     :param pass_file_object: a PassFile
     :return: None
     """
     self.clear()
     self.pass_file = pass_file_object
     password_field_label = QLabel('password')
     password_field = QLineEdit()
     password_field.setEchoMode(QLineEdit.EchoMode(2))
     password_field.setDisabled(True)
     copy_password_button = QPushButton('copy')
     copy_password_button.clicked.connect(self.copy_password)
     self.layout().addWidget(password_field_label, 0, 0)
     self.layout().addWidget(password_field, 0, 1)
     self.layout().addWidget(copy_password_button, 0, 2)
     password_field.setText(self.pass_file.password)
     for key, value in self.pass_file.attributes:
         current_grid_view_row = self.layout().rowCount()
         additional_field_label = QLabel(key)
         additional_field = QLineEdit()
         self.layout().addWidget(additional_field_label,
                                 current_grid_view_row + 1, 0)
         self.layout().addWidget(additional_field,
                                 current_grid_view_row + 1, 1)
         additional_field.setText(value)
     comment_browser_label = QLabel('comments')
     comment_browser = QTextBrowser()
     self.layout().addWidget(comment_browser_label,
                             self.layout().rowCount() + 1, 0)
     self.layout().addWidget(comment_browser, self.layout().rowCount(), 1)
     comment_browser.setPlainText(self.pass_file.comments)
Ejemplo n.º 2
0
 def createSourceSelect(self, init_text):
     filePath = QLineEdit()
     filePath.setPlaceholderText("Choose " + init_text + " folder")
     filePath.setDisabled(True)
     btn = QPushButton("Browse", self)
     if init_text == "source":
         btn.clicked.connect(lambda: self.getMultipleSelected(init_text))
     else:
         btn.clicked.connect(lambda: self.getSeletedFile(init_text))
     return filePath, btn
class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.threadpool = QThreadPool()

        self.sourcefile = QLineEdit()
        self.sourcefile.setDisabled(
            True)  # must use the file finder to select a valid file.

        self.file_select = QPushButton("Select CSV...")
        self.file_select.pressed.connect(self.choose_csv_file)

        self.generate_btn = QPushButton("Generate PDF")
        self.generate_btn.pressed.connect(self.generate)

        layout = QFormLayout()
        layout.addRow(self.sourcefile, self.file_select)
        layout.addRow(self.generate_btn)

        self.setLayout(layout)

    def choose_csv_file(self):
        filename, _ = QFileDialog.getOpenFileName(self,
                                                  "Select a file",
                                                  filter="CSV files (*.csv)")
        if filename:
            self.sourcefile.setText(filename)

    def generate(self):
        if not self.sourcefile.text():
            return  # If the field is empty, ignore.

        self.generate_btn.setDisabled(True)

        data = {
            'sourcefile': self.sourcefile.text(),
        }
        g = Generator(data)
        g.signals.finished.connect(self.generated)
        g.signals.error.connect(print)  # Print errors to console.
        self.threadpool.start(g)

    def generated(self):
        self.generate_btn.setDisabled(False)
        QMessageBox.information(self, "Finished", "PDFs have been generated")
Ejemplo n.º 4
0
class ClientRegister(QDialog):
    def __init__(self):
        super(ClientRegister, self).__init__()
        self.setFixedSize(300, 150)
        self.setWindowIcon(QIcon('icons/auth.png'))
        self.setWindowTitle('客户端注册')

        client = DbHelper.query_client()

        lbl_app_id = QLabel('APP ID', self)
        lbl_app_id.setGeometry(10, 20, 50, 26)
        lbl_app_id.setAlignment(Qt.AlignCenter)
        self.le_app_id = QLineEdit(self)
        self.le_app_id.setText(client[0] if client is not None else '')
        self.le_app_id.setGeometry(70, 20, 200, 26)
        self.le_app_id.setDisabled(True if client is not None else False)
        lbl_security = QLabel('密钥', self)
        lbl_security.setGeometry(10, 66, 50, 26)
        lbl_security.setAlignment(Qt.AlignCenter)
        self.le_security = QLineEdit(self)
        self.le_security.setEchoMode(QLineEdit.Password)
        self.le_security.setText(client[1] if client is not None else '')
        self.le_security.setGeometry(70, 66, 200, 26)
        self.le_security.setDisabled(True if client is not None else False)

        self.btn_save = QPushButton(self)
        self.btn_save.setText('已注册' if client is not None else '注册')
        self.btn_save.setDisabled(True if client is not None else False)
        self.btn_save.setGeometry(10, 110, 280, 30)
        self.btn_save.clicked.connect(self.register)

    def register(self):
        app_id = self.le_app_id.text().strip(' ')
        if app_id == '':
            self.btn_save.setText('请输入APP ID')
            self.le_app_id.setFocus()
            return
        security = self.le_security.text().strip(' ')
        if security == '':
            self.btn_save.setText('请输入密钥')
            self.le_security.setFocus()
            return
        self.btn_save.setDisabled(True)
        result, data = Tool.client_register(app_id, Tool.get_md5(security))
        if result:
            DbHelper.insert_client(data, app_id, security)
            self.le_app_id.setDisabled(True)
            self.le_security.setDisabled(True)
            self.btn_save.setText("注册成功")
        elif data is not None:
            self.btn_save.setDisabled(False)
            self.btn_save.setText(data)
        else:
            self.btn_save.setDisabled(False)
            self.btn_save.setText("注册失败")
Ejemplo n.º 5
0
class FolderPickerView(QWidget):
    """Provides a view which facilitates selecting the source folder where renamable files are found.

    Attributes:
        layout (QHBoxLayout): Main layout for view.
        label (QLabel): Label for file picker widget.
        base_dir (QLineEdit): LineEdit which displays location of where renamable files live.
        file_browser_btn (QPushButton): Button which, when pressed, presents user with a file browsers.
        refresh_btn (QPushButton): Button which, when pressed, Refreshes files in selected directory.
    """
    def __init__(self):
        super(FolderPickerView, self).__init__()
        self.layout = QHBoxLayout()
        self.label = QLabel(prefs.FILE_LOCATION)
        self.base_dir = QLineEdit(prefs.SELECT_DIR)
        self.file_browser_btn = QPushButton(prefs.OPEN_DIR)
        self.refresh_btn = QPushButton(prefs.REFRESH)
        self._configure()

    def _configure(self) -> None:
        """Configuration of FolderPickerView."""
        self.base_dir.setMinimumHeight(prefs.MIN_HEIGHT)
        self.base_dir.setDisabled(True)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.base_dir)
        self.layout.addWidget(self.file_browser_btn)
        self.layout.addWidget(self.refresh_btn)
        self.setLayout(self.layout)

    def get_base_dir(self) -> str:
        """Return path to basedir."""
        return str(self.base_dir.text())

    def get_base_dir_set(self) -> bool:
        """Return if the user has selected a base dir."""
        dir_path = self.get_base_dir()
        return dir_path and dir_path != prefs.SELECT_DIR

    def set_base_dir(self, dir_path: str) -> None:
        """Set the value of base dir.

        Args:
            dir_path : Value to add to the base dir field.
        """
        self.base_dir.setText(dir_path)
Ejemplo n.º 6
0
class LabelTextBox(QWidget):
    def __init__(self, label, orientation="V", read_only=True, line=False, *args, **kwargs):
        super(LabelTextBox, self).__init__(*args, **kwargs)
        self.layout = QVBoxLayout() if orientation == "V" else QHBoxLayout()
        self.label = QLabel(label)
        self.text_box = QLineEdit() if line else QTextEdit()
        self._read_only = read_only
        self._line_edit = line
        self._text = ""
        self._setup()

    def _setup(self):
        self.text_box.setReadOnly(self._read_only)
        if not self._line_edit:
            self.text_box.setFixedSize(400, 200)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.text_box)
        self.setLayout(self.layout)

    def text(self):
        return self.text_box.text()

    def set_text(self, text):
        self.text_box.setText(text)

    def init_update(self, data):
        print("Just joined? Let's set up all the data we know about")
        pass

    def update(self, data):
        print(f"Updating {self} with data - {data}")

    def setDisabled(self, disabled):
        self.text_box.setDisabled(disabled)

    def isEnabled(self):
        return self.text_box.isEnabled()
Ejemplo n.º 7
0
class QTagWidget(QWidget):
    def __init__(self, parent, items):
        super(QTagWidget, self).__init__()
        self.parent = parent
        self.items = items

        self.tags = []
        self.mainFrame = QFrame()
        self.mainFrame.setStyleSheet(
            'border:1px solid #76797C; border-radius: 1px;')

        self.mainLayout = QVBoxLayout()
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.mainLayout)
        self.mainLayout.addWidget(self.mainFrame)

        self.hLayout = QHBoxLayout()
        self.hLayout.setSpacing(4)

        self.lineEdit = QLineEdit()
        self.lineEdit.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)

        completer = QPartialMatchCompleter(self.lineEdit)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        self.lineEdit.setCompleter(completer)

        model = QStringListModel()
        completer.setModel(model)
        model.setStringList(self.items)

        self.mainFrame.setLayout(self.hLayout)

        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.hLayout.setContentsMargins(2, 2, 2, 2)

        self.refresh()

        self.setup_ui()

    def show(self):
        self.show()

    def setup_ui(self):
        self.lineEdit.returnPressed.connect(self.create_tags)

    def create_tags(self):
        new_tags = self.lineEdit.text().split(', ')
        self.lineEdit.setText('')
        self.tags.extend(new_tags)
        self.tags = list(set(self.tags))
        self.tags.sort(key=lambda x: x.lower())
        self.refresh()

    def refresh(self):
        for i in reversed(range(self.hLayout.count())):
            self.hLayout.itemAt(i).widget().setParent(None)
        for tag in self.tags:
            self.add_tag_to_bar(tag)
        self.hLayout.addWidget(self.lineEdit)
        self.lineEdit.setFocus()

        # Accept to add only 5 tags
        if len(self.tags) >= 5:
            self.lineEdit.setDisabled(True)
            return

    def add_tag_to_bar(self, text):
        tag = QFrame()
        tag.setStyleSheet(
            'border:1px solid rgb(192, 192, 192); border-radius: 4px;')
        tag.setContentsMargins(2, 2, 2, 2)
        tag.setFixedHeight(28)

        hbox = QHBoxLayout()
        hbox.setContentsMargins(4, 4, 4, 4)
        hbox.setSpacing(10)

        tag.setLayout(hbox)

        label = QLabel(text)
        label.setStyleSheet('border:0px')
        label.setFixedHeight(16)
        hbox.addWidget(label)

        x_button = QPushButton('x')
        x_button.setFixedSize(20, 20)
        x_button.setStyleSheet('border:0px; font-weight:bold')
        x_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        x_button.clicked.connect(partial(self.delete_tag, text))
        hbox.addWidget(x_button)

        tag.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)

        self.hLayout.addWidget(tag)

    def delete_tag(self, tag_name):
        self.tags.remove(tag_name)

        # Make input available if tags count is less than 5
        if len(self.tags) < 5:
            self.lineEdit.setDisabled(False)
        self.refresh()
Ejemplo n.º 8
0
class NetworkSettingsDialog(QDialog):
    def __init__(self, parent=None):
        super(NetworkSettingsDialog, self).__init__(parent)
        self.parent = parent
        self.setModal(True)
        self.setWindowTitle("Hálózati beállítások")
        self.resize(400, 300)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        adatok = QGridLayout()
        self.layout.addLayout(adatok)

        self.ip = QLineEdit()
        self.token = QLineEdit()
        self.station = QLineEdit()
        self.valtozott = False
        self.uj_station = False

        self.is_exist_config()
        self.check_config()

        adatok.addWidget(QLabel("IP cím: "), 0, 0)
        self.ip.setDisabled(True)
        adatok.addWidget(self.ip, 0, 1)

        adatok.addWidget(QLabel("Token: "), 1, 0)
        self.token.setDisabled(True)
        adatok.addWidget(self.token, 1, 1)
        adatok.addWidget(QLabel("Állomás azonosító: "), 2, 0)

        self.station.setDisabled(True)
        self.station.editingFinished.connect(self.change_name)
        adatok.addWidget(self.station, 2, 1)
        self.space = QSpacerItem(0, 0, QSizePolicy.Minimum,
                                 QSizePolicy.Expanding)
        self.layout.addItem(self.space)

        self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.buttonbox.addButton("Módosít", QDialogButtonBox.ActionRole)
        self.buttonbox.button(QDialogButtonBox.Ok).setText('Mentés')
        self.buttonbox.button(QDialogButtonBox.Ok).setDisabled(True)
        self.buttonbox.button(QDialogButtonBox.Cancel).setText('Mégsem')
        self.buttonbox.clicked.connect(self.buttonbox_click)
        self.layout.addWidget(self.buttonbox)

    def is_exist_config(self):
        ip_fizikai = get_ip_address()

        if os.path.exists('config.ini'):
            # Van config.ini, ki kell értékelni
            config.read('config.ini')

            station_id = config['DEFAULT'].get('station id')
            if (station_id is None) or len(station_id) < 4:
                self.valtozott = True
                msg = QMessageBox()
                msg.setStyleSheet("fonz-size: 20px")
                msg.setWindowTitle("Hibás állomás név!")
                msg.setText(
                    '<html style="font-size: 14px; color: red">Nem megfelelő állomás-azonosító!<br></html>'
                    +
                    '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>'
                )
                msg.exec_()
                station = secrets.token_hex(8)
                config.set('DEFAULT', 'station id', station)
                self.station.setText(station)

            ip_config = config['DEFAULT'].get('station ip')
            if ip_fizikai != ip_config:
                self.valtozott = True
                msg = QMessageBox()
                msg.setStyleSheet("fonz-size: 20px")
                msg.setWindowTitle("Hibás beállítás!")
                msg.setText(
                    '<html style="font-size: 14px; color: red">A fizikai IP cím eltér a konfigurációtól!<br></html>'
                    +
                    '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>'
                )
                msg.exec_()
                self.ip.setText(ip_fizikai)
                config.set('DEFAULT', 'station ip', ip_fizikai)

            secret = config['DEFAULT'].get('secret key')
            if (secret is None) or len(secret) != 32:
                self.valtozott = True
                newsecret = secrets.token_hex(16)
                config.set('DEFAULT', 'secret key', newsecret)
                self.token.setText(newsecret)

            with open('config.ini', 'w') as configfile:
                config.write(configfile)

        else:
            # Nincs config.ini, alapértékekkel inicializálni
            self.valtozott = True
            msg = QMessageBox()
            msg.setStyleSheet("fonz-size: 20px")
            msg.setWindowTitle("Hiányzó beállítás file!")
            msg.setText(
                '<html style="font-size: 14px; color: red">Nem tudtam beolvasni a konfigurációt!<br></html>'
                +
                '<html style="font-size: 16px">Kérem módosítsa a beállításokat!</html>'
            )
            msg.exec_()
            kulcs = secrets.token_hex(16)
            station = secrets.token_hex(8)
            self.token.setText(kulcs)
            self.ip.setText(ip_fizikai)
            self.station.setText(station)
            config.set('DEFAULT', 'secret key', kulcs)
            config.set('DEFAULT', 'station ip', ip_fizikai)
            config.set('DEFAULT', 'station id', secrets.token_hex(8))
            with open('config.ini', 'w') as configfile:
                config.write(configfile)

    def check_config(self):
        ip_config = config['DEFAULT'].get('station ip')
        station_id = config['DEFAULT'].get('station id')
        secret = config['DEFAULT'].get('secret key')
        self.ip.setText(ip_config)
        self.station.setText(station_id)
        self.token.setText(secret)
        # todo Ellenőrizni, hogy van-e, és mi van a db-ben
        model2 = QSqlQueryModel()
        query = QSqlQuery(
            f"SELECT * FROM reged_station where secret_key = '{secret}'",
            db=db)
        model2.setQuery(query)
        if model2.record(0).value(0):
            if self.station.text() != model2.record(0).value(
                    1) or self.ip.text() != model2.record(0).value(
                        2) or self.token.text() != model2.record(0).value(3):
                self.valtozott = True
                self.rec_id = model2.record(0).value(0)
        else:
            self.uj_station = True

    def change_name(self):
        self.valtozott = True

    def buttonbox_click(self, b):
        if b.text() == "Mentés":
            self.accept()
        elif b.text() == "Módosít":
            self.modify()
        else:
            self.reject()

    def modify(self):
        self.buttonbox.button(QDialogButtonBox.Ok).setDisabled(False)
        self.station.setDisabled(False)

    def accept(self):
        if self.uj_station:
            now = QDateTime.currentDateTime()
            network = QSqlTableModel()
            network.setTable("reged_station")
            rec_net = network.record()
            rec_net.remove(0)
            rec_net.setValue(0, self.station.text())
            rec_net.setValue(1, self.ip.text())
            rec_net.setValue(2, self.token.text())
            rec_net.setValue(3, now)
            if network.insertRecord(-1, rec_net):
                network.submitAll()
            else:
                db.rollback()
            self.uj_station = False

        if self.valtozott:
            # Módosítani a db-t és a config.ini-t
            # config.ini update
            config.set('DEFAULT', 'secret key', self.token.text())
            config.set('DEFAULT', 'station ip', self.ip.text())
            config.set('DEFAULT', 'station id', self.station.text())
            with open('config.ini', 'w') as configfile:
                config.write(configfile)
            # db update
            now = QDateTime.currentDateTime().toString(
                "yyyy-MM-ddThh:mm:ss.sss")
            model3 = QSqlQueryModel()
            query3 = QSqlQuery(
                f"update reged_station set station_id='{self.station.text()}', station_ip='{self.ip.text()}', timestamp='{now}' where secret_key='{self.token.text()}'",
                db=db)
            model3.setQuery(query3)
            self.valtozott = False
        super().accept()

    def reject(self):
        # Ha valami db művelet kell
        if self.valtozott or self.uj_station:
            msg = QMessageBox()
            msg.setStyleSheet("fonz-size: 20px")
            msg.setWindowTitle("Regisztrációs eltérés!")
            msg.setText(
                '<html style="font-size: 14px; color: red">Kérem módosítás után mentse a beállításokat!<br></html>'
            )
            msg.exec_()
        else:
            super().reject()


#
# if __name__ == '__main__':
#     app = QApplication([])
#     win = NetworkSettingsDialog()
#     win.show()
#     app.exec_()
Ejemplo n.º 9
0
class UpdatePrompt(QDialog):
    def __init__(self):
        super().__init__()
        self.makeView()
        return

    def makeView(self):
        layout = QVBoxLayout()
        btnLayout = QHBoxLayout()
        self.centStack = QStackedWidget()
        self.updateButton = QPushButton('Update')
        self.cancelButton = QPushButton('Cancel')
        notifyLabel = QLabel('There are upgrades scheduled')
        self.inputBox = QLineEdit()
        self.outputBox = QTextBrowser()
        #refreshIcon = QIcon.fromTheme('process-working')
        self.refreshIcon = QMovie('assets/spin3.gif')
        refreshAnimation = QLabel()

        layout.addWidget(notifyLabel)
        layout.addWidget(self.centStack)
        layout.addWidget(self.inputBox)
        layout.addLayout(btnLayout)
        btnLayout.addWidget(self.cancelButton)
        btnLayout.addWidget(self.updateButton)

        self.centStack.addWidget(refreshAnimation)
        self.centStack.addWidget(self.outputBox)
        refreshAnimation.setMovie(self.refreshIcon)
        refreshAnimation.setAlignment(Qt.AlignCenter)
        self.refreshIcon.start()

        self.inputBox.setEchoMode(QLineEdit.Password)
        self.inputBox.setFocus()
        self.inputBox.returnPressed.connect(self.pkgUpdates)
        self.updateButton.clicked.connect(self.pkgUpdates)
        self.cancelButton.clicked.connect(self.cancelUpdates)
        self.updateButton.setDefault(True)

        self.centStack.setCurrentIndex(1)
        notifyLabel.setAlignment(Qt.AlignTop)
        self.outputBox.setReadOnly(True)
        #self.outputBox.setAlignment(Qt.AlignTop)
        self.setWindowTitle('Package Upgrades')
        self.setLayout(layout)
        self.resize(450, 250)
        return

    async def asetup(self, password):
        async with trio.open_nursery() as nursery:
            finishedState = trio.Event()
            nursery.start_soon(self.upProc, password, 'update', finishedState)
            #nursery.start_soon(self.KEAlive, finishedState)
        return

    async def upProc(self, password, cmd, finishedState):
        proc = await trio.open_process(['sudo', '-S', 'apt-get', cmd, '-y'],
                                       stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.STDOUT)
        await proc.stdin.send_all((password + '\n').encode())

        while (proc.poll() == None):
            QCoreApplication.processEvents()
            await trio.sleep(0.1)

        result = ''
        result = await self.pullOutput(proc)
        self.appendToOutput(result)
        proc.terminate()

        if (cmd == 'update'):
            await self.upProc(password, 'upgrade', finishedState)
            finishedState.set()
        return

    async def pullOutput(self, proc):
        x = await proc.stdout.receive_some()
        x = x.decode()
        result = ''
        while (x != ''):
            QCoreApplication.processEvents()
            result = result + x
            x = await proc.stdout.receive_some()
            x = x.decode()
        return result

    async def KEAlive(self, finishedState):
        while finishedState.is_set():
            QCoreApplication.processEvents()
            trio.sleep(0.1)
        return

        return

    def appendToOutput(self, add):
        currentText = self.outputBox.toPlainText()
        self.outputBox.setText(currentText + 'Running updates\n' + add + '\n')
        print(add)
        return

    def pkgUpdates(self):
        self.centStack.setCurrentIndex(0)
        self.refreshIcon.start()
        QCoreApplication.processEvents()

        password = self.inputBox.text()

        if (password == ''):
            self.passError('The password field cannot be empty')
            return

        self.inputBox.clear()
        self.inputBox.setDisabled(True)
        self.updateButton.setDisabled(True)
        trio.run(self.asetup, password)
        self.centStack.setCurrentIndex(1)
        self.refreshIcon.stop()
        self.updateButton.setDisabled(False)
        self.inputBox.setDisabled(False)
        return

    def passError(self, s):
        passError = QDialog(self)
        msg = QLabel(s)
        layout = QVBoxLayout()
        layout.addWidget(msg)
        passError.setLayout(layout)

        okBtn = QPushButton('OK')
        okBtn.clicked.connect(passError.reject)
        layout.addWidget(okBtn)

        passError.exec_()
        return

    def cancelUpdates(self):
        #Needs way of closing subprocess during async run
        self.reject()
        return
Ejemplo n.º 10
0
class LimitOptionsView(QWidget):
    """View which displays options for limiting they type of files to work with.

    Attributes:
        layout (QVBoxLayout): Main layout for view.
        frame (QFrame): Frame around limit options.
        frame_layout (QHBoxLayout): Layout associated with frame.
        title (QLabel): Title associated with view.
        limit_type_cb (QCheckBox): CheckBox which controls if view should be limited by file type.
        limit_type (QLineEdit): File type to limit to.
        search_cb (QCheckBox): CheckBox which controls if the view should limit by a search word.
        search (QLineEdit): Word to search for when renaming.
    """
    def __init__(self):
        super(LimitOptionsView, self).__init__()
        self.layout = QVBoxLayout()
        self.title = QLabel(prefs.TITLE)
        self.frame_layout = QHBoxLayout()
        self.frame = QFrame()
        self.limit_type_cb = QCheckBox(prefs.LIMIT)
        self.limit_type = QLineEdit(prefs.LIMIT_DEFAULT)
        self.search_cb = QCheckBox(prefs.SEARCH)
        self.search = QLineEdit(prefs.SEARCH_DEFAULT)

        self._configure()

    def _configure(self) -> None:
        """Configure LimitOptionsView."""
        self.frame.setLayout(self.frame_layout)
        self.layout.setAlignment(Qt.AlignLeft)
        self.limit_type_cb.setToolTip(prefs.LIMIT_TOOLTIP)
        self.search_cb.setToolTip(prefs.SEARCH_TOOLTIP)

        self.frame_layout.addWidget(self.limit_type_cb)
        self.frame_layout.addWidget(self.limit_type)
        self.frame_layout.addSpacerItem(QSpacerItem(*prefs.ITEM_SPACING))
        self.frame_layout.addWidget(self.search_cb)
        self.frame_layout.addWidget(self.search)
        self.layout.addWidget(self.title)
        self.layout.addWidget(self.frame)
        self.setLayout(self.layout)

    def disable_limit_type(self) -> None:
        """Disable limit type functionality."""
        self.limit_type.setDisabled(True)

    def disable_search(self) -> None:
        """Disable search functionality."""
        self.search.setDisabled(True)

    def enable_limit_type(self) -> None:
        """Enable limit type functionality."""
        self.limit_type.setEnabled(True)

    def enable_search(self) -> None:
        """Enable limit type functionality."""
        self.limit_type.setEnabled(True)

    def get_limit_type(self) -> str:
        """Return the limit type."""
        return str(self.limit_type.text())

    def get_do_limit_type(self) -> bool:
        """Return if end user wants to limit the file type."""
        return self.limit_type_cb.isChecked()

    def get_search(self) -> str:
        """Return the search value."""
        return str(self.search.text())

    def get_do_search(self) -> bool:
        """Return if the end user wants to limit files by a search."""
        return self.search_cb.isChecked()

    def set_disabled(self) -> None:
        """Disable View."""
        self.setDisabled(True)

    def set_enable(self) -> None:
        """Enable View."""
        self.setEnabled(True)

    def set_limit_type(self, value: str) -> None:
        """Set the value in the limit type."""
        self.limit_type.setText(value)

    def set_limit_type_style(self, style: str) -> None:
        """Set the style applied to limit type."""
        self.limit_type.setStyleSheet(style)

    def set_search(self, value: str) -> None:
        """Set the value in the search."""
        self.search.setText(value)

    def set_search_style(self, style: str) -> None:
        """Set the style applied to search."""
        self.search.setStyleSheet(style)
Ejemplo n.º 11
0
class DataProcessingDialog(QDialog):
    '''
    Signals
    '''
    newDataAdded = Signal(str, str, str)
    dataChanged = Signal(str, str, str)

    def __init__(self, dataManager, parent=None, inputList=None):
        self.operations = {
            'Filtering': [FilterGroup, 'Filter settings', -1],
            'Average': [AverageGroup, 'Window settings', -1],
            'Energy': [EnergyGroup, 'Window settings', -1],
            'Power': [PowerGroup, 'Window settings', -1],
            'Peak-To-Peak': [Peak2PeakGroup, 'Window settings', -1],
            'Variance': [VarianceGroup, 'Window settings', -1],
            'Entropy': [EntropyGroup, 'Window settings', -1],
            'Skewness': [SkewnessGroup, 'Window settings', -1],
            'Thresholding': [ThresholdGroup, 'Settings', -1],
            'Detrend': [DetrendGroup, 'Settings', -1],
            'STFT': [STFTGroup, 'Spectrum settings', -1],
            'CWT': [CWTGroup, 'CWT settings', -1]
        }
        QDialog.__init__(self, parent)
        self.dataManager = dataManager
        self.setWindowTitle('Data Processing')
        self.inputList = inputList

        #Setup Layouts
        self.mainLayout = QGridLayout(self)
        self.setupInLayout()
        self.setupProcessLayout()
        self.setupOutLayout()
        self.setupBtnBoxLayout()

    '''
    Input Layout
    '''

    def setupInLayout(self):
        inGroup = QGroupBox('Input to process')
        inLayout = QFormLayout(inGroup)

        self.inTree = DataSelector(self.dataManager, inputList=self.inputList)

        self.operChooser = QComboBox()
        [self.operChooser.addItem(i) for i in self.operations]
        self.operChooser.currentTextChanged.connect(self.setOperation)
        inLayout.addRow(QLabel('Select Input'))
        inLayout.addRow(self.inTree)
        inLayout.addRow(QLabel('Operation'), self.operChooser)

        self.mainLayout.addWidget(inGroup, 0, 0)

    def setOperation(self):
        print('Set Operation')
        index = self.operations[self.operChooser.currentText()][2]
        self.processLayout.setCurrentIndex(index)

    '''
    Signal Processing Settings Layout
    '''

    def setupProcessLayout(self):
        processGroup = QGroupBox('Processing settings')
        self.processLayout = QStackedLayout()
        self.mainLayout.addLayout(self.processLayout, 1, 0)

        # Setup Processing Sublayouts
        for op in self.operations:
            index = self.createGroup(self.operations[op][0], op,
                                     self.operations[op][1])
            self.operations[op][2] = index

    '''
    Create Groups
    '''

    def createGroup(self, GroupClass, name, title):
        newGroup = GroupClass(title, fs=self.dataManager.getFs())
        newGroup.progress.connect(self.updateProgress)
        index = self.processLayout.addWidget(newGroup)
        return index

    '''
    Output Layout
    '''

    def setupOutLayout(self):
        outGroup = QGroupBox('Output')
        outLayout = QFormLayout(outGroup)
        self.outNameEdit = QLineEdit()
        inAsOutCheck = QCheckBox('Replace input')
        inAsOutCheck.toggled.connect(self.setInputAsOutput)
        outLayout.addWidget(inAsOutCheck)
        outLayout.addRow('Output name', self.outNameEdit)
        self.mainLayout.addWidget(outGroup, 2, 0)

    def setInputAsOutput(self, isOn):
        if isOn:
            inStruct = self.inTree.getSelectedStruct()
            wName = list(inStruct.keys())[0]
            gName = list(inStruct[wName].keys())[0]
            self.outNameEdit.setText(gName)
            self.outNameEdit.setDisabled(True)
        else:
            self.outNameEdit.setEnabled(True)

    '''
    Button Box Layout
    '''

    def setupBtnBoxLayout(self):
        bottomLayout = QHBoxLayout()
        self.progBar = QProgressBar()
        self.progBar.setVisible(False)
        bottomLayout.addWidget(self.progBar)
        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Close)
        buttonBox.accepted.connect(self.okBtnBox)
        buttonBox.rejected.connect(self.close)
        bottomLayout.addWidget(buttonBox)
        self.mainLayout.addLayout(bottomLayout, 3, 0)

    def okBtnBox(self):
        inStruct = self.inTree.getSelectedStruct()
        data = self.dataManager.getData(inStruct, inv=True)
        wName = list(data.keys())[0]
        gName = list(data[wName].keys())[0]
        outName = self.outNameEdit.text()

        if outName in self.dataManager[wName].getColumnNames():
            msgBox = QMessageBox()
            msgBox.setText('Signal already exists')
            msgBox.setInformativeText("Do you want to replace it?")
            msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            msgBox.setDefaultButton(QMessageBox.Ok)
            ret = msgBox.exec()
            if ret == QMessageBox.Ok:
                self.dataManager.removeSignal(wName, outName)
                print('removed')
            else:
                return

        self.progBar.setVisible(True)
        outData = self.processLayout.currentWidget().process(
            data[wName][gName])
        self.dataManager.appendSignal(wName, outName, inStruct[wName][gName],
                                      outData)
        self.newDataAdded.emit(wName, gName, self.outNameEdit.text())

    def updateProgress(self, prog):
        self.progBar.setValue(prog)
Ejemplo n.º 12
0
class ClientLogin(QDialog):
    def __init__(self):
        super(ClientLogin, self).__init__()
        self.setFixedSize(300, 180)
        self.setWindowIcon(QIcon('icons/auth.png'))
        self.setWindowTitle('服务连接')

        client = DbHelper.query_client()

        lbl_app_id = QLabel('APP ID', self)
        lbl_app_id.setGeometry(10, 20, 50, 26)
        lbl_app_id.setAlignment(Qt.AlignCenter)
        self.le_app_id = QLineEdit(self)
        self.le_app_id.setText(client[1] if client is not None else '')
        self.le_app_id.setGeometry(70, 20, 200, 26)
        self.le_app_id.setDisabled(True if client is not None else False)
        lbl_security = QLabel('密钥', self)
        lbl_security.setGeometry(10, 66, 50, 26)
        lbl_security.setAlignment(Qt.AlignCenter)
        self.le_security = QLineEdit(self)
        self.le_security.setEchoMode(QLineEdit.Password)
        self.le_security.setText(client[2] if client is not None else '')
        self.le_security.setGeometry(70, 66, 200, 26)
        self.le_security.setDisabled(True if client is not None else False)

        self.btn_login = QPushButton(self)
        self.btn_login.setText('已连接' if client is not None else '连接')
        self.btn_login.setDisabled(True if client is not None else False)
        self.btn_login.setGeometry(10, 110, 280, 30)
        self.btn_login.clicked.connect(self.login)

        self.btn_logout = QPushButton(self)
        self.btn_logout.setText('注销' if client is not None else '未连接')
        self.btn_logout.setDisabled(True if client is None else False)
        self.btn_logout.setGeometry(10, 140, 280, 30)
        self.btn_logout.clicked.connect(self.logout)

    def login(self):
        app_id = self.le_app_id.text().strip(' ')
        if app_id == '':
            self.btn_login.setText('请输入APP ID')
            self.le_app_id.setFocus()
            return
        security = self.le_security.text().strip(' ')
        if security == '':
            self.btn_login.setText('请输入密钥')
            self.le_security.setFocus()
            return
        self.btn_login.setDisabled(True)
        result, data = Tool.client_login(app_id, Tool.get_md5(security))
        if result:
            DbHelper.insert_client(data, app_id, security)
            self.btn_login.setText("已连接")
            self.le_app_id.setDisabled(True)
            self.le_security.setDisabled(True)
            self.btn_logout.setDisabled(False)
            self.btn_logout.setText("注销")
            self.btn_logout.setFocus()
        else:
            self.btn_login.setDisabled(False)
            self.btn_login.setText("APP ID或者密钥不正确")

    def logout(self):
        self.btn_logout.setDisabled(True)
        self.btn_logout.setText("未连接")
        self.btn_login.setDisabled(False)
        self.btn_login.setText("连接")
        self.le_app_id.setDisabled(False)
        self.le_security.setDisabled(False)
        DbHelper.delete_client()
Ejemplo n.º 13
0
class DownloadWindow(QDialog):
    def __init__(self,
                 parent: Optional[QWidget] = None,
                 url: str = '') -> None:
        super().__init__(parent, )

        if parent:
            self.setWindowTitle('Download Mod')
        else:
            self.setWindowTitle(getTitleString('Download Mod'))
            self.setAttribute(Qt.WA_DeleteOnClose)

        mainLayout = QVBoxLayout(self)

        self.signals = DownloadWindowEvents(self)

        # URL input

        gbUrl = QGroupBox('Mod URL')
        gbUrlLayout = QVBoxLayout()
        gbUrl.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.url = QLineEdit()
        self.url.setPlaceholderText(
            'https://www.nexusmods.com/witcher3/mods/...')
        self.url.setText(url)
        self.url.textChanged.connect(lambda: self.validateUrl(self.url.text()))
        gbUrlLayout.addWidget(self.url)

        self.urlInfo = QLabel('🌐')
        self.urlInfo.setContentsMargins(4, 4, 4, 4)
        self.urlInfo.setMinimumHeight(36)
        self.urlInfo.setWordWrap(True)
        gbUrlLayout.addWidget(self.urlInfo)

        gbUrl.setLayout(gbUrlLayout)
        mainLayout.addWidget(gbUrl)

        # File selection

        gbFiles = QGroupBox('Mod Files')
        gbFilesLayout = QVBoxLayout()
        gbFiles.setSizePolicy(QSizePolicy.MinimumExpanding,
                              QSizePolicy.MinimumExpanding)

        self.files = QTableWidget(0, 4)
        self.files.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.files.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.files.setContextMenuPolicy(Qt.CustomContextMenu)
        self.files.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.files.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.files.setWordWrap(False)
        self.files.setSortingEnabled(True)
        self.files.setFocusPolicy(Qt.StrongFocus)
        self.files.verticalHeader().hide()
        self.files.setSortingEnabled(True)
        self.files.sortByColumn(2, Qt.DescendingOrder)
        self.files.verticalHeader().setVisible(False)
        self.files.verticalHeader().setDefaultSectionSize(25)
        self.files.horizontalHeader().setHighlightSections(False)
        self.files.horizontalHeader().setStretchLastSection(True)
        self.files.setHorizontalHeaderLabels(
            ['File Name', 'Version', 'Upload Date', 'Description'])
        self.files.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.files.verticalScrollBar().valueChanged.connect(
            lambda: self.files.clearFocus())
        self.files.itemSelectionChanged.connect(lambda: self.validateFiles())
        self.files.setDisabled(True)
        self.files.setStyleSheet('''
            QTableView {
                gridline-color: rgba(255,255,255,1);
            }
            QTableView::item {
                padding: 5px;
                margin: 1px 0;
            }
            QTableView::item:!selected:hover {
                background-color: rgb(217, 235, 249);
                padding: 0;
            }
            ''')
        gbFilesLayout.addWidget(self.files)

        _mouseMoveEvent = self.files.mouseMoveEvent
        self.files.hoverIndexRow = -1

        def mouseMoveEvent(event: QMouseEvent) -> None:
            self.files.hoverIndexRow = self.files.indexAt(event.pos()).row()
            _mouseMoveEvent(event)

        self.files.mouseMoveEvent = mouseMoveEvent  # type: ignore
        self.files.setItemDelegate(ModListItemDelegate(self.files))
        self.files.setMouseTracking(True)

        gbFiles.setLayout(gbFilesLayout)
        mainLayout.addWidget(gbFiles)

        # Actions

        actionsLayout = QHBoxLayout()
        actionsLayout.setAlignment(Qt.AlignRight)
        self.download = QPushButton('Download', self)
        self.download.clicked.connect(lambda: self.downloadEvent())
        self.download.setAutoDefault(True)
        self.download.setDefault(True)
        self.download.setDisabled(True)
        actionsLayout.addWidget(self.download)
        cancel = QPushButton('Cancel', self)
        cancel.clicked.connect(self.cancelEvent)
        actionsLayout.addWidget(cancel)
        mainLayout.addLayout(actionsLayout)

        # Setup

        self.setMinimumSize(QSize(420, 420))
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)
        self.resize(QSize(720, 420))

        self.finished.connect(
            lambda: self.validateUrl.cancel())  # type: ignore
        self.finished.connect(
            lambda: self.downloadEvent.cancel())  # type: ignore

        self.modId = 0
        self.validateUrl(self.url.text())

    def cancelEvent(self) -> None:
        self.close()

    @debounce(200, cancel_running=True)
    async def validateUrl(self, url: str) -> bool:
        self.download.setDisabled(True)
        self.files.setDisabled(True)
        self.files.clearSelection()
        self.files.clearFocus()
        self.files.clearContents()
        self.files.setRowCount(0)
        self.files.setSortingEnabled(False)
        self.url.setStyleSheet('')
        self.modId = 0
        if not url:
            self.urlInfo.setText('''
                <font color="#888">Please enter a valid mod url.</font>
                ''')
            return False
        modId = getModId(url)
        if not modId:
            self.files.setDisabled(True)
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText('''
                <font color="#888">Please enter a valid mod url.</font>
                ''')
            return False
        self.urlInfo.setText('🌐')
        try:
            filesResponse = await getModFiles(modId)
        except (RequestError, ResponseError, Exception) as e:
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText(f'''
                <font color="#888">Could not get mod files: {e}.</font>
                ''')
            return False
        try:
            files = filesResponse['files']
            if not len(files):
                self.urlInfo.setText(f'''
                    <font color="#888">Mod "{modId}" has no files!</font>
                    ''')
                return False

            self.files.setRowCount(len(files))
            for i in range(len(files)):
                file = files[i]
                fileid = int(file['file_id'])
                name = str(file['name'])
                version = str(file['version'])
                _uploadtime = dateparser.parse(file['uploaded_time'])
                uploadtime = _uploadtime.astimezone(tz=None).strftime(
                    '%Y-%m-%d %H:%M:%S') if _uploadtime else '?'
                description = html.unescape(str(file['description']))
                nameItem = QTableWidgetItem(name)
                nameItem.setToolTip(name)
                nameItem.setData(Qt.UserRole, fileid)
                self.files.setItem(i, 0, nameItem)
                versionItem = QTableWidgetItem(version)
                versionItem.setToolTip(version)
                self.files.setItem(i, 1, versionItem)
                uploadtimeItem = QTableWidgetItem(uploadtime)
                uploadtimeItem.setToolTip(uploadtime)
                self.files.setItem(i, 2, uploadtimeItem)
                descriptionItem = QTableWidgetItem(description)
                descriptionItem.setToolTip(description)
                self.files.setItem(i, 3, descriptionItem)
        except KeyError as e:
            logger.exception(
                f'Could not find key "{str(e)}" in mod files response')
            self.urlInfo.setText(f'''
                <font color="#888">Could not find key "{str(e)}" in mod files response.</font>
                ''')
            return False

        self.urlInfo.setText(f'''
            <font color="#888">Found {len(files)} available files.</font>
            ''')
        self.files.resizeColumnsToContents()
        self.files.setDisabled(False)
        self.files.setSortingEnabled(True)
        self.modId = modId
        return True

    def validateFiles(self) -> bool:
        selection = self.files.selectionModel().selectedRows()
        if len(selection) > 0:
            self.download.setText(f'Download {len(selection)} mods')
            self.download.setDisabled(False)
            return True
        return False

    @debounce(25, cancel_running=True)
    async def downloadEvent(self) -> None:
        self.download.setDisabled(True)
        self.url.setDisabled(True)
        selection = self.files.selectionModel().selectedRows()
        files = [
            self.files.item(index.row(), 0).data(Qt.UserRole)
            for index in selection
        ]
        self.files.setDisabled(True)
        try:
            urls = await asyncio.gather(
                *[getModFileUrls(self.modId, file) for file in files],
                loop=asyncio.get_running_loop())
        except (RequestError, ResponseError, Exception) as e:
            self.url.setStyleSheet('''
                *{
                    border: 1px solid #B22222;
                    padding: 1px 0px;
                }
                ''')
            self.urlInfo.setText(f'''
                <font color="#888">Could not download mod files: {e}.</font>
                ''')
            return
        try:
            self.signals.download.emit([url[0]['URI'] for url in urls])
        except KeyError as e:
            logger.exception(
                f'Could not find key "{str(e)}" in file download response')
            self.urlInfo.setText(f'''
                <font color="#888">Could not find key "{str(e)}" in file download response.</font>
                ''')
            return
        self.close()
Ejemplo n.º 14
0
class ContinuousCriteriaPage(EnableNextOnBackMixin, QWizardPage):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent_wizard = weakref.proxy(parent)

        self.setTitle('Continuous criteria')
        # Radio button, if yes then ask for inputs
        self.yes = QRadioButton(
            '&Yes, there are criteria that needs to be calculated')
        no = QRadioButton(
            'N&o, I will manually give a rating for every choice and criteria')
        no.setChecked(True)

        self.registerField('yes', self.yes)
        self.yes.toggled.connect(self.toggled)

        group = QButtonGroup(self)
        group.addButton(self.yes)
        group.addButton(no)

        # Duplicated from AbstractMultiInputPage
        self.line_edit = QLineEdit()
        self.list_widget = QListWidget()
        self.add_button = QPushButton('&Add criterion')
        self.delete_button = QPushButton('&Delete')

        self.line_edit.setDisabled(True)
        self.list_widget.setDisabled(True)
        self.add_button.setDisabled(True)
        self.delete_button.setDisabled(True)

        self.line_edit.returnPressed.connect(self.add_item)
        self.add_button.clicked.connect(self.add_item)
        self.delete_button.clicked.connect(self.delete_item)

        grid = QGridLayout(self)
        grid.addWidget(self.yes, 0, 0)
        grid.addWidget(no, 1, 0)
        grid.addWidget(self.line_edit, 2, 0)
        grid.addWidget(self.add_button, 2, 1)
        grid.addWidget(self.list_widget, 3, 0)
        grid.addWidget(self.delete_button, 3, 1, Qt.AlignTop)
        self.setLayout(grid)

    def initializePage(self):
        for criterion in self.parent_wizard.main_parent.matrix.continuous_criteria:
            self.list_widget.addItem(QListWidgetItem(criterion))

        if self.list_widget.count() != 0:
            self.yes.setChecked(True)
            self.parent_wizard.next_button.setEnabled(True)

    def toggled(self, checked: bool):
        if checked:
            self.line_edit.setEnabled(True)
            self.list_widget.setEnabled(True)
            self.add_button.setEnabled(True)
            self.parent_wizard.next_button.setDisabled(True)
        else:
            self.line_edit.setDisabled(True)
            self.list_widget.setDisabled(True)
            self.parent_wizard.next_button.setEnabled(True)

    def add_item(self):
        # Duplicated
        if not (name := self.line_edit.text()):
            return
        item = QListWidgetItem(name)
        self.list_widget.addItem(item)
        self.line_edit.clear()
        self.line_edit.setFocus()
        self.parent_wizard.next_button.setEnabled(True)
        self.delete_button.setEnabled(True)

        self.parent_wizard.main_parent.line_edit_cc_tab.setText(name)
        self.parent_wizard.main_parent.matrix.add_continuous_criterion(
            name, weight=float('nan'))
        self.parent_wizard.main_parent.add_continuous_criteria()
Ejemplo n.º 15
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__(flags=Qt.WindowContextHelpButtonHint
                         | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setUpMainWindow()

    def setUpMainWindow(self):
        self.setWindowTitle(
            "Lab Tools 1.0 (2021 Edition) - Applications of ML in Mechatronics"
        )
        # set icon
        self.setUpIcon()

        #set up the info and start processing button
        self.inputLineEdit = QLineEdit()
        self.outputFolderLineEdit = QLineEdit()
        self.modelLineEdit = QLineEdit()
        self.browseOutputButton = QPushButton()
        self.browseInputButton = QPushButton()
        self.browseModelButton = QPushButton()
        self.startStopButton = QPushButton()
        self.useDefaultModelCheckbox = QCheckBox()

        self.inputLineEdit = self.findChild(QLineEdit, "inputLineEdit")
        self.modelLineEdit = self.findChild(QLineEdit, "modelLineEdit")
        self.outputFolderLineEdit = self.findChild(QLineEdit,
                                                   "outputFolderLineEdit")
        self.browseOutputButton = self.findChild(QPushButton,
                                                 "browseOutputButton")
        self.browseInputButton = self.findChild(QPushButton,
                                                "browseInputButton")
        self.browseModelButton = self.findChild(QPushButton,
                                                "browseModelButton")
        self.startStopButton = self.findChild(QPushButton, "startStopButton")
        self.useDefaultModelCheckbox = self.findChild(
            QCheckBox, "useDefaultModelCheckbox")
        self.chooseModelLabel = self.findChild(QLabel, "chooseModelLabel")

        #disabling model transfer capability if needed
        self._modelRPiPath = False
        if DISABLE_MODEL_TRASFER:
            self.useDefaultModelCheckbox.hide()
            self.modelLineEdit.setEnabled(1)
            self.browseModelButton.hide()
            self.chooseModelLabel.setText(self.chooseModelLabel.text() +
                                          " Name")
            # self.gridLayout = self.findChild(QGridLayout, "gridLayout")
            # self.gridLayout.removeWidget(self.browseModelButton)
            # self.gridLayout.removeWidget(self.useDefaultModelCheckbox)
            self._modelRPiPath = True

        self.startStopButton.clicked.connect(self.handleStartStopButtonClicked)
        self.browseOutputButton.clicked.connect(self.handleBrowseOutputButton)
        self.browseInputButton.clicked.connect(self.handleBrowseInputButton)
        self.browseModelButton.clicked.connect(self.handleBrowseModelButton)
        self.useDefaultModelCheckbox.stateChanged.connect(
            self.handleUseDefaultModelCheckboxStateChanged)
        self.useDefaultModelCheckbox.setChecked(True)

        #set up the log and progress bar
        self.logTextBrowser = QTextBrowser()
        self.lastLogTextLabel = QLabel()
        self.logTextBrowser = self.findChild(QTextBrowser, "logTextBrowser")
        self.progressBar = self.findChild(QProgressBar, "progressBar")
        self.clearLogButton = self.findChild(QPushButton, "clearLogButton")
        self.saveLogButton = self.findChild(QPushButton, "saveLogButton")
        self.lastLogTextLabel = self.findChild(QLabel, "lastLogTextLabel")
        self.clearLogButton.clicked.connect(self.handleClearLogButton)
        self.saveLogButton.clicked.connect(self.handleSaveLogButton)

        #set up menu bar
        self.actionHelp = self.findChild(QAction, "actionHelp")
        self.actionAbout = self.findChild(QAction, "actionAbout")
        self.actionHelp.triggered.connect(self.handleActionHelpClicked)
        self.actionAbout.triggered.connect(self.handleActionAboutClicked)

        # Add additional menu actions
        self.utilitiesMenu = self.menuBar().addMenu("Utilities")
        self.actionGetRPiIP = QAction("Get RPi IP")
        self.utilitiesMenu.addAction(self.actionGetRPiIP)
        self.actionGetRPiIP.triggered.connect(self.handleActionGetRPiIPClicked)
        self.actionUpdateRPiScript = QAction("Updare RPi Script")
        self.utilitiesMenu.addAction(self.actionUpdateRPiScript)
        self.actionUpdateRPiScript.triggered.connect(
            self.handleActionUpdateRPiScript)

        #create objects from the other classes
        self.logger = Logger(self.logTextBrowser, self.lastLogTextLabel)

        #initialize member variables
        self._b_processRunning = False

        # set up lab names
        self.setUpLabNames()

        #set up serial comms
        self.setupSerial()
        self.refreshSerialPorts()

        self.logger.log("The application is ready!", type="INFO")

    def setUpIcon(self):
        self.appIcon = QIcon("images/favicon.png")
        self.setWindowIcon(self.appIcon)

    def setUpLabNames(self):
        self.labNameComboBox = QComboBox()

        self.labNameComboBox = self.findChild(QComboBox, "labNameComboBox")
        self.labNameComboBox.currentIndexChanged.connect(
            self.handleLabNameComboboxCurrentIndexChanged)
        for code, name in utils.lab_names.items():
            self.labNameComboBox.addItem(code + ": " + name)
        self.labNameComboBox.setCurrentIndex(1)

    def setupSerial(self):
        self.refreshSerialPortsButton = QPushButton()
        self.connectDisconnectSerialButton = QPushButton()
        self.serialPortComboBox = QComboBox()

        self.refreshSerialPortsButton = self.findChild(
            QPushButton, "refreshSerialPortsButton")
        self.connectDisconnectSerialButton = self.findChild(
            QPushButton, "connectDisconnectSerialButton")
        self.serialPortComboBox = self.findChild(QComboBox,
                                                 "serialPortComboBox")
        self.refreshSerialPortsButton.clicked.connect(self.refreshSerialPorts)
        self.connectDisconnectSerialButton.clicked.connect(
            self.handleSerialConnectDisconnect)
        self._b_serialConnected = False

    def refreshSerialPorts(self):
        availablePorts = utils.find_serial_ports()
        self.serialPortComboBox.clear()
        for portName in availablePorts:
            self.serialPortComboBox.addItem(portName)

    def handleSerialConnectDisconnect(self):
        if not self.b_serialConnected:
            try:
                currentPortName = self.serialPortComboBox.currentText()
                self.port = serial.Serial(currentPortName,
                                          115200,
                                          timeout=1,
                                          write_timeout=240,
                                          bytesize=8,
                                          parity='N',
                                          stopbits=1)
                self.port.set_buffer_size(rx_size=10**3, tx_size=10**8)
                self.serialPortComboBox.setItemText(
                    self.serialPortComboBox.currentIndex(),
                    currentPortName + " (CONNECTED)")
                self.connectDisconnectSerialButton.setText("Disconnect")
                self.b_serialConnected = True
                self.refreshSerialPortsButton.setDisabled(1)
            except (OSError, serial.SerialException):
                print("Problem with Serial Connection!")
                self.logger.log(
                    "Problem with Serial Connection, Make sure you chose the right port",
                    type="ERROR")
        else:
            try:
                self.port.close()
                self.refreshSerialPorts()
                self.connectDisconnectSerialButton.setText("Connect")
                self.b_serialConnected = False
                self.refreshSerialPortsButton.setEnabled(1)
            except (OSError, serial.SerialException):
                print("Problem with Serial Connection!")
                self.logger.log("Problem with Serial Connection", type="ERROR")

    def _startButtonClicked(self):
        self.logger.log("Attempting to start the processing", type="INFO")
        if not self.b_serialConnected:
            self.logger.log(
                "Serial is not connected, Please connect serial first",
                type="ERROR")
            return
        if self.inputLineEdit.text()[-4:].lower() != ".csv":
            self.logger.log("Please select a valid input csv file",
                            type="ERROR")
            return
        if self.outputFolderLineEdit.text() == "":
            self.logger.log("Please select an output directory", type="ERROR")
            return
        self.executer = Executer(serialObj=self.port, loggerObj=self.logger)

        if self.modelLineEdit.text() != "":
            modelPath = self.modelLineEdit.text()
        else:
            modelPath = None
            if self._modelRPiPath:
                self.logger.log(
                    "Please select a valid model that is already available in the folder saved_models on the RPi",
                    type="ERROR")
                return

        #Read the Input File
        try:
            inputDataFrame = pd.read_csv(self.inputLineEdit.text())
        except:
            self.logger.log("CSV File Reading Failed, select a valid csv file",
                            type="ERROR")

        possibleInputs = list(inputDataFrame.columns)

        #Display a dialog to ask the user to choose what inputs they want
        dialog = QDialog(self)

        dialog.setWindowTitle("Select the Input Fields")
        dialogButtons = QDialogButtonBox(QDialogButtonBox.Ok
                                         | QDialogButtonBox.Cancel)
        dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0)
        dialogButtons.accepted.connect(dialog.accept)
        dialogButtons.rejected.connect(dialog.reject)

        mainLayout = QVBoxLayout(dialog)
        scroll = QScrollArea(dialog)
        scroll.setWidgetResizable(True)
        layoutWidget = QWidget()
        layout = QVBoxLayout(layoutWidget)
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll.setWidget(layoutWidget)

        chosenInputs = []
        checkboxes = []

        def handleCheckboxClicked():
            dialogButtons.button(QDialogButtonBox.Ok).setDisabled(1)
            for checkbox in checkboxes:
                if checkbox.isChecked():
                    dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0)

        for input in possibleInputs:
            checkbox = QCheckBox(text=input)
            checkbox.clicked.connect(handleCheckboxClicked)
            checkbox.setChecked(True)
            checkboxes.append(checkbox)
            layout.addWidget(checkbox)

        mainLayout.addWidget(
            QLabel(text="Please select the input fields from the following:"))
        mainLayout.addWidget(scroll)
        mainLayout.addWidget(dialogButtons)
        dialog.setLayout(mainLayout)
        # dialog.setFixedHeight(400)
        if dialog.exec_() == QDialog.Accepted:
            for checkbox in checkboxes:
                if checkbox.isChecked():
                    chosenInputs.append(checkbox.text())
            self.logger.log("The chosen input fields are: " +
                            ', '.join(chosenInputs),
                            type="INFO")
        else:
            return

        self.startStopButton.setText("Stop Processing")
        self.b_processRunning = True
        executionResult = self.executer.execute(self.labNameComboBox.currentText().split(":")[0], inputDataFrame, \
                                self.outputFolderLineEdit.text(), inputFields=chosenInputs, progressBar=self.progressBar, \
                                    model=modelPath if not self._modelRPiPath else "RPI:"+modelPath)
        if executionResult == ExecutionResult.COMPLETED:
            self._stopButtonClicked(finishedProcessing=True)
        elif executionResult == ExecutionResult.INTERRUPTED or executionResult == ExecutionResult.FAILED:
            self._stopButtonClicked()
            if self.executer.reset() == ExecutionResult.FAILED:
                self.logger.log(
                    "Resetting the serial state of RPi Failed, please power cycle the RPi",
                    type="ERROR")
            else:
                self.logger.log("The serial state of RPi has been reset",
                                type="INFO")

    def _stopButtonClicked(self, finishedProcessing=False):
        self.startStopButton.setText("Start Processing")
        if finishedProcessing:
            self.logger.log("", special="ProcessingCompleted")
        else:
            self.logger.log("", special="ProcessingStopped")
        self.b_processRunning = False
        #TODO: Complete Implementing this

    def handleStartStopButtonClicked(self):
        if (self.b_processRunning):
            self.executer.requestStop()
        else:
            self._startButtonClicked()

    def handleActionHelpClicked(self):
        helpBox = QMessageBox()
        helpBox.setIcon(QMessageBox.Information)
        helpBox.setStandardButtons(QMessageBox.Ok)
        helpBox.setWindowTitle("Need Help?")
        helpBox.setText(
            "For Help, please reach out to your Instructor or TA or read the lab manual"
        )
        helpBox.setTextFormat(Qt.RichText)
        helpBox.setInformativeText(
            f"You can access the project <a href=\"{utils.docs_link}\">Manual</a> and source in the <a href=\"{utils.repo_link}\">Github Repo!</a>"
        )
        helpBox.setWindowIcon(self.appIcon)
        helpBox.exec_()

    def handleActionAboutClicked(self):
        aboutBox = QMessageBox()
        aboutBox.setIcon(QMessageBox.Information)
        aboutBox.setStandardButtons(QMessageBox.Ok)
        aboutBox.setWindowTitle("About the Software")
        aboutBox.setText(utils.license_text)
        aboutBox.setWindowIcon(self.appIcon)
        aboutBox.exec_()

    def handleBrowseInputButton(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        dialog.setNameFilter("*.csv")
        if dialog.exec_():
            filePath = dialog.selectedFiles()
            if len(filePath) == 1:
                self.inputLineEdit.setText(filePath[0])

    def handleBrowseModelButton(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        dialog.setNameFilter(utils.lab_model_extensions[
            self.labNameComboBox.currentText().split(":")[0]])
        if dialog.exec_():
            filePath = dialog.selectedFiles()
            if len(filePath) == 1:
                self.modelLineEdit.setText(filePath[0])

    def handleBrowseOutputButton(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.DirectoryOnly)
        dialog.setOption(QFileDialog.ShowDirsOnly)
        if dialog.exec_():
            folderPath = dialog.selectedFiles()
            if len(folderPath) == 1:
                self.outputFolderLineEdit.setText(folderPath[0])

    def handleSaveLogButton(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.AnyFile)
        dialog.AcceptMode(QFileDialog.AcceptSave)
        dialog.setNameFilter("*.txt")
        if dialog.exec_():
            filePath = dialog.selectedFiles()
            if len(filePath) == 1:
                if self.logger.saveLog(filePath[0]) != -1:
                    self.logger.log(
                        "The log has been saved, feel free to clear the log now",
                        type="SUCCESS")
                    return
        self.logger.log("Failed to save the log, please select a valid file",
                        type="ERROR")

    def handleClearLogButton(self):
        self.logger.clearLog()

    def handleUseDefaultModelCheckboxStateChanged(self):
        if self._modelRPiPath:
            return
        if not self.useDefaultModelCheckbox.checkState():
            self.modelLineEdit.setDisabled(0)
            self.browseModelButton.setDisabled(0)
        else:
            self.modelLineEdit.clear()
            self.modelLineEdit.setDisabled(1)
            self.browseModelButton.setDisabled(1)

    def handleLabNameComboboxCurrentIndexChanged(self):
        if self._modelRPiPath:
            self.modelLineEdit.setText(utils.lab_default_models[
                self.labNameComboBox.currentText().split(":")[0]])
        # Disable the model options when the lab selected is Communication Test
        if self.labNameComboBox.currentIndex() == 0:
            self.modelLineEdit.setDisabled(1)
            self.browseModelButton.setDisabled(1)
            self.modelLineEdit.setText("Model is not required")
        else:
            self.modelLineEdit.setDisabled(0)
            self.browseModelButton.setDisabled(0)

    def handleActionGetRPiIPClicked(self):
        self.logger.log("Attempting to Get Raspberry Pi IP Address",
                        type="INFO")
        if not self.b_serialConnected:
            replyMessage = "Serial is not connected, Please connect serial first"
        else:
            self.executer = Executer(serialObj=self.port,
                                     loggerObj=self.logger)
            ipaddr = self.executer.executeOther("GET_IP")
            replyMessage = (
                "Raspberry Pi IP is " + ipaddr
            ) if ipaddr != ExecutionResult.FAILED else "Failed to obtain the IP Address"
        self.logger.log(replyMessage)
        ipAddrMessage = QMessageBox()
        ipAddrMessage.setIcon(QMessageBox.Information)
        ipAddrMessage.setStandardButtons(QMessageBox.Ok)
        ipAddrMessage.setWindowTitle("Raspberry Pi IP Address")
        ipAddrMessage.setText(replyMessage)
        ipAddrMessage.setTextFormat(Qt.RichText)
        ipAddrMessage.setWindowIcon(self.appIcon)
        ipAddrMessage.exec_()

    def handleActionUpdateRPiScript(self):
        self.logger.log(
            'Attempting to "git pull" for the Raspberry Pi Python Script',
            type="INFO")
        if not self.b_serialConnected:
            replyMessage = "Serial is not connected, Please connect serial first"
        else:
            self.executer = Executer(serialObj=self.port,
                                     loggerObj=self.logger)
            result = self.executer.executeOther("UPDATE_SCRIPT")
            if result != ExecutionResult.FAILED and "FAIL" not in result:
                replyMessage = "Raspberry Pi Script has been updated. You still need to reboot or power cycle the Raspberry Pi for the updated script to run" \
                                    "\nReceived: " + result
            else:
                replyMessage = "Failed to update the RPi Script"
                if "FAIL" in result:
                    replyMessage = replyMessage + "\nReceived: " + result

        self.logger.log(replyMessage)
        ipAddrMessage = QMessageBox()
        ipAddrMessage.setIcon(QMessageBox.Information)
        ipAddrMessage.setStandardButtons(QMessageBox.Ok)
        ipAddrMessage.setWindowTitle("Raspberry Pi Script Updating Status")
        ipAddrMessage.setText(replyMessage)
        ipAddrMessage.setTextFormat(Qt.RichText)
        ipAddrMessage.setWindowIcon(self.appIcon)
        ipAddrMessage.exec_()

    @property
    def b_processRunning(self):
        return self._b_processRunning

    @property
    def b_serialConnected(self):
        return self._b_serialConnected

    @b_serialConnected.setter
    def b_serialConnected(self, newValue):
        self._b_serialConnected = newValue
        if newValue is True:
            self.logger.log("", special="SerialConnected")
        else:
            self.logger.log("", special="SerialDisconnected")

    @b_processRunning.setter
    def b_processRunning(self, newValue):
        self._b_processRunning = newValue
        if newValue is True:
            self.logger.log("", special="ProcessingStarted")

    def __del__(self):
        if self.b_serialConnected:
            self.port.close()
class RenameOptionsView(QWidget):
    """View responsible for holding renaming options.
    
    Attributes:
        layout (QVBoxLayout): Main layout of view.
        frame_layout (QVBoxLayout: Layout of frame which holds options.
        frame (QFrame): Frame surrounding options.
        prefix_h_layout (QHBoxLayout): Layout holding prefix options.
        complete_rename_h_layout (QHBoxLayout): Layout holding complete rename options.
        search_and_replace_h_layout (QHBoxLayout): Layout holding search and replace options.
        renumber_h_layout (QHBoxLayout): Layout holding renumber options.
        remove_ext_h_layout (QHBoxLayout): Layout holding remove options.
        change_ext_h_layout (QHBoxLayout): Layout holding change extension options.
        create_backup_h_layout (QHBoxLayout): Layout holding backup options.
        preview_h_layout (QHBoxLayout): Layout holding preview options.
        start_lbl (QLabel): Label for renumbering start.
        padding_lbl (QLabel): Label for renumbering padding.
        add_prefix_cb (QCheckBox): Used to signify the user wants to add a prefix to the renaming.
        prefix (QLineEdit): prefix to add.
        complete_rename_cb (QCheckBox): Used to signify the user wants to completely rename the file.
        new_name (QLineEdit): New name used when renaming.
        search_and_replace_cb (QCheckBox): Used to signify the user wants to partially rename files.
        find (QLineEdit): When searching and replacing this is what the user wants to search for.
        replace (QLineEdit): When searching and replacing this is what the user wants to replace with.
        renumber_cb (QCheckBox): Used to signify the user wants to renumber while renaming.
        start_num (QSpinBox): Number to start with when renumbering files.
        padding (QComboBox): Padding to apply to renaming when renumbering files.
        dot_cb (QCheckBox): When checked a dot will be used to separate the renumber from the name.
        remove_ext_cb (QCheckBox): Used to signify the user wants to remove extensions when renaming.
        backup_files_cb (QCheckBox): Used to signify the user wants to backup old files before renaming.
        change_ext_cb (QCheckBox): Used to signify the user wants to change the extension while renaming.
        change_ext (QLineEdit): New extension to add to the renamed file.
        preview_cb (QCheckBox): Used to signify the user wants to preview the rename before renaming.
    """
    def __init__(self):
        super(RenameOptionsView, self).__init__()
        self.layout = QVBoxLayout()
        self.frame_layout = QVBoxLayout()
        self.options_lbl = QLabel(prefs.OPTIONS)
        self.frame = QFrame()
        self.prefix_h_layout = QHBoxLayout()
        self.complete_rename_h_layout = QHBoxLayout()
        self.search_and_replace_h_layout = QHBoxLayout()
        self.renumber_h_layout = QHBoxLayout()
        self.remove_ext_h_layout = QHBoxLayout()
        self.change_ext_h_layout = QHBoxLayout()
        self.create_backup_h_layout = QHBoxLayout()
        self.preview_h_layout = QHBoxLayout()
        self.start_lbl = QLabel(prefs.START_NUM)
        self.padding_lbl = QLabel(prefs.PADDING)
        self.add_prefix_cb = QCheckBox(prefs.PREFIX)
        self.prefix = QLineEdit(prefs.PREFIX_DEFAULT)
        self.complete_rename_cb = QCheckBox(prefs.COMPLETE_RENAME)
        self.new_name = QLineEdit(prefs.COMPLETE_RENAME_DEFAULT)
        self.search_and_replace_cb = QCheckBox(prefs.SEARCH_AND_REPLACE)
        self.find = QLineEdit(prefs.SEARCH_AND_REPLACE_DEFAULT)
        self.replace = QLineEdit(prefs.REPLACE_WITH_DEFAULT)
        self.renumber_cb = QCheckBox(prefs.RENUMBER)
        self.start_num = QSpinBox()
        self.padding = QComboBox()
        self.dot_cb = QCheckBox(prefs.USE_DOT)
        self.remove_ext_cb = QCheckBox(prefs.REMOVE_EXT)
        self.backup_files_cb = QCheckBox(prefs.BACKUP)
        self.change_ext_cb = QCheckBox(prefs.CHANGE_EXT)
        self.change_ext = QLineEdit(prefs.CHANGE_EXT_DEFAULT)
        self.preview_cb = QCheckBox(prefs.PREVIEW)

        self._configure()

    def _configure(self) -> None:
        """Configure the RenameOptionsView."""
        self.frame.setLayout(self.frame_layout)
        self.frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.layout.addWidget(self.options_lbl)
        self.layout.addWidget(self.frame)
        self.add_prefix_cb.setToolTip(prefs.PREFIX_TOOLTIP)
        self.prefix.setDisabled(True)
        self.prefix.setMaximumWidth(prefs.PREFIX_WIDTH)
        self.prefix.setMinimumWidth(prefs.PREFIX_WIDTH)
        self.complete_rename_cb.setToolTip(prefs.COMPLETE_RENAME_TOOLTIP)
        self.new_name.setDisabled(True)
        self.new_name.setMaximumWidth(prefs.NEW_NAME_WIDTH)
        self.new_name.setMinimumWidth(prefs.NEW_NAME_WIDTH)
        self.search_and_replace_cb.setToolTip(prefs.SEARCH_AND_REPLACE_TOOLTIP)
        self.find.setDisabled(True)
        self.find.setMinimumWidth(prefs.FIND_WIDTH)
        self.find.setMaximumWidth(prefs.FIND_WIDTH)
        self.replace.setDisabled(True)
        self.replace.setMaximumWidth(prefs.REPLACE_WIDTH)
        self.replace.setMinimumWidth(prefs.REPLACE_WIDTH)
        self.renumber_cb.setToolTip(prefs.RENUMBER_TOOLTIP)
        self.start_num.setToolTip(prefs.START_NUM_TOOLTIP)
        self.start_num.setDisabled(True)
        self.start_num.setValue(prefs.START_NUM_DEFAULT)
        self.start_num.setMinimumWidth(prefs.START_NUM_MIN_WIDTH)
        self.padding.setToolTip(prefs.PADDING_TOOLTIP)
        self.padding.setDisabled(True)
        self.padding.addItems([str(x) for x in range(10)])
        self.padding.setCurrentIndex(4)
        self.padding.setMinimumWidth(prefs.PADDING_MIN_WIDTH)
        self.dot_cb.setToolTip(prefs.USE_DOT_TOOLTIP)
        self.dot_cb.setDisabled(True)
        self.dot_cb.setChecked(True)
        self.dot_cb.setMinimumWidth(prefs.DOT_WIDTH)
        self.dot_cb.setMaximumWidth(prefs.DOT_WIDTH)
        self.remove_ext_cb.setToolTip(prefs.REMOVE_EXT_TOOLTIP)
        self.change_ext.setToolTip(prefs.CHANGE_EXT_TOOLTIP)
        self.change_ext.setDisabled(True)
        self.backup_files_cb.setToolTip(prefs.BACKUP_TOOLTIP)
        self.backup_files_cb.setChecked(True)
        self.preview_cb.setToolTip(prefs.PREVIEW_TOOLTIP)
        self.preview_cb.setChecked(True)

        self.prefix_h_layout.addWidget(self.add_prefix_cb)
        self.prefix_h_layout.addWidget(self.prefix)
        self.frame_layout.addLayout(self.prefix_h_layout)

        self.complete_rename_h_layout.addWidget(self.complete_rename_cb)
        self.complete_rename_h_layout.addWidget(self.new_name)
        self.frame_layout.addLayout(self.complete_rename_h_layout)

        self.search_and_replace_h_layout.addWidget(self.search_and_replace_cb)
        self.search_and_replace_h_layout.addWidget(self.find)
        self.search_and_replace_h_layout.addWidget(self.replace)
        self.frame_layout.addLayout(self.search_and_replace_h_layout)

        self.renumber_h_layout.addWidget(self.renumber_cb)
        self.renumber_h_layout.addStretch(1)
        self.renumber_h_layout.addWidget(self.start_lbl)
        self.renumber_h_layout.addWidget(self.start_num)
        self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))
        self.renumber_h_layout.addWidget(self.padding_lbl)
        self.renumber_h_layout.addWidget(self.padding)
        self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))
        self.renumber_h_layout.addWidget(self.dot_cb)
        self.frame_layout.addLayout(self.renumber_h_layout)

        self.change_ext_h_layout.addWidget(self.change_ext_cb)
        self.change_ext_h_layout.addWidget(self.change_ext)
        self.frame_layout.addLayout(self.change_ext_h_layout)

        self.remove_ext_h_layout.addWidget(self.remove_ext_cb)
        self.frame_layout.addLayout(self.remove_ext_h_layout)

        self.create_backup_h_layout.addWidget(self.backup_files_cb)
        self.frame_layout.addLayout(self.create_backup_h_layout)

        self.preview_h_layout.addWidget(self.preview_cb)
        self.frame_layout.addLayout(self.preview_h_layout)

        self.frame_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))

        self.setLayout(self.layout)

    def disable_change_ext(self) -> None:
        """Disable change extension."""
        self.change_ext.setDisabled(True)

    def disable_dot(self) -> None:
        """Disable dot checkbox."""
        self.dot_cb.setDisabled(True)

    def disable_find(self) -> None:
        """Disable find."""
        self.find.setDisabled(True)

    def disable_new_name(self) -> None:
        """Disable new name."""
        print("disable new name")
        self.new_name.setDisabled(True)

    def disable_padding(self) -> None:
        """Disable padding."""
        self.padding.setDisabled(True)

    def disable_prefix(self) -> None:
        """Disable prefix."""
        self.prefix.setDisabled(True)

    def disable_start_num(self) -> None:
        """Disable start num."""
        self.start_num.setDisabled(True)

    def disable_replace(self) -> None:
        """Disable replace."""
        self.replace.setDisabled(True)

    def enable_change_ext(self) -> None:
        """Disable change extension."""
        self.change_ext.setDisabled(False)

    def enable_dot(self) -> None:
        """Enable dot checkbox."""
        self.dot_cb.setEnabled(True)

    def enable_find(self) -> None:
        """Enable find."""
        self.find.setEnabled(True)

    def enable_new_name(self) -> None:
        """Enable new name."""
        print("enable new name.")
        self.new_name.setEnabled(True)

    def enable_padding(self) -> None:
        """Enable padding."""
        self.padding.setEnabled(True)

    def enable_prefix(self) -> None:
        """Enable prefix."""
        self.prefix.setEnabled(True)

    def enable_replace(self) -> None:
        """Enable replace."""
        self.replace.setEnabled(True)

    def enable_start_num(self) -> None:
        """Enable start num."""
        self.start_num.setEnabled(True)

    def get_add_prefix(self) -> bool:
        """Return if end user wants to add a prefix and it is not the default value."""
        result = self.get_prefix_checked()
        if result and self.get_prefix() == prefs.PREFIX_DEFAULT:
            result = False
        return result

    def get_do_backup(self) -> bool:
        """Return if end user wants to backup files."""
        return self.backup_files_cb.isChecked()

    def get_change_ext(self) -> bool:
        """Return if the change extension checkbox is checked."""
        return self.change_ext_cb.isChecked()

    def get_do_complete_rename(self) -> bool:
        """Get if end user wants to completely rename."""
        return self.complete_rename_cb.isChecked()

    def get_dot(self) -> str:
        """Return dot string based on end users configuration.

        Note:
            If the end user has not enable using dot separators an empty string will be returned.
        """
        return "." if self.get_do_dot() else ""

    def get_do_dot(self) -> bool:
        """Return if the end user wants to use dot separators when renaming."""
        return self.dot_cb.isChecked()

    def get_do_change_ext(self) -> bool:
        """Return if the end user wants to change the extension."""
        result = self.change_ext_cb.isChecked()
        if self.get_new_ext() == prefs.CHANGE_EXT_DEFAULT:
            return False
        return result

    def get_do_padding(self) -> bool:
        """Return if the end user wants to add padding."""
        return False if self.get_padding() == 0 else True

    def get_do_preview(self) -> bool:
        """Return if the end user wants to preview changes."""
        return self.preview_cb.isChecked()

    def get_do_rename(self) -> bool:
        """Return if end user wants to rename the full item and it is not the default value."""
        result = self.complete_rename_cb.isChecked()
        if result and self.get_new_name() == prefs.COMPLETE_RENAME_DEFAULT:
            result = False
        return result

    def get_do_renumber(self) -> bool:
        """Return if the end user wants to renumber."""
        return self.renumber_cb.isChecked()

    def get_do_search(self) -> bool:
        """Return if end user wants to perform a search and replace AND it is not the default values respectfully.

        Note:
            If you only want to know if search and replace is checked use get_search_and_replace.
        """
        result = self.search_and_replace_cb.isChecked()
        if result and (self.get_find() == prefs.SEARCH_AND_REPLACE_DEFAULT
                       or self.get_replace() == prefs.REPLACE_WITH_DEFAULT):
            result = False
        return result

    def get_do_search_and_replace(self) -> bool:
        """Return if end user wants to perform a search and replace."""
        return self.search_and_replace_cb.isChecked()

    def get_find(self) -> str:
        """Return find value."""
        return str(self.find.text())

    def get_new_ext(self) -> str:
        """Return new ext."""
        return str(self.change_ext.text())

    def get_new_name(self) -> str:
        """Return new_name value."""
        return str(self.new_name.text())

    def get_padding(self) -> int:
        """Return the current padding value."""
        return int(self.padding.currentText())

    def get_prefix_checked(self) -> bool:
        """Return if the prefix checkbox is checked."""
        return self.add_prefix_cb.isChecked()

    def get_prefix(self) -> str:
        """Return the current prefix value end user has entered."""
        return str(self.prefix.text())

    def get_remove_ext(self) -> bool:
        """Return if end user has checked the remove extension checkbox."""
        return self.remove_ext_cb.isChecked()

    def get_replace(self) -> str:
        """Return the current replace value end user has entered."""
        return str(self.replace.text())

    def get_start_num(self) -> int:
        """Return start number from view."""
        return int(self.start_num.value())

    def set_change_ext_style(self, style: str) -> None:
        """Set style of change extension.

        Args:
            style: Style sheet applied to change extension.
        """
        self.change_ext.setStyleSheet(style)

    def set_disabled(self) -> None:
        """Disable View."""
        self.setDisabled(True)

    def set_enable(self) -> None:
        """Enable View."""
        self.setEnabled(True)

    def set_find(self, value: str) -> None:
        """Set the value of find.

        Args:
            value: Value applied to find
        """
        self.find.setText(value)

    def set_find_style(self, style: str) -> None:
        """Set style of find.

        Args:
            style: Style sheet applied to find.
        """
        self.find.setStyleSheet(style)

    def set_new_name(self, value: str) -> None:
        """Set the value of new name.

        Args:
            value: Value applied to new_name
        """
        self.new_name.setText(value)

    def set_new_name_style(self, style: str) -> None:
        """Set style of new_name.
        
        Args:
            style: Style sheet applied to new_name.
        """
        self.new_name.setStyleSheet(style)

    def set_prefix(self, value: str) -> None:
        """Set the value of prefix.

        Args:
            value: Value applied to prefix
        """
        self.prefix.setText(value)

    def set_prefix_style(self, style: str) -> None:
        """Set style of prefix.

        Args:
            style: Style sheet applied to prefix.
        """
        self.prefix.setStyleSheet(style)

    def set_remove_ext(self, state: bool) -> None:
        """Set the remove_ext checkbox as checked or unchecked.

        Args:
            state: Check state of remove_ext.
        """
        self.remove_ext_cb.setCheckState(Qt.Checked if state else Qt.Unchecked)

    def set_replace(self, value: str) -> None:
        """Set the value of replace.

        Args:
            value: Value applied to replace
        """
        self.replace.setText(value)

    def set_replace_style(self, style: str) -> None:
        """Set style of replace.

        Args:
            style: Style sheet applied to replace.
        """
        self.replace.setStyleSheet(style)
Ejemplo n.º 17
0
class AddUser(QDialog):

    def __init__(self):
        super(AddUser, self).__init__()
        self.setFixedSize(300, 190)
        self.setWindowIcon(QIcon('icons/auth.png'))
        self.setWindowTitle('添加用户')

        self.client = DbHelper.query_client()

        lbl_name = QLabel('姓名', self)
        lbl_name.setGeometry(10, 20, 50, 26)
        lbl_name.setAlignment(Qt.AlignCenter)
        self.le_name = QLineEdit(self)
        self.le_name.setGeometry(70, 20, 200, 26)
        self.le_name.setDisabled(True if self.client is None else False)
        lbl_color = QLabel('颜色值', self)
        lbl_color.setGeometry(10, 100, 50, 26)
        lbl_color.setAlignment(Qt.AlignCenter)
        self.le_color = QLineEdit(self)
        self.le_color.setGeometry(70, 100, 200, 26)
        self.le_color.setDisabled(True if self.client is None else False)

        self.btn_save = QPushButton(self)
        self.btn_save.setText('添加' if self.client is not None else '客户端未连接')
        self.btn_save.setGeometry(10, 140, 280, 30)
        self.btn_save.clicked.connect(self.save)
        self.btn_save.setDisabled(True if self.client is None else False)

    def save(self):
        name = self.le_name.text().strip(' ')
        if name == '':
            self.btn_save.setText('请输入姓名')
            self.le_name.setFocus()
            return
        if not name.replace(' ', '').encode("utf-8").isalpha():
            self.btn_save.setText('只支持英文字母和空格~')
            self.le_name.setFocus()
            return
        color = self.le_color.text().strip(' ')
        if color == '':
            self.btn_save.setText('请输入颜色值')
            self.le_color.setFocus()
            return
        colors = color.split(',')
        if len(colors) != 3:
            self.btn_save.setText('颜色值不正确')
            self.le_color.setFocus()
            return
        for c in colors:
            if not c.isdigit() or int(c) > 255 or int(c) < 0:
                self.btn_save.setText('颜色值不正确')
                self.le_color.setFocus()
                return
        self.btn_save.setDisabled(True)

        result, data = Tool.user_add(self.client[0], name, color)
        if result:
            DbHelper.insert_user(data, name, color, self.client[0])
            self.close()
        elif data is not None:
            self.btn_save.setDisabled(False)
            self.btn_save.setText(data)
        else:
            self.btn_save.setDisabled(False)
            self.btn_save.setText("添加失败")
Ejemplo n.º 18
0
class StatisticsOptions(QWidget):
    polymorphic_states = {
      'Regression': {'dtext': 'Tipo de Regressão:', 'xtext': 'Selecione a coluna dos valores correspondentes à variável independente:', 'ytext': 'Selecione a coluna dos valores correspondentes à variável dependente:', 'dropdown': ['Linear', 'Exponencial', 'Logarítmica', 'Potencial', 'Polinomial'], 'ctext':''},
      'Statistics': {'dtext': 'Tipo de análise estatística:', 'xtext': 'Selecione a coluna dos valores para realizar a análise:', 'ytext': '', 'dropdown': ['Média aritmética', 'Mediana', 'Moda', 'Variância Pop.', 'Variância Am.', 'Desvio Padrão Pop.', 'Desvio Padrão Am.', 'Máximo', 'Mínimo', 'Amplitude', 'Quartil', 'Percentil'], 'ctext':''},
      'Histogram':  {'dtext': '', 'xtext': 'Selecione a coluna com os valores:', 'ytext': '', 'dropdown': [], 'ctext':''},
      'Boxplot':    {'dtext': 'Orientação do gráfico:', 'ctext': 'Selecione as colunas onde se encontram as séries para o Boxplot (i.e. B, B:C, A:C:F:G):', 'ytext': '', 'dropdown': ['Vertical', 'Horizontal'], 'xtext':''},
    }
    ready2calculate = Signal(bool)

    def __init__(self, _mode='Regression'):
        super().__init__()

        self.mode = _mode

        self.subtitle = QLabel(str(_mode))

        self.dropdown_text = QLabel(self.polymorphic_states[self.mode]['dtext'])
        self.dropdown_text.setWordWrap(True)
        self.dropdown = Dropdown(self.polymorphic_states[self.mode]['dropdown'])

        self.x_column_text = QLabel(self.polymorphic_states[self.mode]['xtext'])
        self.x_column_text.setWordWrap(True)
        self.x_column = QLineEdit()

        self.y_column_text = QLabel(self.polymorphic_states[self.mode]['ytext'])
        self.y_column_text.setWordWrap(True)
        self.y_column = QLineEdit()

        self.column_range_text = QLabel(self.polymorphic_states[self.mode]['ctext'])
        self.column_range_text.setWordWrap(True)
        self.column_range = QLineEdit()

        self.output_text = QLabel('Select Output:')
        self.output_text.setWordWrap(True)
        self.table_button = QRadioButton('Tabela (Planilha)')
        self.plot_button = QRadioButton('Gráfico (Plot)')
        self.output_destination_text = QLabel('Selecione a coluna onde deve-se armazenar os resultados:')
        self.output_destination_text.setWordWrap(True)
        self.output_destination = QLineEdit()
        self.run_button = QPushButton('Começar Análise')
        self.selected_output = ''

        self.degree_l = QLabel('Ordem:')
        self.degree = QSpinBox()
        self.degree.setRange(1, 6)

        self.quartile_l = QLabel('Quartil:')
        self.quartile = QSpinBox()
        self.quartile.setRange(1, 4)

        self.percentile_l = QLabel('Percentil:')
        self.percentile = QSpinBox()
        self.percentile.setRange(1, 100)

        self.default_degree_stylesheet = self.degree.styleSheet()
        self.default_quartile_stylesheet = self.quartile.styleSheet()
        self.default_percentile_stylesheet = self.percentile.styleSheet()

        self.degree.setDisabled(True)

        self.results_l = QLabel('Resultados da análise: ')
        self.results = QTextEdit()

        self.payload = []

        self.buildLayout()
        self.connections()
        self.initialState()
        self.setStyleSheet('color:white; font-family: Bahnschrift SemiLight Condensed; font-size: 14px;')

        self.x_column.setStyleSheet('background-color: white; color: black')
        self.y_column.setStyleSheet('background-color: white; color: black')
        self.column_range.setStyleSheet('background-color: white; color: black')
        self.results.setStyleSheet('background-color: white; color: black')


    def buildLayout(self):
        layout = QFormLayout()
        if len(self.dropdown_text.text()) > 0:
            layout.addWidget(self.dropdown_text)
            layout.addWidget(self.dropdown)
        if len(self.x_column_text.text()) > 0:
            layout.addWidget(self.x_column_text)
            layout.addWidget(self.x_column)
        if len(self.y_column_text.text()) > 0:
            layout.addWidget(self.y_column_text)
            layout.addWidget(self.y_column)
        if len(self.column_range_text.text()) > 0:
            layout.addWidget(self.column_range_text)
            layout.addWidget(self.column_range)
        if self.mode == 'Statistics':
            layout.addWidget(self.quartile_l)
            layout.addWidget(self.quartile)
            layout.addWidget(self.percentile_l)
            layout.addWidget(self.percentile)
        if self.mode == 'Regression':
            layout.addWidget(self.degree_l)
            layout.addWidget(self.degree)
        layout.addWidget(self.run_button)
        if self.mode != 'Boxplot' and self.mode != "Histogram":
            layout.addWidget(self.results_l)
            layout.addWidget(self.results)

        self.setLayout(layout)

    def initialState(self):
        self.output_destination.setDisabled(True)

    def connections(self):
        self.plot_button.clicked.connect(self.plotSelected)
        self.table_button.clicked.connect(self.tableSelected)
        self.run_button.clicked.connect(self.collectPayload)
        self.dropdown.currentTextChanged.connect(self.enableDegreeBox)

    def columnRangeDecomposition(self, text):
        try:
            return text.split(sep=':')
        except Exception:
            print('A problem happened in decomposing the column ranges. Probable bad user input.')

    def plotSelected(self):
        self.output_destination.setDisabled(True)
        self.selected_output = 'Plot'

    def enableDegreeBox(self, text):
        if text == 'Polinomial':
            self.degree.setStyleSheet('background-color: white; color: black')
            self.degree.setEnabled(True)
        else:
            self.degree.setStyleSheet(self.default_degree_stylesheet)
            self.degree.setDisabled(True)

        if text == 'Quartil':
            self.quartile.setStyleSheet('background-color: white; color: black')
            self.quartile.setEnabled(True)
        else:
            self.quartile.setStyleSheet(self.default_degree_stylesheet)
            self.quartile.setDisabled(True)

        if text == 'Percentil':
            self.percentile.setStyleSheet('background-color: white; color: black')
            self.percentile.setEnabled(True)
        else:
            self.percentile.setStyleSheet(self.default_degree_stylesheet)
            self.percentile.setDisabled(True)


    def tableSelected(self):
        self.output_destination.setDisabled(False)
        self.selected_output = 'Table'

    def collectPayload(self):
        if len(self.column_range_text.text()) > 0:
            a = self.columnRangeDecomposition(self.column_range.text())
            b = 'V'
        else:
            a = None
            b = self.y_column.text()
        payload = {
            'calculate': self.dropdown.currentText(),
            'x_column': self.x_column.text(),
            'y_column': b,
            'z_column': None,
            'column_range': a,
            'output_selection': self.selected_output,
            'output_column': self.output_destination.text()
        }
        self.payload = payload
        self.ready2calculate.emit(True)
Ejemplo n.º 19
0
        class MyWidget(QWidget):
            def __init__(self, parent):
                super().__init__(parent)
                self.buttons_id_value = {
                    1: ('comma', ','),
                    2: ('space', '\b'),
                    3: ('tab', '\t'),
                    4: ('semicolon', ';')
                }
                self.separator = QButtonGroup()
                lab = QLabel()
                lab.setText('Choose a separator:')
                for bid, value in self.buttons_id_value.items():
                    self.separator.addButton(QRadioButton(value[0]), id=bid)
                self.separator.setExclusive(True)
                self.default_button = self.separator.button(1)
                button_layout = QHBoxLayout()
                for button in self.separator.buttons():
                    button_layout.addWidget(button)
                self.default_button.click()

                openFileChooser = QPushButton('Choose')
                fileChooser = QFileDialog(self, 'Open csv', str(os.getcwd()),
                                          'Csv (*.csv *.tsv *.dat)')
                fileChooser.setFileMode(QFileDialog.ExistingFile)
                self.filePath = QLineEdit()
                openFileChooser.released.connect(fileChooser.show)
                fileChooser.fileSelected.connect(self.filePath.setText)
                self.filePath.textChanged.connect(self.checkFileExists)
                nameLabel = QLabel('Select a name:', self)
                self.nameField = QLineEdit(self)
                self.nameErrorLabel = QLabel(self)

                self.file_layout = QVBoxLayout()
                fileChooserLayout = QHBoxLayout()
                nameRowLayout = QHBoxLayout()
                fileChooserLayout.addWidget(openFileChooser)
                fileChooserLayout.addWidget(self.filePath)
                nameRowLayout.addWidget(nameLabel)
                nameRowLayout.addWidget(self.nameField)
                self.fileErrorLabel = QLabel(self)
                self.file_layout.addLayout(fileChooserLayout)
                self.file_layout.addWidget(self.fileErrorLabel)
                self.file_layout.addLayout(nameRowLayout)
                self.file_layout.addWidget(self.nameErrorLabel)
                self.fileErrorLabel.hide()
                self.nameErrorLabel.hide()
                self.tablePreview = SearchableAttributeTableWidget(self, True)
                self.tableSpinner = QtWaitingSpinner(
                    self.tablePreview,
                    centerOnParent=True,
                    disableParentWhenSpinning=True)
                self.nameField.textEdited.connect(self.nameErrorLabel.hide)

                # Split file by row
                splitRowLayout = QHBoxLayout()
                self.checkSplit = QCheckBox('Split file by rows', self)
                self.numberRowsChunk = QLineEdit(self)
                self.numberRowsChunk.setPlaceholderText(
                    'Number of rows per chunk')
                self.numberRowsChunk.setValidator(QIntValidator(self))
                splitRowLayout.addWidget(self.checkSplit)
                splitRowLayout.addWidget(self.numberRowsChunk)
                self.checkSplit.stateChanged.connect(self.toggleSplitRows)

                layout = QVBoxLayout()
                layout.addLayout(self.file_layout)
                layout.addWidget(lab)
                layout.addLayout(button_layout)
                layout.addLayout(splitRowLayout)
                layout.addWidget(QLabel('Preview'))
                layout.addWidget(self.tablePreview)
                self.setLayout(layout)

                self.filePath.textChanged.connect(self.loadPreview)
                self.separator.buttonClicked.connect(self.loadPreview)

            @Slot(object)
            def loadPreview(self) -> None:
                if not os.path.isfile(self.filePath.text()):
                    return

                class WorkerThread(QThread):
                    resultReady = Signal(Frame)

                    def __init__(self, path: str, separ: str, parent=None):
                        super().__init__(parent)
                        self.__path = path
                        self.__sep = separ

                    def run(self):
                        header = pd.read_csv(self.__path,
                                             sep=self.__sep,
                                             index_col=False,
                                             nrows=0)
                        self.resultReady.emit(Frame(header))

                sep: int = self.separator.checkedId()
                sep_s: str = self.buttons_id_value[sep][
                    1] if sep != -1 else None
                assert sep_s is not None

                # Async call to load header
                worker = WorkerThread(path=self.filePath.text(),
                                      separ=sep_s,
                                      parent=self)
                worker.resultReady.connect(self.onPreviewComputed)
                worker.finished.connect(worker.deleteLater)
                self.tableSpinner.start()
                worker.start()

            @Slot(Frame)
            def onPreviewComputed(self, header: Frame):
                self.tablePreview.setSourceFrameModel(FrameModel(self, header))
                self.tablePreview.model().setAllChecked(True)
                self.tableSpinner.stop()

            @Slot(str)
            def checkFileExists(self, path: str) -> None:
                file_exists = os.path.isfile(path)
                if not file_exists:
                    self.fileErrorLabel.setText('File does not exists!')
                    self.fileErrorLabel.setStyleSheet('color: red')
                    self.filePath.setToolTip('File does not exists!')
                    self.filePath.setStyleSheet('border: 1px solid red')
                    # self.file_layout.addWidget(self.fileErrorLabel)
                    self.parentWidget().disableOkButton()
                    self.fileErrorLabel.show()
                else:
                    # self.file_layout.removeWidget(self.fileErrorLabel)
                    self.fileErrorLabel.hide()
                    self.filePath.setStyleSheet('')
                    self.parentWidget().enableOkButton()
                    if not self.nameField.text():
                        name: str = os.path.splitext(os.path.basename(path))[0]
                        self.nameField.setText(name)

            @Slot(Qt.CheckState)
            def toggleSplitRows(self, state: Qt.CheckState) -> None:
                if state == Qt.Checked:
                    self.numberRowsChunk.setEnabled(True)
                else:
                    self.numberRowsChunk.setDisabled(True)

            def showNameError(self, msg: str) -> None:
                self.nameErrorLabel.setText(msg)
                self.nameErrorLabel.setStyleSheet('color: red')
                self.nameErrorLabel.show()
Ejemplo n.º 20
0
class SizeDetectorGUI(QWidget):
    EXIT_CODE_REBOOT = -123
    PREVIOUS_GEOMETRY = None

    def __init__(self):
        QWidget.__init__(self)

        self.app_initialize = False

        self.logger = logging.getLogger(LOGGER_NAME)

        self.struct = SizeDetectorCore().get_config_params()
        self.cmd_args = SizeDetectorCore.get_cmd_params()

        self.gs_button = QPushButton(text="Detect Size!")
        self.ref_button = QPushButton(text="Refresh")

        self.inf_label = QLabel("Enter directory for sizing:")
        self.inf_label.setAlignment(Qt.AlignLeft)

        self.inp_line = QLineEdit("/")

        self.res_table = QTableWidget()
        self.res_table.setColumnCount(2)
        self.res_table.setHorizontalHeaderItem(0, QTableWidgetItem("size"))
        self.res_table.setHorizontalHeaderItem(1, QTableWidgetItem("file"))

        header = self.res_table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Stretch)

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.inf_label)
        self.layout.addWidget(self.inp_line)
        self.layout.addWidget(self.gs_button)
        self.layout.addWidget(self.res_table)
        self.layout.addWidget(self.ref_button)

        self.setLayout(self.layout)

        self.gs_button.clicked.connect(self.run_detecting)
        self.ref_button.clicked.connect(self.__refresh)

        self.__initialize()

    def __initialize(self):
        if self.struct is None:
            self._disable_form()
            self._set_info_message("Invalid or missing config file.\n"
                                   "For detailed information see log file")
            return

        if self.struct['pmanager'] != 'rpm':
            self._disable_form()
            self._set_info_message("Unsupported package manager {}"
                                   "".format(self.struct['pmanager']))
            return

        shell = 'rpm -qa {}'.format(self.struct['dupackage'])
        try:
            proc = Popen(shell.split(), stdout=PIPE, stderr=PIPE)
        except FileNotFoundError as e:
            self.logger.debug("Command `{}` failed: {}".format(shell, e.strerror))
            self._set_info_message(
                "Runtime error. For detailed information see log file.")
            return

        out, err = proc.communicate()

        if err:
            self.logger.debug(
                "Command `{}` failed: {}".format(shell, err.decode()))
            self._set_info_message(
                "Runtime error. For detailed information see log file.")
            return

        if not out:
            self._disable_form()
            self._set_info_message("No package `{}` found on the system"
                                   .format(self.struct['dupackage']))
            return

        self.app_initialize = True

    def run_detecting(self):
        self.inf_label.setText("Enter directory for sizing:")
        dir_ = self.inp_line.text()
        if dir_[-1] != '/':
            dir_ += '/'

        sd = SizeDetector(dir_)
        status, retval = sd.detect_size()
        if status is False:
            self._set_info_message(retval)
            return

        self.res_table.setRowCount(len(retval) - 1)

        count = 0
        for line in retval:
            if line:
                line = line.split()
                self.res_table.setItem(count, 0, QTableWidgetItem(line[0]))
                dir_name = line[1].replace(dir_, '')
                if len(retval) - 2 == count:
                    dir_name = '* Summary size *'
                self.res_table.setItem(count, 1, QTableWidgetItem(dir_name))
                count += 1

    def _set_info_message(self, message):
        self.inf_label.setText(message)

    def _disable_form(self):
        self.inp_line.setDisabled(True)
        self.gs_button.setDisabled(True)
        self.ref_button.setFocus()

    def __refresh(self):
        SizeDetectorGUI.PREVIOUS_GEOMETRY = self.geometry()
        QtGui.qApp.exit(SizeDetectorGUI.EXIT_CODE_REBOOT)
Ejemplo n.º 21
0
class PlotControls(QWidget):
    erasePlot = Signal(bool)

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

        types_of_axes = ['Cartesiano', 'Logarítmico']
        types_of_traces = ['Scatter', "Boxplot"]

        self.modify_title = QLineEdit()
        self.modify_x_axis_type = Dropdown(types_of_axes)
        self.modify_y_axis_type = Dropdown(types_of_axes)
        self.modify_x_axis_label = QLineEdit()
        self.modify_y_axis_label = QLineEdit()
        self.modify_lower_x_range = QLineEdit()
        self.modify_upper_x_range = QLineEdit()
        self.modify_lower_y_range = QLineEdit()
        self.modify_upper_y_range = QLineEdit()
        self.type_of_trace = Dropdown(types_of_traces)
        self.add_x_trace = QLineEdit()
        self.add_y_trace = QLineEdit()
        self.trace_name = QLineEdit()
        self.run_button = QPushButton('Confirmar alterações')
        self.erase_button = QPushButton('Apagar gráfico')

        self.erase_button.clicked.connect(lambda: self.erasePlot.emit(True))
        self.type_of_trace.currentIndexChanged.connect(self.typeChanged)

        self.buildLayout()
        self.setStyleSheet('color:white; font-family: Bahnschrift SemiLight Condensed; font-size: 14px;')

        self.modify_title.setStyleSheet('background-color: white; color: black')
        self.modify_x_axis_type.setStyleSheet('background-color: white; color: black')
        self.modify_y_axis_type.setStyleSheet('background-color: white; color: black')
        self.modify_x_axis_label.setStyleSheet('background-color: white; color: black')
        self.modify_y_axis_label.setStyleSheet('background-color: white; color: black')
        self.modify_lower_x_range.setStyleSheet('background-color: white; color: black')
        self.modify_upper_x_range.setStyleSheet('background-color: white; color: black')
        self.modify_lower_y_range.setStyleSheet('background-color: white; color: black')
        self.modify_upper_y_range.setStyleSheet('background-color: white; color: black')
        self.type_of_trace.setStyleSheet('background-color: white; color: black')
        self.add_x_trace.setStyleSheet('background-color: white; color: black')
        self.add_y_trace.setStyleSheet('background-color: white; color: black')
        self.trace_name.setStyleSheet('background-color: white; color: black')
        self.run_button.setStyleSheet('background-color: white; color: black')
        self.erase_button.setStyleSheet('background-color: white; color: black')

    def buildLayout(self):
        layout = QFormLayout()
        layout.addRow('Título', self.modify_title)
        layout.addRow('Escala do eixo X', self.modify_x_axis_type)
        layout.addRow('Escala do eixo Y', self.modify_y_axis_type)
        layout.addRow('Rótulo do eixo X', self.modify_x_axis_label)
        layout.addRow('Rótulo do eixo Y', self.modify_y_axis_label)
        layout.addRow('Limite inferior do eixo X', self.modify_lower_x_range)
        layout.addRow('Limite superior do eixo X', self.modify_upper_x_range)
        layout.addRow('Limite inferior do eixo Y', self.modify_lower_y_range)
        layout.addRow('Limite superior do eixo Y', self.modify_upper_y_range)
        layout.addRow('Tipo de série', self.type_of_trace)
        layout.addRow('Adicionar série/box no gráfico (eixo x)', self.add_x_trace)
        layout.addRow('Adicionar série/box no gráfico (eixo y)', self.add_y_trace)
        layout.addRow('Nome da série', self.trace_name)
        layout.addWidget(self.run_button)
        layout.addWidget(self.erase_button)
        self.setLayout(layout)

    def fillFromJson(self, _json):
        # json is the standard json export from the plotly library
        assert (isinstance(_json, str))

        j_obj = j.loads(_json)
        
        try:
            if 'title' in list(j_obj['layout'].keys()):
                self.modify_title.setText(str(j_obj['layout']['title']['text']))
    
            if 'type' in list(j_obj['layout']['xaxis'].keys()):
                if j_obj['layout']['xaxis']['type'].lower() == 'log':
                    self.modify_x_axis_type.setCurrentIndex(1)
                else:
                    self.modify_y_axis_type.setCurrentIndex(0)
                    
            if 'type' in list(j_obj['layout']['yaxis'].keys()):
                if j_obj['layout']['yaxis']['type'].lower() == 'log':
                    self.modify_y_axis_type.setCurrentIndex(1)
                else:
                    self.modify_y_axis_type.setCurrentIndex(0)
    
            if 'title' in list(j_obj['layout']['xaxis'].keys()):
                self.modify_x_axis_label.setText(str(j_obj['layout']['xaxis']['title']['text']))
            if 'title' in list(j_obj['layout']['yaxis'].keys()):
                self.modify_y_axis_label.setText(str(j_obj['layout']['yaxis']['title']['text']))
            if 'range' in list(j_obj['layout']['xaxis'].keys()):
                self.modify_lower_x_range.setText(str(j_obj['layout']['xaxis']['range'][0]))
                self.modify_upper_x_range.setText(str(j_obj['layout']['xaxis']['range'][1]))
            if 'range' in list(j_obj['layout']['yaxis'].keys()):
                self.modify_lower_y_range.setText(str(j_obj['layout']['yaxis']['range'][0]))
                self.modify_upper_y_range.setText(str(j_obj['layout']['yaxis']['range'][1]))
        except KeyError:
            if 'title' in list(j_obj['layout']['template']['layout'].keys()):
                self.modify_title.setText(str(j_obj['layout']['template']['layout']['title']['text']))

            if 'type' in list(j_obj['layout']['template']['layout']['xaxis'].keys()):
                if j_obj['layout']['template']['layout']['xaxis']['type'].lower() == 'log':
                    self.modify_x_axis_type.setCurrentIndex(1)
                else:
                    self.modify_y_axis_type.setCurrentIndex(0)

            if 'type' in list(j_obj['layout']['template']['layout']['yaxis'].keys()):
                if j_obj['layout']['template']['layout']['yaxis']['type'].lower() == 'log':
                    self.modify_y_axis_type.setCurrentIndex(1)
                else:
                    self.modify_y_axis_type.setCurrentIndex(0)

            if 'title' in list(j_obj['layout']['template']['layout']['xaxis'].keys()):
                self.modify_x_axis_label.setText(str(j_obj['layout']['template']['layout']['xaxis']['title']['text']))
            if 'title' in list(j_obj['layout']['template']['layout']['yaxis'].keys()):
                self.modify_y_axis_label.setText(str(j_obj['layout']['template']['layout']['yaxis']['title']['text']))
            if 'range' in list(j_obj['layout']['template']['layout']['xaxis'].keys()):
                self.modify_lower_x_range.setText(str(j_obj['layout']['template']['layout']['xaxis']['range'][0]))
                self.modify_upper_x_range.setText(str(j_obj['layout']['template']['layout']['xaxis']['range'][1]))
            if 'range' in list(j_obj['layout']['template']['layout']['yaxis'].keys()):
                self.modify_lower_y_range.setText(str(j_obj['layout']['template']['layout']['yaxis']['range'][0]))
                self.modify_upper_y_range.setText(str(j_obj['layout']['template']['layout']['yaxis']['range'][1]))

        return

    def typeChanged(self, ind):
        currentType = self.type_of_trace.currentText()
        if currentType == "Boxplot":
            self.add_y_trace.setDisabled(True)
            self.add_y_trace.setStyleSheet('background-color: grey; color: black')
        else:
            self.add_y_trace.setEnabled(True)
            self.add_y_trace.setStyleSheet('background-color: white; color: black')
Ejemplo n.º 22
0
class Snippets(QDialog):

    def __init__(self, parent=None):
        super(Snippets, self).__init__(parent)
        # Create widgets
        self.setWindowModality(Qt.ApplicationModal)
        self.title = QLabel(self.tr("Snippet Editor"))
        self.saveButton = QPushButton(self.tr("Save"))
        self.closeButton = QPushButton(self.tr("Close"))
        self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey"))
        self.setWindowTitle(self.title.text())
        self.newFolderButton = QPushButton("New Folder")
        self.deleteSnippetButton = QPushButton("Delete")
        self.newSnippetButton = QPushButton("New Snippet")
        self.edit = QPlainTextEdit()
        self.edit.setPlaceholderText("python code")
        self.resetting = False
        self.columns = 3

        self.keySequenceEdit = QKeySequenceEdit(self)
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel = QLabel("")
        self.currentFileLabel = QLabel()
        self.currentFile = ""
        self.snippetDescription = QLineEdit()
        self.snippetDescription.setPlaceholderText("optional description")

        #Set Editbox Size
        font = getMonospaceFont(self)
        self.edit.setFont(font)
        font = QFontMetrics(font)
        self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API

        #Files
        self.files = QFileSystemModel()
        self.files.setRootPath(snippetPath)
        self.files.setNameFilters(["*.py"])

        #Tree
        self.tree = QTreeView()
        self.tree.setModel(self.files)
        self.tree.setSortingEnabled(True)
        self.tree.hideColumn(2)
        self.tree.sortByColumn(0, Qt.AscendingOrder)
        self.tree.setRootIndex(self.files.index(snippetPath))
        for x in range(self.columns):
            #self.tree.resizeColumnToContents(x)
            self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) 
        treeLayout = QVBoxLayout()
        treeLayout.addWidget(self.tree)
        treeButtons = QHBoxLayout()
        treeButtons.addWidget(self.newFolderButton)
        treeButtons.addWidget(self.newSnippetButton)
        treeButtons.addWidget(self.deleteSnippetButton)
        treeLayout.addLayout(treeButtons)
        treeWidget = QWidget()
        treeWidget.setLayout(treeLayout)

        # Create layout and add widgets
        buttons = QHBoxLayout()
        buttons.addWidget(self.clearHotkeyButton)
        buttons.addWidget(self.keySequenceEdit)
        buttons.addWidget(self.currentHotkeyLabel)
        buttons.addWidget(self.closeButton)
        buttons.addWidget(self.saveButton)

        description = QHBoxLayout()
        description.addWidget(QLabel(self.tr("Description: ")))
        description.addWidget(self.snippetDescription)

        vlayoutWidget = QWidget()
        vlayout = QVBoxLayout()
        vlayout.addLayout(description)
        vlayout.addWidget(self.edit)
        vlayout.addLayout(buttons)
        vlayoutWidget.setLayout(vlayout)

        hsplitter = QSplitter()
        hsplitter.addWidget(treeWidget)
        hsplitter.addWidget(vlayoutWidget)

        hlayout = QHBoxLayout()
        hlayout.addWidget(hsplitter)

        self.showNormal() #Fixes bug that maximized windows are "stuck"
        self.settings = QSettings("Vector35", "Snippet Editor")
        if self.settings.contains("ui/snippeteditor/geometry"):
            self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry"))
        else:
            self.edit.setMinimumWidth(80 * font.averageCharWidth())
            self.edit.setMinimumHeight(30 * font.lineSpacing())

        # Set dialog layout
        self.setLayout(hlayout)

        # Add signals
        self.saveButton.clicked.connect(self.save)
        self.closeButton.clicked.connect(self.close)
        self.clearHotkeyButton.clicked.connect(self.clearHotkey)
        self.tree.selectionModel().selectionChanged.connect(self.selectFile)
        self.newSnippetButton.clicked.connect(self.newFileDialog)
        self.deleteSnippetButton.clicked.connect(self.deleteSnippet)
        self.newFolderButton.clicked.connect(self.newFolder)

        if self.settings.contains("ui/snippeteditor/selected"):
            selectedName = self.settings.value("ui/snippeteditor/selected")
            self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
            if self.tree.selectionModel().hasSelection():
                self.selectFile(self.tree.selectionModel().selection(), None)
                self.edit.setFocus()
                cursor = self.edit.textCursor()
                cursor.setPosition(self.edit.document().characterCount()-1)
                self.edit.setTextCursor(cursor)
            else:
                self.readOnly(True)
        else:
            self.readOnly(True)


    @staticmethod
    def registerAllSnippets():
        for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())):
            if action == "Snippets\\Snippet Editor...":
                continue
            UIActionHandler.globalActions().unbindAction(action)
            Menu.mainMenu("Tools").removeAction(action)
            UIAction.unregisterAction(action)

        for snippet in includeWalk(snippetPath, ".py"):
            snippetKeys = None
            (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet)
            if not snippetDescription:
                actionText = "Snippets\\" + os.path.basename(snippet).rstrip(".py")
            else:
                actionText = "Snippets\\" + snippetDescription
            if snippetCode:
                if snippetKeys == None:
                    UIAction.registerAction(actionText)
                else:
                    UIAction.registerAction(actionText, snippetKeys)
                UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode)))
                Menu.mainMenu("Tools").addAction(actionText, actionText)

    def clearSelection(self):
        self.keySequenceEdit.clear()
        self.currentHotkey = QKeySequence()
        self.currentHotkeyLabel.setText("")
        self.currentFileLabel.setText("")
        self.snippetDescription.setText("")
        self.edit.setPlainText("")
        self.currentFile = ""

    def reject(self):
        self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry())

        if self.snippetChanged():
            question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?"))
            if question != QMessageBox.StandardButton.Yes:
                return
        self.accept()

    def newFolder(self):
        (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: "))
        if ok and folderName:
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                QDir(selection).mkdir(folderName)
            else:
                QDir(snippetPath).mkdir(folderName)    

    def selectFile(self, new, old):
        if (self.resetting):
            self.resetting = False
            return
        newSelection = self.files.filePath(new.indexes()[0])
        self.settings.setValue("ui/snippeteditor/selected", newSelection)
        if QFileInfo(newSelection).isDir():
            self.readOnly(True)
            self.tree.clearSelection()
            self.currentFile = ""
            return

        if old and old.length() > 0:
            oldSelection = self.files.filePath(old.indexes()[0])
            if not QFileInfo(oldSelection).isDir() and self.snippetChanged():
                question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?"))
                if question != QMessageBox.StandardButton.Yes:
                    self.resetting = True
                    self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
                    return False

        self.currentFile = newSelection
        self.loadSnippet()

    def loadSnippet(self):
        self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName())
        (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile)
        self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("")
        self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence(""))
        self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("")
        self.readOnly(False)

    def newFileDialog(self):
        (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: "))
        if ok and snippetName:
            if not snippetName.endswith(".py"):
                snippetName += ".py"
            index = self.tree.selectionModel().currentIndex()
            selection = self.files.filePath(index)
            if QFileInfo(selection).isDir():
                path = os.path.join(selection, snippetName)
            else:
                path = os.path.join(snippetPath, snippetName)
                self.readOnly(False)
            open(path, "w").close()
            self.tree.setCurrentIndex(self.files.index(path))
            log_debug("Snippet %s created." % snippetName)

    def readOnly(self, flag):
        self.keySequenceEdit.setEnabled(not flag)
        self.snippetDescription.setReadOnly(flag)
        self.edit.setReadOnly(flag)
        if flag:
            self.snippetDescription.setDisabled(True)
            self.edit.setDisabled(True)
        else:
            self.snippetDescription.setEnabled(True)
            self.edit.setEnabled(True)

    def deleteSnippet(self):
        selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row
        snippetName = self.files.fileName(selection)
        question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName)
        if (question == QMessageBox.StandardButton.Yes):
            log_debug("Deleting snippet %s." % snippetName)
            self.clearSelection()
            self.files.remove(selection)
            self.registerAllSnippets()

    def snippetChanged(self):
        if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()):
            return False
        (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile)
        if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty():
            return True
        if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString():
            return True
        return self.edit.toPlainText() != snippetCode or \
               self.snippetDescription.text() != snippetDescription

    def save(self):
        log_debug("Saving snippet %s" % self.currentFile)
        outputSnippet = open(self.currentFile, "w")
        outputSnippet.write("#" + self.snippetDescription.text() + "\n")
        outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
        outputSnippet.write(self.edit.toPlainText())
        outputSnippet.close()
        self.registerAllSnippets()

    def clearHotkey(self):
        self.keySequenceEdit.clear()
Ejemplo n.º 23
0
class guiMain(QMainWindow):
    def __init__(self, bk, prefs):
        super(guiMain, self).__init__()
        self.taglist = taglist
        # Edit Plugin container object
        self.bk = bk

        # Handy prefs groupings
        self.gui_prefs = prefs['gui_selections']
        self.misc_prefs = prefs['miscellaneous_settings']
        self.update_prefs = prefs['update_settings']
        self.combobox_values = prefs['combobox_values']

        self._ok_to_close = False
        # Check online github files for newer version
        self.update, self.newversion = self.check_for_update()
        self.setup_ui()

    def setup_ui(self):
        app = QApplication.instance()
        p = app.palette()
        link_color = p.color(p.Active, p.Link).name()

        DELETE_STR = _t('guiMain', 'Delete')
        MODIFY_STR = _t('guiMain', 'Modify')
        self.NO_ATTRIB_STR = _t('guiMain', 'No attributes (naked tag)')
        self.NO_CHANGE_STR = _t('guiMain', 'No change')
        self.setWindowTitle(_t('guiMain', 'Tag Mechanic'))

        configAct = QAction(_t('guiMain', '&Config'), self)
        configAct.triggered.connect(self.showConfig)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu(_t('guiMain', '&Edit'))
        fileMenu.addAction(configAct)

        layout = QVBoxLayout()

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        if self.update:
            update_layout = QHBoxLayout()
            layout.addLayout(update_layout)
            self.label = QLabel()
            self.label.setText(
                _t('guiMain', 'Plugin Update Available') + ' ' +
                str(self.newversion))
            self.label.setStyleSheet(
                'QLabel {{color: {};}}'.format(link_color))
            update_layout.addWidget(self.label)

        action_layout = QHBoxLayout()
        layout.addLayout(action_layout)
        label = QLabel(_t('guiMain', 'Action type:'), self)
        action_layout.addWidget(label)
        self.action_combo = QComboBox()
        action_layout.addWidget(self.action_combo)
        self.action_combo.addItems([DELETE_STR, MODIFY_STR])
        self.action_combo.setCurrentIndex(self.gui_prefs['action'])
        self.action_combo.currentIndexChanged.connect(self.update_gui)

        tag_layout = QHBoxLayout()
        layout.addLayout(tag_layout)
        label = QLabel(_t('guiMain', 'Tag name:'), self)
        tag_layout.addWidget(label)
        self.tag_combo = QComboBox()
        tag_layout.addWidget(self.tag_combo)
        self.tag_combo.addItems(self.taglist)
        self.tag_combo.setCurrentIndex(self.gui_prefs['tag'])
        self.tag_combo.currentIndexChanged.connect(self.update_gui)

        attr_layout = QHBoxLayout()
        layout.addLayout(attr_layout)
        label = QLabel(_t('guiMain', 'Having the attribute:'), self)
        attr_layout.addWidget(label)
        self.attr_combo = QComboBox()
        attr_layout.addWidget(self.attr_combo)
        self.attr_combo.addItems(self.combobox_values['attrs'])
        self.attr_combo.addItem(self.NO_ATTRIB_STR)
        self.attr_combo.setCurrentIndex(self.gui_prefs['attrs'])
        self.attr_combo.currentIndexChanged.connect(self.update_gui)

        srch_layout = QHBoxLayout()
        layout.addLayout(srch_layout)
        label = QLabel(_t('guiMain', 'Whose value is (no quotes):'), self)
        srch_layout.addWidget(label)
        self.srch_txt = QLineEdit('', self)
        srch_layout.addWidget(self.srch_txt)
        self.srch_method = QCheckBox(_t('guiMain', 'Regex'), self)
        srch_layout.addWidget(self.srch_method)

        newtag_layout = QHBoxLayout()
        layout.addLayout(newtag_layout)
        label = QLabel(_t('guiMain', 'Change tag to:'), self)
        newtag_layout.addWidget(label)
        self.newtag_combo = QComboBox()
        newtag_layout.addWidget(self.newtag_combo)

        self.newtag_combo.addItem(self.NO_CHANGE_STR)
        self.newtag_combo.addItems(self.combobox_values['{}_changes'.format(
            str(self.tag_combo.currentText()))])

        if self.action_combo.currentIndex() == 0:
            self.newtag_combo.setDisabled(True)

        newattr_layout = QVBoxLayout()
        layout.addLayout(newattr_layout)
        label = QLabel(
            _t('guiMain', 'New attribute string to insert (entire):'), self)
        newattr_layout.addWidget(label)
        self.newattr_txt = QLineEdit('', self)
        newattr_layout.addWidget(self.newattr_txt)
        self.copy_attr = QCheckBox(
            _t('guiMain', 'Copy existing attribute string'), self)
        self.copy_attr.stateChanged.connect(self.update_txt_box)
        newattr_layout.addWidget(self.copy_attr)
        if self.action_combo.currentIndex() == 0:
            self.copy_attr.setDisabled(True)
            self.newattr_txt.setDisabled(True)

        layout.addSpacing(10)
        self.text_panel = QTextEdit()
        self.text_panel.setReadOnly(True)
        layout.addWidget(self.text_panel)

        layout.addSpacing(10)
        button_layout = QHBoxLayout()
        layout.addLayout(button_layout)
        self.process_button = QPushButton(_t('guiMain', 'Process'), self)
        self.process_button.setToolTip('<p>{}'.format(
            _t('guiMain', 'Process selected files with current criteria')))
        self.process_button.clicked.connect(self._process_clicked)
        button_layout.addWidget(self.process_button)

        self.abort_button = QPushButton(_t('guiMain', 'Abort Changes'), self)
        self.abort_button.setToolTip('<p>{}'.format(
            _t('guiMain', 'Make no changes and exit')))
        self.abort_button.clicked.connect(self._abort_clicked)
        self.abort_button.setDisabled(True)
        button_layout.addWidget(self.abort_button)

        self.quit_button = QPushButton(_t('guiMain', 'Quit'), self)
        self.quit_button.setToolTip('<p>{}'.format(
            _t('guiMain', 'Quit with no changes')))
        self.quit_button.clicked.connect(self._quit_clicked)
        button_layout.addWidget(self.quit_button)

        if self.misc_prefs['windowGeometry'] is not None:
            try:
                self.restoreGeometry(
                    QByteArray.fromHex(
                        self.misc_prefs['windowGeometry'].encode('ascii')))
            except Exception:
                pass
        self.show()

    def update_gui(self):
        if self.attr_combo.currentIndex() == self.attr_combo.count() - 1:
            self.srch_txt.clear()
            self.srch_txt.setDisabled(True)
            self.srch_method.setChecked(False)
            self.srch_method.setDisabled(True)
        else:
            self.srch_txt.setDisabled(False)
            self.srch_method.setDisabled(False)

        self.newtag_combo.clear()
        self.newtag_combo.addItem(self.NO_CHANGE_STR)
        self.newtag_combo.addItems(self.combobox_values['{}_changes'.format(
            str(self.tag_combo.currentText()))])

        if self.action_combo.currentIndex() == 0:
            self.newtag_combo.setCurrentIndex(0)
            self.newtag_combo.setDisabled(True)
            self.newattr_txt.clear()
            self.newattr_txt.setDisabled(True)
            self.copy_attr.setChecked(False)
            self.copy_attr.setDisabled(True)
        else:
            self.newtag_combo.setDisabled(False)
            self.newattr_txt.setDisabled(False)
            self.copy_attr.setDisabled(False)

        self.update_txt_box()

    def update_txt_box(self):
        if self.copy_attr.isChecked() or not self.copy_attr.isEnabled():
            self.newattr_txt.clear()
            self.newattr_txt.setDisabled(True)
        else:
            self.newattr_txt.setDisabled(False)

    def refresh_attr_values(self):
        self.attr_combo.clear()
        self.attr_combo.addItems(self.combobox_values['attrs'])
        self.attr_combo.addItem(self.NO_ATTRIB_STR)

    def _process_clicked(self):
        criteria = {}
        global PROCESSED
        criteria['tag'] = str(self.tag_combo.currentText())
        if self.action_combo.currentIndex() == 0:
            criteria['action'] = 'delete'
        else:
            criteria['action'] = 'modify'
        if self.attr_combo.currentIndex() == self.attr_combo.count() - 1:
            criteria['attrib'] = None
        else:
            criteria['attrib'] = str(self.attr_combo.currentText())
        srch_str = str(self.srch_txt.displayText())
        if not len(srch_str):
            srch_str = None
        if srch_str is None and criteria['attrib'] is not None:
            title = _t('guiMain', 'Error')
            msg = '<p>{0}'.format(
                _t('guiMain', 'Must enter a value for the attribute selected'))
            return QMessageBox.warning(self, title, msg, QMessageBox.Ok)
        criteria['srch_str'] = srch_str

        criteria['srch_method'] = 'normal'
        if self.srch_method.isChecked():
            criteria['srch_method'] = 'regex'
        if self.newtag_combo.currentIndex() == 0:
            criteria['new_tag'] = None
        else:
            criteria['new_tag'] = str(self.newtag_combo.currentText())
        if criteria['action'] == 'modify' and criteria[
                'new_tag'] is None and self.copy_attr.isChecked():
            title = _t('guiMain', 'Error')
            msg = '<p>{0}'.format(
                _t('guiMain', 'What--exactly--would that achieve?'))
            return QMessageBox.question(self, title, msg, QMessageBox.Ok)

        criteria['new_str'] = str(self.newattr_txt.displayText())
        criteria['copy'] = False
        if self.copy_attr.isChecked():
            criteria['copy'] = True
        if not len(criteria['new_str']):
            criteria['new_str'] = ''

        # Disable the 'Process' button, disable the context customization menu
        self.process_button.setDisabled(True)
        PROCESSED = True

        totals = 0
        self.text_panel.clear()
        self.text_panel.insertHtml('<h4>{}...</h4><br>'.format(
            _t('guiMain', 'Starting')))

        # Loop through the files selected in Sigil's Book View
        for (typ, ident) in self.bk.selected_iter():
            # Skip the ones that aren't the "Text" mimetype.
            if self.bk.id_to_mime(ident) != 'application/xhtml+xml':
                continue
            href = self.bk.id_to_href(ident)
            # Param 1 - the contents of the (x)html file.
            criteria['html'] = self.bk.readfile(ident)
            if not isinstance(criteria['html'], str):
                criteria['html'] = str(criteria['html'], 'utf-8')

            # Hand off the "criteria" parameters dictionary to the parsing engine
            parser = MarkupParser(criteria)

            # Retrieve the new markup and the number of occurrences changed
            try:
                html, occurrences = parser.processml()
            except Exception:
                self.text_panel.insertHtml('<p>{} {}! {}.</p>\n'.format(
                    _t('guiMain', 'Error parsing'), href,
                    _t('guiMain', 'File skipped')))
                continue

            # Report whether or not changes were made (and how many)
            totals += occurrences
            if occurrences:
                # write changed markup back to file
                self.bk.writefile(ident, html)
                self.text_panel.insertHtml(
                    '<p>{} {}:&#160;&#160;&#160;{}</p>\n'.format(
                        _t('guiMain', 'Occurrences found/changed in'), href,
                        int(occurrences)))
            else:
                self.text_panel.insertHtml('<p>{} {}</p>\n'.format(
                    _t('guiMain', 'Criteria not found in'), href))

        # report totals
        if totals:
            self.quit_button.setText(_t('guiMain', 'Commit and Exit'))
            self.quit_button.setToolTip('<p>{}'.format(
                _t('guiMain', 'Commit all changes and exit')))
            self.abort_button.setDisabled(False)
            self.text_panel.insertHtml(
                '<br><h4>{}:&#160;&#160;&#160;{}</h4>'.format(
                    _t('guiMain', 'Total occurrences found/changed'),
                    int(totals)))
        else:
            self.text_panel.insertHtml('<br><h4>{}</h4>'.format(
                _t('guiMain', 'No changes made to book')))
        self.text_panel.insertHtml('<br><h4>{}</h4>'.format(
            _t('guiMain', 'Finished')))

    def _quit_clicked(self):
        self.misc_prefs['windowGeometry'] = self.saveGeometry().toHex().data(
        ).decode('ascii')
        if PROCESSED:
            self.gui_prefs['action'] = self.action_combo.currentIndex()
            self.gui_prefs['tag'] = self.tag_combo.currentIndex()
            self.gui_prefs['attrs'] = self.attr_combo.currentIndex()
        self._ok_to_close = True
        self.close()

    def _abort_clicked(self):
        global BAIL_OUT
        BAIL_OUT = True
        self._ok_to_close = True
        self.close()

    def getAbort(self):
        return BAIL_OUT

    def showConfig(self):
        ''' Launch Customization Dialog '''
        dlg = ConfigDialog(self, self.combobox_values)
        if dlg.exec_() == QDialog.Accepted:
            self.refresh_attr_values()
            self.update_gui()

    def check_for_update(self):
        '''Use updatecheck.py to check for newer versions of the plugin'''
        last_time_checked = self.update_prefs['last_time_checked']
        last_online_version = self.update_prefs['last_online_version']
        chk = UpdateChecker(last_time_checked, last_online_version, self.bk._w)
        update_available, online_version, time = chk.update_info()
        # update preferences with latest date/time/version
        self.update_prefs['last_time_checked'] = time
        if online_version is not None:
            self.update_prefs['last_online_version'] = online_version
        if update_available:
            return (True, online_version)
        return (False, online_version)

    def closeEvent(self, event):
        if self._ok_to_close:
            event.accept()  # let the window close
        else:
            self._abort_clicked()