class FontColorButton(QPushButton):
    """ Displays the currently selected color and provides an icon for indicating what is being colored """
    def __init__(self, icon: QIcon, s: str):
        super().__init__()
        self._lo = QHBoxLayout()
        self._filler = QToolBar()
        self._filler.addAction(icon, s)
        self._filler.setMaximumHeight(25)
        self._filler.setMaximumWidth(25)
        self._filler.setStyleSheet("""
            QWidget {
                border: 0px
            }
        """)
        self._filler.actionTriggered.connect(lambda _: self.pressed.emit())
        self._lo.addWidget(self._filler)
        self._lo.setSpacing(0)
        self.setMaximumWidth(40)
        self._color_display = QLabel("")
        self._color_display.setMinimumWidth(8)
        self._lo.addWidget(self._color_display)
        self.setLayout(self._lo)
        self._lo.setMargin(3)

    def set_color_display(self, color: QColor):
        self._color_display.setStyleSheet(
            "border-radius: 2px; background-color: {};".format(color.name()))
Example #2
0
    def setupTopBar(self):
        tb = QWidget()
        tbl = QHBoxLayout()
        tb.setLayout(tbl)
        tb.setStyleSheet("border: 3px solid black; background-color: #7289da;")

        tbl.setAlignment(Qt.AlignLeft)

        homeBtn = QPushButton()
        homeBtn.setIcon(self.client.ico)

        saTB = QToolBar()
        self.ServerActions = QMenu()
        self.ServerActions.setTitle("⠀           Home             ⠀")

        saTB.addAction(self.ServerActions.menuAction())

        tbl.addWidget(homeBtn)
        tbl.addWidget(saTB)
        saTB.setStyleSheet("border: 1px solid black;")
        homeBtn.setStyleSheet("border: none;")
        self.ServerActions.setStyleSheet("border: none;")

        return tb
Example #3
0
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
Example #4
0
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
Example #5
0
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_()
Example #6
0
class PlotBarWindow(QMainWindow):
    def __init__(self, title, labels=[], data=[], table_name="", field_name="", x_label=None, y_label=None, parent=None, already_sorted=False):
        super().__init__(parent)
        self._title = title 
        self._labels = labels 
        self._data = data
        self._last_column = excelColumnFromNumber(len(self._data))
        self._plot_location = excelColumnFromNumber(len(self._data)+2)
        self._x_label = x_label
        self._y_label = y_label
        self._already_sorted = already_sorted

        self.setWindowTitle(self._title)
        self._figure = plt.figure(figsize=(5, 4), dpi=100, facecolor=(1,1,1), edgecolor=(0,0,0))
        self.ax = self._figure.add_subplot()
        self.ax.set_title(self._title)
        self._canvas = FigureCanvas(self._figure)
        self._navigation_toolbar = NavigationToolbar(self._canvas, None)
        self.addToolBar(self._navigation_toolbar)
        self._bottom_toolbar = QToolBar(self)
        self._bottom_toolbar.setMovable(False)
        self._bottom_toolbar.setFloatable(False)
        self._bottom_toolbar.setStyleSheet("QToolBar {border-bottom: None; border-top: 1px solid #BBBBBB;}")
        self._table_name_label = QLabel(" Table:")
        self._field_name_label = QLabel(" Field:")
        self._table_name = FormEntry(self)
        self._table_name.setMaximumHeight(20)
        self._field_name = FormEntry(self)
        self._field_name.setMaximumHeight(20)
        self._table_name.setReadOnly(True)
        self._field_name.setReadOnly(True)
        self._table_name.setText(table_name)
        self._field_name.setText(field_name)
        self._bottom_toolbar.addWidget(self._table_name_label)
        self._bottom_toolbar.addWidget(self._table_name)
        self._bottom_toolbar.addWidget(self._field_name_label)
        self._bottom_toolbar.addWidget(self._field_name)
        self._export_chart_button = QPushButton("Export")
        self._export_chart_button.setIcon(QIcon(QPixmap("export.png")))
        self._export_chart_button.clicked.connect(self.exportChart)
        self._bottom_toolbar.addWidget(HorizontalFiller(self))

        self._bottom_toolbar.addWidget(self._export_chart_button)
        self.addToolBar(Qt.BottomToolBarArea, self._bottom_toolbar)

        self.ax.bar(self._labels, self._data)
        if self._x_label != None:
            self.ax.set_xlabel(self._x_label)
        if self._y_label != None:
            self.ax.set_ylabel(self._y_label)
        self.setCentralWidget(self._canvas)

    def exportChartFileDialog(self):
        file_dialog = QFileDialog()
        file_dialog.setNameFilters(["*. xlsx"])
        file_name, ext = file_dialog.getSaveFileName(self, 'Export File', "", "Excel (*.xlsx)")
            
        if file_name and ext == "Excel (*.xlsx)":
            return file_name 
        return ""

    def exportChart(self):
        file_name = self.exportChartFileDialog()
        field_name = self._field_name.text()
        if file_name != "":
            title = self.ax.title.get_text()
            last_row = len(self._data)+2
            labels = sorted(self._labels)
            if self._already_sorted:
                sorted_data = {label:[self._data[i]] for i, label in enumerate(self._labels)}
            else:
                data = {label:[self._data[i]] for i, label in enumerate(self._labels)}
                sorted_data = {label:data[label] for label in labels}
            df = pd.DataFrame(data=sorted_data)
            
            writer = pd.ExcelWriter(file_name, engine='xlsxwriter')
            df.to_excel(writer, sheet_name=field_name, index=False)
            workbook = writer.book
            worksheet = writer.sheets[field_name]
            chart = workbook.add_chart({"type": 'column'})
            chart.set_title({"name": title})
            if self._x_label != None:
                chart.set_x_axis({'name': self._x_label})
            if self._y_label != None:
                chart.set_y_axis({'name': self._y_label})
            chart.add_series({"categories": "={fn}!$A$1:${lc}$1".format(lc=self._last_column, fn=field_name), 
                              "values": "={fn}!$A$2:${lc}$2".format(lc=self._last_column, fn=field_name), 
                              "fill": {'color': '#0000CC'}})
            worksheet.insert_chart(self._plot_location+"2", chart)
            writer.save()
            writer.close()
