예제 #1
0
 def connect_to_signal(signal: pyqtSignal, callbacks):
     if not callbacks:
         return
     if callable(callbacks):
         callbacks = [callbacks]
     for callback in callbacks:
         signal.connect(callback)
예제 #2
0
def connect(signal: pyqtSignal, callback: Callable):
    """
    By default calling ``signal.connect(callback)`` will dispose of context information.

    Practically, this leads to single line tracebacks when ``signal.emit()`` is invoked.
    This is very hard to debug.

    This function wraps the ``connect()`` call to give you additional traceback information, if the callback does crash.

    :param signal: the signal to ``connect()`` to.
    :param callback: the callback to connect (will be called after ``signal.emit(...)``.
    """

    # Step 1: At time of calling this function: get the stack frames.
    #         We reconstruct the stack as a ``traceback`` object.
    source = None
    for frame in list(inspect.stack())[1:]:
        source = types.TracebackType(source, frame.frame, frame.index or 0,
                                     frame.lineno)

    # Step 2: construct a lightweight StackSummary object which does not contain
    # actual frames or locals, to avoid memory leak
    try:
        summary = traceback.StackSummary.extract(traceback.walk_tb(source),
                                                 capture_locals=False)
    finally:
        del source

    # Step 3: Wrap the ``callback`` and inject our creation stack if an error occurs.
    #         The BaseException instead of Exception is intentional: this makes sure interrupts of infinite loops show
    #         the source callback stack for debugging.
    def trackback_wrapper(*args, **kwargs):
        try:
            callback(*args, **kwargs)
        except BaseException as exc:
            traceback_str = '\n' + ''.join(summary.format())
            raise exc from CreationTraceback(traceback_str)

    try:
        setattr(callback, "tb_wrapper", trackback_wrapper)
    except AttributeError:
        # This is not a free function, but either an external library or a method bound to an instance.
        if not hasattr(callback, "tb_wrapper") and hasattr(
                callback, "__self__") and hasattr(callback, "__func__"):
            # methods are finicky: you can't set attributes on them.
            # Instead, we inject the handlers for each method in a dictionary on the instance.
            bound_obj = callback.__self__
            if not hasattr(bound_obj, "tb_mapping"):
                setattr(bound_obj, "tb_mapping", {})
            bound_obj.tb_mapping[
                callback.__func__.__name__] = trackback_wrapper
        else:
            logging.warning(
                "Unable to hook up connect() info to %s. Probably a 'builtin_function_or_method'.",
                repr(callback))

    # Step 3: Connect our wrapper to the signal.
    signal.connect(trackback_wrapper)
예제 #3
0
 def __init__(self,
              conditions: List[SelectionCondition],
              control: QWidget,
              signal: pyqtSignal,
              action: Callable,
              activate_action: Callable = None,
              deactivate_action: Callable = None,
              model_change_handler: Callable = None):
     self.__conditions = conditions
     self.__control = control
     self.__action = action
     self.__activate_action = activate_action
     self.__deactivate_action = deactivate_action
     self.__model_change_handler = model_change_handler
     signal.connect(lambda *args: action(self._selection, *args))
     self.update_selection([])
예제 #4
0
    def __init__(self, settings: Settings, db: DB, on_create: Callable,
                 on_date_click: Callable, tasks_changed_event: qtc.pyqtSignal,
                 factory: TaskWidgetFactory):
        super().__init__()
        tasks_changed_event.connect(self._redraw_tasks)

        self._db = db
        self._factory = factory
        self._settings = settings

        main_layout = qtw.QVBoxLayout()
        self.setLayout(main_layout)

        self._current_date = date.today()
        self._date_button = qtw.QPushButton(clicked=on_date_click)
        font = self._date_button.font()
        font.setPixelSize(20)
        self._date_button.setFont(font)
        self._update_date_label()

        tasks_scroll_area = qtw.QScrollArea(widgetResizable=True)
        tasks_scroll_area_content = qtw.QWidget()
        tasks_scroll_area.setWidget(tasks_scroll_area_content)

        self._tasks_layout = qtw.QVBoxLayout(tasks_scroll_area_content)
        self._tasks_layout.setAlignment(qtc.Qt.AlignTop)

        self._prev_day_button = qtw.QPushButton(
            text=settings.PREVIOUS_DAY_BUTTON_TEXT,
            clicked=lambda: self._change_day(-1))

        self._create_task_button = qtw.QPushButton(
            text=settings.CREATE_TASK_BUTTON_TEXT, clicked=on_create)

        self._next_day_button = qtw.QPushButton(
            text=settings.NEXT_DAY_BUTTON_TEXT,
            clicked=lambda: self._change_day(1))

        buttons_layout = qtw.QHBoxLayout()
        buttons_layout.addWidget(self._prev_day_button)
        buttons_layout.addWidget(self._create_task_button)
        buttons_layout.addWidget(self._next_day_button)

        main_layout.addWidget(self._date_button)
        main_layout.addWidget(tasks_scroll_area)
        main_layout.addLayout(buttons_layout)
예제 #5
0
def is_connected(signal: pyqtSignal, slot: pyqtSlot) -> bool:
    """
    Determine if a (bound) signal is connected to a slot.
    :param signal: signal (a return value from a call to pyqtSignal(...))
    :param slot: slot (a method on a QObject, decorated with pyqtSlot)
    :return: True if connected, False otherwise
    """
    try:
        signal.connect(slot, Qt.UniqueConnection)

    except TypeError as exc:
        assert str(exc) in (
            'connection is not unique',
            'connect() failed between MyObj.sig_test[] and meth()')
        return True

    else:
        signal.disconnect(slot)
        return False