Example #1
0
def load_ui(name, custom_widgets=[], parent=None):
    loader = QUiLoader()
    for cw in custom_widgets:
        loader.registerCustomWidget(cw)
    path = resource_path(posixpath.join("ui", name))
    ui_file = QFile(path)
    if not ui_file.open(QFile.ReadOnly):
        logger.error("Cannot open {}: {}".format(path, ui_file.errorString()))
        sys.exit(-1)
    ui = loader.load(ui_file, parent)
    ui_file.close()
    return ui
Example #2
0
def load_ui(name, custom_widgets=[], parent=None):
    loader = QUiLoader()
    for cw in custom_widgets:
        loader.registerCustomWidget(cw)
    path = os.path.join(os.path.dirname(__file__), "ui", name)
    ui_file = QFile(path)
    if not ui_file.open(QFile.ReadOnly):
        logging.critical("Cannot open {}: {}".format(path,
                                                     ui_file.errorString()))
        sys.exit(-1)
    ui = loader.load(ui_file, parent)
    ui_file.close()
    return ui
Example #3
0
File: qt.py Project: FFY00/ViewSB
class QtFrontend(ViewSBFrontend):
    """ Qt Frontend that consumes packets for display. """

    UI_NAME = 'qt'
    UI_DESCRIPTION = 'unstable GUI in Qt'

    # So, Qt's tree widgets require that column 0 have the expand arrow, but you _can_ change
    # where column 0 is displayed.
    # We want the summary column to have the expand arrow, so we'll swap it
    # with the timestamp column in __init__().
    COLUMN_TIMESTAMP = 5
    COLUMN_DEVICE = 1
    COLUMN_ENDPOINT = 2
    COLUMN_DIRECTION = 3
    COLUMN_LENGTH = 4
    COLUMN_SUMMARY = 0
    COLUMN_STATUS = 6
    COLUMN_DATA = 7

    @staticmethod
    def reason_to_be_disabled():
        try:
            import PySide6
        except (ImportError, ModuleNotFoundError):
            return "PySide6 (Qt library) not available."

        return None

    @staticmethod
    def _stringify_list(lst):
        """
        Tiny helper than runs the str constructor on every item in a list, but specifically handles two cases:
        1) the object in question is None, which we instead want to display as an empty string,
        2) the resulting string contains a null character, which Qt doesn't like, so we'll
        represent it to the user as, literally, \0.
        """
        return [
            str(x).replace('\0', r'\0') if x is not None else '' for x in lst
        ]

    def _create_item_for_packet(self, viewsb_packet):
        """ Creates a QTreeWidgetItem for a given ViewSBPacket.

        Args:
            viewsb_packet -- The ViewSBPacket to create the QTreeWidgetItem from.

        Returns a QTreeWidgetItem.
        """
        def get_packet_string_array(viewsb_packet):
            """ Tiny helper to return and stringify the common fields used for the columns of tree items. """

            direction = viewsb_packet.direction.name if viewsb_packet.direction is not None else ''

            length = len(
                viewsb_packet.data) if viewsb_packet.data is not None else ''

            return self._stringify_list([
                viewsb_packet.summarize(), viewsb_packet.device_address,
                viewsb_packet.endpoint_number, direction, length,
                viewsb_packet.timestamp,
                viewsb_packet.summarize_status(),
                viewsb_packet.summarize_data()
            ]) + [viewsb_packet]

        item = QTreeWidgetItem(get_packet_string_array(viewsb_packet))

        # Give the item a reference to the original packet object.
        item.setData(0, QtCore.Qt.UserRole, viewsb_packet)

        return item

    def _recursively_walk_packet(self, viewsb_packet):
        """ Recursively walks packet subordinates, batching QTreeWidgetItem.addChildren as much as possible.

        Args:
            viewsb_packet -- The top-level packet (as far as the caller's context is concerned).
        """

        packet_item = self._create_item_for_packet(viewsb_packet)

        packet_children_list = []

        for sub_packet in viewsb_packet.subordinate_packets:

            # Create the item for this packet, and recursively fill its children.
            packet_children_list.append(
                self._recursively_walk_packet(sub_packet))

        packet_item.addChildren(packet_children_list)

        return packet_item

    def __init__(self):
        """ Sets up the Qt UI. """

        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)

        signal.signal(
            signal.SIGINT,
            signal.SIG_DFL)  # fix SIGINT handling - cleanly exit on ctrl+c

        self.app = QApplication.instance() or QApplication([])

        try:
            import qt_material

            qt_material.apply_stylesheet(self.app, 'light_blue.xml')
        except ImportError:
            pass

        self.ui_file = QtCore.QFile(
            os.path.dirname(os.path.realpath(__file__)) + '/qt.ui')
        self.loader = QUiLoader()
        self.loader.registerCustomWidget(ViewSBQTreeWidget)
        self.loader.registerCustomWidget(ViewSBHexView)
        self.window = self.loader.load(self.ui_file)  # type: QMainWindow

        # Swap columns 0 and 5 to put the expand arrow on the summary column.
        self.window.usb_tree_widget.header().swapSections(0, 5)

        self.window.usb_tree_widget.header().setSectionResizeMode(
            QHeaderView.ResizeToContents)

        self.window.update_timer = QtCore.QTimer()
        self.window.update_timer.timeout.connect(self._update)

        self.window.usb_tree_widget.currentItemChanged.connect(
            self._tree_current_item_changed)

        self.window.usb_tree_widget = self.window.usb_tree_widget
        self.window.usb_tree_widget.sortByColumn(0,
                                                 Qt.SortOrder.AscendingOrder)

    def ready(self):
        """ Called when the backend is ready to stream. """
        self.window.showMaximized()

    def _update(self):
        """ Called by the QTimer `update_timer`; collects packets the queue and adds them to the tree view.

        We use this instead of calling `handle_communications` and defining `handle_incoming_packet`,
        because adding items one at a time as we receive them is slower than batching them.

        Note: Since this is called via a QTimer signal, this method runs in the UI thread.
        """

        # Handle exceptions
        if self._exception_conn.poll():
            self.handle_exception(*self._exception_conn.recv())
            # TODO: overide handle_exception to show a Qt dialog message

        # If the process manager told us to stop (which might happen if e.g. the backend exits),
        # then stop and exit.
        if self.termination_event.is_set():
            self.app.closeAllWindows()

        packet_list = []

        try:

            # Get as many packets as we can as quick as we can.
            while True:

                packet = self.data_queue.get_nowait()
                packet_list.append(packet)

        # But the instant it's empty, don't wait for any more; just send them to be processed.
        except multiprocessing.queues.Empty:
            pass

        finally:
            self.add_packets(packet_list)

    def _tree_current_item_changed(self, current_item, _previous_item):
        """
        Handler for the QTreeWidget.currentItemChanged() signal that populates the side panels with
        detail fields and a hex representation of the current packet.
        """

        # Clear the details widget.
        self.window.usb_details_tree_widget.clear()

        current_packet = current_item.data(0, QtCore.Qt.UserRole)

        # A list of 2-tuples: first element is a table title, and the second is usually a string:string dict.
        detail_fields = current_packet.get_detail_fields()

        if detail_fields:
            self.update_detail_fields(detail_fields)

        self.window.usb_hex_view.populate(current_packet.get_raw_data())

    def update_detail_fields(self, detail_fields):
        """ Populates the detail view with the relevant fields for the selected packet. """

        # Each table will have a root item in the details view.
        root_items = []

        for table in detail_fields:
            title = table[0]

            root = QTreeWidgetItem([title])
            children = []

            fields = table[1]

            # The usual case: a str:str dict.
            if isinstance(fields, dict):
                for key, value in fields.items():
                    children.append(
                        QTreeWidgetItem(self._stringify_list([key, value])))

            # Sometimes it'll just be a 1-column list.
            elif isinstance(fields, list):
                for item in fields:
                    children.append(
                        QTreeWidgetItem(self._stringify_list([item])))

            # Sometimes it'll just be a string, or a `bytes` instance.
            else:
                children.append(QTreeWidgetItem(self._stringify_list([fields
                                                                      ])))

            root.addChildren(children)

            # Add an empty "item" between each table.
            root_items.extend([root, QTreeWidgetItem([])])

        self.window.usb_details_tree_widget.addTopLevelItems(root_items)

        self.window.usb_details_tree_widget.expandAll()

        self.window.usb_details_tree_widget.resizeColumnToContents(0)
        self.window.usb_details_tree_widget.resizeColumnToContents(1)

    def add_packets(self, viewsb_packets):
        """ Adds a list of top-level ViewSB packets to the tree.

        We're in the UI thread; every bit of overhead counts, so let's batch as much as possible.
        """

        top_level_items_list = []

        for viewsb_packet in viewsb_packets:

            # Create the item for this packet, and recursively fill its children.
            top_level_items_list.append(
                self._recursively_walk_packet(viewsb_packet))

        self.window.usb_tree_widget.addTopLevelItems(top_level_items_list)

    def run(self):
        """ Overrides ViewSBFrontend.run(). """

        self.wait_for_backend_ready()

        # TODO: is there a better value than 100 ms? Should it be configurable by the Analyzer?
        self.window.update_timer.start(100)
        self.app.exec_()
        self.stop()

    def stop(self):
        self.app.closeAllWindows()
        self.termination_event.set()