Example #7
0
class PlotPieWindow(QMainWindow):
    def __init__(self, title, labels=[], data=[], table_name="", field_name="", random_colors=False, has_explode=True, parent=None):
        super().__init__(parent)
        self._main_widget = QWidget(self)
        self._layout = QVBoxLayout(self._main_widget)
        self._title = title 
        self._data = data
        self._df_data = {k:v for (k,v) in zip(labels, data)} 
        
        self.setWindowTitle(title)
        # Pie chart, where the slices will be ordered and plotted counter-clockwise:
        self._labels = labels 
        self._sizes = data 
        self._legend_labels = [label+" - "+"{:.1f}".format(value)+"%" for label, value in zip(self._labels, self._sizes)]
        self._last_column = excelColumnFromNumber(len(self._labels))
        self._plot_location = excelColumnFromNumber(len(self._data)+2)
        explode = [0 for value in data]
        explode[1] = 0.1
        explode = tuple(explode)

        self._figure = plt.figure(figsize=(5, 4), dpi=100, facecolor=(1,1,1), edgecolor=(0,0,0))
        self.ax = self._figure.add_subplot()
        self._canvas = FigureCanvas(self._figure)
        colors = ['yellowgreen', 'lightskyblue']
        self._navigation_toolbar = NavigationToolbar(self._canvas, None)
        self.addToolBar(self._navigation_toolbar)
        if random_colors:
            if has_explode:
                self.ax.pie(self._sizes, explode=explode, autopct = "%1.1f%%", shadow=True, startangle=int(90))
            else:
                self.ax.pie(self._sizes, autopct = "%1.1f%%", shadow=True, startangle=int(90))
        else:
            if has_explode:
                self.ax.pie(self._sizes, explode=explode, colors=colors, autopct = "%1.1f%%", shadow=True, startangle=int(90))
            else:
                self.ax.pie(self._sizes, colors=colors, autopct = "%1.1f%%", shadow=True, startangle=int(90))
        self.ax.legend(labels=self._legend_labels, loc="best")
        # Equal aspect ratio ensures that pie is drawn as a circle.
        self.ax.axis("equal")
        self.ax.set_title(title)

        self._bottom_toolbar = QToolBar(self)
        self._bottom_toolbar.setMovable(False)
        self._bottom_toolbar.setFloatable(False)
        self._bottom_toolbar.setStyleSheet("QToolBar {border-bottom: None; border-top: 1px solid #BBBBBB;}")
        self._table_name_label = QLabel(" Table:")
        self._field_name_label = QLabel(" Field:")
        self._table_name = FormEntry(self)
        self._table_name.setMaximumHeight(20)
        self._field_name = FormEntry(self)
        self._field_name.setMaximumHeight(20)
        self._table_name.setReadOnly(True)
        self._field_name.setReadOnly(True)
        self._table_name.setText(table_name)
        self._field_name.setText(field_name)
        self._bottom_toolbar.addWidget(self._table_name_label)
        self._bottom_toolbar.addWidget(self._table_name)
        self._bottom_toolbar.addWidget(self._field_name_label)
        self._bottom_toolbar.addWidget(self._field_name)
        
        self._export_chart_button = QPushButton("Export")
        self._export_chart_button.setIcon(QIcon(QPixmap("export.png")))
        self._export_chart_button.clicked.connect(self.exportChart)
        
        self._bottom_toolbar.addWidget(HorizontalFiller(self))
        self._bottom_toolbar.addWidget(self._export_chart_button)
        self.addToolBar(Qt.BottomToolBarArea, self._bottom_toolbar)
        self.setCentralWidget(self._canvas)
 
    def exportChartFileDialog(self):
        file_dialog = QFileDialog()
        file_dialog.setNameFilters(["*. xlsx"])
        file_name, ext = file_dialog.getSaveFileName(self, 'Export File', "", "Excel (*.xlsx)")
            
        if file_name and ext == "Excel (*.xlsx)":
            return file_name 
        return ""

    def exportChart(self):
        file_name = self.exportChartFileDialog()
        if file_name != "":
            title = self.ax.title.get_text()
            df = pd.DataFrame(data=[self._df_data])
            writer = pd.ExcelWriter(file_name, engine='xlsxwriter')
            df.to_excel(writer, sheet_name='Pie_Chart', index=False)
            workbook = writer.book
            worksheet = writer.sheets["Pie_Chart"]
            chart = workbook.add_chart({"type": 'pie'})
            chart.set_title({"name": title})
            chart.add_series({"categories": "=Pie_Chart!$A$1:${lc}$1".format(lc = self._last_column), "values": "=Pie_Chart!$A$2:${lc}$2".format(lc = self._last_column)})
            worksheet.insert_chart(self._plot_location+"2", chart)
            writer.save()
            writer.close()
Example #8
0
class PlotHBarWindow(QMainWindow):
    def __init__(self, title, labels=[], data=[], table_name="", field_name="", parent=None):
        super().__init__(parent)
        self._title = title 
        self._labels = labels 
        self._data = data
        self._last_column = excelColumnFromNumber(len(self._data))
        self._plot_location = excelColumnFromNumber(len(self._data)+2)

        self.setWindowTitle(self._title)
        self._figure = plt.figure(figsize=(5, 4), dpi=100, facecolor=(1,1,1), edgecolor=(0,0,0))
        self.ax = self._figure.add_subplot()
        self.ax.set_title(self._title)
        self._canvas = FigureCanvas(self._figure)
        self._navigation_toolbar = NavigationToolbar(self._canvas, None)
        self.addToolBar(self._navigation_toolbar)
        self._bottom_toolbar = QToolBar(self)
        self._bottom_toolbar.setMovable(False)
        self._bottom_toolbar.setFloatable(False)
        self._bottom_toolbar.setStyleSheet("QToolBar {border-bottom: None; border-top: 1px solid #BBBBBB;}")
        self._table_name_label = QLabel(" Table:")
        self._field_name_label = QLabel(" Field:")
        self._table_name = FormEntry(self)
        self._table_name.setMaximumHeight(20)
        self._field_name = FormEntry(self)
        self._field_name.setMaximumHeight(20)
        self._table_name.setReadOnly(True)
        self._field_name.setReadOnly(True)
        self._table_name.setText(table_name)
        self._field_name.setText(field_name)
        self._bottom_toolbar.addWidget(self._table_name_label)
        self._bottom_toolbar.addWidget(self._table_name)
        self._bottom_toolbar.addWidget(self._field_name_label)
        self._bottom_toolbar.addWidget(self._field_name)
        self._export_chart_button = QPushButton("Export")
        self._export_chart_button.setIcon(QIcon(QPixmap("export.png")))
        self._export_chart_button.clicked.connect(self.exportChart)
        self._bottom_toolbar.addWidget(HorizontalFiller(self))

        self._bottom_toolbar.addWidget(self._export_chart_button)
        self.addToolBar(Qt.BottomToolBarArea, self._bottom_toolbar)

        y_pos = np.arange(len(data))
        self.ax.barh(y_pos, data, align="center", color='lightskyblue', alpha=0.5)
        self.ax.set_xlabel(labels[len(labels)-1])
        #self.ax.set_ylabel(labels[1])
        
        rects = self.ax.patches
        low_rect = rects[0]
        high_rect = rects[len(rects)-1]
        width = low_rect.get_width()
        self.ax.text(low_rect.get_x()+width, low_rect.get_y()+1, "Lowest: $"+str(data[0]))
        width = high_rect.get_width()
        self.ax.text(high_rect.get_x(), high_rect.get_y()+1, "Highest: $"+str(data[len(data)-1]))
        self.setCentralWidget(self._canvas)
    
    def exportChartFileDialog(self):
        file_dialog = QFileDialog()
        file_dialog.setNameFilters(["*. xlsx"])
        file_name, ext = file_dialog.getSaveFileName(self, 'Export File', "", "Excel (*.xlsx)")
            
        if file_name and ext == "Excel (*.xlsx)":
            return file_name 
        return ""

    def exportChart(self):
        file_name = self.exportChartFileDialog()
        field_name = self._field_name.text()
        if file_name != "":
            title = self.ax.title.get_text()
            last_row = len(self._data)+2
            col_1 = ["" for data in self._data]
            col_2 = col_1.copy()
            col_1[0] = self._data[0]
            col_2[0] = self._data[len(self._data)-1]
            data = {
                self._labels[0]: self._data,
                self._labels[1]: col_1,
                self._labels[2]: col_2
            }
            df = pd.DataFrame(data=data)
            
            writer = pd.ExcelWriter(file_name, engine='xlsxwriter')
            df.to_excel(writer, sheet_name=field_name, index=False)
            workbook = writer.book
            worksheet = writer.sheets[field_name]
            chart = workbook.add_chart({"type": 'bar'})
            chart.set_title({"name": title})
            chart.set_x_axis({'name': self._labels[0]})
            chart.add_series({"values": "={fn}!$A$2:$A${lr}".format(lr=last_row, fn=field_name), 'fill': {'color': '#0000CC'}, 'border': {'color': '#0000CC'}})
            worksheet.insert_chart(self._plot_location+"2", chart)
            writer.save()
            writer.close()
