def __init__(self, parent=None): """Class constructor. Args: parent (QWidget): the widget's parent """ super().__init__(parent) self.option = QStyleOptionMenuItem() zoom_action = QAction("Zoom") QMenu(parent).initStyleOption(self.option, zoom_action) layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) tool_bar = QToolBar(self) tool_bar.setFixedHeight(self.option.rect.height()) minus_action = tool_bar.addAction("-") reset_action = tool_bar.addAction("Reset") plus_action = tool_bar.addAction("+") layout.addSpacing(self.option.rect.width()) layout.addWidget(tool_bar) minus_action.setToolTip("Zoom out") reset_action.setToolTip("Reset zoom") plus_action.setToolTip("Zoom in") minus_action.triggered.connect(lambda x: self.minus_pressed.emit()) plus_action.triggered.connect(lambda x: self.plus_pressed.emit()) reset_action.triggered.connect(lambda x: self.reset_pressed.emit())
def __init__(self, text, actions, parent=None): """Class constructor. Args: parent (QWidget): the widget's parent """ super().__init__(parent) self.option = QStyleOptionMenuItem() action = QAction(text) QMenu(parent).initStyleOption(self.option, action) layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) tool_bar = QToolBar(self) tool_bar.setFixedHeight(self.option.rect.height()) layout.addSpacing(self.option.rect.width()) layout.addStretch() layout.addWidget(tool_bar) for name, tool_tip in actions.items(): action = tool_bar.addAction(name) action.setToolTip(tool_tip) action.triggered.connect( lambda x=False, name=name: self.action_triggered.emit(name))
class USBFrame(QWidget): # --- Init methods --- def __init__(self, config): """ USB control frame :param config: application configuration file """ QWidget.__init__(self) self.setFixedSize(QSize(600, 200)) self.config = config self.setWindowTitle("DigiQt - USB Control") self.sig_button_pressed = None # signal configured by serialControler self.sig_firmware_update = None # # Firmware update output self.out = ConsoleOutput() # Buttons self.to_dr_btn = ToDigiruleButton(config) self.to_dr_btn.to_digirule = lambda: self.sig_button_pressed.emit(0) self.from_dr_btn = FromDigiruleButton(config) self.from_dr_btn.from_digirule = lambda: self.sig_button_pressed.emit(1 ) # Firmware self.firmware_btn = FirmwareUpdate(config) self.firmware_btn.firmware_update = self.firmware_update # Port selection self.lab_port = QLabel("Port:") self.usb_combo = UsbPortCombo() self.refresh_btn = RefreshPortButton(config) self.refresh_btn.on_refresh = lambda: self.sig_button_pressed.emit(2) self._init_tool_bar() self._set_layout() self._set_stylesheet() def _init_tool_bar(self): """ Creates the main toolbar with all its content """ self.toolbar = QToolBar() self.toolbar.setFixedHeight(70) self.toolbar.addWidget(self.to_dr_btn) self.toolbar.addWidget(self.from_dr_btn) self.toolbar.addSeparator() self.toolbar.addWidget(self.firmware_btn) self.toolbar.addSeparator() self.toolbar.addWidget(self.lab_port) self.toolbar.addWidget(self.usb_combo) self.toolbar.addWidget(self.refresh_btn) def _set_layout(self): """ Creates this Widget's Layout """ box = QGridLayout() box.setContentsMargins(0, 0, 0, 0) box.addWidget(self.toolbar, 0, 0) box.addWidget(self.out, 1, 0) self.setLayout(box) def _set_stylesheet(self): self.toolbar.setStyleSheet(style.get_stylesheet("qtoolbar")) self.setStyleSheet(style.get_stylesheet("common")) self.lab_port.setStyleSheet( "background-color: transparent; color: #75BA6D; font-weight: bold;" ) self.out.setStyleSheet("background-color: #505050; color: white;") def firmware_update(self): dlg = QFileDialog() dlg.setWindowTitle("Choose a digirule Firmware") dlg.setFileMode(QFileDialog.AnyFile) dlg.setNameFilter("HEX files (*.hex)") if dlg.exec_(): file_path = dlg.selectedFiles()[0] # Call the controller method for update self.sig_firmware_update.emit(file_path) # --- Close handler --- def closeEvent(self, event): """ Event called upon a red-cross click. """ self.on_close() def on_close(self): """ Reroute this method in the Main Frame in order to Updates the execution frame's open editor icon and tooltip """ pass
class ExecutionFrame(QWidget): # --- Init methods --- def __init__(self, config, sig_update_config): """ Main application frame. Contains the MenuBar, main toolbar, DR canvas and status bar. :param config: application configuration file """ QWidget.__init__(self) self.config = config self.is_quitting = False app_version = self.config.get('main', 'APP_VERSION') self.setWindowTitle("DigiQt - Emulator for Digirule - " + str(app_version)) window_width = int(self.config.get('main', 'WINDOW_WIDTH')) self.setFixedSize(window_width, 320) self.current_digirule_model = self.config.get('digirule', 'DR_MODEL') sliderbar_width = 200 bottom_widget_height = 26 self.statusbar = StatusBar(window_width - sliderbar_width, bottom_widget_height, config) self.dr_canvas = DRCanvas(self.statusbar.sig_temp_message, window_width, self.current_digirule_model, config) self.slider = SpeedSlider(sliderbar_width, bottom_widget_height, config) # Buttons open/hide frames self.editor_frame = EditorFrame(config, self.statusbar.sig_temp_message) self.open_editor_btn = OpenEditorButton(self.editor_frame, config) self.editor_frame.on_close = lambda: self.open_editor_btn.show_editor_frame( False) self.ram_frame = RAMFrame(config) self.open_ram_btn = OpenRamButton(self.ram_frame, config) self.ram_frame.on_close = lambda: self.open_ram_btn.show_ram_frame( False) self.monitor_frame = TerminalFrame(config) self.open_monitor_btn = OpenTerminalButton(self.monitor_frame, config) self.monitor_frame.on_close = lambda: self.open_monitor_btn.show_terminal_frame( False) self.usb_frame = USBFrame(config) self.open_usb_btn = OpenUSBButton(self.usb_frame, config) self.usb_frame.on_close = lambda: self.open_usb_btn.show_usb_frame( False) self.open_monitor_btn.is_opened = lambda b: self.open_usb_btn.setEnabled( not b) self.open_usb_btn.is_opened = lambda b: self.open_monitor_btn.setEnabled( not b) self.symbol_frame = SymbolViewFrame(config) self.open_symbol_btn = OpenSymbolButton(self.symbol_frame, config) self.symbol_frame.on_close = lambda: self.open_symbol_btn.show_symbol_frame( False) self.symbol_frame.place_search_text = self.editor_frame.do_search self.about_frame = AboutFrame(self.config) self.open_about_btn = AboutButton(self.about_frame, config) self.about_frame.on_close = lambda: self.open_about_btn.show_about_frame( False) self._init_tool_bar() self._set_layout() self._set_stylesheets() self.sig_update_config = sig_update_config def _init_tool_bar(self): """ Creates the main toolbar with all its content """ self.toolbar = QToolBar() self.toolbar.setFixedHeight(70) # Open/hide buttons self.toolbar.addWidget(self.open_editor_btn) self.toolbar.addWidget(self.open_ram_btn) self.toolbar.addWidget(self.open_usb_btn) self.toolbar.addWidget(self.open_monitor_btn) self.toolbar.addWidget(self.open_symbol_btn) # Digirule model selection self.toolbar.addSeparator() self.digimodel_dropdown = DigiruleModelDropdown( self.on_digimodel_dropdown_changed) self.toolbar.addWidget(self.digimodel_dropdown) # Empty space to align the about button to the right spacer = QWidget() spacer.setStyleSheet("background-color: transparent;") spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer) # About button self.toolbar.addWidget(self.open_about_btn) def _set_layout(self): """ Creates this Widget's Layout """ box = QVBoxLayout() box.setContentsMargins(0, 0, 0, 0) box.addWidget(self.toolbar) box.setAlignment(self.toolbar, Qt.AlignTop) box.addWidget(self.dr_canvas) box.setAlignment(self.dr_canvas, Qt.AlignTop) bottom_box = QHBoxLayout() bottom_box.setContentsMargins(0, 0, 0, 0) bottom_box.addWidget(self.statusbar) bottom_box.addWidget(self.slider) box.addLayout(bottom_box) box.setAlignment(bottom_box, Qt.AlignBottom) self.setLayout(box) def _set_stylesheets(self): self.toolbar.setStyleSheet(style.get_stylesheet("qtoolbar")) # Execution Frame self.setStyleSheet(style.get_stylesheet("common")) # --- Callbacks methods --- def on_digimodel_dropdown_changed(self): """ Handles the Digirule's model-combo-box-selection-changed process. Calls the canvas redraw. """ self.sig_update_config.emit( self.digimodel_dropdown.get_digirule_model()) self.dr_canvas.digirule_changed(self.config.get( 'digirule', 'DR_MODEL')) # --- Close handler --- def do_quit(self): pass def closeEvent(self, event): """ Event called upon a red-cross click """ if self.ask_quit_confirmation(): self.is_quitting = True self.do_quit() # Reset status bar self.statusbar.sig_persistent_message.emit("") # Call the secondary frames close methods as well self.editor_frame.on_close() self.ram_frame.on_close() self.monitor_frame.on_close() self.usb_frame.on_close() self.symbol_frame.on_close() self.about_frame.on_close() event.accept() else: event.ignore() def ask_quit_confirmation(self): """ Asks a quit confirmation message :return: True if the user wants to quit the app """ return DialogQuitConfirmation().exec_()
class EditorFrame(QWidget): # --- Init methods --- def __init__(self, config, sig_message): """ Editor frame. Contains a toolbar and an editor widget :param config: application configuration file :param sig_message: signal to emit to display a message in the main frame's status bar """ QWidget.__init__(self) self.setMinimumSize(QSize(630, 500)) self.config = config # Widgets self.editor = CodeEditor(config) self.editor.setMinimumSize(QSize(600, 430)) self.open_file_btn = OpenFileButton(config) self.open_file_btn.set_content = self.editor.setPlainText # Reroute text set method directly to the text editor widget self.open_file_btn.set_new_file_name = self.__init_title self.save_as_btn = SaveAsFileButton(config, sig_message) self.save_as_btn.get_content_to_save = self.retrieve_text # Bind the text retrieve method in order to get the text to save self.save_as_btn.set_new_file_name = self.__init_title self.save_btn = SaveFileButton(config, sig_message) self.save_btn.get_content_to_save = self.retrieve_text self.assemble_btn = AssembleButton(config) self.search_field = QLineEdit() self.search_field.setPlaceholderText("Search (press return)") self.search_field.returnPressed.connect(self.do_search) # Editor's shortcuts binding self.editor.on_ctrl_o_activated = self.open_file_btn.on_open # Open action self.editor.on_ctrl_s_activated = self.do_save self.editor.on_ctrl_f_activated = self.do_search # Final initialization self.__init_title() self._init_tool_bar() self._set_layout() self._connect_all() self._set_stylesheet() self.editor.setFocus() # Set the default focus on the Editor def __init_title(self, file_name=""): """ Sets the currently edited file in this frame's title :param file_name: full file path """ if file_name: self.setWindowTitle("DigiQt - Editing '" + file_name.split("/")[-1] + "'") else: # In the case no file name is specified, we have an empty editor, we display default text self.setWindowTitle("DigiQt - Assemble Editor") self.save_btn.setEnabled(file_name != "") self.save_btn.set_file_path(file_name) def _init_tool_bar(self): """ Creates the main toolbar with all its content """ self.toolbar = QToolBar() self.toolbar.setFixedHeight(70) self.toolbar.addWidget(self.open_file_btn) self.toolbar.addWidget(self.save_btn) self.toolbar.addWidget(self.save_as_btn) self.toolbar.addSeparator() self.toolbar.addWidget(self.search_field) # Empty space to align the assemble button to the right spacer = QWidget() spacer.setStyleSheet("background-color: transparent;") spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer) self.toolbar.addWidget(self.assemble_btn) def _set_layout(self): """ Creates this Widget's Layout """ box = QGridLayout() box.setContentsMargins(0, 0, 0, 0) box.addWidget(self.toolbar, 0, 0) box.addWidget(self.editor, 1, 0) self.setLayout(box) def _connect_all(self): """ Connects all the buttons to methods """ def _set_stylesheet(self): self.toolbar.setStyleSheet(style.get_stylesheet("qtoolbar")) self.editor.setStyleSheet( "background-color: " + self.config.get('colors', 'editor_bg') + "; color: " + self.config.get('colors', 'asm_text_default') + ";") self.search_field.setStyleSheet( "border: 2px solid gray; border-radius: 10px; padding: 0 8px; background: #585858; color: white" ) # Execution Frame self.setStyleSheet(style.get_stylesheet("common")) # --- Buttons callbacks methods --- def retrieve_text(self): """ Gets the content of the code editor widget :return: the code """ return self.editor.toPlainText() def do_save(self): """ Delegating method triggered upon Ctrl+S action. Performs a Save if a file is opened, or a SaveAs if not. """ if self.save_btn.file_path: self.save_btn.on_save() else: self.save_as_btn.on_save_as() def do_search(self, text=None): """ Searches the value present in the search field, or the places the specified text if there is one as search value, but does not triggers the search. :param text: Text to search (None will trigger a search on the field content) """ if text: self.search_field.setText(text) else: self.editor.selectNext(self.search_field.text()) # --- Close handler --- def closeEvent(self, event): """ Event called upon a red-cross click. """ self.on_close() def on_close(self): """ Reroot this method in the Main Frame in order to Updates the execution frame's open editor icon and tooltip :return: """ pass
class TerminalFrame(QWidget): # --- Init methods --- def __init__(self, config): """ Serial Console frame :param config: application configuration file """ QWidget.__init__(self) self.config = config self.setWindowTitle("DigiQt - Terminal") self.sig_keyseq_pressed = None # signal configured by serialControler self.sig_button_pressed = None # signal configured by serialControler # Virtual Serial out self.serial_out = SerialOut(self.config) # Serial terminal (real) self.terminal = SerialTerminalFrame(self.config) # Tab self.tab_widget = QTabWidget() self.tab_widget.addTab(self.serial_out, "Virtual terminal") self.tab_widget.addTab(self.terminal, "Serial terminal") # Serial in self.serial_in = QLabel() self.serial_in.setAlignment(Qt.AlignCenter) self.serial_in.setFixedSize(QSize(44, 36)) font = QFont() font.setPointSize(30) font.setBold(True) self.serial_in.setFont(font) # Buttons self.clear_btn = ClearButton(config) self.clear_btn.on_clear = lambda: self.sig_button_pressed.emit(3) shortcut_space = QShortcut(QKeySequence(Qt.Key_Space), self) shortcut_space.activated.connect(lambda: self.__send_key(" ")) self.tab_widget.currentChanged.connect(self.__on_tab_changed) self._init_tool_bar() # Compute max size max_w = self.terminal.maximumWidth() max_h = self.terminal.maximumHeight() + self.tab_widget.tabBar( ).minimumHeight() + self.toolbar.maximumHeight() self.setMaximumSize(QSize(max_w, max_h)) self._set_layout() self._set_stylesheet() def _init_tool_bar(self): """ Creates the main toolbar with all its content """ self.toolbar = QToolBar() self.toolbar.setFixedHeight(70) self.toolbar.addWidget(self.clear_btn) self.toolbar.addWidget(self.serial_in) def __on_tab_changed(self): """ Hides the serial in display when current widget is the real terminal """ if self.tab_widget.currentWidget() == self.serial_out: self.serial_in.setStyleSheet(style.get_stylesheet("serial_in")) else: self.serial_in.setStyleSheet( "background: #333333; color: #333333;") self.terminal.textbox.setFocus() def _set_layout(self): """ Creates this Widget's Layout """ box = QGridLayout() box.setContentsMargins(0, 0, 0, 0) box.addWidget(self.toolbar, 0, 0) box.addWidget(self.tab_widget, 1, 0) self.setLayout(box) def clear(self): """ Clears the content of the current tab """ if self.tab_widget.currentWidget() == self.serial_out: self.serial_out.console.setPlainText("") self.set_serial_in(" ") else: self.terminal.textbox.setPlainText("") self.terminal.textbox.setFocus() def _set_stylesheet(self): self.toolbar.setStyleSheet(style.get_stylesheet("qtoolbar")) self.setStyleSheet(style.get_stylesheet("common")) self.serial_in.setStyleSheet(style.get_stylesheet("serial_in")) self.serial_out.console.setStyleSheet( "background-color: #000000; color: white; padding-left: 10px;") self.terminal.textbox.setStyleSheet( "background-color: #000000; color: #44DD44;") self.tab_widget.setStyleSheet(style.get_stylesheet("tab")) def keyPressEvent(self, event): """ Intercepts key press events """ self.__send_key(event.text()) def __send_key(self, key_typed): """ Sends signal to serialControler :param key_type: key typed """ if self.tab_widget.currentWidget() == self.serial_out: self.sig_keyseq_pressed.emit(key_typed) def set_serial_in(self, val): """ Sets the serial in value """ self.serial_in.setText(val) def append_serial_out(self, byte): """ Appends the given text inside the serial out area """ # First, we place the cursor at the end (this will also clear the selection before inserting new text) if byte != 10: cursor = self.serial_out.console.textCursor() cursor.movePosition(QTextCursor.End) self.serial_out.console.setTextCursor(cursor) try: self.serial_out.console.insertPlainText(chr(byte)) except ValueError: self.serial_out.console.insertPlainText(" ") def closeEvent(self, event): """ Event called upon a red-cross click. """ self.terminal.sig_terminal_open.emit( False) # ask Serial Controller to terminate the thread self.on_close() def on_close(self): """ Reroute this method in the Main Frame in order to Updates the execution frame's open editor icon and tooltip """ pass