Example #4
0
    def __init__(self, data_model, main_app):
        """Initialize the widget.

        Args:
            data_model: the data model holding the data.
        """
        self.data_model = data_model

        loader = QUiLoader()
        loader.registerCustomWidget(pg.PlotWidget)
        loader.registerCustomWidget(pg.SpinBox)
        self.ui = loader.load(resources.path("tailor.resources",
                                             "plot_tab.ui"))
        # store reference to this code in widget
        self.ui.code = self

        self.main_app = main_app

        self.ui.param_layout = QtWidgets.QVBoxLayout()
        self.ui.param_layout.setContentsMargins(4, 0, 0, 0)
        self.ui.parameter_box.setLayout(self.ui.param_layout)
        self._params = {}
        self._symbols = set(asteval.Interpreter().symtable.keys())

        # FIXME move this to create_plot, or vice versa?
        self._initial_param_plot = self.ui.plot_widget.plot(symbol=None,
                                                            pen=pg.mkPen(
                                                                color="00F4",
                                                                width=4))
        self._fit_plot = self.ui.plot_widget.plot(symbol=None,
                                                  pen=pg.mkPen(color="r",
                                                               width=4))
        self.ui.fit_domain_area = pg.LinearRegionItem(movable=True,
                                                      brush="00F1")

        # Set options affecting the UI
        self.ui.fit_start_box.setOpts(value=-np.inf,
                                      dec=True,
                                      step=0.1,
                                      finite=True,
                                      compactHeight=False)
        self.ui.fit_end_box.setOpts(value=np.inf,
                                    dec=True,
                                    step=0.1,
                                    finite=True,
                                    compactHeight=False)
        self.ui.fit_start_box.setMaximumWidth(75)
        self.ui.fit_end_box.setMaximumWidth(75)
        self.ui.draw_curve_option.addItems(DRAW_CURVE_OPTIONS)
        # lambda is necessary to gobble the 'index' parameter of the
        # currentIndexChanged signal
        self.ui.draw_curve_option.currentIndexChanged.connect(
            lambda index: self.update_best_fit_plot())

        # Connect signals
        self.ui.model_func.textChanged.connect(self.update_fit_params)
        self.ui.show_initial_fit.stateChanged.connect(self.plot_initial_model)
        self.ui.fit_start_box.sigValueChanging.connect(self.update_fit_domain)
        self.ui.fit_end_box.sigValueChanging.connect(self.update_fit_domain)
        self.ui.use_fit_domain.stateChanged.connect(self.toggle_use_fit_domain)
        self.ui.fit_domain_area.sigRegionChanged.connect(
            self.fit_domain_region_changed)
        self.ui.fit_button.clicked.connect(self.perform_fit)
        self.ui.xlabel.textChanged.connect(self.update_xlabel)
        self.ui.ylabel.textChanged.connect(self.update_ylabel)
        self.ui.xmin.textChanged.connect(self.update_limits)
        self.ui.xmax.textChanged.connect(self.update_limits)
        self.ui.ymin.textChanged.connect(self.update_limits)
        self.ui.ymax.textChanged.connect(self.update_limits)
        self.ui.set_limits_button.clicked.connect(self.update_limits)
        self.ui.plot_widget.sigXRangeChanged.connect(self.updated_plot_range)

        self.ui.plot_widget.setMenuEnabled(False)
        self.ui.plot_widget.hideButtons()