Example #9
0
class Window(QMainWindow):
    """Class to create main widnow

    Creates main window for displaying frame read from a connected camera.
    The main window contains memu bar, tool bar, status bar, sliders and the
    boxes showing the camera's information. These widget are created and added to
    main window in the instance method of this class.

    """
    def __init__(
            self, device: int = 0, suffix: str = "png", camtype: str = "usb_cam",
            color: str = "RGB", dst: str = ".", param: str = "full",
            rule: str = "Sequential", parent=None):
        super(Window, self).__init__(parent)
        self.device = device
        self.camtype = camtype
        self.colorspace = color
        self.image_suffix = suffix
        self.video_codec = "AVC1"
        self.video_suffix = "avi"
        self.dst = Path(dst)
        self.parent_dir = Path(__file__).parent.resolve()

        self.filename_rule_lst = FileIO.file_save
        self.filename_rule = FileIO.file_save_lst[-1]

        self.is_display = True
        self.param_separate = False

        self.slot = Slot(self)

        cam = self.get_cam()
        self.camera = cam(self.device, self.colorspace, parent=self)
        self.support_params = self.camera.get_supported_params()
        self.current_params = self.camera.get_current_params(param)

        # List of camera properties with temporal initial values
        self.prop_table = [
            ["Fourcc", "aa"],
            ["Width", 640],
            ["Height", 480],
            ["FPS", 30.0],
            ["Bit depth", 8],
            ["File naming style", self.filename_rule]
        ]
        self.setup()
        self.set_timer()

    def get_cam(self) -> str:
        """Return camera object according to current OS.

        Detects what OS you are using, return camera objects  in order to function properly.

            - Linux: LinuxCamera
            - RaspberryPi OS: RaspiCamera
            - Windows: WindowsCamera

        Returns:
            Camera class
        """
        if self.camtype == "raspi":
            return RaspiCamera

        self.system = platform.system()
        if re.search("linux", self.system, re.IGNORECASE):
            return LinuxCamera
        elif re.search("windows", self.system, re.IGNORECASE):
            return WindowsCamera
        else:
            return "Unknown type"

    def setup(self):
        """Setup the main window for displaying frame and widget.

        Creates a QMainWindow object, then add menubar, toolbar, statusbar, widgets and layout
        into the window.
        """
        self.setFocusPolicy(Qt.ClickFocus)
        self.setContentsMargins(20, 0, 20, 0)
        self.information_window_setup()
        self.view_setup()
        self.layout_setup()
        self.image_setup()
        self.toolbar_setup()
        self.setWindowTitle("usbcamGUI")
        self.update_prop_table()
        self.adjust_windowsize()
        self.set_theme()

    def adjust_windowsize(self):
        """Adjusts the main window size
        """
        system = Utility.get_os()
        if system == "linux":
            w, h, _ = self.get_screensize()
            wscale = 0.5
            hscale = 0.7
            self.resize(wscale * w, hscale * h)
        else:
            self.resize(800, 600)

    def set_theme(self):
        """Set color theme of the main window.
        """
        self.style_theme = "light"
        self.style_theme_sheet = ":/{}.qss".format(self.style_theme)
        self.slot.switch_theme()
        self.set_font(self.camera.font_family, self.camera.font_size)

    def set_font(self, family: str = "Yu Gothic", size: int = 14):
        """Sets font-family and size of UI.

        Args:
            family (str, optional): Font-family. Defaults to "Yu Gothic".
            size (int, optional): Font-size. Defaults to 20.
        """
        self.setStyleSheet('font-family: "{}"; font-size: {}px;'.format(family, size))

    def set_timer(self):
        """Set QTimer

        Creates a QTimer object to update frame on view area. The interval is set to the inverse
        of camera FPS.
        """
        self.qtime_factor = 0.8
        self.fps = 30.0
        if self.fps:
            self.msec = 1 / self.fps * 1000 * self.qtime_factor
        else:
            self.msec = 1 / 30.0 * 1000 * self.qtime_factor
        self.timer = QTimer()
        self.timer.setInterval(self.msec)
        self.timer.timeout.connect(self.next_frame)
        self.timer.start()

    def stop_timer(self):
        """Deactivate the Qtimer object.
        """
        self.timer.stop()

    def start_timer(self):
        """Activate the Qtimer object.
        """
        self.fps = 30.0
        if self.fps:
            self.msec = 1 / self.fps * 1000 * self.qtime_factor
        else:
            self.msec = 1 / 30.0 * 1000 * self.qtime_factor
        self.timer.setInterval(self.msec)
        self.timer.start()

    def toolbar_setup(self):
        """Create toolbar
        """
        self.toolbar = QToolBar("test", self)
        self.addToolBar(self.toolbar)

        current_size = str(self.font().pointSize())
        lst = [str(i) for i in range(6, 14)]
        lst.extend([str(i) for i in range(14, 40, 2)])
        index = lst.index(current_size)

        self.fontsize_combo = QComboBox()
        self.fontsize_combo.addItems(lst)
        self.fontsize_combo.setCurrentIndex(index)
        self.fontsize_combo.currentTextChanged.connect(self.slot.set_fontsize)
        self.fontsize_label = QLabel("Font size")
        self.fontsize_label.setFrameShape(QFrame.Box)

        self.comb = QFontComboBox()

        self.toolbar.addWidget(self.save_button)
        self.toolbar.addWidget(self.stop_button)
        self.toolbar.addWidget(self.rec_button)
        self.toolbar.addWidget(self.close_button)
        self.toolbar.addWidget(self.theme_button)
        self.toolbar.addWidget(self.help_button)
        self.toolbar.addWidget(self.fontsize_label)
        self.toolbar.addWidget(self.fontsize_combo)
        self.toolbar.setStyleSheet(
            """
            QToolBar {spacing:5px;}
            """
            )

    def view_setup(self):
        """Set view area to diplay read frame in part of the main window
        """
        self.view = QGraphicsView()
        self.scene = QGraphicsScene()
        self.view.setScene(self.scene)
        self.width = 640
        self.height = 480
        self.scene.setSceneRect(0, 0, self.width, self.height)
        self.view.setMouseTracking(True)
        self.view.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        self.view.setCacheMode(QGraphicsView.CacheBackground)
        self.view.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate)

    def layout_setup(self):
        """Set layout of objects on the window.
        """
        self.window = QWidget()
        self.setCentralWidget(self.window)
        #self.view.mouseMoveEvent = self.get_coordinates

        self.main_layout = QHBoxLayout()
        self.window.setLayout(self.main_layout)

        self.add_actions()
        self.add_menubar()
        self.add_statusbar()
        self.button_block = self.add_buttons()
        self.slider_group = self.add_params()
        self.prop_block = self.add_prop_window()
        self.create_mainlayout()

    def image_setup(self):
        """Creates a Qimage to assign frame, then initialize with an image which has zero in all pixels.
        """
        self.frame = np.zeros((640, 480, 3), dtype=np.uint8)
        #cinit = np.ctypeslib.as_ctypes(self.frame)
        #self.frame.buffer = sharedctypes.RawArray(cinit._type_, cinit)
        self.qimage = QImage(
            self.frame.data,
            640,
            480,
            640 * 3,
            QImage.Format_RGB888
            )
        self.pixmap = QPixmap.fromImage(self.qimage)

    def add_actions(self):
        """Add actions executed when press each item in the memu window.
        """
        self.save_act = self.create_action("&Save", self.save_frame, "Ctrl+s")
        self.stop_act = self.create_action("&Pause", self.stop_frame, "Ctrl+p", checkable=True)
        self.rec_act = self.create_action("&Record", self.record, "Ctrl+r", True)
        self.quit_act = self.create_action("&Quit", self.slot.quit, "Ctrl+q")

        self.theme_act = self.create_action("Switch &Theme", self.slot.switch_theme, "Ctrl+t")
        self.param_act = self.create_action("Choose parameter slider", self.slot.switch_paramlist, "Ctrl+g")
        self.show_paramlist_act = self.create_action("Parameters &List", self.slot.show_paramlist, "Ctrl+l")
        self.show_shortcut_act = self.create_action("&Keybord shortcut", self.slot.show_shortcut, "Ctrl+k")
        self.font_act = self.create_action("&Font", self.slot.set_font, "Ctrl+f")

        self.usage_act = self.create_action("&Usage", self.slot.usage, "Ctrl+h")
        self.about_act = self.create_action("&About", self.slot.about, "Ctrl+a")

    def create_action(self, text: str, slot: Callable, key: str = None, checkable: bool = False,
        check_defalut: bool = False) -> QAction:
        """Create a QAction object.

        Args:
            text (str): Text shown on menu.
            slot (Callable): A method called when click the menu.
            key (str, optional): Shortcut key. Defaults to None.
            checkable (bool, optional): Add a checkbox into the menu. Defaults to False.
            check_defalut (bool, optional): Check default status. Defaults to False.

        Returns:
            QAction: PySide2 QAction
        """
        act = QAction(text)
        act.setShortcut(key)
        if checkable:
            act.setCheckable(True)
            act.setChecked(check_defalut)
            act.toggled.connect(slot)
        else:
            act.triggered.connect(slot)

        return act

    def add_menubar(self):
        """Create menu bar, then add to the main window.
        """
        self.menubar = QMenuBar()
        self.setMenuBar(self.menubar)

        self.file_tab = QMenu("&File")
        self.file_tab.addAction(self.save_act)
        self.file_tab.addAction(self.stop_act)
        self.file_tab.addAction(self.rec_act)
        self.file_tab.addSeparator()
        self.file_tab.addAction(self.quit_act)
        #self.file_tab.setSizePolicy(policy)

        self.view_tab = QMenu("&View")
        self.view_tab.addAction(self.theme_act)
        self.view_tab.addAction(self.font_act)
        self.view_tab.addAction(self.param_act)
        self.view_tab.addAction(self.show_shortcut_act)
        self.view_tab.addAction(self.show_paramlist_act)

        self.help_tab = QMenu("&Help")
        self.help_tab.addAction(self.usage_act)
        self.help_tab.addAction(self.about_act)

        self.menubar.addMenu(self.file_tab)
        self.menubar.addMenu(self.view_tab)
        self.menubar.addMenu(self.help_tab)
        self.menubar.setStyleSheet(
            """
            QMenuBar {
                    font-size: 16px;
                    spacing:10px;
                    padding-top: 5px;
                    padding-bottom: 10px;
                }
            """
            )

    def add_statusbar(self):
        """Create status bar, then add to the main window.

        The status bar shows the coordinates on the frame where the cursor is located and
        its pixel value. The pixel value has RGB if the format of is color (RGB), does grayscale
        value if grayscale.
        """
        self.statbar_list = []
        if self.colorspace == "rgb":
            self.stat_css = {
                "postion": "color: white",
                "R": "color: white;",
                "G": "color: white;",
                "B": "color: white;",
                "alpha": "color: white;",
            }
        else:
            self.stat_css = {
                "postion": "color: black;",
                "gray": "color: black"
            }

        for s in self.stat_css.values():
            stat = QStatusBar(self)
            stat.setStyleSheet(s)
            self.statbar_list.append(stat)

        first = True
        for stat in self.statbar_list:
            if first:
                self.setStatusBar(stat)
                self.statbar_list[0].reformat()
                first = False
            else:
                self.statbar_list[0].addPermanentWidget(stat)

    def add_buttons(self):
        """Add push buttons on the window.

        Add quit, save stop and usage buttons on the windows. When press each button, the set
        method (called "slot" in Qt framework) are execeuted.
        """
        self.save_button = self.create_button("&Save", self.save_frame, None, None, "Save the frame")
        self.stop_button = self.create_button("&Pause", self.stop_frame, None, None, "Stop reading frame", True)
        self.rec_button = self.create_button("&Rec", self.record, None, None, "Start recording", True)
        self.close_button = self.create_button("&Quit", self.slot.quit, None, None, "Quit the program")
        self.theme_button = self.create_button("Light", self.slot.switch_theme, None, None, "Switche color theme")
        self.help_button = self.create_button("&Usage", self.slot.usage, None, None, "Show usage")

        self.frame_button = self.create_button(
            "Properties",
            self.slot.change_frame_prop,
            None,
            tip="Change properties",
            minsize=(150, 30)
            )
        self.default_button = self.create_button(
            "&Default params",
            self.set_param_default,
            "Ctrl+d",
            tip="Set default parameters",
            minsize=(150, 30)
            )
        self.filerule_button = self.create_button(
            "&Naming style",
            self.slot.set_file_rule,
            "Ctrl+n",
            tip="Change naming style",
            minsize=(150, 30)
            )

        hbox = QHBoxLayout()
        hbox.addWidget(self.save_button)
        hbox.addWidget(self.stop_button)
        hbox.addWidget(self.rec_button)
        hbox.addWidget(self.close_button)
        hbox.addWidget(self.theme_button)
        hbox.addWidget(self.help_button)
        return hbox

    def create_button(self, text: str, slot: Callable, key: str = None, icon: Icon = None,
        tip: str = None, checkable: bool = False, minsize: tuple = None) -> QPushButton:
        """Create a QPushButton object.

        Args:
            text (str): Text shown on the button.
            slot (Callable): A method called when click the button.
            key (str, optional): Shortcut key. Defaults to None.
            icon (Icon, optional): An icon shown on the button. Defaults to None.
            tip (str, optional): A tips shown when position the pointer on the button. Defaults to None.
            checkable (bool, optional): Add button to checkbox. Defaults to False.
            msize (tuple, optional): Minimum size of the button box, (width, height).

        Returns:
            QPushButton: PySide2 QPushButton
        """
        button = QPushButton(text)
        if checkable:
            button.setCheckable(True)
            button.toggled.connect(slot)
        else:
            button.clicked.connect(slot)

        if key:
            button.setShortcut(key)
        if icon:
            button.setIcon(QIcon(icon))
        if tip:
            button.setToolTip(tip)
        if minsize:
            button.setMinimumSize(minsize[0], minsize[1])
        else:
            button.setMinimumSize(80, 30)
        return button

    def add_params(self) -> QGridLayout:
        """Set the properties of camera parameter.

        Set the properties of camera parameter, then add sliders to change each parameter.
        When change value on the slider, the value of paramter also changes by the caller
        function.

        """
        lst = self.current_params
        for key, value in lst.items():
            self.add_slider(key)

        # add sliders
        self.slider_table = QGridLayout()
        self.slider_table.setSpacing(15)
        self.slider_table.setContentsMargins(20, 20, 20, 20)
        for row, param in enumerate(self.current_params):
            self.slider_table.addWidget(self.current_params[param]["slider_label"], row, 0)
            self.slider_table.addWidget(self.current_params[param]["slider"], row, 1)
            self.slider_table.addWidget(self.current_params[param]["slider_value"], row, 2)
        if len(self.current_params) > 15:
            self.param_separate = True
        else:
            self.param_separate = False
        return self.slider_table

    def update_params(self, plist: list) -> QGridLayout:
        """Update camera's paramters and sliders shown on the windows.
        """
        #self.current_params.clear()
        self.current_params = self.camera.get_current_params("selected", plist)
        for key, value in self.current_params.items():
            self.add_slider(key)

        # add sliders
        grid = QGridLayout()
        grid.setSpacing(15)
        grid.setContentsMargins(20, 20, 20, 20)
        for row, param in enumerate(self.current_params):
            grid.addWidget(self.current_params[param]["slider_label"], row, 0)
            grid.addWidget(self.current_params[param]["slider"], row, 1)
            grid.addWidget(self.current_params[param]["slider_value"], row, 2)
        if len(self.current_params) > 15:
            self.param_separate = True
        else:
            self.param_separate = False
        self.slider_group = grid
        self.update_mainlayout()
        self.update_prop_table()
        self.write_text("update sliders")
        return grid

    def add_slider(self, param: str):
        """Creates slider, labels to show pamarater's name and its value.

        Args:
            param (str): A parameter to create slider.
        """
        min_ = self.current_params[param]["min"]
        max_ = self.current_params[param]["max"]
        step = self.current_params[param]["step"]
        value = self.current_params[param]["value"]

        slider = QSlider(Qt.Horizontal)
        if max_:
            slider.setRange(min_, max_)
        else:
            slider.setRange(0, 1)
        slider.setValue(int(value))
        slider.setTickPosition(QSlider.TicksBelow)
        slider.valueChanged.connect(lambda val, p=param: self.set_sliderval(p, val))

        if step:
            if max_ < 5:
                slider.setTickInterval(step)
            else:
                slider.setTickInterval(10)

        slider_label = QLabel(param)
        slider_value = QLabel(str(value))

        slider_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        slider_value.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        self.current_params[param]["slider"] = slider
        self.current_params[param]["slider_label"] = slider_label
        self.current_params[param]["slider_value"] = slider_value

    def add_prop_window(self) -> QGridLayout:
        """Create a table to show the current properties of camera.

        Returns:
            QGridLayout: PySide2 QGridLayout
        """
        header = ["property", "value"]
        self.prop_table_widget = QTableWidget(self)
        self.prop_table_widget.setColumnCount(len(header))
        self.prop_table_widget.setRowCount(len(self.prop_table))

        self.prop_table_widget.setHorizontalHeaderLabels(header)
        self.prop_table_widget.verticalHeader().setVisible(False)
        self.prop_table_widget.setAlternatingRowColors(True)
        self.prop_table_widget.horizontalHeader().setStretchLastSection(True)
        self.prop_table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.prop_table_widget.setFocusPolicy(Qt.NoFocus)

        for row, content in enumerate(self.prop_table):
            for col, elem in enumerate(content):
                self.item = QTableWidgetItem(elem)

                self.prop_table_widget.setItem(row, col, self.item)
        self.prop_table_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        #self.prop_table_widget.resizeColumnsToContents()
        #self.prop_table_widget.resizeRowsToContents()
        self.prop_table_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.prop_table_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.prop_table_widget.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContentsOnFirstShow)
        self.prop_table_widget.setColumnWidth(0, 150)
        self.prop_table_widget.setColumnWidth(1, 150)

        vbox = QVBoxLayout()
        vbox.addWidget(self.prop_table_widget)
        vbox.setContentsMargins(20, 20, 20, 20)
        self.prop_group = QGroupBox("Frame Properties")
        self.prop_group.setLayout(vbox)
        return self.prop_group

    def information_window_setup(self):
        """Creates information window.

        Creates the information window where the event related to camera or window.
        """
        self.text_edit = QTextEdit()
        self.text_edit.setReadOnly(True)
        self.text_edit.show()
        vbox = QVBoxLayout()
        vbox.addWidget(self.text_edit)
        self.text_edit_box = QGroupBox("Information", self)
        self.text_edit_box.setLayout(vbox)
        self.text_edit_box.setAlignment(Qt.AlignLeft)

    def create_mainlayout(self):
        """Create the main layout which consists of view area and information window.
        """
        self.main_layout.addLayout(self.create_view_area_layout())
        self.main_layout.addLayout(self.create_information_layout())

    def update_mainlayout(self):
        """Recreate the main layout.
        """
        self.delete_layout(self.information_layout)
        self.delete_layout(self.upper_right)
        self.add_prop_window()
        self.main_layout.addLayout(self.create_information_layout())

    def delete_layout(self, layout):
        """Delete layout

        Args:
            layout (QBoxLayout): QBoxLayout class object to delete
        """
        while layout.count():
            child = layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
            try:
                child.spacerIitem().deleteLater()
            except:
                pass

    def create_view_area_layout(self) -> QVBoxLayout:
        """Creates view area layout
        """
        self.view_area_layout = QVBoxLayout()
        self.view_area_layout.addWidget(self.view, 2)
        self.view_area_layout.addWidget(self.text_edit_box)
        return self.view_area_layout

    def create_information_layout(self):
        """Creates information part layout

        upper-left: current properties
        upper-right: buttons
        lower: sliders
        """
        if self.param_separate:
            self.entry_box = QVBoxLayout()
            self.entry_box.addWidget(self.frame_button)
            self.entry_box.addWidget(self.filerule_button)
            self.entry_box.addWidget(self.default_button)
            self.entry_box.addStretch(1)
            self.entry_box.setSpacing(20)
            self.entry_box.setContentsMargins(20, 20, 20, 20)

            self.button_group_box = QGroupBox("Buttons", self)
            self.button_group_box.setLayout(self.entry_box)
            self.button_group_box.setAlignment(Qt.AlignLeft)

            self.upper_right = QVBoxLayout()
            self.upper_right.addWidget(self.prop_group, 1)
            self.upper_right.addWidget(self.button_group_box, 1)

            self.slider_group_box = QGroupBox("Parameters")
            self.slider_group_box.setLayout(self.slider_group)
            self.slider_group_box.setContentsMargins(20, 20, 20, 20)

            self.information_layout = QHBoxLayout()
            self.information_layout.addLayout(self.upper_right, 1)
            self.information_layout.addWidget(self.slider_group_box, 2)
            #self.information_layout.addStretch(1)
            return self.information_layout
        else:
            self.entry_box = QVBoxLayout()
            self.entry_box.addWidget(self.frame_button)
            self.entry_box.addWidget(self.filerule_button)
            self.entry_box.addWidget(self.default_button)
            self.entry_box.addStretch(1)
            self.entry_box.setSpacing(20)
            self.entry_box.setContentsMargins(20, 20, 20, 20)

            self.button_group_box = QGroupBox("Buttons", self)
            self.button_group_box.setLayout(self.entry_box)
            self.button_group_box.setAlignment(Qt.AlignLeft)

            self.upper_right = QHBoxLayout()
            self.upper_right.addWidget(self.prop_group)
            self.upper_right.addWidget(self.button_group_box)

            self.slider_group_box = QGroupBox("Parameters")
            self.slider_group_box.setLayout(self.slider_group)
            self.slider_group_box.setContentsMargins(20, 20, 20, 20)

            self.information_layout = QVBoxLayout()
            self.information_layout.addLayout(self.upper_right)
            self.information_layout.addWidget(self.slider_group_box)
            self.information_layout.setSpacing(30)
            return self.information_layout

    # decorator
    def display(func):
        def wrapper(self, *args, **kwargs):
            try:
                self.is_display = False
                self.stop_timer()
                func(self, *args, **kwargs)
            finally:
                self.is_display = True
                self.start_timer()
        return wrapper

    def stop_frame(self, checked: bool):
        """Stop reading next frame.

        Args:
            checked (bool): True when presse the Stop button (toggle on). False when press
                again (toggel off).
        """
        if checked:
            self.write_text("Stop !!")
            self.is_display = False
            self.stop_button.setText('Start')
            self.stop_button.setChecked(True)
            self.stop_act.setText('Start')
            self.stop_act.setChecked(True)
        else:
            self.write_text("Start !!")
            self.is_display = True
            self.stop_button.setText('&Pause')
            self.stop_button.setChecked(False)
            self.stop_act.setText('&Pause')
            self.stop_act.setChecked(False)

    def keyPressEvent(self, event):
        """Exit the program

        This method will be called when press the Escape key on the window.
        """
        if event.key() == Qt.Key_Escape:
            QApplication.quit()

    def get_coordinates(self, event):
        """Show the current coordinates and value in the pixel where the cursor is located.

        The status bar is updates by the obtained values.
        """
        if self.item is self.view.itemAt(event.pos()):
            sp = self.view.mapToScene(event.pos())
            lp = self.item.mapFromScene(sp).toPoint()
            (x, y) = lp.x(), lp.y()
            #color = self.frame.image.pixel(x, y)
            color = self.qimage.pixelColor(x, y)
            if self.colorspace == "rgb":
                value = color.getRgb()
            elif self.colorspace == "gray":
                value = color.value()

            # Return none if the coordinates are out of range
            if x < 0 and self.frame.width < x:
                return
            elif y < 0 and self.frame.height < y:
                return

            if self.frame.img_is_rgb:
                status_list = [
                    "( x : {}, y :{} )".format(x, y),
                    "R : {}".format(value[0]),
                    "G : {}".format(value[1]),
                    "B : {}".format(value[2]),
                    "alpha : {}".format(value[3])
                ]
            else:
                status_list = [
                    "( x : {}, y :{} )".format(x, y),
                    "gray value : {}".format(value),
                ]

            for statbar, stat in zip(self.statbar_list, status_list):
                statbar.showMessage(stat)

    def next_frame(self):
        """Get next frame from the connected camera.

        Get next frame, set it to the view area and update.
        """
        #print("display :", self.is_display)
        if self.is_display:
            self.camera.read_frame()
            self.convert_frame()
            self.scene.clear()
            self.scene.addPixmap(self.pixmap)
            self.update()
            #print("update")

    def convert_frame(self):
        """Convert the class of frame

        Create qimage, qpixmap objects from ndarray frame for displaying on the window.

        """
        if self.colorspace == "rgb":
            self.qimage = QImage(
                self.camera.frame.data,
                self.camera.frame.shape[1],
                self.camera.frame.shape[0],
                self.camera.frame.shape[1] * 3,
                QImage.Format_RGB888
                )
        elif self.colorspace == "gray":
            self.qimage = QImage(
                self.camera.frame.data,
                self.camera.frame.shape[1],
                self.camera.frame.shape[0],
                self.camera.frame.shape[1] * 1,
                QImage.Format_Grayscale8)

        self.pixmap.convertFromImage(self.qimage)

    def save_frame(self):
        """Save the frame on the window as an image.
        """
        if self.filename_rule == "Manual":
            self.save_frame_manual()
            if not self.filename:
                return None
            prm = re.sub(r"\.(.*)", ".csv", str(self.filename))
        else:
            self.filename = FileIO.get_filename(self.filename_rule, self.image_suffix, self.parent_dir)
            prm = str(self.filename).replace(self.image_suffix, "csv")

        if not self.dst.exists():
            self.dst.mkdir(parents=True)
        im = Image.fromarray(self.camera.frame)
        im.save(self.filename)

        # make a parameter file
        with open(prm, "w") as f:
            for name, key in self.current_params.items():
                f.write("{},{}\n".format(name, self.current_params[name]["value"]))

        self.write_text("{:<10}: {}".format("save image", self.filename))
        self.write_text("{:<10}: {}".format("save param", prm))

    def update_prop_table(self):
        """Updates the table that shows the camera properties.
        """
        w, h, cc, f = self.camera.get_properties()
        self.prop_table = [
            ["Fourcc", cc],
            ["Width", int(w)],
            ["Height", int(h)],
            ["FPS", "{:.1f}".format(f)],
            ["Bit depth", 8],
            ["Naming Style", self.filename_rule]
        ]
        col = 1
        for row in range(len(self.prop_table)):
            text = str(self.prop_table[row][col])
            self.prop_table_widget.item(row, col).setText(text)

    def record(self):
        """Start or end recording
        """
        if self.camera.is_recording:
            self.camera.stop_recording()
            self.rec_button.setText('&Rec')
            self.rec_act.setText('&Record')
            self.write_text("save : {}".format(self.video_filename))
        else:
            self.video_filename = FileIO.get_filename(self.filename_rule, self.video_suffix, self.parent_dir)
            self.camera.start_recording(self.video_filename, self.video_codec)
            self.rec_button.setText('Stop rec')
            self.rec_act.setText('Stop record')

    @display
    def save_frame_manual(self) -> bool:
        """Determine file name of image to save with QFileDialog
        """
        self.dialog = QFileDialog()
        self.dialog.setWindowTitle("Save File")
        self.dialog.setNameFilters([
            "image (*.jpg *.png *.tiff *.pgm)",
            "All Files (*)"
            ])
        self.dialog.setAcceptMode(QFileDialog.AcceptSave)
        self.dialog.setOption(QFileDialog.DontUseNativeDialog)

        if self.dialog.exec_():
            r = self.dialog.selectedFiles()

            # If the file name doesn't include supproted suffixes, add to the end.
            if re.search(".pgm$|.png$|.jpg$|.tiff$", r[0]):
                self.filename = r[0]
            else:
                self.filename = "{}.{}".format(r[0], self.image_suffix)
            return True
        else:
            return False

    def get_screensize(self):
        """Get current screen size from the output of linux cmd `xrandr`.
        """
        cmd = ["xrandr"]
        ret = subprocess.check_output(cmd)
        output = ret.decode()
        pattern = r"current(\s+\d+\s+x\s+\d+)"

        m = re.search(pattern, output)
        if m:
            size = re.sub(" ", "", m.group(1))
            w, h = map(int, size.split("x"))
            return w, h, size
        else:
            return None

    def set_sliderval(self, param: str, value: int):
        """Changes a camera parameter.

        Updates the label on the right of the slider if input value is valid.

        Args:
            param (str): A camera parameter
            value (int): its value
        """
        val = self.camera.set_parameter(param, value)
        self.current_params[param]["value"] = str(val)
        self.current_params[param]["slider_value"].setText(str(value))

    def set_param_default(self):
        """Sets all paramters to default.
        """
        for param, values in self.current_params.items():
            default = values["default"]
            self.camera.set_parameter(param, default)
            self.current_params[param]["slider"].setValue(int(default))
            self.current_params[param]["slider_value"].setText(str(default))

    def get_properties(self) -> list:
        """Get the current camera properties.

        Returns:
            list: parameters. fourcc, width, height, fps.
        """
        tmp = []
        for row in range(4):
            tmp.append(self.prop_table[row][1])
        return tmp

    def write_text(self, text: str, level: str = "info", color: str = None):
        """Writes the message into information window.

        Args:
            text (str): A text to write.
            level (str, optional): Log lebel of the message. Defaults to "info".
            color (str, optional): Font color. Defaults to None.
        """
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
        now = now[:-3]
        if color == "red":
            form = f"<font color='red'>[{level.upper():<4} {now}] {text}</font>"
        elif color == "yellow":
            form = f"<font color='yellow'>[{level.upper():<4} {now}] {text}</font>"
        else:
            form = f"[{level.upper():<4} {now}] {text}"
        self.text_edit.append(form)
class MainUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.FramelessWindowHint)
        #self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setFixedSize(601, 401)
        self.toolbar_selected = False

        self.toolbar = QToolBar()
        self.toolbar.setObjectName("toolbar")
        self.toolbar.setMovable(False)
        self.toolbar.setFloatable(False)
        self.toolbar.setContextMenuPolicy(
            Qt.PreventContextMenu)  # no right-click on toolbar
        self.addToolBar(self.toolbar)
        self.toolbar.setStyleSheet("background-color: khaki")

        self.admin_btn = QPushButton("Admin", self)
        self.admin_btn.setObjectName("admin_btn")
        self.admin_btn.setFixedSize(65, 30)

        self.info_btn = QPushButton("Info", self)
        self.info_btn.setObjectName("info_btn")
        self.info_btn.setFixedSize(35, 30)

        self.spacer = QWidget()
        self.spacer.setObjectName("toolbar_spacer")
        self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.toolbar_label = QLabel("")
        self.toolbar_label.setObjectName("toolbar_label")
        self.toolbar_label.setAlignment(Qt.AlignCenter)
        self.toolbar_label.setSizePolicy(QSizePolicy.Expanding,
                                         QSizePolicy.Fixed)

        self.close_btn = QPushButton(" X")
        self.close_btn.setObjectName("close_btn")
        self.close_btn.setFixedSize(30, 30)

        self.toolbar.addWidget(self.admin_btn)
        # self.toolbar.addWidget(self.spacer)
        self.toolbar.addWidget(self.toolbar_label)
        # self.toolbar.addWidget(self.spacer)
        self.toolbar.addWidget(self.info_btn)
        self.toolbar.addWidget(self.close_btn)

        self.toolbar_label.mousePressEvent = self.mousePressEvent  # to move window
        self.mousePressEvent = self.mousePressEvent_  # to check if window content is selected or not
        self.close_btn.pressed.connect(self.close_window)

    def mousePressEvent_(self, e):
        """ to be able to drag window just via toolbar
		I made this custome mousePressEvent to filter any click,
		other than toolbar.
		If we click on any place on the window(except the toolbar),
		this method will be activated and set the toolbar_selected=false,
		so that we won't be able do anything in normal mousePressEvent			
		"""
        self.toolbar_selected = False

    def mousePressEvent(self, e):
        self.toolbar_selected = True
        """ If defiend section is pressed 
		Activate the mouseMoveEvent to
		get the mouse positions
		"""
        self.mouse_pressed = True
        self.old_pos = e.globalPos()

    def mouseMoveEvent(self, e):
        """ move the whole window to mouse position
		"""
        if self.toolbar_selected:
            delta = QPoint(e.globalPos() - self.old_pos)
            self.move(self.x() + delta.x(), self.y() + delta.y())
            self.old_pos = e.globalPos()

    def close_window(self):
        self.close()
