class MainWindow(QWidget): # Inherits QWidget def __init__(self): super().__init__() self.label = QLabel(self) self.button = QPushButton('Очистить', self) self.line_edit = QLineEdit(self) self.initializeUI() def initializeUI(self): self.setGeometry(100, 100, 400, 200) self.setWindowTitle('QLineEdit Test') QLabel("Введите login", self).move(100, 10) name_label = QLabel("Login:"******"Очистить" if sender.text() == 'Очистить': self.line_edit.clear() # очищаем текст
class LabeledSlider(QWidget): slider_min = 0 slider_max = 64 slider_step = 1 line_edit_width = 70 line_edit_heigth = 20 def __init__(self): super().__init__() self.locale = QLocale() self.setContentsMargins(0, 0, 0, 0) hbox = QHBoxLayout(self) hbox.setContentsMargins( 0, 0, 0, 0, ) self.slider = QSlider(Qt.Horizontal, self) self.slider.setMinimum(self.slider_min) self.slider.setMaximum(self.slider_max) self.slider.setSingleStep(self.slider_step) self.line_edit = QLineEdit('', self) self.line_edit.setAlignment(Qt.AlignCenter) self.line_edit.setMaximumWidth(self.line_edit_width) self.line_edit.setMaximumHeight(self.line_edit_heigth) self.line_edit.setMinimumHeight(self.line_edit_heigth) hbox.addWidget(self.slider) hbox.addWidget(self.line_edit) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
def createEditor(self, parent, option, index): """ Creates the double-click editor for renaming render setup entries. The override entry is right aligned. """ editor = QLineEdit(parent) item = self._getItem(index) if item.type() == renderSetup.RENDER_OVERRIDE_TYPE: editor.setAlignment(Qt.AlignRight | Qt.AlignVCenter) return editor
def getData(self, currentAmount): amtTitle = QLabel('Total: $') amtTitle.setMaximumWidth(120) validator = QIntValidator(1, 99999, self) amtEdit = QLineEdit() amtEdit.setValidator(validator) amtEdit.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) amtEdit.returnPressed.connect(self.okClicked) amtEdit.setText(str(currentAmount)) amtEdit.selectAll() okButton = QPushButton('OK') okButton.clicked.connect(self.okClicked) amtLayout = QHBoxLayout() amtLayout.addWidget(amtTitle) amtLayout.addWidget(amtEdit) amtLayout.addWidget(okButton) self.setLayout(amtLayout) retValue = self.exec() if retValue: return amtEdit.text() else: return 0
def _add_line_edit(self, text=""): lineedit = QLineEdit(text) lineedit.setMaximumHeight(28) lineedit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) lineedit.setAlignment(QtCore.Qt.AlignLeft) self.layout.addWidget(lineedit) return lineedit
def _create_meas(self, name, layout, row, col, temp): label = QLabel(name, self) font = label.font() font.setPointSize(8) font.setBold(True) label.setFont(font) layout.addWidget(label, row, col) layout.setAlignment(label, Qt.AlignRight) meas_value = QLineEdit(self) meas_value.setReadOnly(True) meas_value.setMaximumSize(65, 32) font = QFont('Monospace') font.setStyleHint(QFont.TypeWriter) font.setPointSize(10) meas_value.setFont(font) meas_value.setAlignment(Qt.AlignCenter) if temp: meas_value.setText('0.00 C') else: meas_value.setText('0.00 V') layout.addWidget(meas_value, row, col + 1) layout.setAlignment(meas_value, Qt.AlignLeft) return meas_value
def createEditor(self, parent, option, index): editor = QLineEdit(parent) editor.setAlignment(Qt.AlignRight) # Editor only accepts integers, period, or nothing regex = QRegExp('[0-9]*(\.[0-9]+)?') editor.setValidator(QRegExpValidator(regex, self)) return editor
class Window(QWidget): def __init__(self): QWidget.__init__(self) self.setWindowTitle('Calculator') self.setFixedSize(260, 320) self.layout = QGridLayout() self.setLayout(self.layout) col_nbr = 4 self.line_edit = QLineEdit() self.line_edit.setAlignment(Qt.AlignRight) self.line_edit.setReadOnly(True) self.line_edit.setText('0') self.layout.addWidget(self.line_edit, 0, 0, 1, col_nbr) buttons_content = 'C CE 7 8 9 / 4 5 6 * 1 2 3 - 0 . = +' buttons_per_rows = [2, 4, 4, 4, 4] self.buttons = [] for button_content in buttons_content.split(' '): self.buttons.append(QPushButton(button_content)) added_buttons = 0 for row, buttons_per_row in enumerate(buttons_per_rows): button_width = col_nbr / buttons_per_row for i in range(buttons_per_row): self.layout.addWidget(self.buttons[added_buttons], 1 + row, i * button_width, 1, button_width) added_buttons += 1 for i, button in enumerate(self.buttons): button.setFixedHeight(50) button.clicked.connect(self.clicked_event) def clicked_event(self): button = self.sender().text() line_edit_content = self.line_edit.text() if self.line_edit.text() == '0': if button.isnumeric() or button in '-+': self.line_edit.setText(button) elif button == '.': self.line_edit.setText('0.') else: if button == 'C': self.line_edit.setText('0') elif button == 'CE': if line_edit_content == 'ERREUR': self.line_edit.setText('0') else: self.line_edit.setText(line_edit_content[:-1] if len(line_edit_content) > 1 else '0') elif button.isnumeric(): self.line_edit.setText(line_edit_content + button) elif button in '-+*/.' and line_edit_content[-1] not in '-+*/.': self.line_edit.setText(line_edit_content + button) elif button == '=': try: self.line_edit.setText(str(eval(line_edit_content))) except Exception: self.line_edit.setText('ERREUR')
class CommandParser(QWidget): def __init__(self, controller, parent=None): super(CommandParser, self).__init__(parent) self.controller = controller cmnd2send_label = QLabel('Command:') cmnd2send_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.cmnd2send_editor = QLineEdit() # cmnd2send_editor.setKeyboardTracking(False) self.cmnd2send_editor.setAlignment(Qt.AlignLeft) self.cmnd2send_editor.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) # cmnd2send_editor.setClearButtonEnabled() send_button = QPushButton('Send') send_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) separator.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) scrollbar = QScrollBar() cmnd_received_label = QLabel('RESPONSE:') self.cmnd_received_window = QTextEdit() # self.cmnd_received_window.moveCursor(QTextCursor.Down) self.cmnd_received_window.setReadOnly(True) self.cmnd_received_window.setVerticalScrollBar(scrollbar) # Layout editor_layout = QGridLayout() editor_layout.addWidget(cmnd2send_label, 0, 0) editor_layout.addWidget(self.cmnd2send_editor, 0, 1) editor_layout.addWidget(send_button, 0, 2) editor_layout.addWidget(separator, 1, 0, 1, 3) editor_layout.addWidget(cmnd_received_label, 2, 0, 1, 3) editor_layout.addWidget(self.cmnd_received_window, 3, 0, 1, 3) layout = QHBoxLayout() layout.addLayout(editor_layout) self.setLayout(layout) # Signals and slots send_button.clicked.connect(self.parse_cmnd) @Slot() def parse_cmnd(self): request = self.cmnd2send_editor.text() response = self.controller.send_cmnd(request) self.cmnd_received_window.insertPlainText('REQUEST: ' + request + '\n') self.cmnd_received_window.insertPlainText('RESPONSE: ' + response + '\n')
class EditTextModal(QDialog): def __init__(self, oldText, windowTitle): QDialog.__init__(self) self.setGeometry(125, 100, 200, 0) self.setModal(True) self.setWindowTitle(windowTitle) self.oldText = oldText self.show() def getData(self): titleLabel = QLabel('Title: ') self.titleEdit = QLineEdit() self.titleEdit.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self.titleEditSs = self.titleEdit.styleSheet() self.titleEdit.textChanged.connect( lambda text: self.titleEdit.setStyleSheet(self.titleEditSs)) self.titleEdit.setMaxLength(20) self.titleEdit.returnPressed.connect(self.okClicked) self.titleEdit.setText(self.oldText) self.titleEdit.selectAll() okButton = QPushButton('OK') okButton.clicked.connect(self.okClicked) menuLayout = QVBoxLayout() topLine = QHBoxLayout() topLine.addWidget(titleLabel) topLine.addWidget(self.titleEdit) menuLayout.addLayout(topLine) menuLayout.addWidget(okButton) self.setLayout(menuLayout) retValue = self.exec() if retValue: return self.titleEdit.text() else: return 0 def okClicked(self): validator = re.compile('.*\S.*') if not re.match(validator, self.titleEdit.text()): self.titleEdit.setStyleSheet("border: 2px solid red;") else: self.accept() def cancelClicked(self): self.reject()
class ApolloLabeledEntry(ApolloControl): def __init__(self, parent, text, value_text, lines=1, *args, **kwargs): super().__init__(parent, *args, **kwargs) # Make sure the text is bottom aligned in the specified number of lines text = "\n" * (lines - text.count("\n") - 1) + text self.label = QLabel(text, self) self.addWidget(self.label) self.layout.addSpacing(3) self.value = QLineEdit(self) self.value.setFixedSize(65, 32) self.value.setText(value_text) self.value.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.value)
class ScoreWidget(QWidget): normal_style = """ background-color: black; color: green; """ highlight_style = """ background-color: green; color: white; """ font = QFont("mono", 22) def __init__(self, parent=None): super().__init__(parent=parent) self.layout = QVBoxLayout() self.setLayout(self.layout) self.score = QLCDNumber() self.score.setMinimumHeight(80) self.score.setStyleSheet(self.normal_style) self.layout.addWidget(self.score) self.player = QLineEdit() self.player.setStyleSheet(self.normal_style) self.player.setFont(self.font) self.player.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.player) def lock(self): self.player.setReadOnly(True) self.player.setFocusPolicy(Qt.NoFocus) def unlock(self): self.score.display(0) self.player.setReadOnly(False) self.player.setFocusPolicy(Qt.StrongFocus) def highlight(self): self.player.setStyleSheet(self.highlight_style) self.score.setStyleSheet(self.highlight_style) def unhighlight(self): self.player.setStyleSheet(self.normal_style) self.score.setStyleSheet(self.normal_style) def adjustScore(self, score_adjustment): score = self.score.intValue() + score_adjustment self.score.display(score) def getScore(self): return self.score.intValue()
class Form(QWidget): def __init__(self): super(Form, self).__init__() self.setWindowTitle("Grid layout 공부할거야") self.vb = QVBoxLayout() self.setLayout(self.vb) self.ln = QLineEdit() self.ln.setAlignment(Qt.AlignRight) ### 이야 이렇게 하면 스타일 바꿀수 있구낭 self.ln.setStyleSheet("font-size: 24px;" "font-weight: bold;") self.vb.addWidget(self.ln) self.gl = QGridLayout() self.vb.addLayout(self.gl) self.value = [ [QPushButton('+-'), [0, 0]], [QPushButton('/'), [0, 1]], [QPushButton('*'), [0, 2]], [QPushButton('-'), [0, 3]], [QPushButton('1'), [1, 0]], [QPushButton('2'), [1, 1]], [QPushButton('3'), [1, 2]], [QPushButton('+'), [1, 3, 2, 1]], [QPushButton('4'), [2, 0]], [QPushButton('5'), [2, 1]], [QPushButton('6'), [2, 2]], [QPushButton('7'), [3, 0]], [QPushButton('8'), [3, 1]], [QPushButton('9'), [3, 2]], [QPushButton('='), [3, 3, 2, 1]], [QPushButton('0'), [4, 0, 1, 2]], [QPushButton('.'), [4, 2]], ] for i, p in self.value: if len(p) > 2: i.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.gl.addWidget(i, p[0],p[1],p[2],p[3]) else: self.gl.addWidget(i, p[0],p[1]) i.setStyleSheet("font-size:20px;" "font-weight: bold;") i.clicked.connect(self.clk) def clk(self): self.ln.setText(self.ln.text()+self.sender().text())
class MyWidget(QWidget): def __init__(self): QWidget.__init__(self) self.login = QPushButton("Login (anonymous)") self.logout = QPushButton("Logout") self.goto = QPushButton("Goto dir") self.server = QLineEdit("Server") self.dir = QLineEdit() self.status = QLabel() self.server.setAlignment(Qt.AlignCenter) self.layout = QVBoxLayout() self.layout.addWidget(self.login) self.layout.addWidget(self.logout) self.layout.addWidget(self.goto) self.layout.addWidget(self.server) self.layout.addWidget(self.dir) self.layout.addWidget(self.status) self.setLayout(self.layout) self.ftp = None # Connecting the signal self.login.clicked.connect(self.loginSlot) self.logout.clicked.connect(self.logoutSlot) self.goto.clicked.connect(self.goToDirSlot) @Slot() def loginSlot(self): self.ftp = FTP(QLineEdit.text(self.server)) self.status.setText(self.ftp.login()) @Slot() def goToDirSlot(self): self.ftp.cwd(QLineEdit.text(self.dir)) a = self.ftp.nlst() self.status.setText("\n".join(a)) @Slot() def logoutSlot(self): try: self.ftp.quit() self.status.setText("Connection closed successfuly") except (Exception) as e: self.status.setText(f"Error while terminating connecioin: {e}")
def createEditor(self, parent, option, index): """ Create the editor widget for the table """ if index.column() == 0: # Customer Name Column cellWidget = QLineEdit(parent=parent) cellWidget.setText(index.data()) cellWidget.selectAll() else: # Time Column cellWidget = QTimeEdit(parent=parent) cellWidget.setTime( dt.datetime.strptime(index.data(), '%H:%M').time()) cellWidget.setAlignment(Qt.AlignCenter) return cellWidget
def _int_field( self, min_val: int, max_val: int, placeholder: str = "", tooltip: str = "", ) -> QLineEdit: result = QLineEdit() # TODO derive the max width from the text repr of min_val, max_val. result.setMaximumWidth(self._FIELD_WIDTH) result.setAlignment(Qt.AlignRight) validator = QIntValidator(min_val, max_val) result.setValidator(validator) if placeholder: result.setPlaceholderText(placeholder) if tooltip: result.setToolTip(tooltip) return result
class JDaApp(QWidget): def __init__(self): super().__init__() self.iniciaUI() def iniciaUI(self): """ Inicializa a janela e mostra seu conteuda na tela """ self.setGeometry(100,100, 400, 200) self.setWindowTitle("Login") self.displayWidgets() self.show() def displayWidgets(self): """ Configura os widgets da app """ # Criando um label e um edit para o nome nome_label = QLabel("Nome:",self) nome_label.move(70, 50) # localiza o label na tela self.nome_edit = QLineEdit(self) self.nome_edit.setAlignment(Qt.AlignLeft) # Este é o padrão self.nome_edit.move(130, 50) self.nome_edit.resize(200, 20) # mudando o tamanho da caixa de texto self.limpar_btn = QPushButton('Limpar', self) self.limpar_btn.clicked.connect(self.limparCxTxt) self.limpar_btn.move(160, 110) # localizando o botão na tela def limparCxTxt(self): """ Quando acionado o butão limpa a caixa de texto """ sender = self.sender() if sender.text() == 'Limpar': self.nome_edit.clear()
class UserUI(QWidget): def __init__(self, *args, **kwargs): super(UserUI, self).__init__(*args, **kwargs) layout = QVBoxLayout() layout.setContentsMargins(QMargins(3, 3, 3, 3)) opts_layout = QHBoxLayout() opts_layout.addWidget(QLabel("用户角色:", self)) self.user_role_combobox = QComboBox(self) opts_layout.addWidget(self.user_role_combobox) self.search_input = QLineEdit(self) self.search_input.setPlaceholderText("搜索(关键字按Enter搜索)") self.search_input.setAlignment(Qt.AlignCenter) opts_layout.addWidget(self.search_input) layout.addLayout(opts_layout) self.user_table = QTableWidget(self) layout.addWidget(self.user_table) self.setLayout(layout)
def _displayStudentQuestions(self): # Checking if we have a student if self.student == None: self.UI.studentQuestionsStack.setCurrentIndex(0) return self.UI.studentQuestionsStack.setCurrentIndex(1) # Questions panel if self.student.questions not in [None, {}]: # Setting our widget and clearing its contents studentDetailsWidget = self.UI.studentQuestions [ studentDetailsWidget.removeRow(0) for _ in range(studentDetailsWidget.rowCount()) ] # Iterating through our student's questions and adding them to our widget for currentRow, (label, field) in enumerate( self.student.questions.items()): labelWidget = QLineEdit(label) labelWidget.setAlignment(Qt.AlignRight | Qt.AlignVCenter) fieldWidget = QLineEdit(field) fieldWidget.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) studentDetailsWidget.addRow(labelWidget, fieldWidget)
class DebugControlsWidget(QToolBar): def __init__(self, parent, name, data, debug_state): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = debug_state QToolBar.__init__(self, parent) # TODO: Is there a cleaner way to do this? self.setStyleSheet(""" QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;} QToolButton:disabled{color: palette(alternate-base)} """) self.actionRun = QAction("Run", self) self.actionRun.triggered.connect(lambda: self.perform_run()) self.actionRun.setIcon(load_icon('run.svg')) self.actionRestart = QAction("Restart", self) self.actionRestart.triggered.connect(lambda: self.perform_restart()) self.actionRestart.setIcon(load_icon('restart.svg')) self.actionQuit = QAction("Quit", self) self.actionQuit.triggered.connect(lambda: self.perform_quit()) self.actionQuit.setIcon(load_icon('cancel.svg')) self.actionAttach = QAction("Attach", self) self.actionAttach.triggered.connect(lambda: self.perform_attach()) self.actionAttach.setIcon(load_icon('connect.svg')) self.actionDetach = QAction("Detach", self) self.actionDetach.triggered.connect(lambda: self.perform_detach()) self.actionDetach.setIcon(load_icon('disconnect.svg')) self.actionSettings = QAction("Settings...", self) self.actionSettings.triggered.connect(lambda: self.perform_settings()) self.actionPause = QAction("Pause", self) self.actionPause.triggered.connect(lambda: self.perform_pause()) self.actionPause.setIcon(load_icon('pause.svg')) self.actionResume = QAction("Resume", self) self.actionResume.triggered.connect(lambda: self.perform_resume()) self.actionResume.setIcon(load_icon('resume.svg')) self.actionStepIntoAsm = QAction("Step Into (Assembly)", self) self.actionStepIntoAsm.triggered.connect( lambda: self.perform_step_into_asm()) self.actionStepIntoAsm.setIcon(load_icon('stepinto.svg')) self.actionStepIntoIL = QAction("Step Into", self) self.actionStepIntoIL.triggered.connect( lambda: self.perform_step_into_il()) self.actionStepIntoIL.setIcon(load_icon('stepinto.svg')) self.actionStepOverAsm = QAction("Step Over (Assembly)", self) self.actionStepOverAsm.triggered.connect( lambda: self.perform_step_over_asm()) self.actionStepOverAsm.setIcon(load_icon('stepover.svg')) self.actionStepOverIL = QAction("Step Over", self) self.actionStepOverIL.triggered.connect( lambda: self.perform_step_over_il()) self.actionStepOverIL.setIcon(load_icon('stepover.svg')) self.actionStepReturn = QAction("Step Return", self) self.actionStepReturn.triggered.connect( lambda: self.perform_step_return()) self.actionStepReturn.setIcon(load_icon('stepout.svg')) # session control menu self.controlMenu = QMenu("Process Control", self) self.controlMenu.addAction(self.actionRun) self.controlMenu.addAction(self.actionRestart) self.controlMenu.addAction(self.actionQuit) self.controlMenu.addSeparator() self.controlMenu.addAction(self.actionAttach) self.controlMenu.addAction(self.actionDetach) self.controlMenu.addSeparator() self.controlMenu.addAction(self.actionSettings) self.stepIntoMenu = QMenu("Step Into", self) self.stepIntoMenu.addAction(self.actionStepIntoIL) self.stepIntoMenu.addAction(self.actionStepIntoAsm) self.stepOverMenu = QMenu("Step Over", self) self.stepOverMenu.addAction(self.actionStepOverIL) self.stepOverMenu.addAction(self.actionStepOverAsm) self.btnControl = QToolButton(self) self.btnControl.setMenu(self.controlMenu) self.btnControl.setPopupMode(QToolButton.MenuButtonPopup) self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnControl.setDefaultAction(self.actionRun) self.addWidget(self.btnControl) # execution control buttons self.btnPauseResume = QToolButton(self) self.btnPauseResume.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnPauseResume.setDefaultAction(self.actionPause) self.addWidget(self.btnPauseResume) #self.addAction(self.actionPause) #self.addAction(self.actionResume) self.btnStepInto = QToolButton(self) self.btnStepInto.setMenu(self.stepIntoMenu) self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup) self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnStepInto.setDefaultAction(self.actionStepIntoIL) self.addWidget(self.btnStepInto) self.btnStepOver = QToolButton(self) self.btnStepOver.setMenu(self.stepOverMenu) self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup) self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnStepOver.setDefaultAction(self.actionStepOverIL) self.addWidget(self.btnStepOver) # TODO: Step until returning from current function self.btnStepReturn = QToolButton(self) self.btnStepReturn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnStepReturn.setDefaultAction(self.actionStepReturn) self.addWidget(self.btnStepReturn) #self.addAction(self.actionStepReturn) self.threadMenu = QMenu("Threads", self) self.btnThreads = QToolButton(self) self.btnThreads.setMenu(self.threadMenu) self.btnThreads.setPopupMode(QToolButton.InstantPopup) self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly) self.addWidget(self.btnThreads) self.set_thread_list([]) self.editStatus = QLineEdit('INACTIVE', self) self.editStatus.setReadOnly(True) self.editStatus.setAlignment(QtCore.Qt.AlignCenter) self.addWidget(self.editStatus) # disable buttons self.set_actions_enabled(Run=self.can_exec(), Restart=False, Quit=False, Attach=self.can_connect(), Detach=False, Pause=False, Resume=False, StepInto=False, StepOver=False, StepReturn=False) self.set_resume_pause_action("Pause") self.set_default_process_action( "Attach" if self.can_connect() else "Run") def __del__(self): # TODO: Move this elsewhere # This widget is tasked with cleaning up the state after the view is closed # binjaplug.delete_state(self.bv) pass # ------------------------------------------------------------------------- # Helpers # ------------------------------------------------------------------------- def can_exec(self): return DebugAdapter.ADAPTER_TYPE.use_exec( self.debug_state.adapter_type) def can_connect(self): return DebugAdapter.ADAPTER_TYPE.use_connect( self.debug_state.adapter_type) def alert_need_install(self, proc): message = "Cannot start debugger: {} not found on {}.".format( proc, "the target machine" if self.can_connect() else "your machine") adapter_type = self.debug_state.adapter_type # TODO: detect remote os correctly, as gdb/lldb are compatible with both macos and linux if adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_GDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_GDB: remote_os = "Linux" elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_LLDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_LLDB: remote_os = "Darwin" elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_DBGENG or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_DBGENG: remote_os = "Windows" else: # Uncertain remote_os = platform.system() if remote_os == "Linux": message += "\nYou can find this in your package manager or build it from source." elif remote_os == "Darwin": if proc == "lldb": message += "\nYou need to install it by running the following command in Terminal:\nxcode-select --install" elif proc == "gdbserver": message += "\nYou can find this in your package manager or build it from source." else: message += "\nYou need to install this manually." elif remote_os == "Windows": # TODO: dbgeng does not currently throw this message += "\nYou need to reinstall the debugger plugin." else: message += "\nYou need to install this manually." show_message_box("Cannot Start Debugger", message, icon=MessageBoxIcon.ErrorIcon) # ------------------------------------------------------------------------- # UIActions # ------------------------------------------------------------------------- def perform_run(self): def perform_run_thread(): while True: try: self.debug_state.run() execute_on_main_thread_and_wait(perform_run_after) except ConnectionRefusedError: execute_on_main_thread_and_wait( lambda: perform_run_error('ERROR: Connection Refused')) except DebugAdapter.ProcessStartError as e: execute_on_main_thread_and_wait( lambda: perform_run_error(str(e))) except DebugAdapter.NotExecutableError as e: fpath = e.args[0] if platform.system() != 'Windows': msg = '%s is not executable, would you like to set +x and retry?' % fpath res = show_message_box( 'Error', msg, MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.ErrorIcon) if res == MessageBoxButtonResult.YesButton: os.chmod(fpath, os.stat(fpath).st_mode | 0o100) continue execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Target Not Executable')) except DebugAdapter.NotInstalledError as e: execute_on_main_thread_and_wait( lambda: self.alert_need_install(e.args[0])) execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Debugger Not Installed')) except DebugAdapter.PermissionDeniedError as e: execute_on_main_thread_and_wait( lambda: perform_run_error('ERROR: Permission denied')) if platform.system() == 'Darwin': res = show_message_box( 'Error', 'Developer tools need to be enabled to debug programs. This can be authorized either from here or by starting a debugger in Xcode.', MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) except Exception as e: execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: ' + ' '.join(e.args))) traceback.print_exc(file=sys.stderr) break def perform_run_after(): self.state_stopped() self.debug_state.ui.on_step() def perform_run_error(e): self.state_error(e) self.state_starting('STARTING') threading.Thread(target=perform_run_thread).start() def perform_restart(self): def perform_restart_thread(): try: self.debug_state.restart() execute_on_main_thread_and_wait(perform_restart_after) except ConnectionRefusedError: execute_on_main_thread_and_wait( lambda: perform_restart_error('ERROR: Connection Refused')) except Exception as e: execute_on_main_thread_and_wait(lambda: perform_restart_error( 'ERROR: ' + ' '.join(e.args))) traceback.print_exc(file=sys.stderr) def perform_restart_after(): self.state_stopped() self.debug_state.ui.on_step() def perform_restart_error(e): self.state_error(e) self.state_starting('RESTARTING') threading.Thread(target=perform_restart_thread).start() def perform_quit(self): self.debug_state.quit() self.state_inactive() self.debug_state.ui.on_step() def perform_attach(self): def perform_attach_thread(): try: self.debug_state.attach() execute_on_main_thread_and_wait(perform_attach_after) except ConnectionRefusedError: execute_on_main_thread_and_wait( lambda: perform_attach_error('ERROR: Connection Refused')) except TimeoutError: execute_on_main_thread_and_wait( lambda: perform_attach_error('ERROR: Connection Refused')) except Exception as e: execute_on_main_thread_and_wait( lambda: perform_attach_error('ERROR: ' + ' '.join(e.args))) traceback.print_exc(file=sys.stderr) def perform_attach_after(): self.state_stopped() self.debug_state.ui.on_step() def perform_attach_error(e): self.state_error(e) self.state_starting('ATTACHING') threading.Thread(target=perform_attach_thread).start() def perform_detach(self): self.debug_state.detach() self.state_inactive() self.debug_state.ui.on_step() def perform_settings(self): def settings_finished(): if self.debug_state.running: self.state_running() elif self.debug_state.connected: local_rip = self.debug_state.local_ip if self.debug_state.bv.read(local_rip, 1) and len( self.debug_state.bv.get_functions_containing( local_rip)) > 0: self.state_stopped() else: self.state_stopped_extern() else: self.state_inactive() dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv) dialog.show() dialog.finished.connect(settings_finished) def perform_pause(self): self.debug_state.pause() # Don't update state here-- one of the other buttons is running in a thread and updating for us def perform_resume(self): def perform_resume_thread(): (reason, data) = self.debug_state.go() execute_on_main_thread_and_wait( lambda: perform_resume_after(reason, data)) def perform_resume_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_running() threading.Thread(target=perform_resume_thread).start() def perform_step_into_asm(self): def perform_step_into_asm_thread(): (reason, data) = self.debug_state.step_into() execute_on_main_thread_and_wait( lambda: perform_step_into_asm_after(reason, data)) def perform_step_into_asm_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_busy("STEPPING") threading.Thread(target=perform_step_into_asm_thread).start() def perform_step_into_il(self): disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly() graph_type = disasm.getGraphType() def perform_step_into_il_thread(): (reason, data) = self.debug_state.step_into(graph_type) execute_on_main_thread_and_wait( lambda: perform_step_into_il_after(reason, data)) def perform_step_into_il_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_busy("STEPPING") threading.Thread(target=perform_step_into_il_thread).start() def perform_step_over_asm(self): def perform_step_over_asm_thread(): (reason, data) = self.debug_state.step_over() execute_on_main_thread_and_wait( lambda: perform_step_over_asm_after(reason, data)) def perform_step_over_asm_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_busy("STEPPING") threading.Thread(target=perform_step_over_asm_thread).start() def perform_step_over_il(self): disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly() graph_type = disasm.getGraphType() def perform_step_over_il_thread(): (reason, data) = self.debug_state.step_over(graph_type) execute_on_main_thread_and_wait( lambda: perform_step_over_il_after(reason, data)) def perform_step_over_il_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_busy("STEPPING") threading.Thread(target=perform_step_over_il_thread).start() def perform_step_return(self): def perform_step_return_thread(): (reason, data) = self.debug_state.step_return() execute_on_main_thread_and_wait( lambda: perform_step_return_after(reason, data)) def perform_step_return_after(reason, data): self.handle_stop_return(reason, data) self.debug_state.ui.on_step() self.state_busy("STEPPING") threading.Thread(target=perform_step_return_thread).start() # ------------------------------------------------------------------------- # Control state setters # ------------------------------------------------------------------------- def set_actions_enabled(self, **kwargs): def enable_step_into(e): self.actionStepIntoAsm.setEnabled(e) self.actionStepIntoIL.setEnabled(e) def enable_step_over(e): self.actionStepOverAsm.setEnabled(e) self.actionStepOverIL.setEnabled(e) def enable_starting(e): self.actionRun.setEnabled(e and self.can_exec()) self.actionAttach.setEnabled(e and self.can_connect()) def enable_stopping(e): self.actionRestart.setEnabled(e) self.actionQuit.setEnabled(e) self.actionDetach.setEnabled(e) def enable_stepping(e): self.actionStepIntoAsm.setEnabled(e) self.actionStepIntoIL.setEnabled(e) self.actionStepOverAsm.setEnabled(e) self.actionStepOverIL.setEnabled(e) self.actionStepReturn.setEnabled(e) actions = { "Run": lambda e: self.actionRun.setEnabled(e), "Restart": lambda e: self.actionRestart.setEnabled(e), "Quit": lambda e: self.actionQuit.setEnabled(e), "Attach": lambda e: self.actionAttach.setEnabled(e), "Detach": lambda e: self.actionDetach.setEnabled(e), "Pause": lambda e: self.actionPause.setEnabled(e), "Resume": lambda e: self.actionResume.setEnabled(e), "StepInto": enable_step_into, "StepOver": enable_step_over, "StepReturn": lambda e: self.actionStepReturn.setEnabled(e), "Threads": lambda e: self.btnThreads.setEnabled(e), "Starting": enable_starting, "Stopping": enable_stopping, "Stepping": enable_stepping, } for (action, enabled) in kwargs.items(): actions[action](enabled) def set_default_process_action(self, action): actions = { "Run": self.actionRun, "Restart": self.actionRestart, "Quit": self.actionQuit, "Attach": self.actionAttach, "Detach": self.actionDetach, } self.btnControl.setDefaultAction(actions[action]) def set_resume_pause_action(self, action): lookup = {'Resume': self.actionResume, 'Pause': self.actionPause} self.btnPauseResume.setDefaultAction(lookup[action]) def set_thread_list(self, threads): def select_thread_fn(tid): def select_thread(tid): stateObj = binjaplug.get_state(self.bv) if stateObj.connected and not stateObj.running: stateObj.threads.current = tid stateObj.ui.context_display() stateObj.ui.on_step() else: print('cannot set thread in current state') return lambda: select_thread(tid) self.threadMenu.clear() if len(threads) > 0: for thread in threads: item_name = "Thread {} at {}".format(thread['tid'], hex(thread['ip'])) action = self.threadMenu.addAction( item_name, select_thread_fn(thread['tid'])) if thread['selected']: self.btnThreads.setDefaultAction(action) else: defaultThreadAction = self.threadMenu.addAction("Thread List") defaultThreadAction.setEnabled(False) self.btnThreads.setDefaultAction(defaultThreadAction) # ------------------------------------------------------------------------- # State handling # ------------------------------------------------------------------------- def state_starting(self, msg=None): self.editStatus.setText(msg or 'INACTIVE') self.set_actions_enabled(Starting=False, Stopping=False, Stepping=False, Pause=False, Resume=False, Threads=False) self.set_default_process_action( "Attach" if self.can_connect() else "Run") self.set_thread_list([]) self.set_resume_pause_action("Pause") def state_inactive(self, msg=None): self.editStatus.setText(msg or 'INACTIVE') self.set_actions_enabled(Starting=True, Stopping=False, Stepping=False, Pause=False, Resume=False, Threads=False) self.set_default_process_action( "Attach" if self.can_connect() else "Run") self.set_thread_list([]) self.set_resume_pause_action("Pause") def state_stopped(self, msg=None): self.editStatus.setText(msg or 'STOPPED') self.set_actions_enabled(Starting=False, Stopping=True, Stepping=True, Pause=True, Resume=True, Threads=True) self.set_default_process_action("Quit") self.set_resume_pause_action("Resume") def state_stopped_extern(self, msg=None): self.editStatus.setText(msg or 'STOPPED') self.set_actions_enabled(Starting=False, Stopping=True, Stepping=True, StepReturn=False, Pause=True, Resume=True, Threads=True) self.set_default_process_action("Quit") self.set_resume_pause_action("Resume") def state_running(self, msg=None): self.editStatus.setText(msg or 'RUNNING') self.set_actions_enabled(Starting=False, Stopping=True, Stepping=False, Pause=True, Resume=False, Threads=False) self.set_default_process_action("Quit") self.set_resume_pause_action("Pause") def state_busy(self, msg=None): self.editStatus.setText(msg or 'RUNNING') self.set_actions_enabled(Starting=False, Stopping=True, Stepping=False, Pause=True, Resume=False, Threads=False) self.set_default_process_action("Quit") self.set_resume_pause_action("Pause") def state_error(self, msg=None): self.editStatus.setText(msg or 'ERROR') self.set_actions_enabled(Starting=True, Stopping=False, Pause=False, Resume=False, Stepping=False, Threads=False) self.set_default_process_action( "Attach" if self.can_connect() else "Run") self.set_thread_list([]) self.set_resume_pause_action("Resume") def handle_stop_return(self, reason, data): if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE: self.state_stopped('stdout: ' + data) elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED: self.debug_state.quit() self.state_inactive('process exited, return code=%d' % data) elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED: self.debug_state.quit() self.state_inactive('backend disconnected (process exited?)')
class DebugControlsWidget(QToolBar): def __init__(self, parent, name, data, debug_state): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = debug_state QToolBar.__init__(self, parent) # TODO: Is there a cleaner way to do this? self.setStyleSheet(""" QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;} QToolButton:disabled{color: palette(alternate-base)} """) self.actionRun = QAction("Run", self) self.actionRun.triggered.connect(lambda: self.perform_run()) self.actionRestart = QAction("Restart", self) self.actionRestart.triggered.connect(lambda: self.perform_restart()) self.actionQuit = QAction("Quit", self) self.actionQuit.triggered.connect(lambda: self.perform_quit()) self.actionAttach = QAction("Attach... (todo)", self) self.actionAttach.triggered.connect(lambda: self.perform_attach()) self.actionDetach = QAction("Detach", self) self.actionDetach.triggered.connect(lambda: self.perform_detach()) self.actionSettings = QAction("Settings...", self) self.actionSettings.triggered.connect(lambda: self.perform_settings()) self.actionPause = QAction("Pause", self) self.actionPause.triggered.connect(lambda: self.perform_pause()) self.actionResume = QAction("Resume", self) self.actionResume.triggered.connect(lambda: self.perform_resume()) self.actionStepInto = QAction("Step Into", self) self.actionStepInto.triggered.connect(lambda: self.perform_step_into()) self.actionStepOver = QAction("Step Over", self) self.actionStepOver.triggered.connect(lambda: self.perform_step_over()) self.actionStepReturn = QAction("Step Return", self) self.actionStepReturn.triggered.connect( lambda: self.perform_step_return()) # session control menu self.controlMenu = QMenu("Process Control", self) self.controlMenu.addAction(self.actionRun) self.controlMenu.addAction(self.actionRestart) self.controlMenu.addAction(self.actionQuit) self.controlMenu.addSeparator() # TODO: Attach to running process # self.controlMenu.addAction(self.actionAttach) self.controlMenu.addAction(self.actionDetach) # TODO: Switch adapter/etc (could go in regular settings) self.controlMenu.addSeparator() self.controlMenu.addAction(self.actionSettings) self.btnControl = QToolButton(self) self.btnControl.setMenu(self.controlMenu) self.btnControl.setPopupMode(QToolButton.MenuButtonPopup) self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.btnControl.setDefaultAction(self.actionRun) self.addWidget(self.btnControl) # execution control buttons self.addAction(self.actionPause) self.addAction(self.actionResume) self.addAction(self.actionStepInto) self.addAction(self.actionStepOver) # TODO: Step until returning from current function self.addAction(self.actionStepReturn) self.threadMenu = QMenu("Threads", self) self.btnThreads = QToolButton(self) self.btnThreads.setMenu(self.threadMenu) self.btnThreads.setPopupMode(QToolButton.InstantPopup) self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly) self.addWidget(self.btnThreads) self.set_thread_list([]) self.editStatus = QLineEdit('INACTIVE', self) self.editStatus.setReadOnly(True) self.editStatus.setAlignment(QtCore.Qt.AlignCenter) self.addWidget(self.editStatus) # disable buttons self.set_actions_enabled(Run=True, Restart=False, Quit=False, Attach=True, Detach=False, Pause=False, Resume=False, StepInto=False, StepOver=False, StepReturn=False) self.set_resume_pause_action("Pause") def __del__(self): # TODO: Move this elsewhere # This widget is tasked with cleaning up the state after the view is closed # binjaplug.delete_state(self.bv) pass def perform_run(self): self.debug_state.run() self.state_stopped() self.debug_state.ui.context_display() def perform_restart(self): self.debug_state.restart() self.state_stopped() def perform_quit(self): self.debug_state.quit() self.state_inactive() def perform_attach(self): # TODO: Show dialog to select adapter/address/process pass def perform_detach(self): self.debug_state.detach() self.state_inactive() def perform_settings(self): dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv) dialog.show() def perform_pause(self): self.debug_state.pause() def perform_resume(self): def perform_resume_thread(): (reason, data) = self.debug_state.go() execute_on_main_thread_and_wait( lambda: self.handle_stop_return(reason, data)) execute_on_main_thread_and_wait( lambda: self.debug_state.ui.context_display()) self.state_running() threading.Thread(target=perform_resume_thread).start() def perform_step_into(self): def perform_step_into_thread(): (reason, data) = self.debug_state.step_into() execute_on_main_thread_and_wait( lambda: self.handle_stop_return(reason, data)) execute_on_main_thread_and_wait( lambda: self.debug_state.ui.context_display()) self.state_busy("STEPPING") threading.Thread(target=perform_step_into_thread).start() def perform_step_over(self): def perform_step_over_thread(): (reason, data) = self.debug_state.step_over() execute_on_main_thread_and_wait( lambda: self.handle_stop_return(reason, data)) execute_on_main_thread_and_wait( lambda: self.debug_state.ui.context_display()) self.state_busy("STEPPING") threading.Thread(target=perform_step_over_thread).start() def perform_step_return(self): def perform_step_return_thread(): (reason, data) = self.debug_state.step_return() execute_on_main_thread_and_wait( lambda: self.handle_stop_return(reason, data)) execute_on_main_thread_and_wait( lambda: self.debug_state.ui.context_display()) self.state_busy("STEPPING") threading.Thread(target=perform_step_return_thread).start() def set_actions_enabled(self, **kwargs): def enable_starting(e): self.actionRun.setEnabled(e) self.actionAttach.setEnabled(e) def enable_stopping(e): self.actionRestart.setEnabled(e) self.actionQuit.setEnabled(e) self.actionDetach.setEnabled(e) def enable_stepping(e): self.actionStepInto.setEnabled(e) self.actionStepOver.setEnabled(e) self.actionStepReturn.setEnabled(e) actions = { "Run": lambda e: self.actionRun.setEnabled(e), "Restart": lambda e: self.actionRestart.setEnabled(e), "Quit": lambda e: self.actionQuit.setEnabled(e), "Attach": lambda e: self.actionAttach.setEnabled(e), "Detach": lambda e: self.actionDetach.setEnabled(e), "Pause": lambda e: self.actionPause.setEnabled(e), "Resume": lambda e: self.actionResume.setEnabled(e), "StepInto": lambda e: self.actionStepInto.setEnabled(e), "StepOver": lambda e: self.actionStepOver.setEnabled(e), "StepReturn": lambda e: self.actionStepReturn.setEnabled(e), "Threads": lambda e: self.btnThreads.setEnabled(e), "Starting": enable_starting, "Stopping": enable_stopping, "Stepping": enable_stepping, } for (action, enabled) in kwargs.items(): actions[action](enabled) def set_default_process_action(self, action): actions = { "Run": self.actionRun, "Restart": self.actionRestart, "Quit": self.actionQuit, "Attach": self.actionAttach, "Detach": self.actionDetach, } self.btnControl.setDefaultAction(actions[action]) def set_resume_pause_action(self, action): self.actionResume.setVisible(action == "Resume") self.actionPause.setVisible(action == "Pause") def set_thread_list(self, threads): def select_thread_fn(tid): def select_thread(tid): stateObj = binjaplug.get_state(self.bv) if stateObj.state == 'STOPPED': adapter = stateObj.adapter adapter.thread_select(tid) self.debug_state.ui.context_display() else: print('cannot set thread in state %s' % stateObj.state) return lambda: select_thread(tid) self.threadMenu.clear() if len(threads) > 0: for thread in threads: item_name = "Thread {} at {}".format(thread['tid'], hex(thread['rip'])) action = self.threadMenu.addAction( item_name, select_thread_fn(thread['tid'])) if thread['selected']: self.btnThreads.setDefaultAction(action) else: defaultThreadAction = self.threadMenu.addAction("Thread List") defaultThreadAction.setEnabled(False) self.btnThreads.setDefaultAction(defaultThreadAction) def state_inactive(self, msg=None): debug_state = binjaplug.get_state(self.bv) # clear breakpoints debug_state.breakpoint_tag_del() debug_state.breakpoints = {} debug_state.state = 'INACTIVE' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Starting=True, Stopping=False, Stepping=False, Pause=False, Resume=False, Threads=False) self.set_default_process_action("Run") self.set_thread_list([]) self.set_resume_pause_action("Pause") def state_stopped(self, msg=None): debug_state = binjaplug.get_state(self.bv) debug_state.state = 'STOPPED' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Starting=False, Stopping=True, Stepping=True, Pause=True, Resume=True, Threads=True) self.set_default_process_action("Quit") self.set_resume_pause_action("Resume") def state_stopped_extern(self, msg=None): debug_state = binjaplug.get_state(self.bv) debug_state.state = 'STOPPED (outside view)' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Starting=False, Stopping=True, Stepping=True, StepReturn=False, Pause=True, Resume=True, Threads=True) self.set_default_process_action("Quit") self.set_resume_pause_action("Resume") def state_running(self, msg=None): debug_state = binjaplug.get_state(self.bv) debug_state.state = 'RUNNING' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Starting=False, Stopping=True, Stepping=False, Pause=True, Resume=False, Threads=False) self.set_default_process_action("Quit") self.set_resume_pause_action("Pause") def state_busy(self, msg=None): debug_state = binjaplug.get_state(self.bv) debug_state.state = 'RUNNING' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Starting=False, Stopping=True, Stepping=False, Pause=True, Resume=False, Threads=False) self.set_default_process_action("Quit") self.set_resume_pause_action("Pause") def state_error(self, msg=None): debug_state = binjaplug.get_state(self.bv) debug_state.state = 'ERROR' self.editStatus.setText(msg or debug_state.state) self.set_actions_enabled(Run=True, Restart=True, Quit=True, Attach=True, Detach=True, Pause=True, Resume=True, StepInto=True, StepOver=True, StepReturn=True, Threads=True) self.set_default_process_action("Run") self.set_thread_list([]) self.set_resume_pause_action("Resume") def handle_stop_return(self, reason, data): if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE: self.state_stopped('stdout: ' + data) elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED: self.debug_state.quit() self.state_inactive('process exited, return code=%d' % data) elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED: self.debug_state.quit() self.state_inactive('backend disconnected (process exited?)')
class AddressRegister(QWidget): def __init__(self, parent, usbif, color): super().__init__(parent) self._setup_ui(color) usbif.poll("monitor", um.MonRegS()) usbif.poll("monitor", um.MonRegBB()) #usbif.poll("monitor", um.MonChanFEXT()) usbif.listen(self) def handle_msg(self, msg): if isinstance(msg, um.MonRegS): self.set_s_value(msg.s) elif isinstance(msg, um.MonRegBB): self.set_bank_values(msg.eb, msg.fb) elif isinstance(msg, um.MonChanFEXT): self.set_fext_value(msg.fext) def set_bank_values(self, eb, fb): self.eb.set_value(eb) self.fb.set_value(fb) self._update_addr_value() def set_s_value(self, x): self.s.set_value(x) self._update_addr_value() def set_fext_value(self, x): self.fext.set_value(x) self._update_addr_value() def _setup_ui(self, color): # Set up our basic layout layout = QHBoxLayout(self) self.setLayout(layout) layout.setSpacing(3) layout.setMargin(1) # Construct register groups for EB, FEXT, FB, and S self.eb = SubRegister(self, "EBANK", 3, color) self.fext = SubRegister(self, "FEXT", 3, color) self.fb = SubRegister(self, "FBANK", 5, color) self.s = SubRegister(self, "", 12, color) layout.addWidget(self.eb) layout.addWidget(self.fext) layout.addWidget(self.fb) layout.addWidget(self.s) # Create a grouping widget for the S label and decoded octal value box label_value_layout = QHBoxLayout() label_value_layout.setSpacing(3) label_value_layout.setMargin(1) label_value_layout.setContentsMargins(0, 33, 0, 0) layout.addLayout(label_value_layout) # Create a value box for displaying the overall decoded address self._addr_value = QLineEdit(self) self._addr_value.setFixedSize(70, 32) self._addr_value.setReadOnly(True) self._addr_value.setAlignment(Qt.AlignCenter) self._addr_value.setText('0000') self._addr_value.setStyleSheet("QLineEdit { color: #555; }") label_value_layout.addWidget(self._addr_value) # Create a label to show 'S' label = QLabel('S', self) label.setFixedWidth(20) label_value_layout.addWidget(label) def _update_addr_value(self): # Get the values of all tracked registers s = self.s.value eb = self.eb.value fb = self.fb.value fext = self.fext.value self._addr_value.setText(agc.format_addr(s, eb, fb, fext))
class SettingsEditionDialog(QDialog): languages = {"Français": "fr", "English": "en"} def __init__(self): QDialog.__init__(self) self.setWindowTitle(tr("btn_config")) self.setFixedSize(QSize(700, 670)) # Retrieve current settings self.settings = AssetManager.getInstance().config_to_dico( AssetManager.getInstance().get_config_parser()) self.__restart_needed = False self.__restore_required = False # Version self.lab_version = QLabel(self.settings['main']['version']) # Language self.combo_language = QComboBox() self.combo_language.addItems(list(self.languages.keys())) for lang in self.languages: # Look for the current language to select it if self.languages[lang] == self.settings['main']['language']: self.combo_language.setCurrentText(lang) break # CSV separator self.csv_sep_edit = QLineEdit() self.csv_sep_edit.setMaxLength(2) self.csv_sep_edit.setFixedWidth(25) self.csv_sep_edit.setAlignment(Qt.AlignCenter) self.csv_sep_edit.setText(self.settings['main']['csv_separator']) # BDD path self.btn_bdd_path = QPushButton(self.settings['main']['bdd_path']) self.btn_bdd_path.clicked.connect(self.choose_bdd_path) # Port self.wepapp_port = QSpinBox() self.wepapp_port.setMinimum(1024) self.wepapp_port.setMaximum(65535) self.wepapp_port.setValue(int(self.settings['webapp']['port'])) # Colors self.tile_color = ColorChooser(self.settings['colors']['tile']) self.hovered_tile_color = ColorChooser( self.settings['colors']['hovered_tile']) self.hovered_empty_tile_color = ColorChooser( self.settings['colors']['hovered_empty_tile']) self.dragged_tile_color = ColorChooser( self.settings['colors']['dragged_tile']) self.drag_selected_tile_color = ColorChooser( self.settings['colors']['drag_selected_tile']) self.selected_tile_color = ColorChooser( self.settings['colors']['selected_tile']) self.tile_text_color = ColorChooser( self.settings['colors']['tile_text']) self.room_bg_color = ColorChooser(self.settings['colors']['room_bg']) self.room_grid_color = ColorChooser( self.settings['colors']['room_grid']) self.main_bg_color = ColorChooser(self.settings['colors']['main_bg']) self.board_bg_color = ColorChooser(self.settings['colors']['board_bg']) self.attr_colors = "" # Chosen colors self.attributes_colors_chooser = AttrColorsChooser( self.settings['colors']['attr_colors'].split()) # Sizes (unmodifiable) self.unmodifiable = QLabel(tr("unmodifiable_data")) self.unmodifiable.setAlignment(Qt.AlignCenter) self.desk_size = QLineEdit(self.settings['size']['desk']) self.desk_size.setEnabled(False) self.desk_size.setFixedWidth(50) self.grid_rows = QLineEdit(self.settings['size']['default_room_rows']) self.grid_rows.setEnabled(False) self.grid_rows.setFixedWidth(50) self.grid_cols = QLineEdit( self.settings['size']['default_room_columns']) self.grid_cols.setEnabled(False) self.grid_cols.setFixedWidth(50) # --- Buttons --- # Confirm button self.ok_btn = QPushButton(tr("btn_save")) self.ok_btn.clicked.connect(self.accept) self.ok_btn.setFocus() # Cancel button self.cancel_btn = QPushButton(tr("btn_cancel")) self.cancel_btn.clicked.connect(self.reject) # Restore defaults button self.restore_btn = QPushButton(tr("btn_restore")) self.restore_btn.clicked.connect(self.__restore) self.__set_layout() def __set_layout(self) -> None: """ Sets the dialog layout """ # Main layout layout = QVBoxLayout() layout.setMargin(0) layout.addSpacing(5) # Main section main_layout = QFormLayout() main_layout.addRow(tr("app_version"), self.lab_version) main_layout.addRow(tr("language"), self.combo_language) main_layout.addRow(tr("csv_sep"), self.csv_sep_edit) main_layout.addRow(tr("bdd_path"), self.btn_bdd_path) # Web app widget_port = QWidget() layout_port = QHBoxLayout() layout_port.setMargin(0) layout_port.addWidget(self.wepapp_port) layout_port.addWidget(ShutDownToolTip()) widget_port.setLayout(layout_port) main_layout.addRow(tr("web_port"), widget_port) layout.addLayout(main_layout) Separator(self.width(), layout) # Colors colors_layout1 = QFormLayout() colors_layout1.addRow(tr("tile"), self.tile_color) colors_layout1.addRow(tr("hovered_tile"), self.hovered_tile_color) colors_layout1.addRow(tr("hovered_empty_tile"), self.hovered_empty_tile_color) colors_layout1.addRow(tr("dragged_tile"), self.dragged_tile_color) colors_layout1.addRow(tr("drag_selected_tile"), self.drag_selected_tile_color) colors_layout1.addRow(tr("selected_tile"), self.selected_tile_color) colors_layout2 = QFormLayout() colors_layout2.addRow(tr("tile_text"), self.tile_text_color) colors_layout2.addRow(tr("room_bg"), self.room_bg_color) colors_layout2.addRow(tr("room_grid"), self.room_grid_color) colors_layout2.addRow(tr("main_bg"), self.main_bg_color) colors_layout2.addRow(tr("board_bg"), self.board_bg_color) colors_layout = QHBoxLayout() colors_layout.setMargin(0) colors_layout.addLayout(colors_layout1) colors_layout.addLayout(colors_layout2) layout.addLayout(colors_layout) layout.addSpacing(15) colors_layout3 = QFormLayout() colors_layout3.setMargin(0) colors_layout3.addRow(tr("attr_colors"), self.attributes_colors_chooser) layout.addLayout(colors_layout3) Separator(self.width(), layout) # Unmodifiable data sizes_layout = QFormLayout() sizes_layout.setMargin(0) sizes_layout.addRow(tr("desk_size"), self.desk_size) sizes_layout.addRow(tr("grid_rows"), self.grid_rows) sizes_layout.addRow(tr("grid_cols"), self.grid_cols) layout.addWidget(self.unmodifiable, alignment=Qt.AlignCenter) layout.addSpacing(5) layout.addLayout(sizes_layout) Separator(self.width(), layout) # Buttons layout_buttons = QHBoxLayout() layout_buttons.addWidget(self.ok_btn) layout_buttons.addWidget(self.restore_btn) layout_buttons.addWidget(self.cancel_btn) layout.addLayout(layout_buttons) self.setLayout(layout) self.setStyleSheet(get_stylesheet("dialog2")) def __restore(self) -> None: """ Restore default parameters before closing dialog """ self.__restart_needed = True self.__restore_required = True self.accept() def __new_settings(self) -> dict: """ Retrieves the new settings to use """ settings = self.settings # Language language = self.languages[self.combo_language.currentText()] if language != settings['main']['language']: settings['main']['language'] = language self.__restart_needed = True # CSV separator settings['main']['csv_separator'] = self.csv_sep_edit.text() # BDD path if self.btn_bdd_path.text() != settings['main']['bdd_path']: if self.btn_bdd_path.text().endswith("sdc_db"): settings['main']['bdd_path'] = self.btn_bdd_path.text() else: settings['main']['bdd_path'] = "" self.__restart_needed = True # Port if str(self.wepapp_port.value()) != settings['webapp']['port']: settings['webapp']['port'] = str(self.wepapp_port.value()) # Colors settings['colors']['tile'] = self.tile_color.get_color() settings['colors']['hovered_tile'] = self.hovered_tile_color.get_color( ) settings['colors'][ 'hovered_empty_tile'] = self.hovered_empty_tile_color.get_color() settings['colors']['dragged_tile'] = self.dragged_tile_color.get_color( ) settings['colors'][ 'drag_selected_tile'] = self.drag_selected_tile_color.get_color() settings['colors'][ 'selected_tile'] = self.selected_tile_color.get_color() settings['colors']['tile_text'] = self.tile_text_color.get_color() settings['colors']['room_grid'] = self.room_grid_color.get_color() if self.room_bg_color.get_color() != settings['colors']['room_bg']: settings['colors']['room_bg'] = self.room_bg_color.get_color() self.__restart_needed = True if self.main_bg_color.get_color() != settings['colors']['main_bg']: settings['colors']['main_bg'] = self.main_bg_color.get_color() self.__restart_needed = True if self.board_bg_color.get_color() != settings['colors']['board_bg']: settings['colors']['board_bg'] = self.board_bg_color.get_color() self.__restart_needed = True settings['colors']['attr_colors'] = self.attr_colors return settings def new_config(self) -> ConfigParser: """ Retrieves the new config parser object """ conf = ConfigParser() conf.read_dict(self.__new_settings()) return conf def need_restart(self): return self.__restart_needed def restore_default(self) -> bool: return self.__restore_required def accept(self) -> None: """ Performs actions before calling parent's accept method """ self.attr_colors = self.attributes_colors_chooser.get_colors_to_str() super().accept() def choose_bdd_path(self) -> None: """ Opens a file chooser to select the bdd path. Then sets the name as button text. """ bdd_path = QFileDialog.getOpenFileName(self, tr("bdd_path"), self.btn_bdd_path.text())[0] if bdd_path: self.btn_bdd_path.setText(bdd_path)
class SidePanel(QDockWidget): saved = Signal(dict) def __init__(self): super(SidePanel, self).__init__() # Internal variables self._coverdir = path.join("data", "images", "covers") self._id = 0 self._imagedata = "" size = [220, 16] # Width, Height (for QLineEdits/QTextEdits) # QDockWidget settings self.setAllowedAreas(Qt.RightDockWidgetArea) self.setFeatures(QDockWidget.DockWidgetClosable) self.setFixedWidth(350) self.setVisible(False) # Fonts bold = QFont() bold.setBold(True) boldUnderline = QFont() boldUnderline.setBold(True) boldUnderline.setUnderline(True) # Horizontal line hline = QFrame() hline.setFrameShape(QFrame.HLine) hline.setFrameShadow(QFrame.Sunken) """Widgets""" # Cover self.cover = QLabel() self.cover.setAlignment(Qt.AlignCenter) self.cover.setMinimumHeight(200) self.cover.setMaximumSize(self.width(), 250) # Buttons self.fetchInfoButton = QPushButton("Fetch missing info") self.fetchInfoButton.setToolTip("Try to fetch info from MobyGames") self.fetchInfoButton.clicked.connect(self._fetchInfo) self.editDetailsButton = QPushButton("Edit") self.editDetailsButton.setCheckable(True) self.editDetailsButton.clicked.connect(self._editDetails) self.saveDetailsButton = QPushButton("Save") self.saveDetailsButton.clicked.connect(self._saveInfo) self.fetchPriceButton = QPushButton("Update price data") self.fetchPriceButton.setToolTip("Try to update price data from Pricecharting") self.fetchPriceButton.clicked.connect(self._fetchPriceData) self.savePriceButton = QPushButton("Save") self.savePriceButton.clicked.connect(self._saveInfo) # Info layout widgets self.nameInfoLabel = QLabel("Name:") self.nameInfoLabel.setFont(bold) self.nameDataLabel = QLabel() self.nameDataLabel.setWordWrap(True) self.platformInfoLabel = QLabel("Platform:") self.platformInfoLabel.setFont(bold) self.platformDataLabel = QLabel() self.publisherInfoLabel = QLabel("Publisher:") self.publisherInfoLabel.setFont(bold) self.publisherDataLabel = QLabel() self.developerInfoLabel = QLabel("Developer:") self.developerInfoLabel.setFont(bold) self.developerDataLabel = QLabel() self.regionInfoLabel = QLabel("Region:") self.regionInfoLabel.setFont(bold) self.regionDataLabel = QLabel() self.codeInfoLabel = QLabel("Code:") self.codeInfoLabel.setFont(bold) self.codeDataLabel = QLabel() self.codeDataLabel.setWordWrap(True) self.itemInfoLabel = QLabel("Game:") self.itemInfoLabel.setFont(bold) self.itemDataLabel = QLabel() self.boxInfoLabel = QLabel("Box:") self.boxInfoLabel.setFont(bold) self.boxDataLabel = QLabel() self.manualInfoLabel = QLabel("Manual:") self.manualInfoLabel.setFont(bold) self.manualDataLabel = QLabel() self.genreInfoLabel = QLabel("Genre:") self.genreInfoLabel.setFont(bold) self.genreDataLabel = QLabel() self.yearInfoLabel = QLabel("Year:") self.yearInfoLabel.setFont(bold) self.yearDataLabel = QLabel() self.commentInfoLabel = QLabel("Comment:") self.commentInfoLabel.setFont(bold) self.commentDataLabel = QLabel() self.commentDataLabel.setWordWrap(True) self.platformsInfoLabel = QLabel("Available for:") self.platformsInfoLabel.setFont(bold) self.platformsDataLabel = QLabel() self.platformsDataLabel.setWordWrap(True) self.platformsDataLabel.setMaximumHeight(50) # Can get quite large otherwise # Edit layout widgets self.nameEditLabel = QLabel("Name:") self.nameEditLabel.setFont(bold) self.nameDataTE = QTextEdit() self.nameDataTE.setMaximumSize(size[0], size[1] * 2) self.platformEditLabel = QLabel("Platform:") self.platformEditLabel.setFont(bold) self.platformDataLE = QLineEdit() self.platformDataLE.setMaximumSize(size[0], size[1]) self.publisherEditLabel = QLabel("Publisher:") self.publisherEditLabel.setFont(bold) self.publisherDataLE = QLineEdit() self.publisherDataLE.setMaximumSize(size[0], size[1]) self.developerEditLabel = QLabel("Developer:") self.developerEditLabel.setFont(bold) self.developerDataLE = QLineEdit() self.developerDataLE.setMaximumSize(size[0], size[1]) self.regionEditLabel = QLabel("Region:") self.regionEditLabel.setFont(bold) self.regionDataCoB = QComboBox() self.regionDataCoB.addItems(("NTSC (JP)", "NTSC (NA)", "PAL")) self.regionDataCoB.setMinimumWidth(size[0]) # If not set it will be too small self.regionDataCoB.setMaximumSize(size[0], size[1]) self.codeEditLabel = QLabel("Code:") self.codeEditLabel.setFont(bold) self.codeDataLE = QLineEdit() self.codeDataLE.setMaximumSize(size[0], size[1]) self.itemEditLabel = QLabel("Game:") self.itemEditLabel.setFont(bold) self.itemDataCB = QCheckBox() self.itemDataCB.setChecked(False) self.boxEditLabel = QLabel("Box:") self.boxEditLabel.setFont(bold) self.boxDataCB = QCheckBox() self.boxDataCB.setChecked(False) self.manualEditLabel = QLabel("Manual:") self.manualEditLabel.setFont(bold) self.manualDataCB = QCheckBox() self.manualDataCB.setChecked(False) self.genreEditLabel = QLabel("Genre:") self.genreEditLabel.setFont(bold) self.genreDataLE = QLineEdit() self.genreDataLE.setMaximumSize(size[0], size[1]) self.yearEditLabel = QLabel("Year:") self.yearEditLabel.setFont(bold) self.yearDataLE = QLineEdit() self.yearDataLE.setMaximumSize(size[0], size[1]) self.commentEditLabel = QLabel("Comment:") self.commentEditLabel.setFont(bold) self.commentDataTE = QTextEdit() self.commentDataTE.setMaximumSize(size[0], size[1] * 2) self.platformsEditLabel = QLabel("Available for:") self.platformsEditLabel.setFont(bold) self.platformsDataTE = QTextEdit() self.platformsDataTE.setMaximumSize(size[0], size[1] * 2) # Price widgets self.paidPriceLabel = QLabel("Paid:") self.paidPriceLabel.setFont(bold) self.paidPriceDataLE = QLineEdit() self.paidPriceDataLE.setMaximumSize(60, size[1]) self.paidPriceDataLE.setAlignment(Qt.AlignRight) self.loosePriceLabel = QLabel("Loose:") self.loosePriceLabel.setFont(bold) self.loosePriceDataLabel = QLabel() self.loosePriceDataLabel.setAlignment(Qt.AlignRight) self.cibPriceLabel = QLabel("CIB:") self.cibPriceLabel.setFont(bold) self.cibPriceDataLabel = QLabel() self.cibPriceDataLabel.setAlignment(Qt.AlignRight) self.newPriceLabel = QLabel("New:") self.newPriceLabel.setFont(bold) self.newPriceDataLabel = QLabel() self.newPriceDataLabel.setAlignment(Qt.AlignRight) """Layouts""" # Cover self.coverVbox = QVBoxLayout() self.coverVbox.addWidget(self.cover, 1) self.coverVbox.addWidget(hline, 0) # Buttons self.detailsButtonHbox = QHBoxLayout() self.detailsButtonHbox.addWidget(self.fetchInfoButton, 0) self.detailsButtonHbox.addWidget(self.editDetailsButton, 0) self.detailsButtonHbox.addWidget(self.saveDetailsButton, 0) self.priceButtonHbox = QHBoxLayout() self.priceButtonHbox.addWidget(self.fetchPriceButton, 0) self.priceButtonHbox.addWidget(self.savePriceButton, 0) # Info layouts self.nameInfoHbox = QHBoxLayout() self.nameInfoHbox.addWidget(self.nameInfoLabel, 0) self.nameInfoHbox.addWidget(self.nameDataLabel, 0) self.platformInfoHbox = QHBoxLayout() self.platformInfoHbox.addWidget(self.platformInfoLabel, 0) self.platformInfoHbox.addWidget(self.platformDataLabel, 0) self.publisherInfoHbox = QHBoxLayout() self.publisherInfoHbox.addWidget(self.publisherInfoLabel, 0) self.publisherInfoHbox.addWidget(self.publisherDataLabel, 0) self.developerInfoHbox = QHBoxLayout() self.developerInfoHbox.addWidget(self.developerInfoLabel, 0) self.developerInfoHbox.addWidget(self.developerDataLabel, 0) self.regionInfoHbox = QHBoxLayout() self.regionInfoHbox.addWidget(self.regionInfoLabel, 0) self.regionInfoHbox.addWidget(self.regionDataLabel, 0) self.codeInfoHbox = QHBoxLayout() self.codeInfoHbox.addWidget(self.codeInfoLabel, 0) self.codeInfoHbox.addWidget(self.codeDataLabel, 0) self.itemInfoHbox = QHBoxLayout() self.itemInfoHbox.addWidget(self.itemInfoLabel, 0) self.itemInfoHbox.addWidget(self.itemDataLabel, 0) self.boxInfoHbox = QHBoxLayout() self.boxInfoHbox.addWidget(self.boxInfoLabel, 0) self.boxInfoHbox.addWidget(self.boxDataLabel, 0) self.manualInfoHbox = QHBoxLayout() self.manualInfoHbox.addWidget(self.manualInfoLabel, 0) self.manualInfoHbox.addWidget(self.manualDataLabel, 0) self.genreInfoHbox = QHBoxLayout() self.genreInfoHbox.addWidget(self.genreInfoLabel, 0) self.genreInfoHbox.addWidget(self.genreDataLabel, 0) self.yearInfoHbox = QHBoxLayout() self.yearInfoHbox.addWidget(self.yearInfoLabel, 0) self.yearInfoHbox.addWidget(self.yearDataLabel, 0) self.commentInfoHbox = QHBoxLayout() self.commentInfoHbox.addWidget(self.commentInfoLabel, 0) self.commentInfoHbox.addWidget(self.commentDataLabel, 0) self.platformsInfoHbox = QHBoxLayout() self.platformsInfoHbox.addWidget(self.platformsInfoLabel, 0) self.platformsInfoHbox.addWidget(self.platformsDataLabel, 0) # Edit layouts self.nameEditHbox = QHBoxLayout() self.nameEditHbox.addWidget(self.nameEditLabel, 0) self.nameEditHbox.addWidget(self.nameDataTE, 0) self.platformEditHbox = QHBoxLayout() self.platformEditHbox.addWidget(self.platformEditLabel, 0) self.platformEditHbox.addWidget(self.platformDataLE, 0) self.publisherEditHbox = QHBoxLayout() self.publisherEditHbox.addWidget(self.publisherEditLabel, 0) self.publisherEditHbox.addWidget(self.publisherDataLE, 0) self.developerEditHbox = QHBoxLayout() self.developerEditHbox.addWidget(self.developerEditLabel, 0) self.developerEditHbox.addWidget(self.developerDataLE, 0) self.regionEditHbox = QHBoxLayout() self.regionEditHbox.addWidget(self.regionEditLabel, 0) self.regionEditHbox.addWidget(self.regionDataCoB, 0) self.codeEditHbox = QHBoxLayout() self.codeEditHbox.addWidget(self.codeEditLabel, 0) self.codeEditHbox.addWidget(self.codeDataLE, 0) self.itemEditHbox = QHBoxLayout() self.itemEditHbox.addWidget(self.itemEditLabel, 0) self.itemEditHbox.addWidget(self.itemDataCB, 0) self.itemEditHbox.addSpacing(135) self.boxEditHbox = QHBoxLayout() self.boxEditHbox.addWidget(self.boxEditLabel, 0) self.boxEditHbox.addWidget(self.boxDataCB, 0) self.boxEditHbox.addSpacing(135) self.manualEditHbox = QHBoxLayout() self.manualEditHbox.addWidget(self.manualEditLabel, 0) self.manualEditHbox.addWidget(self.manualDataCB, 0) self.manualEditHbox.addSpacing(135) self.genreEditHbox = QHBoxLayout() self.genreEditHbox.addWidget(self.genreEditLabel, 0) self.genreEditHbox.addWidget(self.genreDataLE, 0) self.yearEditHbox = QHBoxLayout() self.yearEditHbox.addWidget(self.yearEditLabel, 0) self.yearEditHbox.addWidget(self.yearDataLE, 0) self.commentEditHbox = QHBoxLayout() self.commentEditHbox.addWidget(self.commentEditLabel, 0) self.commentEditHbox.addWidget(self.commentDataTE, 0) self.platformsEditHbox = QHBoxLayout() self.platformsEditHbox.addWidget(self.platformsEditLabel, 0) self.platformsEditHbox.addWidget(self.platformsDataTE, 0) # Price layouts self.paidPriceHbox = QHBoxLayout() self.paidPriceHbox.addWidget(self.paidPriceLabel, 0) self.paidPriceHbox.addWidget(self.paidPriceDataLE, 0) self.loosePriceHbox = QHBoxLayout() self.loosePriceHbox.addWidget(self.loosePriceLabel, 0) self.loosePriceHbox.addWidget(self.loosePriceDataLabel, 0) self.cibPriceHbox = QHBoxLayout() self.cibPriceHbox.addWidget(self.cibPriceLabel, 0) self.cibPriceHbox.addWidget(self.cibPriceDataLabel, 0) self.newPriceHbox = QHBoxLayout() self.newPriceHbox.addWidget(self.newPriceLabel, 0) self.newPriceHbox.addWidget(self.newPriceDataLabel, 0) # Info layout self.infoLayout = QVBoxLayout() self.infoLayout.addLayout(self.nameInfoHbox, 0) self.infoLayout.addLayout(self.platformInfoHbox, 0) self.infoLayout.addLayout(self.publisherInfoHbox, 0) self.infoLayout.addLayout(self.developerInfoHbox, 0) self.infoLayout.addLayout(self.genreInfoHbox, 0) self.infoLayout.addLayout(self.regionInfoHbox, 0) self.infoLayout.addLayout(self.yearInfoHbox, 0) self.infoLayout.addLayout(self.codeInfoHbox, 0) self.infoLayout.addLayout(self.itemInfoHbox, 0) self.infoLayout.addLayout(self.boxInfoHbox, 0) self.infoLayout.addLayout(self.manualInfoHbox, 0) self.infoLayout.addLayout(self.commentInfoHbox, 0) self.infoLayout.addLayout(self.platformsInfoHbox, 0) # Edit layout self.editLayout = QVBoxLayout() self.editLayout.addLayout(self.nameEditHbox, 0) self.editLayout.addLayout(self.platformEditHbox, 0) self.editLayout.addLayout(self.publisherEditHbox, 0) self.editLayout.addLayout(self.developerEditHbox, 0) self.editLayout.addLayout(self.genreEditHbox, 0) self.editLayout.addLayout(self.regionEditHbox, 0) self.editLayout.addLayout(self.yearEditHbox, 0) self.editLayout.addLayout(self.codeEditHbox, 0) self.editLayout.addLayout(self.itemEditHbox, 0) self.editLayout.addLayout(self.boxEditHbox, 0) self.editLayout.addLayout(self.manualEditHbox, 0) self.editLayout.addLayout(self.commentEditHbox, 0) self.editLayout.addLayout(self.platformsEditHbox, 0) # Price layout self.priceLayout = QVBoxLayout() self.priceLayout.addLayout(self.paidPriceHbox, 0) self.priceLayout.addLayout(self.loosePriceHbox, 0) self.priceLayout.addLayout(self.cibPriceHbox, 0) self.priceLayout.addLayout(self.newPriceHbox, 0) self.priceLayout.addStretch(1) self.priceLayout.addLayout(self.priceButtonHbox, 0) """Main layout""" self.infoWidget = QWidget() self.infoWidget.setLayout(self.infoLayout) self.editWidget = QWidget() self.editWidget.setLayout(self.editLayout) # Add info and edit widgets to a stacked layout so we can switch layouts when editing self.stackedLayout = QStackedLayout() self.stackedLayout.addWidget(self.infoWidget) self.stackedLayout.addWidget(self.editWidget) self.stackedLayout.setCurrentIndex(0) # Default to info layout self.detailsLayout = QVBoxLayout() self.detailsLayout.addLayout(self.coverVbox, 1) self.detailsLayout.addLayout(self.stackedLayout, 0) self.detailsLayout.addLayout(self.detailsButtonHbox, 0) self.detailsWidget = QWidget() self.detailsWidget.setLayout(self.detailsLayout) self.priceWidget = QWidget() self.priceWidget.setLayout(self.priceLayout) self.tab = QTabWidget() self.tab.addTab(self.detailsWidget, "Details") self.tab.addTab(self.priceWidget, "Price info") self.setWidget(self.tab) def _editDetails(self): if self.editDetailsButton.isChecked(): self._updateWidgetData(1) self.stackedLayout.setCurrentIndex(1) else: self._updateWidgetData(0) self.stackedLayout.setCurrentIndex(0) def _fetchInfo(self): name = self.nameDataLabel.text() platform = self.platformDataLabel.text() region = self.regionDataLabel.text() info = getMobyRelease(name, platform, region) if "image" in info.keys() and info["image"] != "": self._imagedata = requests.get(info["image"]).content pixmap = QPixmap() pixmap.loadFromData(self._imagedata) w = self.cover.width() h = self.cover.height() self.cover.setPixmap(pixmap.scaled(w, h, Qt.KeepAspectRatio, Qt.SmoothTransformation)) if self.publisherDataLabel.text() == "": self.publisherDataLabel.setText(info["publisher"]) if self.developerDataLabel.text() == "": self.developerDataLabel.setText(info["developer"]) if self.genreDataLabel.text() == "": self.genreDataLabel.setText(info["genre"]) if self.yearDataLabel.text() == "": self.yearDataLabel.setText(info["year"]) if self.codeDataLabel.text() == "": self.codeDataLabel.setText(info["code"]) if self.platformsDataLabel.text() == "": self.platformsDataLabel.setText(info["platforms"]) # Update edit widgets if we're editing: if self.editDetailsButton.isChecked(): self._updateWidgetData(1) def _fetchPriceData(self): name = self.nameDataLabel.text() platform = self.platformDataLabel.text() region = self.regionDataLabel.text() prices = getPriceData(name, platform, region) self.loosePriceDataLabel.setText(prices["loose"]) self.cibPriceDataLabel.setText(prices["cib"]) self.newPriceDataLabel.setText(prices["new"]) def _saveInfo(self): if self.editDetailsButton.isChecked(): self._updateWidgetData(0) paidPrice = str(self.paidPriceDataLE.text()).replace(",", ".") # Better use '.' as decimal denominator info = {"id": self._id, "name": self.nameDataLabel.text(), "platform": self.platformDataLabel.text(), "publisher": self.publisherDataLabel.text(), "developer": self.developerDataLabel.text(), "genre": self.genreDataLabel.text(), "region": self.regionDataLabel.text(), "year": self.yearDataLabel.text(), "code": self.codeDataLabel.text(), "item": self.itemDataLabel.text(), "box": self.boxDataLabel.text(), "manual": self.manualDataLabel.text(), "comment": self.commentDataLabel.text(), "platforms": self.platformsDataLabel.text(), "price": ",".join((paidPrice, self.loosePriceDataLabel.text(), self.cibPriceDataLabel.text(), self.newPriceDataLabel.text()))} # Save imagedata to file if self._imagedata != "" and not path.exists(path.join(self._coverdir, str(self._id) + ".jpg")): with open(path.join(self._coverdir, str(self._id) + ".jpg"), "wb") as f: f.write(self._imagedata) self.saved.emit(info) def _updateWidgetData(self, layoutIndex: int): if layoutIndex == 0: self.nameDataLabel.setText(self.nameDataTE.toPlainText()) self.platformDataLabel.setText(self.platformDataLE.text()) self.publisherDataLabel.setText(self.publisherDataLE.text()) self.developerDataLabel.setText(self.developerDataLE.text()) self.genreDataLabel.setText(self.genreDataLE.text()) self.regionDataLabel.setText(self.regionDataCoB.currentText()) self.yearDataLabel.setText(self.yearDataLE.text()) self.codeDataLabel.setText(self.codeDataLE.text()) self.itemDataLabel.setText("Yes") if self.itemDataCB.isChecked() else self.itemDataLabel.setText("No") self.boxDataLabel.setText("Yes") if self.boxDataCB.isChecked() else self.boxDataLabel.setText("No") self.manualDataLabel.setText("Yes") if self.manualDataCB.isChecked() else self.manualDataLabel.setText("No") self.commentDataLabel.setText(self.commentDataTE.toPlainText()) self.platformsDataLabel.setText(self.platformsDataTE.toPlainText()) else: self.nameDataTE.setText(self.nameDataLabel.text()) self.platformDataLE.setText(self.platformDataLabel.text()) self.publisherDataLE.setText(self.publisherDataLabel.text()) self.developerDataLE.setText(self.developerDataLabel.text()) self.genreDataLE.setText(self.genreDataLabel.text()) self.regionDataCoB.setCurrentIndex(0) if self.regionDataLabel.text == "NTSC (JP)"\ else self.regionDataCoB.setCurrentIndex(1) if self.regionDataLabel.text == "NTSC (NA)"\ else self.regionDataCoB.setCurrentIndex(2) self.yearDataLE.setText(self.yearDataLabel.text()) self.codeDataLE.setText(self.codeDataLabel.text()) self.itemDataCB.setChecked(True) if self.itemDataLabel.text() == "Yes" \ else self.itemDataCB.setChecked("False") self.boxDataCB.setChecked(True) if self.boxDataLabel.text() == "Yes" \ else self.boxDataCB.setChecked(False) self.manualDataCB.setChecked(True) if self.manualDataLabel.text() == "Yes" \ else self.manualDataCB.setChecked(False) self.commentDataTE.setText(self.commentDataLabel.text()) self.platformsDataTE.setText(self.platformsDataLabel.text()) def showDetails(self, info: dict): if not self.isVisible(): self.setVisible(True) self.tab.setCurrentIndex(0) # Show details tab initially if self.editDetailsButton.isChecked(): self.editDetailsButton.setChecked(False) self.stackedLayout.setCurrentIndex(0) # Show info layout initially self._id = info["id"] self._imagedata = "" pixmap = path.join(self._coverdir, "none.png") if path.exists(path.join(self._coverdir, str(self._id) + ".jpg")) and info["table"] == "games": pixmap = path.join(self._coverdir, str(self._id) + ".jpg") p = QPixmap(pixmap) w = self.cover.width() h = self.cover.height() self.cover.setPixmap(p.scaled(w, h, Qt.KeepAspectRatio, Qt.SmoothTransformation)) # Price data is stored in form $x,$x,$x,$x [paid, loose, cib, new] paidPrice, loosePrice, cibPrice, newPrice = info["price"].split(",") self.nameDataLabel.setText(info["name"]) self.platformDataLabel.setText(info["platform"]) self.regionDataLabel.setText(info["region"]) self.boxDataLabel.setText(info["box"]) self.manualDataLabel.setText(info["manual"]) self.yearDataLabel.setText(str(info["year"])) self.commentDataLabel.setText(info["comment"]) self.paidPriceDataLE.setText(paidPrice) self.loosePriceDataLabel.setText(loosePrice) self.cibPriceDataLabel.setText(cibPrice) self.newPriceDataLabel.setText(newPrice) if info["table"] == "games": self.publisherDataLabel.setText(info["publisher"]) self.developerDataLabel.setText(info["developer"]) self.genreInfoLabel.setText("Genre:") self.genreEditLabel.setText("Genre:") self.genreDataLabel.setText(info["genre"]) self.codeInfoLabel.setText("Code:") self.codeInfoLabel.setVisible(True) self.codeEditLabel.setText("Code") self.codeEditLabel.setVisible(True) self.codeDataLabel.setText(info["code"]) self.codeDataLabel.setVisible(True) self.codeDataLE.setVisible(True) self.itemInfoLabel.setText("Game:") self.itemEditLabel.setText("Game:") self.itemDataLabel.setText(info["game"]) self.platformsDataLabel.setText(info["platforms"]) self.platformsDataLabel.setVisible(True) self.platformsInfoLabel.setVisible(True) self.fetchInfoButton.setEnabled(True) self.fetchInfoButton.setVisible(True) self.fetchInfoButton.setToolTip("Try to fetch info from MobyGames") self.saveDetailsButton.setEnabled(True) self.saveDetailsButton.setVisible(True) elif info["table"] == "consoles": self.genreInfoLabel.setText("Country:") self.genreEditLabel.setText("Country:") self.genreDataLabel.setText(info["country"]) self.codeInfoLabel.setText("Serial Number:") self.codeInfoLabel.setVisible(True) self.codeEditLabel.setText("Serial Number:") self.codeEditLabel.setVisible(True) self.codeDataLabel.setText(info["serial number"]) self.codeDataLabel.setVisible(True) self.codeDataLE.setVisible(True) self.itemInfoLabel.setText("Console:") self.itemEditLabel.setText("Console:") self.itemDataLabel.setText(info["console"]) self.platformsInfoLabel.setVisible(False) self.platformsDataLabel.setVisible(False) self.fetchInfoButton.setEnabled(False) self.fetchInfoButton.setToolTip("Not available for Consoles tab") elif info["table"] == "accessories": self.genreInfoLabel.setText("Country:") self.genreEditLabel.setText("Country:") self.genreDataLabel.setText(info["country"]) self.itemInfoLabel.setText("Accessory:") self.itemEditLabel.setText("Accessory:") self.itemDataLabel.setText(info["accessory"]) self.codeInfoLabel.setVisible(False) self.codeEditLabel.setVisible(False) self.codeDataLabel.setVisible(False) self.codeDataLE.setVisible(False) self.platformsInfoLabel.setVisible(False) self.platformsDataLabel.setVisible(False) self.fetchInfoButton.setEnabled(False) self.fetchInfoButton.setToolTip("Not avaiable for Accessories tab") def hideDetails(self): self.setVisible(False)
class IComparator(QWidget): def __init__(self, parent, usbif): super().__init__(parent) self._usbif = usbif self._br = 0 self._st = 0 self._sqext = 0 self._sq = 0 self._br_ign = 0 self._st_ign = 0 self._sqext_ign = 0 self._sq_ign = 0 self._setup_ui() def _setup_ui(self): layout = QHBoxLayout(self) self.setLayout(layout) layout.setSpacing(3) layout.setMargin(1) self.br = SubComparator(self, 2, on_change=self._update_instruction) layout.addWidget(self.br) self.st = SubComparator(self, 3, on_change=self._update_instruction) layout.addWidget(self.st) self.sq = SubComparator(self, 7, on_change=self._update_instruction) layout.addWidget(self.sq) self.status = SubComparator(self, 7, on_change=self._update_status, include_values=False, item_width=35, separators=False) layout.addWidget(self.status) layout.setAlignment(self.status, Qt.AlignBottom) # Create a grouping widget for the I label and decoded octal value box label_value_layout = QHBoxLayout() label_value_layout.setSpacing(3) label_value_layout.setMargin(1) label_value_layout.setContentsMargins(0, 33, 0, 0) layout.addLayout(label_value_layout) # Create a value box for displaying the overall decoded address self._instr_value = QLineEdit(self) self._instr_value.setFixedSize(70, 32) self._instr_value.setReadOnly(True) self._instr_value.setAlignment(Qt.AlignCenter) self._instr_value.setText('TC0') self._instr_value.setStyleSheet("QLineEdit { color: #555; }") label_value_layout.addWidget(self._instr_value) # Create a label to show 'I' label = QLabel(f"I", self) label.setFixedWidth(20) label_value_layout.addWidget(label) def _update_instruction(self): br = self.br.reg_value st = self.st.reg_value sq = self.sq.reg_value sqext = (sq >> 6) & 0o1 sq &= 0o77 br_ign = self.br.ign_value st_ign = self.st.ign_value sq_ign = self.sq.ign_value sqext_ign = (sq_ign >> 6) & 0o1 sq_ign &= 0o77 if (self._br != br) or (self._st != st) or (self._sq != sq) or ( self._sqext != sqext): self._br = br self._st = st self._sq = sq self._sqext = sqext msg = um.ControlICompVal(br=br, st=st, sqext=sqext, sq=sq) self._usbif.send(msg) if (self._br_ign != br_ign) or (self._st_ign != st_ign) or ( self._sq_ign != sq_ign) or (self._sqext_ign != sqext_ign): self._br_ign = br_ign self._st_ign = st_ign self._sq_ign = sq_ign self._sqext_ign = sqext_ign msg = um.ControlICompIgnore(br=br_ign, st=st_ign, sqext=sqext_ign, sq=sq_ign) self._usbif.send(msg) self._instr_value.setText(agc.disassemble_subinst(sqext, sq, st)) def _update_status(self): reg_states = [s.isChecked() for s in self.status._reg_switches] ign_states = [s.isChecked() for s in self.status._ign_switches] reg_keys = list(STATUS_INDS.keys()) ign_keys = [k + "_ign" for k in reg_keys] status_reg = dict(zip(reg_keys, reversed(reg_states))) status_ign = dict(zip(ign_keys, reversed(ign_states))) status_bits = {**status_reg, **status_ign} msg = um.ControlICompStatus(**status_bits) self._usbif.send(msg)
class VOGView(AbstractView): def __init__(self, name: str = "VOG_NONE", log_handlers: [StreamHandler] = None): self._logger = getLogger(__name__) if log_handlers: for h in log_handlers: self._logger.addHandler(h) self._logger.debug("Initializing") super().__init__(name) """ Min size for the VOG window """ self._subwindow_height = 222 self._subwindow_width = 518 """ min and max sizes for the configuration popup (width, height) """ self._popup_min = (229, 409) self._popup_max = (300, 409) """ Set configuration value display area""" self._config_frame = EasyFrame() self._config_vertical_layout = QVBoxLayout(self._config_frame) self._config_label = QLabel(self._config_frame) self._config_label.setAlignment(Qt.AlignCenter) self._config_val_line_edit = QLineEdit(self._config_frame) self._config_val_line_edit.setAlignment(Qt.AlignCenter) self._config_vertical_layout.addWidget(self._config_label) self._config_vertical_layout.addWidget(self._config_val_line_edit) """ Set preset button selection area. """ self._nhtsa_button = ClickAnimationButton() self._eblindfold_button = ClickAnimationButton() self._direct_control_button = ClickAnimationButton() """ Set open duration, close duration, and debounce time settings display area. """ self._input_box_frame = EasyFrame() self._input_box_grid_layout = QGridLayout(self._input_box_frame) self._input_box_grid_layout.setContentsMargins(0, 6, 0, 6) self._open_dur_label = QLabel(self._input_box_frame) self._open_dur_line_edit = QLineEdit(self._input_box_frame) self._open_dur_line_edit.setFixedWidth(80) self._open_inf_check_box = QCheckBox(self._input_box_frame) self._close_dur_label = QLabel(self._input_box_frame) self._close_dur_line_edit = QLineEdit(self._input_box_frame) self._close_dur_line_edit.setFixedWidth(80) self._close_inf_check_box = QCheckBox(self._input_box_frame) self._debounce_label = QLabel(self._input_box_frame) self._debounce_time_line_edit = QLineEdit(self._input_box_frame) self._debounce_time_line_edit.setFixedWidth(80) self._input_box_grid_layout.addWidget(self._open_dur_label, 0, 0, 1, 1) self._input_box_grid_layout.addWidget(self._open_dur_line_edit, 0, 1, 1, 1) self._input_box_grid_layout.addWidget(self._open_inf_check_box, 0, 2, 1, 1) self._input_box_grid_layout.addWidget(self._close_dur_label, 1, 0, 1, 1) self._input_box_grid_layout.addWidget(self._close_dur_line_edit, 1, 1, 1, 1) self._input_box_grid_layout.addWidget(self._close_inf_check_box, 1, 2, 1, 1) self._input_box_grid_layout.addWidget(self._debounce_label, 2, 0, 1, 1) self._input_box_grid_layout.addWidget(self._debounce_time_line_edit, 2, 1, 1, 1) """ Set button mode setting display area. """ self._button_mode_frame = EasyFrame() self._button_mode_horiz_layout = QGridLayout(self._button_mode_frame) self._button_mode_horiz_layout.setContentsMargins(0, 6, 0, 6) self._button_mode_label = QLabel(self._button_mode_frame) self._button_mode_selector = QComboBox(self._button_mode_frame) self._button_mode_selector.addItem("") self._button_mode_selector.addItem("") self._button_mode_horiz_layout.addWidget(self._button_mode_label, 0, 0, 1, 1) self._button_mode_horiz_layout.addWidget(self._button_mode_selector, 0, 1, 1, 1) """ Set control mode setting display area. """ self._control_mode_label = QLabel(self._button_mode_frame) self._control_mode_selector = QComboBox(self._button_mode_frame) self._control_mode_selector.addItem("") self._control_mode_selector.addItem("") self._button_mode_horiz_layout.addWidget(self._control_mode_label, 1, 0, 1, 1) self._button_mode_horiz_layout.addWidget(self._control_mode_selector, 1, 1, 1, 1) """ Set upload button selection area. """ self._upload_settings_button = ClickAnimationButton() """ Set manual control selection area. """ self._manual_control_button_frame = EasyFrame() self._manual_control_button_layout = QHBoxLayout( self._manual_control_button_frame) self._manual_control_open_button = ClickAnimationButton() self._manual_control_close_button = ClickAnimationButton() self._manual_control_button_layout.addWidget( self._manual_control_open_button) self._manual_control_button_layout.addWidget( self._manual_control_close_button) self._manual_control_button_layout.setMargin(0) """ device settings display """ self._dev_sets_frame = EasyFrame() self._dev_sets_layout = QVBoxLayout(self._dev_sets_frame) self.config_button = ClickAnimationButton() self.config_button.clicked.connect(self._config_button_handler) self._config_win = ConfigPopUp() self._config_win.setMinimumSize(self._popup_min[0], self._popup_min[1]) self._config_win.setMaximumSize(self._popup_max[0], self._popup_max[1]) self._config_win.setLayout(self._dev_sets_layout) """ Add widgets to layout. """ self._dev_sets_layout.addWidget(self._config_frame) self._dev_sets_layout.addWidget(self._input_box_frame) self._dev_sets_layout.addWidget(self._button_mode_frame) self._dev_sets_layout.addWidget(self._nhtsa_button) self._dev_sets_layout.addWidget(self._eblindfold_button) self._dev_sets_layout.addWidget(self._direct_control_button) self._dev_sets_layout.addWidget(self._upload_settings_button) self.layout().addWidget(self.config_button, 0, 0, Qt.AlignTop | Qt.AlignRight) self.config_button.setFixedSize(30, 25) self.layout().addWidget(self._manual_control_button_frame, 0, 0, Qt.AlignBottom | Qt.AlignLeft) self._manual_control_open_button.setFixedSize(70, 25) self._manual_control_close_button.setFixedSize(70, 25) self.layout().setMargin(0) self._strings = dict() self._lang_enum = LangEnum.ENG self.setMinimumSize(self._subwindow_width, self._subwindow_height) self.resize(self._subwindow_width, self._subwindow_height) self._logger.debug("Initialized") def add_graph(self, graph) -> None: """ Add Graph to view. :return None: """ self._logger.debug("running") self.layout().addWidget(graph, 0, 0) self.config_button.raise_() self._manual_control_button_frame.raise_() self._logger.debug("done") def _config_button_handler(self) -> None: """ handles the config button :return None: """ self._logger.debug("running") self._config_win.exec_() self._logger.debug("done") def set_config_val_line_edit_handler(self, func: classmethod) -> None: """ Sets the config val line handler. :param func: classmethod that handles the config val line. :return None: """ self._logger.debug("running") self._config_val_line_edit.textChanged.connect(func) self._logger.debug("done") def set_nhtsa_button_handler(self, func: classmethod) -> None: """ Sets NHTSA button press handler. :param func: classmethod that handles the NHTSA button. :return None: """ self._logger.debug("running") self._nhtsa_button.clicked.connect(func) self._logger.debug("done") def set_eblindfold_button_handler(self, func: classmethod) -> None: """ Sets eBlindfold button press handler. :param func: classmethod that handles the eBlindfold button. :return None: """ self._logger.debug("running") self._eblindfold_button.clicked.connect(func) self._logger.debug("done") def set_direct_control_button_handler(self, func: classmethod) -> None: """ Sets Direct Control button press handler. :param func: classmethod that handles the Direct Control button. :return None: """ self._logger.debug("running") self._direct_control_button.clicked.connect(func) self._logger.debug("done") def set_open_dur_line_edit_handler(self, func: classmethod) -> None: """ Sets open duration line edit handler. :param func: classmethod that handles the line edit. :return None: """ self._logger.debug("running") self._open_dur_line_edit.textChanged.connect(func) self._logger.debug("done") def set_close_dur_line_edit_handler(self, func: classmethod) -> None: """ Sets close duration line edit handler. :param func: classmethod that handles the line edit. :return None: """ self._logger.debug("running") self._close_dur_line_edit.textChanged.connect(func) self._logger.debug("done") def set_open_inf_check_box_handler(self, func: classmethod) -> None: """ Sets open INF checkbox handler. :param func: classmethod that handles the checkbox, requires bool param. :return None: """ self._logger.debug("running") self._open_inf_check_box.stateChanged.connect(func) self._logger.debug("done") def set_close_inf_check_box_handler(self, func: classmethod) -> None: """ Sets close INF checkbox handler. :param func: classmethod that handles the checkbox, requires bool param. :return None: """ self._logger.debug("running") self._close_inf_check_box.stateChanged.connect(func) self._logger.debug("done") def set_debounce_time_line_edit_handler(self, func: classmethod) -> None: """ Sets debounce time line edit handler. :param func: classmethod that handles the line edit. :return None: """ self._logger.debug("running") self._debounce_time_line_edit.textChanged.connect(func) self._logger.debug("done") def set_button_mode_selector_handler(self, func: classmethod) -> None: """ Sets button mode combo box handler. :param func: classmethod that handles the combo. :return None: """ self._logger.debug("running") self._button_mode_selector.currentIndexChanged.connect(func) self._logger.debug("done") def set_control_mode_selector_handler(self, func: classmethod) -> None: """ Sets button mode combo box handler. :param func: classmethod that handles the combo. :return None: """ self._logger.debug("running") self._control_mode_selector.currentIndexChanged.connect(func) self._logger.debug("done") def set_upload_settings_button_handler(self, func: classmethod) -> None: """ Sets upload settings button handler. :param func: classmethod that handles the button. :return None: """ self._logger.debug("running") self._upload_settings_button.clicked.connect(func) self._logger.debug("done") def set_manual_control_open_button_handler(self, func: classmethod) -> None: """ Sets manual open button handler. :param func: classmethod that handles the button. :return None: """ self._logger.debug("running") self._manual_control_open_button.clicked.connect(func) self._logger.debug("done") def set_manual_control_close_button_handler(self, func: classmethod) -> None: """ Sets manual close button handler. :param func: classmethod that handles the button. :return None: """ self._logger.debug("running") self._manual_control_close_button.clicked.connect(func) self._logger.debug("done") @property def config_text(self) -> str: """ Get the string value found in the config text box. :return str: string value in the text box. """ return self._config_val_line_edit.text() @config_text.setter def config_text(self, val: str) -> None: """ Set the string value found in the config text box. :param val: string value to set the config text box. :return None: """ self._logger.debug("running") self._config_val_line_edit.setText(val) self._logger.debug("done") @property def open_duration(self) -> str: """ Get the string value found in the open duration text box. :return str: string value in the text box. """ return self._open_dur_line_edit.text() @open_duration.setter def open_duration(self, val: str) -> None: """ Set the string value found in the open duration text box. :param val: string value to set the open duration text box. :return None: """ self._logger.debug("running") self._open_dur_line_edit.setText(val) self._logger.debug("done") @property def close_duration(self) -> str: """ Get the string value found in the close duration text box. :return str: string value in the text box. """ return self._close_dur_line_edit.text() @close_duration.setter def close_duration(self, val: str) -> None: """ Set the string value found in the close duration text box. :param val: string value to set the close duration text box. :return None: """ self._logger.debug("running") self._close_dur_line_edit.setText(val) self._logger.debug("done") @property def debounce_val(self) -> str: """ Get the string value found in the debounce time text box. :return str: string value in the text box. """ return self._debounce_time_line_edit.text() @debounce_val.setter def debounce_val(self, val: str) -> None: """ Set the string value found in the debounce time text box. :param val: string value to set the debounce text box. :return None: """ self._logger.debug("running") self._debounce_time_line_edit.setText(val) self._logger.debug("done") @property def open_inf_check_box(self) -> bool: """ Get the check box state. :return bool: returns true if the box is checked. """ return self._open_inf_check_box.isChecked() @open_inf_check_box.setter def open_inf_check_box(self, val: bool) -> None: """ Set the check box state. :param val: bool value to set the check box. :return None: """ self._logger.debug("running") self._open_inf_check_box.setChecked(val) self._logger.debug("done") @property def close_inf_check_box(self) -> bool: """ Get the check box state. :return bool: returns true if the box is checked. """ return self._close_inf_check_box.isChecked() @close_inf_check_box.setter def close_inf_check_box(self, val: bool) -> None: """ Set the check box state. :param val: bool value to set the check box. :return None: """ self._logger.debug("running") self._close_inf_check_box.setChecked(val) self._logger.debug("done") @property def button_mode(self) -> int: """ Get index of button mode. :return int: index of current button mode. """ return self._button_mode_selector.currentIndex() @button_mode.setter def button_mode(self, val: int) -> None: """ Set index of button mode. :return None: """ self._logger.debug("running") self._button_mode_selector.setCurrentIndex(val) self._logger.debug("done") @property def control_mode(self) -> int: """ Get index of button mode. :return int: index of current button mode. """ return self._control_mode_selector.currentIndex() @control_mode.setter def control_mode(self, val: int) -> None: """ Set index of button mode. :return None: """ self._logger.debug("running") self._control_mode_selector.setCurrentIndex(val) self._logger.debug("done") def set_open_dur_err(self, err: bool) -> None: """ Set this text entry to error style depending on err. :param err: If this text entry needs to be error styel :return None: """ self._logger.debug("running") if err: self._open_dur_line_edit.setStyleSheet(tab_line_edit_error_style) else: self._open_dur_line_edit.setStyleSheet( tab_line_edit_compliant_style) self._logger.debug("done") def set_close_dur_err(self, err: bool) -> None: """ Set this text entry to error style depending on err. :param err: If this text entry needs to be error styel :return None: """ self._logger.debug("running") if err: self._close_dur_line_edit.setStyleSheet(tab_line_edit_error_style) else: self._close_dur_line_edit.setStyleSheet( tab_line_edit_compliant_style) self._logger.debug("done") def set_debounce_err(self, err: bool) -> None: """ Set this text entry to error style depending on err. :param err: If this text entry needs to be error styel :return None: """ self._logger.debug("running") if err: self._debounce_time_line_edit.setStyleSheet( tab_line_edit_error_style) else: self._debounce_time_line_edit.setStyleSheet( tab_line_edit_compliant_style) self._logger.debug("done") @property def language(self) -> LangEnum: """ Get the current language setting :return LangEnum: The current language enumerator being used """ return self._lang_enum @language.setter def language(self, lang: LangEnum) -> None: """ Set the language for this view object and reload the text and tooltips. :param lang: the language to use. :return None: """ self._logger.debug("running") self._strings = strings[lang] self._set_texts() self._set_tooltips() self._logger.debug("done") def set_upload_button(self, is_active: bool) -> None: """ Set upload button activity to is_active. :param is_active: Whether this button should be active. :return None: """ self._logger.debug("running") self._upload_settings_button.setEnabled(is_active) self._logger.debug("done") def _set_texts(self) -> None: """ Set text fields of view object. :return None: """ self._logger.debug("running") self._config_label.setText(self._strings[StringsEnum.CONFIG_LABEL]) self._config_val_line_edit.setText( self._strings[StringsEnum.CONFIG_LABEL]) self._nhtsa_button.setText(self._strings[StringsEnum.NHTSA_LABEL]) self._eblindfold_button.setText( self._strings[StringsEnum.EBLIND_LABEL]) self._direct_control_button.setText( self._strings[StringsEnum.DCON_LABEL]) self._open_dur_label.setText( self._strings[StringsEnum.OPEN_DURATION_LABEL]) self._open_inf_check_box.setText(self._strings[StringsEnum.INF_LABEL]) self._close_dur_label.setText( self._strings[StringsEnum.CLOSE_DURATION_LABEL]) self._close_inf_check_box.setText(self._strings[StringsEnum.INF_LABEL]) self._debounce_label.setText(self._strings[StringsEnum.DEBOUNCE_LABEL]) self._button_mode_label.setText( self._strings[StringsEnum.BUTTON_MODE_LABEL]) self._button_mode_selector.setItemText( 0, self._strings[StringsEnum.HOLD_VAL_LABEL]) self._button_mode_selector.setItemText( 1, self._strings[StringsEnum.CLICK_VAL_LABEL]) self._control_mode_label.setText( self._strings[StringsEnum.CONTROL_MODE_LABEL]) self._control_mode_selector.setItemText( 0, self._strings[StringsEnum.LENS_VAL_LABEL]) self._control_mode_selector.setItemText( 1, self._strings[StringsEnum.TRIAL_VAL_LABEL]) self._upload_settings_button.setText( self._strings[StringsEnum.UPLOAD_BUTTON_LABEL]) self._manual_control_open_button.setText( self._strings[StringsEnum.MANUAL_OPEN_LABEL]) self._manual_control_close_button.setText( self._strings[StringsEnum.MANUAL_CLOSE_LABEL]) self.config_button.setText(self._strings[StringsEnum.CONFIG_TAB_LABEL]) self.config_button.setText("...") self._config_win.setWindowTitle( self.get_name() + " " + self._strings[StringsEnum.CONFIG_TAB_LABEL]) self._logger.debug("done") def _set_tooltips(self) -> None: """ Set tooltip text fields of view object. :return None: """ self._logger.debug("running") self._config_label.setToolTip( self._strings[StringsEnum.CONFIG_LABEL_TOOLTIP]) self._config_val_line_edit.setToolTip( self._strings[StringsEnum.CONFIG_LABEL_TOOLTIP]) self._open_dur_label.setToolTip( self._strings[StringsEnum.OPEN_DURATION_TOOLTIP]) self._close_dur_label.setToolTip( self._strings[StringsEnum.CLOSE_DURATION_TOOLTIP]) self._debounce_label.setToolTip( self._strings[StringsEnum.DEBOUNCE_TOOLTIP]) self._button_mode_label.setToolTip( self._strings[StringsEnum.BUTTON_MODE_TOOLTIP]) self._control_mode_label.setToolTip( self._strings[StringsEnum.CONTROL_MODE_TOOLTIP]) self._upload_settings_button.setToolTip( self._strings[StringsEnum.UPLOAD_BUTTON_TOOLTIP]) self._manual_control_open_button.setToolTip( self._strings[StringsEnum.MANUAL_OPEN_TOOLTIP]) self._manual_control_close_button.setToolTip( self._strings[StringsEnum.MANUAL_CLOSE_TOOLTIP]) self.config_button.setToolTip( self._strings[StringsEnum.CONFIG_TAB_TOOLTIP]) self._logger.debug("done")
class main(QWidget): def __init__(self, parent=None): super(main, self).__init__(parent) self.setup() # connections, widgets, layouts, etc. self.blksize = 2**20 # 1 MB; must be divisible by 16 self.ext = '.enc' # extension is appended to encrypted files self.path = '' self.encrypted = [] # to highlight done files in list self.decrypted = [] self.clipboard = QApplication.clipboard() self.timeout = None # to clear message label, see setMessage # this program was just an excuse to play with QprogressBar if not hash(os.urandom(11)) % 11: QTimer().singleShot(50, self.windDown) # various random hints hints = [ 'Freshly encrypted files can be renamed in the table!', 'Clipboard is always cleared on program close!', 'Keys can contain emoji if you <em>really</em> want: \U0001f4e6', 'Keys can contain emoji if you <em>really</em> want: \U0001F511', 'This isn\'t a tip, I just wanted to say hello!', 'Keys can be anywhere from 8 to 4096 characters long!', 'This program was just an excuse to play with the progress bars!', 'Select \'Party\' in the hash button for progress bar fun!', ('Did you know you can donate one or all of your vital organs to ' 'the Aperture Science Self-Esteem Fund for Girls? It\'s true!'), ('It\'s been {:,} days since Half-Life 2: Episode ' 'Two'.format(int((time.time() - 1191988800) / 86400))), 'I\'m version {}!'.format(VERSION), 'I\'m version {}.whatever!'.format(VERSION.split('.')[0]), ('Brought to you by me, I\'m <a href="https://orthallelous.word' 'press.com/">Orthallelous!</a>'), #'Brought to you by me, I\'m Htom Sirveaux!', 'I wonder if there\'s beer on the sun', 'Raspberry World: For all your raspberry needs. Off the beltline', #'I\'ve plummented to my death and I can\'t get up', '<em>NOT</em> compatible with the older version!', ('Hello there, fellow space travellers! Until somebody gives me ' 'some new lines in KAS, that is all I can say. - Bentusi Exchange' ) ] if not hash(os.urandom(9)) % 4: self.extraLabel.setText(random.choice(hints)) def genKey(self): "generate a random key" n = self.keySizeSB.value() char = string.printable.rstrip() #map(chr, range(256)) while len(char) < n: char += char key = ''.join(random.sample(char, n)) self.keyInput.setText(key) def showKey(self, state=None): "hide/show key characters" if state is None: state = bool(self.showKeyCB.checkState()) else: state = bool(state) if state: self.keyInput.setEchoMode(QLineEdit.Normal) else: self.keyInput.setEchoMode(QLineEdit.PasswordEchoOnEdit) def getFolder(self): "open file dialog and fill file table" path = QFileDialog(directory=self.path).getExistingDirectory() if not path: return self.path = str(path) self.populateTable(self.path) self.encrypted, self.decrypted = [], [] return def resizeEvent(self, event): self.showFolder(self.path) # update how the folder is shown def splitterChanged(self, pos): self.showFolder(self.path) # likewise def showFolder(self, path): "displays current path, truncating as needed" if not path: return ell, sl = '\u2026', os.path.sep # ellipsis, slash chars lfg, rfg = Qt.ElideLeft, Qt.ElideRight lst, wdh = os.path.basename(path), self.folderLabel.width() path = path.replace(os.path.altsep or '\\', sl) self.folderLabel.setToolTip(path) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short self.folderLabel.setText('\u22ee' if txt != sl else txt) return # but when would this happen? # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = ell + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt[2:]) < len(lst) + 2: txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh)) # you'd think len(txt) < len(lst) would work, but no; you'd be wrong self.folderLabel.setText(txt) def populateTable(self, path): "fill file table with file names" self.showFolder(path) names = [] for n in os.listdir(path): if os.path.isdir(os.path.join(path, n)): continue # folder names.append(n) self.folderTable.clearContents() self.folderTable.setRowCount(len(names)) self.folderTable.setColumnCount(1) if not names: # no files in this folder, inform user self.setMessage('This folder has no files') return self.folderTable.blockSignals(True) selEnab = Qt.ItemIsSelectable | Qt.ItemIsEnabled for i, n in enumerate(names): item = QTableWidgetItem() item.setText(n) item.setToolTip(n) item.setFlags(selEnab) # color code encrypted/decrypted files if n in self.encrypted: item.setTextColor(QColor(211, 70, 0)) # allowed encrypted filenames to be changed item.setFlags(selEnab | Qt.ItemIsEditable) if n in self.decrypted: item.setForeground(QColor(0, 170, 255)) self.folderTable.setItem(i, 0, item) if len(names) > 5: self.setMessage('{:,} files'.format(len(names)), 7) self.folderTable.blockSignals(False) return def editFileName(self, item): "change file name" new, old = str(item.text()), str(item.toolTip()) result = QMessageBox.question( self, 'Renaming?', ("<p align='center'>Do you wish to rename<br>" + '<span style="color:#d34600;">{}</span>'.format(old) + "<br>to<br>" + '<span style="color:#ef4b00;">{}</span>'.format(new) + '<br>?</p>')) self.folderTable.blockSignals(True) if any(i in new for i in '/?<>:*|"^'): self.setMessage('Invalid character in name', 7) item.setText(old) elif result == QMessageBox.Yes: oold = os.path.join(self.path, old) try: os.rename(oold, os.path.join(self.path, new)) self.encrypted.remove(old) self.encrypted.append(new) item.setToolTip(new) except Exception as err: self.setMessage(str(err), 9) item.setText(old) item.setToolTip(old) self.encrypted.remove(new) self.encrypted.append(old) else: item.setText(old) self.folderTable.blockSignals(False) def setMessage(self, message, secs=4, col=None): "show a message for a few seconds - col must be rgb triplet tuple" if self.timeout: # https://stackoverflow.com/a/21081371 self.timeout.stop() self.timeout.deleteLater() if col is None: color = 'rgb(255, 170, 127)' else: try: color = 'rgb({}, {}, {})'.format(*col) except: color = 'rgb(255, 170, 127)' self.messageLabel.setStyleSheet('background-color: {};'.format(color)) self.messageLabel.setText(message) self.messageLabel.setToolTip(message) self.timeout = QTimer() self.timeout.timeout.connect(self.clearMessage) self.timeout.setSingleShot(True) self.timeout.start(secs * 1000) def clearMessage(self): self.messageLabel.setStyleSheet('') self.messageLabel.setToolTip('') self.messageLabel.setText('') def getName(self): "return file name of selected" items = self.folderTable.selectedItems() names = [str(i.text()) for i in items] if names: return names[0] # only the first selected file else: return '' def showKeyLen(self, string): "displays a tooltip showing length of key" s = len(string) note = '{:,} character{}'.format(s, '' if s == 1 else 's') tip = QToolTip pos = self.genKeyButton.mapToGlobal(QPoint(0, 0)) if s < self.minKeyLen: note = '<span style="color:#c80000;">{}</span>'.format(note) else: note = '<span style="color:#258f22;">{}</span>'.format(note) tip.showText(pos, note) def lock(self, flag=True): "locks buttons if True" stuff = [ self.openButton, self.encryptButton, self.decryptButton, self.genKeyButton, self.hashButton, self.showKeyCB, self.copyButton, self.keyInput, self.keySizeSB, self.folderTable, ] for i in stuff: i.blockSignals(flag) i.setEnabled(not flag) return def _lerp(self, v1, v2, numPts=10): "linearly interpolate from v1 to v2\nFrom Orthallelous" if len(v1) != len(v2): raise ValueError("different dimensions") D, V, n = [], [], abs(numPts) for i, u in enumerate(v1): D.append(v2[i] - u) for i in range(n + 1): vn = [] for j, u in enumerate(v1): vn.append(u + D[j] / float(n + 2) * i) V.append(tuple(vn)) return V def weeeeeee(self): "party time" self.lock() self.setMessage('Party time!', 2.5) a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar process, sleep = app.processEvents, time.sleep am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((am, bm, cm), (ax, bx, cx), 100) ivops = loops[::-1] # up and up! for i in range(3): for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) process() sleep(0.01) a.setValue(ax) b.setValue(bx) c.setValue(cx) sleep(0.25) a.setValue(am) b.setValue(bm) c.setValue(cm) # snake! self.setMessage('Snake time!') self.messageLabel.setStyleSheet('background-color: rgb(127,170,255);') for i in range(2): for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) for j, k, l in loops: c.setValue(int(l)) process() sleep(0.002) process() c.setInvertedAppearance(True) process() for j, k, l in ivops: c.setValue(int(l)) process() sleep(0.002) process() b.setInvertedAppearance(True) process() for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) process() a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) # bars sleep(0.5) self.setMessage('Bars!') process() self.messageLabel.setStyleSheet('background-color: rgb(127,255,170);') for i in range(2): a.setValue(ax) time.sleep(0.65) a.setValue(am) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() c.setValue(cx) time.sleep(0.65) c.setValue(cm) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() # okay, enough process() a.setValue(ax) b.setValue(bx) c.setValue(cx) #a.setValue(am); b.setValue(bm); c.setValue(cm) a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) self.lock(False) return def windDown(self, note=None): "silly deload on load" if note is None: note = 'Loading...' self.lock() self.setMessage(note) self.messageLabel.setStyleSheet('background-color: rgb(9, 190, 130);') a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((ax, bx, cx), (am, bm, cm), 100) for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) app.processEvents() time.sleep(0.02) a.reset() b.reset() c.reset() self.lock(False) self.clearMessage() def genHash(self, action): "generate hash of selected file and display it" name, t0 = self.getName(), time.perf_counter() # mark what hash was used in the drop-down menu for i in self.hashButton.menu().actions(): if i == action: i.setIconVisibleInMenu(True) else: i.setIconVisibleInMenu(False) if str(action.text()) == 'Party': self.weeeeeee() self.windDown('Winding down...') return if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return self.lock() hsh = self.hashFile(os.path.join(self.path, name), getattr(hashlib, str(action.text()))) self.lock(False) #hsh = str(action.text()) + ': ' + hsh self.hashLabel.setText(hsh) self.hashLabel.setToolTip(hsh) self.extraLabel.setText( str(action.text()) + ' hash took ' + self.secs_fmt(time.perf_counter() - t0)) def setCancel(self): "cancel operation" self._requestStop = True def showCancelButton(self, state=False): "show/hide cancel button" self.cancelButton.blockSignals(not state) self.cancelButton.setEnabled(state) if state: self.cancelButton.show() self.keyInput.hide() self.genKeyButton.hide() self.keySizeSB.hide() else: self.cancelButton.hide() self.keyInput.show() self.genKeyButton.show() self.keySizeSB.show() def hashFile(self, fn, hasher): "returns the hash value of a file" hsh, blksize = hasher(), self.blksize fsz, csz = os.path.getsize(fn), 0.0 self.hashPbar.reset() self.showCancelButton(True) prog, title = '(# {:.02%}) {}', self.windowTitle() with open(fn, 'rb') as f: while 1: blk = f.read(blksize) if not blk: break hsh.update(blk) csz += blksize self.hashPbar.setValue(int(round(csz * 100.0 / fsz))) app.processEvents() self.setWindowTitle(prog.format(csz / fsz, title)) if self._requestStop: break self.hashPbar.setValue(self.hashPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Hashing canceled!') self.hashPbar.setValue(self.hashPbar.minimum()) self._requestStop = False return return hsh.hexdigest() def hashKey(self, key, salt=b''): "hashes a key for encrypting/decrypting file" salt = salt.encode() if type(salt) != bytes else salt key = key.encode() if type(key) != bytes else key p = app.processEvents self.setMessage('Key Hashing...', col=(226, 182, 249)) p() key = hashlib.pbkdf2_hmac('sha512', key, salt, 444401) p() self.clearMessage() p() return hashlib.sha3_256(key).digest() # AES requires a 32 char key def encrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.encryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.encrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Encrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Encrypting took ' + self.secs_fmt(tt)) def encryptFile(self, key, fn): "encrypts a file using AES (MODE_GCM)" chars = ''.join(map(chr, range(256))).encode() chk = AES.block_size sample = random.sample iv = bytes(sample(chars, chk * 2)) salt = bytes(sample(chars * 2, 256)) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) fsz = os.path.getsize(fn) del key blksize = self.blksize gn = fn + self.ext fne = os.path.basename(fn).encode() fnz = len(fne) if len(fne) % chk: fne += bytes(sample(chars, chk - len(fne) % chk)) csz = 0.0 # current processed value self.encryptPbar.reset() prog, title = '({:.02%}) {}', self.windowTitle() self.showCancelButton(True) with open(fn, 'rb') as src, open(gn, 'wb') as dst: dst.write(bytes([0] * 16)) # spacer for MAC written at end dst.write(iv) dst.write(salt) # store iv, salt # is it safe to store MAC, iv, salt plain right in file? # can't really store them encrypted, # or elsewhere in this model of single file encryption? # can't have another file for the file to lug around # store file size, file name length dst.write(vault.encrypt(struct.pack('<2Q', fsz, fnz))) dst.write(vault.encrypt(fne)) # store filename while 1: dat = src.read(blksize) if not dat: break elif len(dat) % chk: # add padding fil = chk - len(dat) % chk dat += bytes(sample(chars, fil)) dst.write(vault.encrypt(dat)) csz += blksize # show progress self.encryptPbar.setValue(int(round(csz * 100.0 / fsz))) self.setWindowTitle(prog.format(csz / fsz, title)) app.processEvents() if self._requestStop: break if not self._requestStop: stuf = random.randrange(23) # pack in more stuffing fing = b''.join(bytes(sample(chars, 16)) for i in range(stuf)) dst.write(vault.encrypt(fing)) # and for annoyance dst.seek(0) dst.write(vault.digest()) # write MAC self.hashLabel.setText('MAC: ' + vault.hexdigest()) self.encryptPbar.setValue(self.encryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Encryption canceled!') self.encryptPbar.setValue(self.encryptPbar.minimum()) self._requestStop = False os.remove(gn) return return gn def decrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.decryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.decrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Decrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Decrypting took ' + self.secs_fmt(tt)) def decryptFile(self, key, fn): "decrypts a file using AES (MODE_GCM)" blksize = self.blksize gn = hashlib.md5(os.path.basename(fn).encode()).hexdigest() gn = os.path.join(self.path, gn) # temporary name if os.path.exists(gn): self.setMessage('file already exists') return self.decryptPbar.reset() csz = 0.0 # current processed value chk, fnsz = AES.block_size, os.path.getsize(fn) prog, title = '({:.02%}) {}', self.windowTitle() try: with open(fn, 'rb') as src, open(gn, 'wb') as dst: # extract iv, salt MAC = src.read(16) iv = src.read(AES.block_size * 2) salt = src.read(256) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) self.showCancelButton(True) # extract file size, file name length sizes = src.read(struct.calcsize('<2Q')) fsz, fnz = struct.unpack('<2Q', vault.decrypt(sizes)) # extract filename; round up fnz to nearest chk rnz = fnz if not fnz % chk else fnz + chk - fnz % chk rfn = vault.decrypt(src.read(rnz))[:fnz].decode() self.setMessage('Found "{}"'.format(rfn), 13, (255, 211, 127)) while 1: dat = src.read(blksize) if not dat: break dst.write(vault.decrypt(dat)) csz += blksize # show progress self.decryptPbar.setValue(int(round(csz * 100.0 / fnsz))) self.setWindowTitle(prog.format(1 - (csz / fnsz), title)) app.processEvents() if self._requestStop: break if not self._requestStop: dst.truncate(fsz) # remove padding if not self._requestStop: vault.verify(MAC) self.hashLabel.setText('') except (ValueError, KeyError) as err: os.remove(gn) self.setMessage('Invalid decryption!') self.setWindowTitle(title) self.showCancelButton(False) return except Exception as err: os.remove(gn) self.setMessage('Invalid key or file!') self.setWindowTitle(title) self.showCancelButton(False) return self.decryptPbar.setValue(self.decryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Decryption canceled!') self.decryptPbar.setValue(self.decryptPbar.minimum()) self._requestStop = False os.remove(gn) return # restore original file name name, ext = os.path.splitext(rfn) count = 1 fn = os.path.join(self.path, name + ext) while os.path.exists(fn): fn = os.path.join(self.path, name + '_{}'.format(count) + ext) count += 1 os.rename(gn, fn) # restore original name return fn # saved name def copyKeyHash(self, action): "copies either the key or the hash to clipboard" act = str(action.text()).lower() if 'key' in act: txt = str(self.keyInput.text()) elif 'hash' in act: txt = str(self.hashLabel.text()) else: self.setMessage('Invalid copy selection') return if not txt: self.setMessage('Empty text; Nothing to copy') return if 'key' in act: self.setMessage('Key copied to clipboard') elif 'hash' in act: self.setMessage('Hash copied to clipboard') else: self.setMessage('Invalid copy selection') return self.clipboard.clear() self.clipboard.setText(txt) def secs_fmt(self, s): "6357 -> '1h 45m 57s'" Y, D, H, M = 31556952, 86400, 3600, 60 y = int(s // Y) s -= y * Y d = int(s // D) s -= d * D h = int(s // H) s -= h * H m = int(s // M) s -= m * M r = (str(int(s)) if int(s) == s else str(round(s, 3))) + 's' if m: r = str(m) + 'm ' + r if h: r = str(h) + 'h ' + r if d: r = str(d) + 'd ' + r if y: r = str(y) + 'y ' + r return r.strip() def closeEvent(self, event): self.clipboard.clear() def setup(self): "constructs the gui" Fixed = QSizePolicy() MinimumExpanding = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.minKeyLen = 8 self.maxKeyLen = 4096 self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Horizontal) self.splitter.splitterMoved.connect(self.splitterChanged) # left column self.leftColumn = QWidget() self.vl01 = QVBoxLayout() # left column - first item (0; horizonal layout 0) self.hl00 = QHBoxLayout() self.hl00.setSpacing(5) self.openButton = QPushButton('&Open') self.openButton.setToolTip('Open folder') self.openButton.setMinimumSize(60, 20) self.openButton.setMaximumSize(60, 20) self.openButton.setSizePolicy(Fixed) self.openButton.clicked.connect(self.getFolder) #ico = self.style().standardIcon(QStyle.SP_DirIcon) #self.openButton.setIcon(ico) self.folderLabel = QLabel() self.folderLabel.setMinimumSize(135, 20) self.folderLabel.setMaximumSize(16777215, 20) self.folderLabel.setSizePolicy(MinimumExpanding) self.hl00.insertWidget(0, self.openButton) self.hl00.insertWidget(1, self.folderLabel) # left column - second item (1) self.folderTable = QTableWidget() self.folderTable.setMinimumSize(200, 32) self.folderTable.horizontalHeader().setVisible(False) self.folderTable.horizontalHeader().setStretchLastSection(True) self.folderTable.verticalHeader().setVisible(False) self.folderTable.verticalHeader().setDefaultSectionSize(15) self.folderTable.itemChanged.connect(self.editFileName) # left column - third item (2) self.extraLabel = QLabel() self.extraLabel.setMinimumSize(200, 20) self.extraLabel.setMaximumSize(16777215, 20) self.extraLabel.setSizePolicy(MinimumExpanding) self.extraLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) # finalize left column self.vl01.insertLayout(0, self.hl00) self.vl01.insertWidget(1, self.folderTable) self.vl01.insertWidget(2, self.extraLabel) self.leftColumn.setLayout(self.vl01) # right column self.rightColumn = QWidget() self.vl02 = QVBoxLayout() # right column - first item (0) self.messageLabel = QLabel() self.messageLabel.setMinimumSize(290, 20) self.messageLabel.setMaximumSize(16777215, 20) self.messageLabel.setSizePolicy(MinimumExpanding) self.messageLabel.setAlignment(Qt.AlignCenter) # right column - second item (2; horizontal layout 1) self.hl01 = QHBoxLayout() self.hl01.setSpacing(5) self.encryptButton = QPushButton('&Encrypt') #\U0001F512 self.encryptButton.setToolTip('Encrypt selected file') self.encryptButton.setMinimumSize(60, 20) self.encryptButton.setMaximumSize(60, 20) self.encryptButton.setSizePolicy(Fixed) self.encryptButton.clicked.connect(self.encrypt) self.encryptPbar = QProgressBar() self.encryptPbar.setMinimumSize(225, 20) self.encryptPbar.setMaximumSize(16777215, 20) self.encryptPbar.setSizePolicy(MinimumExpanding) self.encryptPbar.setTextVisible(False) palette = self.encryptPbar.palette() # color of progress bar color = QColor(211, 70, 0) palette.setColor(QPalette.Highlight, color) self.encryptPbar.setPalette(palette) self.hl01.insertWidget(0, self.encryptButton) self.hl01.insertWidget(1, self.encryptPbar) # right column - third item (3; horizontal layout 2) self.hl02 = QHBoxLayout() self.hl02.setSpacing(5) self.cancelButton = QPushButton('C&ANCEL') self.cancelButton.setToolTip('Cancels current operation') self.cancelButton.setMinimumSize(70, 24) self.cancelButton.setMaximumSize(70, 24) self.cancelButton.setSizePolicy(Fixed) self.cancelButton.clicked.connect(self.setCancel) font = self.cancelButton.font() font.setBold(True) self.cancelButton.setFont(font) self.cancelButton.blockSignals(True) self.cancelButton.setEnabled(False) self.cancelButton.hide() self._requestStop = False self.keyInput = QLineEdit() self.keyInput.setMinimumSize(225, 20) self.keyInput.setMaximumSize(16777215, 20) self.keyInput.setSizePolicy(MinimumExpanding) self.keyInput.setPlaceholderText('key') self.keyInput.setMaxLength(self.maxKeyLen) self.keyInput.setAlignment(Qt.AlignCenter) self.keyInput.textEdited.connect(self.showKeyLen) self.genKeyButton = QPushButton('&Gen Key') #\U0001F511 self.genKeyButton.setToolTip('Generate a random key') self.genKeyButton.setMinimumSize(60, 20) self.genKeyButton.setMaximumSize(60, 20) self.genKeyButton.setSizePolicy(Fixed) self.genKeyButton.clicked.connect(self.genKey) self.keySizeSB = QSpinBox() self.keySizeSB.setToolTip('Length of key to generate') self.keySizeSB.setRange(32, 1024) self.keySizeSB.setMinimumSize(40, 20) self.keySizeSB.setMaximumSize(40, 20) self.keySizeSB.setSizePolicy(Fixed) self.keySizeSB.setAlignment(Qt.AlignCenter) self.keySizeSB.setButtonSymbols(QSpinBox.NoButtons) self.keySizeSB.setWrapping(True) self.hl02.insertWidget(0, self.cancelButton) self.hl02.insertWidget(1, self.keyInput) self.hl02.insertWidget(2, self.genKeyButton) self.hl02.insertWidget(3, self.keySizeSB) # right column - fourth item (4; horizontal layout 3) self.hl03 = QHBoxLayout() self.hl03.setSpacing(5) self.decryptButton = QPushButton('&Decrypt') #\U0001F513 self.decryptButton.setToolTip('Decrypt selected file') self.decryptButton.setMinimumSize(60, 20) self.decryptButton.setMaximumSize(60, 20) self.decryptButton.setSizePolicy(Fixed) self.decryptButton.clicked.connect(self.decrypt) self.decryptPbar = QProgressBar() self.decryptPbar.setMinimumSize(225, 20) self.decryptPbar.setMaximumSize(16777215, 20) self.decryptPbar.setSizePolicy(MinimumExpanding) self.decryptPbar.setTextVisible(False) self.decryptPbar.setInvertedAppearance(True) palette = self.decryptPbar.palette() # color of progress bar color = QColor(0, 170, 255) palette.setColor(QPalette.Highlight, color) self.decryptPbar.setPalette(palette) self.hl03.insertWidget(0, self.decryptButton) self.hl03.insertWidget(1, self.decryptPbar) # right column - fifth item (7; horizontal layout 4) self.hl04 = QHBoxLayout() self.hl04.setSpacing(5) self.showKeyCB = QCheckBox('&Show Key') self.showKeyCB.setToolTip('Show/Hide key value') self.showKeyCB.setMinimumSize(75, 20) self.showKeyCB.setMaximumSize(75, 20) self.showKeyCB.setSizePolicy(Fixed) self.showKeyCB.clicked.connect(self.showKey) self.showKeyCB.setChecked(True) self.hashPbar = QProgressBar() self.hashPbar.setMinimumSize(150, 20) self.hashPbar.setMaximumSize(16777215, 20) self.hashPbar.setSizePolicy(MinimumExpanding) self.hashPbar.setTextVisible(False) palette = self.hashPbar.palette() # color of progress bar color = QColor(31, 120, 73) palette.setColor(QPalette.Highlight, color) self.hashPbar.setPalette(palette) self.hashButton = QPushButton('&Hash') self.hashButton.setToolTip('Determine file hash') self.hashButton.setMinimumSize(60, 20) self.hashButton.setMaximumSize(60, 20) self.hashButton.setSizePolicy(Fixed) menu = QMenu(self.hashButton) ico = self.style().standardIcon(QStyle.SP_DialogYesButton) for alg in sorted( filter(lambda x: 'shake' not in x, hashlib.algorithms_guaranteed), key=lambda n: (len(n), sorted(hashlib.algorithms_guaranteed).index(n))): menu.addAction( ico, alg ) # drop shake algs as their .hexdigest requires an argument - the rest don't menu.addAction(ico, 'Party') for i in menu.actions(): i.setIconVisibleInMenu(False) self.hashButton.setMenu(menu) menu.triggered.connect(self.genHash) self.hl04.insertWidget(0, self.showKeyCB) self.hl04.insertWidget(1, self.hashPbar) self.hl04.insertWidget(2, self.hashButton) # right column - sixth item (8; horizontal layout 5) self.hl05 = QHBoxLayout() self.hl05.setSpacing(5) self.copyButton = QPushButton('&Copy') #\U0001F4CB self.copyButton.setToolTip('Copy key or hash to clipboard') self.copyButton.setMinimumSize(60, 20) self.copyButton.setMaximumSize(60, 20) self.copyButton.setSizePolicy(Fixed) menu2 = QMenu(self.copyButton) menu2.addAction('Copy Key') menu2.addAction('Copy Hash') self.copyButton.setMenu(menu2) menu2.triggered.connect(self.copyKeyHash) self.hashLabel = QLabel() self.hashLabel.setMinimumSize(225, 20) self.hashLabel.setMaximumSize(16777215, 20) self.hashLabel.setSizePolicy(MinimumExpanding) self.hashLabel.setTextFormat(Qt.PlainText) self.hashLabel.setAlignment(Qt.AlignCenter) self.hashLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) self.hl05.insertWidget(0, self.copyButton) self.hl05.insertWidget(1, self.hashLabel) # finalize right column self.vl02.insertWidget(0, self.messageLabel) self.vl02.insertSpacerItem(1, QSpacerItem(0, 0)) self.vl02.insertLayout(2, self.hl01) self.vl02.insertLayout(3, self.hl02) self.vl02.insertLayout(4, self.hl03) self.vl02.insertSpacerItem(5, QSpacerItem(0, 0)) self.vl02.insertWidget(6, QFrame()) self.vl02.insertLayout(7, self.hl04) self.vl02.insertLayout(8, self.hl05) self.rightColumn.setLayout(self.vl02) # finalize main window self.splitter.insertWidget(0, self.leftColumn) self.splitter.insertWidget(1, self.rightColumn) layout = QHBoxLayout(self) layout.addWidget(self.splitter) self.setLayout(layout) self.setWindowTitle('Simple File Encryptor/Decryptor') self.resize(self.sizeHint())
def createReadOnlyLineEdit(content: str): lineEdit = QLineEdit(content) lineEdit.setAlignment(Qt.AlignCenter) lineEdit.setReadOnly(True) return lineEdit
class PyCalcUi(QMainWindow): """PyCalc's View (GUI).""" def __init__(self, win_title: str = 'PyCalc', win_size: Tuple[int, int] = (235, 235), icon_path: Union[Path, str, None] = Path(R'.\coffeebean.ico') ) -> None: """View initializer.""" super().__init__() # Set some main window's properties self.setWindowTitle(win_title) self.setFixedSize(win_size[0], win_size[1]) if isinstance(icon_path, Path): icon_path = str(icon_path.resolve()) self.setWindowIcon(QIcon(icon_path)) # Set the central widget self._centralWidget = QWidget(self) self.setCentralWidget(self._centralWidget) self.generalLayout = QVBoxLayout() self._centralWidget.setLayout(self.generalLayout) # Create the display and the buttons self._createDisplay() self._createButtons() def _createDisplay(self) -> None: """Create the display.""" # Create the display widget self.display = QLineEdit() # Set some display's properties self.display.setFixedHeight(35) self.display.setAlignment(Qt.AlignRight) self.display.setReadOnly(True) # Add the display to the general layout self.generalLayout.addWidget(self.display) def _createButtons(self) -> None: """Create the buttons.""" self.buttons: Dict[str, QPushButton] = {} buttonsLayout = QGridLayout() # Button text | position on the QGridLayout buttons: Dict[str, Tuple[int, int]] = {'7': (0, 0), '8': (0, 1), '9': (0, 2), '/': (0, 3), 'C': (0, 4), '4': (1, 0), '5': (1, 1), '6': (1, 2), '*': (1, 3), '(': (1, 4), '1': (2, 0), '2': (2, 1), '3': (2, 2), '-': (2, 3), ')': (2, 4), '0': (3, 0), '00': (3, 1), '.': (3, 2), '+': (3, 3), '=': (3, 4), } # Create the buttons and add them to the grid layout for btnText, pos in buttons.items(): self.buttons[btnText] = QPushButton(btnText) self.buttons[btnText].setFixedSize(40, 40) buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1]) # Add buttonsLayout to the general layout self.generalLayout.addLayout(buttonsLayout) def setDisplayText(self, text: str) -> None: """Set display's text.""" self.display.setText(text) self.display.setFocus() def displayText(self) -> str: """Get display's text.""" return cast(str, self.display.text()) def clearDisplay(self) -> None: """Clear the display.""" self.setDisplayText('')
class InstructionRegister(QWidget): def __init__(self, parent, usbif, color): super().__init__(parent) self._status_inds = {} self._setup_ui(color) usbif.poll("monitor", um.MonRegI()) usbif.listen(self) def handle_msg(self, msg): if isinstance(msg, um.MonRegI): self.set_i_values(msg.br, msg.st, msg.sqext, msg.sq) elif isinstance(msg, um.MonRegStatus): self._status_inds['iip'].indicator.set_on(msg.iip) self._status_inds['inhl'].indicator.set_on(msg.inhl) self._status_inds['inkl'].indicator.set_on(msg.inkl) #elif isinstance(msg, um.StatusPeripheral): # self._status_inds['ld'].indicator.set_on(msg.ld) # self._status_inds['chld'].indicator.set_on(msg.chld) # self._status_inds['rd'].indicator.set_on(msg.rd) # self._status_inds['chrd'].indicator.set_on(msg.chrd) def set_i_values(self, br, st, sqext, sq): self.br.set_value(br) self.st.set_value(st) self.sq.set_value(sqext << 6 | sq) self._inst_value.setText(agc.disassemble_subinst(sqext, sq, st)) def _setup_ui(self, color): # Set up our basic layout layout = QHBoxLayout(self) self.setLayout(layout) layout.setSpacing(3) layout.setMargin(1) # Construct register groups for BR, ST, and SQ self.br = SubRegister(self, "BR", 2, color) self.st = SubRegister(self, "ST", 3, color) self.sq = SubRegister(self, "SQ", 7, color) layout.addWidget(self.br) layout.addWidget(self.st) layout.addWidget(self.sq) # Status indicators wl = QHBoxLayout() wl.setSpacing(1) wl.setContentsMargins(2, 3, 3, 2) layout.addLayout(wl) for name, label in STATUS_INDS.items(): w = ApolloLabeledIndicator(self, label, QColor(0, 255, 255), lines=2, labelwidth=35) wl.addWidget(w) self._status_inds[name] = w # Create a grouping widget for the I label and decoded instruction value box label_value_layout = QHBoxLayout() label_value_layout.setSpacing(3) label_value_layout.setContentsMargins(0, 33, 0, 0) layout.addLayout(label_value_layout) # Create a value box for displaying the overall decoded instruction self._inst_value = QLineEdit(self) self._inst_value.setFixedSize(70, 32) self._inst_value.setReadOnly(True) self._inst_value.setAlignment(Qt.AlignCenter) self._inst_value.setText('TC0') self._inst_value.setStyleSheet("QLineEdit { color: #555; }") label_value_layout.addWidget(self._inst_value) # Create a label to show 'I' label = QLabel('I', self) label.setFixedWidth(20) label_value_layout.addWidget(label)