Example #11
0
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
Example #12
0
    def __init__(self):
        super().__init__();
        self.setWindowTitle("Roots")
        self.setFixedWidth(1200)
        self.resize(1200, 1200)
        self.threadpool = QThreadPool();
        self.object_list = list()
        self.is_training_on = False
        self.interaction_under_training = None
        self.n_measurements_collected = 0
        self.n_measurements_to_collect = 3
        self.sensor_not_responding = True
        self.sensor_not_responding_timeout = 2000        # milliseconds
        self.database_connection = self.create_temporary_database()
        self.active_object = None
        self.number_of_objects_added = 0
        self.sensor_start_freq = 250000
        self.sensor_end_freq = 3000000

    # creates the plot
        self.plotWidget = pyqtgraph.PlotWidget(title = "Sensor Response")
        self.plotWidget.setFixedHeight(300)
        self.plotWidget.getAxis("bottom").setLabel("Excitation frequency", "Hz")
        self.plotWidget.getAxis("left").setLabel("Volts", "V")
        self.dataPlot = self.plotWidget.plot()

    # timer used to see if the sensor is responding
        self.timer = QTimer()
        self.timer.setInterval(self.sensor_not_responding_timeout)
        self.timer.timeout.connect(self.timer_timeout)
        self.timer_timeout()

    # defines the actions in the file menu with button actions
        iconExit = QIcon("icons/icon_exit.png")
        btnActionExit = QAction(iconExit, "Exit", self)
        btnActionExit.setStatusTip("Click to terminate the program")
        btnActionExit.triggered.connect(self.exit)

        iconSave = QIcon("icons/icon_save.ico")
        buttonActionSave = QAction(iconSave, "Save current set of objects", self)
        # buttonActionSave.setStatusTip("Click to perform action 2")
        buttonActionSave.triggered.connect(self.save)

        iconOpen = QIcon("icons/icon_load.png")
        buttonActionOpen = QAction(iconOpen, "Load set of objects", self)
        buttonActionOpen.triggered.connect(self.open)

    # toolbar
        toolBar = QToolBar("Toolbar")
        toolBar.addAction(buttonActionSave)
        toolBar.addAction(buttonActionOpen)
        toolBar.setIconSize(QSize(64, 64))
        toolBar.setStyleSheet(styles.toolbar)
        self.addToolBar(toolBar)

    # menu
        menuBar = self.menuBar()
        menuBar.setStyleSheet(styles.menuBar)
        menuFile = menuBar.addMenu("File")
        menuOptions = menuBar.addMenu("Options")
        menuView = menuBar.addMenu("View")
        menuConnect = menuBar.addMenu("Connect")
        menuFile.addAction(buttonActionSave)
        menuFile.addAction(buttonActionOpen)
        menuFile.addAction(btnActionExit)

    # status bar
        self.setStatusBar(QStatusBar(self))

    # creates the "My Objects" label
        labelMyObjects = QLabel("My Objects")
        labelMyObjects.setFixedHeight(100)
        labelMyObjects.setAlignment(Qt.AlignCenter)
        labelMyObjects.setStyleSheet(styles.labelMyObjects)

    # button "add object"
        icon_plus = QIcon("icons/icon_add.png")
        self.btn_create_object = QPushButton("Add Object")
        self.btn_create_object.setCheckable(False)
        self.btn_create_object.setIcon(icon_plus)
        self.btn_create_object.setFixedHeight(80)
        self.btn_create_object.setStyleSheet(styles.addObjectButton)
        self.btn_create_object.clicked.connect(self.create_object)

    # defines the layout of the "My Objects" section
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setContentsMargins(0,0,0,0)
        self.verticalLayout.addWidget(labelMyObjects)
        self.verticalLayout.addWidget(self.btn_create_object)
        self.spacer = QSpacerItem(0,2000, QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.addSpacerItem(self.spacer)  #adds spacer

    # defines the ComboBox which holds the names of the objects
        self.comboBox = QComboBox()
        self.comboBox.addItem("- no object selected")
        self.comboBox.currentIndexChanged.connect(self.comboBox_index_changed)
        self.comboBox.setFixedSize(300, 40)
        self.comboBox.setStyleSheet(styles.comboBox)
        self.update_comboBox()

    # defines the label "Selected Object" (above the comboBox)
        self.labelComboBox = QLabel()
        self.labelComboBox.setText("Selected Object:")
        self.labelComboBox.setStyleSheet(styles.labelComboBox)
        self.labelComboBox.adjustSize()

    # vertical layout for the combobox and its label
        self.VLayoutComboBox = QVBoxLayout()
        self.VLayoutComboBox.addWidget(self.labelComboBox)
        self.VLayoutComboBox.addWidget(self.comboBox)

    # label with the output text (the big one on the right)
        self.labelClassification = QLabel()
        self.labelClassification.setText("No interaction detected")
        self.labelClassification.setFixedHeight(80)
        self.labelClassification.setStyleSheet(styles.labelClassification)
        self.labelClassification.adjustSize()

        HLayoutComboBox = QHBoxLayout()
        HLayoutComboBox.addLayout(self.VLayoutComboBox)
        HLayoutComboBox.addSpacerItem(QSpacerItem(1000,0, QSizePolicy.Expanding, QSizePolicy.Expanding));  #adds spacer
        HLayoutComboBox.addWidget(self.labelClassification)

    # creates a frame that contains the combobox and the labels
        frame = QFrame()
        frame.setStyleSheet(styles.frame)
        frame.setLayout(HLayoutComboBox)

    # sets the window layout with the elements created before
        self.windowLayout = QVBoxLayout()
        self.windowLayout.addWidget(self.plotWidget)
        self.windowLayout.addWidget(frame)
        self.windowLayout.addLayout(self.verticalLayout)

    # puts everything into a frame and displays it on the window
        self.mainWindowFrame = QFrame()
        self.mainWindowFrame.setLayout(self.windowLayout)
        self.mainWindowFrame.setStyleSheet(styles.mainWindowFrame)
        self.setCentralWidget(self.mainWindowFrame)

        self.create_object()        # creates one object at the beginning