Esempio n. 1
0
class CheckBox(Widget):
    changed_signal = Signal()

    def __init__(self, parent, text="", check=False):
        # self._widget = QCheckBox(text, parent.get_container())
        super().__init__(parent, QCheckBox(text, QParent(parent)))
        self.changed = SignalWrapper(self, "changed")

        # self.init_widget(parent)
        if check:
            self.setChecked(True)
        # self._widget.stateChanged.connect(self.__state_changed)
        self._qwidget.stateChanged.connect(self.__state_changed)

    def __state_changed(self):
        if not self.changed.inhibit:
            self.on_changed()

    def checked(self):
        return self._qwidget.isChecked()

    def check(self, checked=True):
        self.set_checked(checked)

    # FIXME: Rename: on_change?
    def on_changed(self):
        self.changed.emit()

    def set_checked(self, checked):
        self._qwidget.setChecked(checked)

    def uncheck(self):
        self.set_checked(False)
class SpinCtrl(Widget):
    changed_signal = Signal()

    def __init__(self, parent, min_value, max_value, initial_value):
        super().__init__(parent, QSpinBox(QParent(parent)))
        self._widget.setRange(min_value, max_value)
        self._widget.setValue(initial_value)
        self._widget.valueChanged.connect(self.__value_changed)
        # FIXME: What did this to, again?
        self.changed = SignalWrapper(self, "changed")
        self.update_style()

    def update_style(self):
        theme = get_theme(self)
        padding = theme.textfield_padding()
        if not padding:
            # Indicates that we do not want custom styling
            return
        # There seems to be an issue with specifying padding-top and
        # padding-bottom for a QSpinBox.
        fontmetrics = QFontMetrics(self._widget.font())
        fontheight = fontmetrics.height()
        print(fontheight)
        border = 4
        min_height = fontheight + padding.top + padding.bottom + border
        self.set_min_height(min_height)
        print("MINHEIGHT (SPINCTRL)", min_height)
        # FIXME: This widget seems to have some margin error, the border is
        # drawn so that the widget height is two less than it should be. May
        # need to draw own border in order to get this right! (Sigh)
        self._widget.setStyleSheet(
            f"""
            QSpinBox {{
                /*
                border: 0;
                margin: 0px;
                */
                /*
                padding-top: 2px;
                padding-bottom: 2px;
                */
                padding-right: {padding.right}px;
                padding-left: {padding.left}px;
            }}
            """
        )

    def get_value(self):
        return self._widget.value()

    def set_value(self, value):
        self._widget.setValue(value)

    def __value_changed(self, _):
        if not self.changed.inhibit:
            self.on_changed()

    def on_change(self):
        self.changed.emit()
Esempio n. 3
0
class TextField(QLineEdit, WidgetMixin):

    changed_signal = Signal()
    activated_signal = Signal()

    def __init__(self, parent, text="", read_only=False):
        QLineEdit.__init__(self, text, parent.get_container())
        # Widget.__init__(self, parent)
        self.init_widget(parent)
        self.setReadOnly(read_only)
        # noinspection PyUnresolvedReferences
        self.textChanged.connect(self.__text_changed)
        # noinspection PyUnresolvedReferences
        self.returnPressed.connect(self.__return_pressed)

        self.changed = SignalWrapper(self, "changed")
        self.activated = SignalWrapper(self, "activated")

    def get_text(self):
        return self.text()

    def set_text(self, text):
        self.setText(text)

    def set_cursor_position(self, position):
        self.setCursorPosition(position)

    def on_changed(self):
        pass

    def __text_changed(self, _):
        self.changed.emit()
        self.on_changed()

    def select_all(self):
        self.selectAll()

    def __return_pressed(self):
        self.activated.emit()
Esempio n. 4
0
class IntervalTimer(QObject):
    activated = Signal()

    def __init__(self, interval):
        super().__init__()
        self.startTimer(interval)

    def __del__(self):
        print("IntervalTimer.__del__", self)

    # noinspection PyPep8Naming
    def timerEvent(self, _):
        self.activated.emit()

    def stop(self):
        print("[TIMER] Stop")
        self.activated.disconnect()
Esempio n. 5
0
class SpinCtrl(Widget):
    changed_signal = Signal()

    def __init__(self, parent, min_value, max_value, initial_value):
        super().__init__()
        self.set_widget(QSpinBox(QParent(parent)))
        self._widget.setRange(min_value, max_value)
        self._widget.setValue(initial_value)
        self._widget.valueChanged.connect(self.__value_changed)
        self.changed = SignalWrapper(self, "changed")

    def get_value(self):
        return self._widget.value()

    def set_value(self, value):
        self._widget.setValue(value)

    def __value_changed(self, _):
        if not self.changed.inhibit:
            self.on_changed()

    def on_change(self):
        self.changed.emit()
Esempio n. 6
0
class Widget(QObject):
    # FIXME: Consider renaming to destroy, since the signal is sent before
    # the object is deleted and children are destroyed
    destroyed = Signal()
    # FIXME: Only toplevel need to have a signal and sent out notifications
    # window_focus_changed = Signal()
    shown = Signal()

    resized = Signal()

    def __init__(self, parent, qwidget):
        super().__init__()
        # self._widget = widget
        # self.__qwidget = ref(widget)  # ??
        self.__qwidget = None
        if qwidget is not None:
            self.set_qwidget(qwidget)

        if parent is not None:
            self._parent = ref(parent)
            # noinspection PyProtectedMember
            self._window = parent._window
        else:
            self._parent = None

        self.__window_focus = False
        # self._widget = None
        self._explicitly_hidden = False
        self._shown = True
        # The timer id, set when starting an interval timer. Will be checked
        # by the event filter
        self.__timer_id = None

        # For debugging purposes, can be used to see from where this widget was
        # created.
        # self._init_stack = traceback.format_stack()

    @deprecated
    def disable(self):
        return self.set_enabled(False)

    @deprecated
    def enable(self, enable=True):
        self.set_enabled(enable)

    def enabled(self):
        return self._qwidget.isEnabled()

    def eventFilter(self, obj, event):
        event_type = event.type()
        if event_type == QEvent.Resize:
            if obj == self._qwidget:
                self.on_resize()

        elif event_type == QEvent.Show:
            if obj == self._qwidget:
                self.on_show()

        elif event_type == QEvent.Timer:
            if event.timerId() == self.__timer_id:
                # print("-> on_timer")
                self.on_timer()
                return True
            # else:
            #     print("other timer event")

        elif event_type == QEvent.WindowActivate:
            # print("activated", obj)
            if obj == self._qwidget:
                if not self.__window_focus:
                    # Important to update this before emitting the signal.
                    self.__window_focus = True
                    # self.window_focus_changed.emit()
                    self.on_window_focus_changed()
                # return True
        elif event_type == QEvent.WindowDeactivate:
            if obj == self._qwidget:
                # print("deactivateEvent", obj)
                if self.__window_focus:
                    # Important to update this before emitting the signal.
                    self.__window_focus = False
                    # self.window_focus_changed.emit()
                    self.on_window_focus_changed()
                # return True

        return False

    def explicitly_hidden(self):
        return self._explicitly_hidden

    def focus(self):
        self._qwidget.setFocus()

    def font(self):
        return Font(self._qwidget.font())

    def get_background_color(self):
        # noinspection PyUnresolvedReferences
        # FIXME: Use cached value from set_background_color?
        return Color(self._qwidget.palette().color(QPalette.Window))

    def get_container(self):
        return self.widget()

    @deprecated
    def get_font(self):
        return self.font()

    def ideal_height(self, width):
        return self.ideal_size_for_dimension(1, width=width)

    def ideal_width(self):
        return self.ideal_size_for_dimension(0)

    def ideal_size(self):
        width = self.ideal_width()
        height = self.ideal_height(width)
        return (width, height)

    def ideal_size_for_dimension(self, d, width=None):
        widget = getattr(self, "_widget", self)
        size = 0

        style = getattr(self, "style", {})
        min_size = style.get("minWidth" if d == 0 else "minHeight")
        max_size = style.get("maxWidth" if d == 0 else "maxHeight")
        size = style.get("width" if d == 0 else "height")

        if min_size is None:
            min_size = getattr(self, "min_width" if d == 0 else "min_height",
                               None)
            # min_width = self.min_width

        def clamp_size(size, min_size, max_size):
            if max_size is not None:
                size = min(size, max_size)
            if min_size is not None:
                size = max(size, min_size)
            return size

        if size is not None:
            if max_size is not None:
                size = min(size, max_size)
            if min_size is not None:
                size = max(size, min_size)
            return clamp_size(size, min_size, max_size)

        if hasattr(self, "layout") and isinstance(self.layout, Layout):
            if d == 0:
                size = self.layout.get_min_width()
            else:
                size = self.layout.get_min_height(width)
            # if hasattr(self, "style"):
            if d == 0:
                size += style.get("paddingLeft", 0) + style.get(
                    "paddingRight", 0)
            else:
                size += style.get("paddingTop", 0) + style.get(
                    "paddingBottom", 0)
            # size = max(layout_size, size)
            # return size
            return clamp_size(size, min_size, max_size)
        # result = max(width, widget.minimumSizeHint().width())
        # if widget.maximumWidth():
        #     print(widget.maximumWidth())
        #     return min(result, widget.maximumWidth())
        # return min(result, widget.maximumWidth())
        # return result
        if d == 0:
            # result = max(size, widget.minimumSizeHint().width())
            size = widget.minimumSizeHint().width()
        else:
            # result = max(size, widget.minimumSizeHint().height())
            size = widget.minimumSizeHint().height()
        # return min(result, widget.maximumWidth())
        if max_size is not None:
            size = min(size, max_size)
        if min_size is not None:
            size = max(size, min_size)
        return clamp_size(size, min_size, max_size)

    def get_min_height(self, width):
        return self.ideal_size_for_dimension(1, width=width)
        # widget = getattr(self, "_widget", self)
        # assert isinstance(widget, QWidget)
        # height = 0
        # if hasattr(self, "min_height"):
        #     if self.min_height:
        #         height = max(self.min_height, height)
        # if hasattr(self, "layout") and isinstance(self.layout, Layout):
        #     layout_height = self.layout.get_min_height(width)

        #     if hasattr(self, "style"):
        #         print(self, "layout_height", layout_height)
        #         layout_height += self.style.padding_top + self.style.padding_bottom
        #         # if self.style.padding_top or self.style.padding_bottom:
        #         print("+ padding", self.style.padding_top, self.style.padding_bottom)
        #     height = max( layout_height, height)
        #     return height
        # return max(height, widget.minimumSizeHint().height())
        # # return max(height, widget.minimumHeight())

    def get_min_width(self):
        return self.ideal_size_for_dimension(0)
        # widget = getattr(self, "_widget", self)
        # width = 0

        # style = getattr(self, "style", {})
        # min_width = style.get("minWidth")
        # max_width = style.get("maxWidth")
        # width = style.get("width")

        # if min_width is None and hasattr(self, "min_width"):
        #     min_width = self.min_width

        # if hasattr(self, "min_width"):
        #     if self.min_width:
        #         width = max(self.min_width, width)
        # if hasattr(self, "width"):
        #     pass
        # if hasattr(self, "layout") and isinstance(self.layout, Layout):
        #     layout_width = self.layout.get_min_width()
        #     if hasattr(self, "style"):
        #         layout_width += self.style.padding_left + self.style.padding_right
        #     width = max(layout_width, width)
        #     return width
        # # result = max(width, widget.minimumSizeHint().width())
        # # if widget.maximumWidth():
        # #     print(widget.maximumWidth())
        # #     return min(result, widget.maximumWidth())
        # # return min(result, widget.maximumWidth())
        # # return result
        # result = max(width, widget.minimumSizeHint().width())
        # return min(result, widget.maximumWidth())
        # # return max(width, widget.minimumWidth())

    @deprecated
    def get_parent(self):
        return self.parent()

    @deprecated
    def get_position(self):
        return self.position()

    @deprecated
    def get_size(self):
        return self.size()

    @deprecated
    def get_window(self):
        # noinspection PyCallingNonCallable
        return self._window()

    def height(self):
        return self.size()[1]

    def hide(self):
        self.set_visible(False)

    def is_enabled(self):
        return self._qwidget.isEnabled()

    @deprecated
    def is_visible(self):
        return self.visible()

    def is_under_mouse(self):
        # underMouse does not seem to work "correctly" when the mouse button is
        # kept pressed. The docs say "This value is not updated properly during
        # drag and drop operations."
        # return self._qwidget.underMouse()

        def get_absolute_widget_position(widget):
            wx = 0
            wy = 0
            while widget:
                pos = widget.position()
                wx += pos[0]
                wy += pos[1]
                widget = widget.parent()
            return wx, wy

        def is_mouse_within_widget(widget):
            wx, wy = get_absolute_widget_position(widget)
            ww, wh = widget.size()
            mx, my = get_mouse_position()
            if mx >= wx and my >= wy and mx < wx + ww and my < wy + wh:
                return True
            else:
                return False

        return is_mouse_within_widget(self)

    def measure_text(self, text):
        font = self._qwidget.font()
        metrics = QFontMetrics(font)
        return metrics.width(text), metrics.height()

    def on_destroy(self):
        self.destroyed.emit()

    def __on_destroyed(self):
        # print(f"Widget.__on_destroyed self={self}")
        self.on_destroy()

    def on_resize(self):
        if hasattr(self, "layout") and isinstance(self.layout, Layout):
            if hasattr(self, "style"):
                x = self.style.padding_left
                y = self.style.padding_top
                width, height = self.size()
                width -= x + self.style.padding_right
                height -= y + self.style.padding_bottom
                self.layout.set_position((x, y))
                self.layout.set_size((width, height))
            else:
                self.layout.set_size(self.size())
            self.layout.update()
        self.resized.emit()

    def on_show(self):
        self.on_resize()
        self.shown.emit()

    def on_timer(self):
        pass

    def on_window_focus_changed(self):
        pass

    def parent(self):
        return self.getParent()

    def getParent(self):
        if self._parent is None:
            return None
        # noinspection PyCallingNonCallable
        return self._parent()

    def popup_menu(self, menu, pos=(0, 0), blocking=True):
        # popup does not block, and if menu goes out of the scope of the
        # caller, it will disappear (unless we keep a reference here
        # FIXME: using exec now
        # self.__last_popup_menu = menu
        widget = getattr(self, "_widget", self)
        global_pos = widget.mapToGlobal(QPoint(pos[0], pos[1]))
        menu.set_parent(self)
        if blocking:
            menu.qmenu.exec_(global_pos)
        else:
            menu.qmenu.popup(global_pos)

        # Firing off a fake mouse left up event really assumes that the
        # menu was opened with a left down event, so implementation isn't
        # ideal. Better to add a listener to menu close instead.

        # if hasattr(self, "on_left_up"):
        #     self.on_left_up()

    def position(self):
        pos = self._qwidget.pos()
        return pos.x(), pos.y()

    @property
    def _qwidget(self):
        # return self.__qwidget()
        return self.__qwidget

    def refresh(self):
        return self._qwidget.update()

    def set_background_color(self, color):
        # FIXME: Check if background_color is already set
        w = self._qwidget
        if color is not None:
            w.setAutoFillBackground(True)
            p = w.palette()
            p.setColor(w.backgroundRole(), color)
            w.setPalette(p)
        else:
            print("FIXME: Clear background color")
            w.setAutoFillBackground(False)

    def set_enabled(self, enabled=True):
        self._qwidget.setEnabled(enabled)

    def set_font(self, font):
        self._qwidget.setFont(font.font)

    def set_hand_cursor(self):
        self._qwidget.setCursor(Qt.PointingHandCursor)

    def set_min_height(self, height):
        # noinspection PyAttributeOutsideInit
        self.min_height = height

    def set_min_size(self, size):
        # noinspection PyAttributeOutsideInit
        self.min_width = size[0]
        # noinspection PyAttributeOutsideInit
        self.min_height = size[1]

    def set_min_width(self, width):
        # noinspection PyAttributeOutsideInit
        self.min_width = width

    def set_move_cursor(self):
        self._qwidget.setCursor(Qt.SizeAllCursor)

    def set_resize_cursor(self):
        self._qwidget.setCursor(Qt.SizeFDiagCursor)

    def set_normal_cursor(self):
        self._qwidget.setCursor(Qt.ArrowCursor)

    def set_position(self, position, y=None):
        if y is None:
            self._qwidget.move(position[0], position[1])
        else:
            self._qwidget.move(position, y)

    def set_position_and_size(self, position, size):
        self._qwidget.setGeometry(position[0], position[1], size[0], size[1])

    def set_size(self, size):
        self._qwidget.resize(size[0], size[1])

    def set_tooltip(self, tooltip):
        widget = getattr(self, "_widget", self)
        widget.setToolTip(tooltip)

    def set_visible(self, show=True):
        was_shown = self._shown
        if show:
            self._qwidget.show()
        else:
            self._qwidget.hide()
        self._explicitly_hidden = not show
        self._shown = show
        return was_shown != show

    @deprecated
    def set_widget(self, widget):
        self.set_qwidget(widget)

    def set_qwidget(self, widget):
        # self._widget = widget
        # self.__qwidget = ref(widget)
        self.__qwidget = widget
        self._qwidget.installEventFilter(self)
        self._qwidget.destroyed.connect(self.__on_destroyed)

    def show(self):
        self.set_visible(True)

    @deprecated
    def show_or_hide(self, show=True):
        self.set_visible(show)

    def size(self):
        return self._qwidget.width(), self._qwidget.height()

    def start_timer(self, interval):
        self.__timer_id = self._qwidget.startTimer(interval)

    def visible(self):
        return self._qwidget.isVisible()

    # FIXME: Deprecated
    @property
    def _widget(self):
        return self._qwidget

    # FIXME: Deprecated?
    def widget(self):
        return self._widget

    def width(self):
        return self.size()[0]

    @property
    def window(self):
        # noinspection PyCallingNonCallable
        return self._window()

    def getWindow(self):
        return self._window()

    def window_focus(self):
        return self.__window_focus
class VerticalItemView(Widget):
    item_selected = Signal(int)
    item_activated = Signal(int)

    def __init__(self, parent, border=True):
        super().__init__(parent, QListView(QParent(parent)))
        # FIXME: Why?
        # self._qwidget.viewport().installEventFilter(self.get_window())
        # self._qwidget.verticalScrollBar().installEventFilter(self.get_window())
        if not border:
            self._qwidget.setFrameStyle(0)

        # self.setSelectionModel()
        # self.model = QStandardItemModel(self)
        self.model = Model(self)
        self._qwidget.setModel(self.model)
        # self.itemSelectionChanged.connect(self.__selection_changed)
        selection_model = self._qwidget.selectionModel()
        print("VerticalItemView selectionModel = ", selection_model)
        selection_model.selectionChanged.connect(self.__selection_changed)
        self._qwidget.doubleClicked.connect(self.__double_clicked)
        # self.returnPressed.connect(self.__double_clicked)
        # self.activated.connect(self.__double_clicked)
        self._row_height = 26

    def set_row_height(self, height):
        self._row_height = height

    # FIXME
    # def keyPressEvent(self, event):
    #     if event.key() == Qt.Key_Return:
    #         self.__double_clicked()
    #     else:
    #         QListView.keyPressEvent(self, event)

    def __double_clicked(self):
        index = self.index()
        if index is not None:
            self.on_activate_item(index)
            self.item_activated.emit(index)

    def __selection_changed(self):
        index = self.index()
        self.on_select_item(index)
        self.item_selected.emit(index)

    def on_activate_item(self, index):
        pass

    def get_item_icon(self, index):
        return None

    def get_item_text(self, index):
        return ""

    def get_item_text_color(self, index):
        return None

    # def set_item_count(self, count):
    #     #self.model.rowCoun
    #     self.model.set_item_count(count)
    #     #self.update()
    #     #self.invalidate()
    #     self.dataChanged(self.model.createIndex(0, 0),
    #             self.model.createIndex(count, 0))

    def set_default_icon(self, image):
        pass

    # def set_items(self, items):
    #    #print("set_items", items)
    #    self.model.clear()
    #    for label in items:
    #        item = QStandardItem(label)
    #        self.model.appendRow(item)

    def index(self):
        indices = self._qwidget.selectionModel().selectedIndexes()
        if len(indices) == 0:
            return None
        return indices[0].row()

    def set_index(self, index):
        if index is None:
            index = -1
        # print(self.rootIndex)
        # idx = QModelIndex.createIndex(index)
        idx = self.model.index(index, 0)
        self._qwidget.scrollTo(idx)
        self._qwidget.setCurrentIndex(idx)

    def select_item(self, index):
        self.set_index(index)

    def on_select_item(self, index):
        pass

    def update(self):
        # self.model.rowCoun
        count = self.get_item_count()
        # self.model.set_item_count(count)
        # self.update()
        # self.invalidate()
        self._qwidget.dataChanged(self.model.createIndex(0, 0),
                                  self.model.createIndex(count, 0))
Esempio n. 8
0
class Choice(QComboBox, WidgetMixin):

    changed_signal = Signal()
    item_selected = Signal(int)
    ITEM_SEPARATOR = "---"

    def __init__(self, parent, items=None, cursor_keys=True):
        if items is None:
            items = []
        QComboBox.__init__(self, parent.get_container())
        # Widget.__init__(self, parent)
        self.init_widget(parent)
        self.inhibit_change_event = False
        self.cursor_keys = cursor_keys

        for i, item in enumerate(items):
            self.insertItem(i, item)

        if len(items) > 0:
            self.set_index(0)
        self.currentIndexChanged.connect(self.__current_index_changed)

        self.changed = SignalWrapper(self, "changed")
        # self.changed.inhibit = self.inhibit_signal

    def keyPressEvent(self, event):
        if not self.cursor_keys:
            print("cursor keys is false", event.key(), Qt.Key_Up)
            if event.key() == Qt.Key_Up or event.key() == Qt.Key_Down:
                print("ignoring")
                return
        super().keyPressEvent(event)

    # @contextmanager
    # def inhibit_signal(self, name):
    #     attr = "_inhibit_" + name
    #     old = getattr(self, attr, False)
    #     print("setattr", self, attr, True)
    #     setattr(self, attr, True)
    #     yield
    #     print("setattr", self, attr, old)
    #     setattr(self, attr, old)

    def add_item(self, label, icon=None):
        # item = QStandardItem(label)
        # if icon:
        #     item.setIcon(icon.qicon)
        # item.setSizeHint(QSize(-1, 24))
        if label == self.ITEM_SEPARATOR:
            self.insertSeparator(self.count())
        elif icon is not None:
            self.addItem(icon.qicon, label)
        else:
            self.addItem(label)
        return self.count() - 1

    def remove_item(self, index):
        self.removeItem(index)

    def __current_index_changed(self):
        # print("__current_index_changed", self.currentIndex(),
        #       "inhibit", self.inhibit_change_event)
        if not self.inhibit_change_event:
            # print("Choice.__current_index_changed")
            # if not getattr(self, "_inhibit_changed", False):
            if not self.changed.inhibit:
                if not getattr(self, "_inhibit_item_selected", False):
                    index = self.currentIndex()
                    self.item_selected.emit(index)
                self.changed.emit()
                self.on_changed()

    def get_index(self):
        return self.currentIndex()

    def set_index(self, index, signal=True):
        try:
            if not signal:
                self.inhibit_change_event = True
            self.setCurrentIndex(-1 if index is None else index)
        finally:
            if not signal:
                self.inhibit_change_event = False

    def set_item_text(self, index, text):
        self.setItemText(index, text)

    def on_changed(self):
        pass

    def __len__(self):
        return self.count()
Esempio n. 9
0
class Choice(Widget):
    changed_signal = Signal()
    item_selected = Signal(int)
    ITEM_SEPARATOR = "---"

    def __init__(self, parent, items=None, cursor_keys=True):
        if items is None:
            items = []
        super().__init__(parent, QComboBox(QParent(parent)))
        self.inhibit_change_event = False
        self.cursor_keys = cursor_keys

        for i, item in enumerate(items):
            self._qwidget.insertItem(i, item)
        if len(items) > 0:
            self.set_index(0)
        self._qwidget.currentIndexChanged.connect(
            self.__on_current_index_changed)
        self.changed = SignalWrapper(self, "changed")
        self.update_style()

    # FIXME: This needs to be fixed
    def keyPressEvent(self, event):
        if not self.cursor_keys:
            print("cursor keys is false", event.key(), Qt.Key_Up)
            if event.key() == Qt.Key_Up or event.key() == Qt.Key_Down:
                print("ignoring")
                return
        super().keyPressEvent(event)

    # @contextmanager
    # def inhibit_signal(self, name):
    #     attr = "_inhibit_" + name
    #     old = getattr(self, attr, False)
    #     print("setattr", self, attr, True)
    #     setattr(self, attr, True)
    #     yield
    #     print("setattr", self, attr, old)
    #     setattr(self, attr, old)

    def add_item(self, label, icon=None):
        # item = QStandardItem(label)
        # if icon:
        #     item.setIcon(icon.qicon)
        # item.setSizeHint(QSize(-1, 24))
        if label == self.ITEM_SEPARATOR:
            self._qwidget.insertSeparator(self.count())
        elif icon is not None:
            self._qwidget.addItem(icon.qicon, label)
        else:
            self._qwidget.addItem(label)
        return self.count() - 1

    def clear(self):
        return self._qwidget.clear()

    def count(self):
        return self._qwidget.count()

    def index(self):
        return self._qwidget.currentIndex()

    def on_changed(self):
        pass

    def __on_current_index_changed(self):
        # print("__current_index_changed", self.currentIndex(),
        #       "inhibit", self.inhibit_change_event)
        if not self.inhibit_change_event:
            # print("Choice.__current_index_changed")
            # if not getattr(self, "_inhibit_changed", False):
            if not self.changed.inhibit:
                if not getattr(self, "_inhibit_item_selected", False):
                    index = self._qwidget.currentIndex()
                    self.item_selected.emit(index)
                self.changed.emit()
                self.on_changed()

    def remove_item(self, index):
        self._qwidget.removeItem(index)

    def set_index(self, index, signal=True):
        try:
            if not signal:
                self.inhibit_change_event = True
            self._qwidget.setCurrentIndex(-1 if index is None else index)
        finally:
            if not signal:
                self.inhibit_change_event = False

    def set_item_text(self, index, text):
        self._qwidget.setItemText(index, text)

    def update_style(self):
        # There seems to be an issue with specifying padding-top and
        # padding-bottom for a QComboBox. The internal pop menu also gets
        # padding added, resulting in ugly white borders at the top/bottom of
        # the popup, and there seems to be no way to remove this. So instead,
        # we calculate minimum height based on font height manually.
        theme = get_theme(self)
        padding = theme.choice_padding()
        if padding:
            fontmetrics = QFontMetrics(self._qwidget.font())
            fontheight = fontmetrics.height()
            print(fontheight)
            # FIXME: Assumed
            border = 4
            min_height = fontheight + padding.top + padding.bottom + border
            self.set_min_height(min_height)
        self._qwidget.setStyleSheet(f"""
            QComboBox {{
                padding-right: 8px;
                padding-left: 8px;
            }}
        """)
Esempio n. 10
0
class Widget(QObject):

    destroyed = Signal()

    def __init__(self, parent, *_):
        super().__init__()
        self._parent = None
        # noinspection PyProtectedMember
        self._window = parent._window
        self._widget = None

    def widget(self):
        return self._widget

    def get_container(self):
        return self.widget()

    def set_widget(self, widget):
        self._widget = widget
        # self.init_mixin_base()
        # self.layout = None
        # self._parent = weakref.ref(parent)
        # noinspection PyProtectedMember
        # self._window = parent._window
        # widget = getattr(self, "_widget", self)
        # widget.move(10000, 10000)

        # if self.get_window() != widget:
        # assert isinstance(widget, QWidget)

        widget.installEventFilter(self)
        widget.destroyed.connect(self.on_destroy)

    def on_destroy(self):
        print("Widget.on_destroy", self)
        self.destroyed.emit()

    def set_visible(self, show=True):
        if show:
            self.widget().show()
        else:
            self.widget().hide()

    def show(self):
        self.set_visible(True)

    def hide(self):
        self.set_visible(False)

    def eventFilter(self, obj, event):
        return False

    def parent(self):
        if self._parent is None:
            return None
        # noinspection PyCallingNonCallable
        return self._parent()

    def font(self):
        return Font(self.widget().font())

    def set_font(self, font):
        self.widget().setFont(font.font)

    def is_visible(self):
        return self.widget().isVisible()

    def measure_text(self, text):
        font = self.widget().font()
        metrics = QFontMetrics(font)
        return metrics.width(text), metrics.height()

    def set_hand_cursor(self):
        self.widget().setCursor(Qt.PointingHandCursor)

    def set_normal_cursor(self):
        self.widget().setCursor(Qt.ArrowCursor)

    def is_enabled(self):
        return self.widget().isEnabled()

    def focus(self):
        self.widget().setFocus()

    def refresh(self):
        return self.widget().update()

    def set_min_size(self, size):
        # noinspection PyAttributeOutsideInit
        self.min_width = size[0]
        # noinspection PyAttributeOutsideInit
        self.min_height = size[1]

    def set_min_width(self, width):
        # noinspection PyAttributeOutsideInit
        self.min_width = width

    def set_min_height(self, height):
        # noinspection PyAttributeOutsideInit
        self.min_height = height

    def get_min_width(self):
        widget = getattr(self, "_widget", self)
        width = 0
        if hasattr(self, "min_width"):
            if self.min_width:
                width = max(self.min_width, width)
        if hasattr(self, "layout") and isinstance(self.layout, Layout):
            width = max(self.layout.get_min_width(), width)
            return width
        # result = max(width, widget.minimumSizeHint().width())
        # if widget.maximumWidth():
        #     print(widget.maximumWidth())
        #     return min(result, widget.maximumWidth())
        # return min(result, widget.maximumWidth())
        # return result
        result = max(width, widget.minimumSizeHint().width())
        return min(result, widget.maximumWidth())
        # return max(width, widget.minimumWidth())

    def get_min_height(self):
        widget = getattr(self, "_widget", self)
        assert isinstance(widget, QWidget)
        height = 0
        if hasattr(self, "min_height"):
            if self.min_height:
                height = max(self.min_height, height)
        if hasattr(self, "layout") and isinstance(self.layout, Layout):
            height = max(self.layout.get_min_height(), height)
            return height
        return max(height, widget.minimumSizeHint().height())
        # return max(height, widget.minimumHeight())

    def set_position(self, position, y=None):
        if y is None:
            self.widget().move(*position)
        else:
            self.widget().move(position, y)

    def set_size(self, size):
        self.widget().resize(*size)

    def set_position_and_size(self, position, size):
        self.widget().setGeometry(position[0], position[1], size[0], size[1])

    def get_window(self):
        # noinspection PyCallingNonCallable
        return self._window()

    def position(self):
        return self.widget().x(), self.widget().y()

    def size(self):
        return self.widget().width(), self.widget().height()

    def width(self):
        return self.size()[0]

    def height(self):
        return self.size()[1]

    def set_tool_tip(self, tool_tip):
        widget = getattr(self, "_widget", self)
        widget.setToolTip(tool_tip)

    def set_tooltip(self, tool_tip):
        self.set_tool_tip(tool_tip)

    def disable(self):
        return self.enable(False)

    def enable(self, enable=True):
        widget = getattr(self, "_widget", self)
        widget.setEnabled(enable)

    def set_enabled(self, enable=True):
        self.enable(enable)

    def on_resize(self):
        if hasattr(self, "layout") and isinstance(self.layout, Layout):
            self.layout.set_size(self.get_size())
            self.layout.update()

    def get_background_color(self):
        # noinspection PyUnresolvedReferences
        return Color(self.widget().palette().color(QPalette.Window))

    def set_background_color(self, color):
        widget = self.widget()
        widget.setAutoFillBackground(True)
        p = widget.palette()
        p.setColor(widget.backgroundRole(), color)
        widget.setPalette(p)

    def popup_menu(self, menu, pos=(0, 0), blocking=True):
        # popup does not block, and if menu goes out of the scope of the
        # caller, it will disappear (unless we keep a reference here
        # FIXME: using exec now
        # self.__last_popup_menu = menu
        widget = getattr(self, "_widget", self)
        global_pos = widget.mapToGlobal(QPoint(pos[0], pos[1]))
        menu.set_parent(self)
        if blocking:
            menu.qmenu.exec(global_pos)
        else:
            menu.qmenu.popup(global_pos)

        # Firing off a fake mouse left up event really assumes that the
        # menu was opened with a left down event, so implementation isn't
        # ideal. Better to add a listener to menu close instead.

        # if hasattr(self, "on_left_up"):
        #     self.on_left_up()

    def is_mouse_over(self):
        # # noinspection PyArgumentList
        # c = QCursor.pos()
        # # noinspection PyUnresolvedReferences
        # p = self.widget().mapToGlobal(QPoint(0, 0))
        # s = self.size()
        # print(c, p, s)
        # if p.x() <= c.x() < p.x() + s[0] and p.y() <= c.y() < p.y() + s[1]:
        #     return True
        # return False
        return self.widget().underMouse()

    # DEPRECATED
    def get_parent(self):
        return self.parent()

    # DEPRECATED
    def visible(self):
        return self.is_visible()

    # DEPRECATED
    def get_font(self):
        return self.font()

    # DEPRECATED
    def show_or_hide(self, show=True):
        self.set_visible(show)

    # DEPRECATED
    def get_position(self):
        return self.position()

    # DEPRECATED
    def get_size(self):
        return self.size()
class LegacyDialog(QDialog):
    closed = Signal()

    def __init__(self, parent=None, title=""):
        QDialog.__init__(self, QParent(parent))
        self._window = weakref.ref(self)

        self.layout = None
        self.setWindowTitle(title)

        # self.container = wx.Panel(self)
        # self.container.get_window = self.get_window
        # self.Bind(wx.EVT_SIZE, self.__resize_event)
        # self.Bind(wx.EVT_WINDOW_DESTROY, self.__destroy_event)
        # self.Bind(wx.EVT_CLOSE, self.__close_event)
        self.destroy_listeners = []
        self.close_listeners = []

    def get_parent(self):
        return None

    def closeEvent(self, event):
        print("Dialog.closeEvent")
        self.closed.emit()
        self.on_close()
        for function in self.close_listeners:
            print(function)
            function()
        event.accept()
        # remove close listeners so they will not keep the object alive
        self.close_listeners = []

    def add_close_listener(self, function):
        self.close_listeners.append(function)

    def get_window(self):
        return self

    def get_container(self):
        return self

    # def show(self):
    #     self.Show()

    # def close(self):
    #     self.Close()

    def show_modal(self):
        # self.setModal(True)
        # return self.showModal()
        return self.exec_()

    def end_modal(self, value):
        # self.EndModal(value)
        self.done(value)

    def center_on_parent(self):
        real_parent = self.parent()
        if real_parent:
            pp = real_parent.x(), real_parent.y()
            ps = real_parent.width(), real_parent.height()
            ss = self.get_size()
            self.move(pp[0] + (ps[0] - ss[0]) // 2,
                      pp[1] + (ps[1] - ss[1]) // 2)
        # elif self.default_center:
        #     x, y = self.default_center
        #     ss = self.get_size()
        #     self.move(x - ss[0] // 2, y - ss[1] // 2,)

    # def destroy(self):
    #     #self.Destroy()
    #     print("FIXME: Dialog.destroy does nothing")

    def set_title(self, title):
        self.setWindowTitle(title)

    def set_size(self, size):
        # self.SetClientSize(size)
        # print("FIXME:\n\nDialog.set_size")
        self.resize(size[0], size[1])

    def on_create(self):
        pass

    def on_close(self):
        pass

    def on_destroy(self):
        pass

    def __destroy_event(self, event):
        self.on_destroy()

    def __close_event(self, event):
        print("__close_event")
        for function in self.close_listeners:
            function()
        self.on_close()
        self.Destroy()

    def showEvent(self, event):
        self.on_resize()

    def get_size(self):
        return self.width(), self.height()

    def resizeEvent(self, event):
        print("resized..")
        self.on_resize()

    def on_resize(self):
        if self.layout:
            self.layout.set_size(self.get_size())
            self.layout.update()

    def raise_and_activate(self):
        self.raise_()
        self.activateWindow()
Esempio n. 12
0
class TextArea(Widget):
    changed = Signal()

    def __init__(
        self,
        parent,
        text="",
        read_only=False,
        font_family=None,
        border=True,
        line_wrap=True,
        text_color=None,
        background_color=None,
        padding=None,
    ):
        super().__init__(parent, QTextEdit("", QParent(parent)))
        if not border:
            self._qwidget.setFrameStyle(QFrame.NoFrame)
        self._qwidget.setReadOnly(read_only)
        if font_family:
            print("FIXME: not respecting font_family yet")
            font = QFont("Courier")
            # font.setStyleHint(QtGui.QFont.TypeWriter)
            self._qwidget.setFont(font)
        if line_wrap == False:
            self._qwidget.setLineWrapMode(QTextEdit.NoWrap)
        if text:
            self.append_text(text)

        stylesheet = []
        if text_color:
            stylesheet.append(f"color: {text_color.to_hex()};")
        if background_color:
            stylesheet.append(
                f"background-color: {background_color.to_hex()};")
        if padding:
            stylesheet.append(f"padding: {padding};")
        if stylesheet:
            nl = "\n"
            stylesheet_str = f"QTextEdit {{\n{nl.join(stylesheet)}\n}}\n"
            # print(stylesheet_str)
            self._qwidget.setStyleSheet(stylesheet_str)

        self._qwidget.textChanged.connect(self.__text_changed)

    def get_text(self):
        return self._qwidget.toPlainText()

    def set_text(self, text):
        self._qwidget.setPlainText(text.replace("\n", "\r\n"))

    def append_text(self, text, color=None):
        # text = text.replace("\n", "\r\n")
        # print("Appending text:", repr(text))
        # self.moveCursor(QTextCursor.End)
        # self.insertPlainText(text.strip())
        if color is not None:
            self._qwidget.setTextColor(QColor(*color))
        # self.appendPlainText(text.strip())
        self._qwidget.append(text)
        self._qwidget.moveCursor(QTextCursor.End)

    def scroll_to_start(self):
        self._qwidget.moveCursor(QTextCursor.Start)

    def __text_changed(self):
        self.changed.emit()
class TextField(Widget):
    changed_signal = Signal()
    activated_signal = Signal()

    # FIXME: Insert * after parent
    def __init__(
        self,
        parent,
        text="",
        read_only=False,
        placeholder="",
        clearbutton=False,
        passwordMode=False,
    ):
        super().__init__(parent, QLineEdit(text, QParent(parent)))
        # Widget.__init__(self, parent)
        # self.init_widget(parent)
        self._qwidget.setReadOnly(read_only)

        self._has_text = text != ""
        self.update_color()

        # noinspection PyUnresolvedReferences
        self._qwidget.textChanged.connect(self.__on_text_changed)
        # noinspection PyUnresolvedReferences
        self._qwidget.returnPressed.connect(self.__on_return_pressed)

        self.changed = SignalWrapper(self, "changed")
        self.activated = SignalWrapper(self, "activated")

        if passwordMode:
            self._qwidget.setEchoMode(QLineEdit.Password)
        if placeholder:
            self._qwidget.setPlaceholderText(placeholder)
        if clearbutton:
            self._qwidget.setClearButtonEnabled(True)
        self.update_style()

    def update_style(self):
        # There seems to be an issue with specifying padding-top and
        # padding-bottom for a QSpinBox.
        theme = get_theme(self)
        padding = theme.textfield_padding()
        if not padding:
            # Indicates that we do not want custom styling
            return
        fontmetrics = QFontMetrics(self._qwidget.font())
        fontheight = fontmetrics.height()
        print(fontheight)
        border = 4
        min_height = fontheight + padding.top + padding.bottom + border
        self.set_min_height(min_height)
        print("MINHEIGHT (TEXTFIELD)", min_height)
        has_text = self.text() != ""
        self._qwidget.setStyleSheet(f"""
            QLineEdit {{
                color: {"#000000" if has_text else "#666666"};
                padding-right: {padding.right}px;
                padding-left: {padding.left}px;
            }}
            """)

    def update_color(self):
        has_text = self.text() != ""
        if has_text != self._has_text:
            self._has_text = has_text
            self.update_style()
        # self.setStyleSheet(f"""
        #     QLineEdit[text=""] {{
        #         color: {"#000000" if has_text else "#666666"};
        #     }}
        #     """
        # )

    @deprecated
    def value(self):
        return self.text()

    @deprecated
    def get_text(self):
        return self.text()

    def on_changed(self):
        pass

    def __on_return_pressed(self):
        self.activated.emit()

    def __on_text_changed(self, _):
        self.update_color()
        self.changed.emit()
        self.on_changed()

    def select_all(self):
        self._qwidget.selectAll()

    def set_cursor_position(self, position):
        self._qwidget.setCursorPosition(position)

    def set_text(self, text):
        self._qwidget.setText(text)

    def text(self):
        return self._qwidget.text()
Esempio n. 14
0
class ListView(Widget):
    item_selected = Signal(int)
    item_activated = Signal(int)

    def __init__(self, parent, border=True):
        # self = QListView(parent.get_container())
        super().__init__(parent, QListView(QParent(parent)))
        # Widget.__init__(self, parent)
        # self.init_widget(parent)

        # FIXME: Hmmm...?
        # self._qwidget.viewport().installEventFilter(self.get_window())
        # self._qwidget.verticalScrollBar().installEventFilter(self.get_window())

        if not border:
            self._qwidget.setFrameStyle(0)

        # self.setSelectionModel()
        self._model = QStandardItemModel(self)
        # self.setModel(self._model)
        self._qwidget.setModel(self._model)
        # self.itemSelectionChanged.connect(self._on_selection_changed)
        selection_model = self._qwidget.selectionModel()
        print("QListView selectionModel", selection_model)
        selection_model.selectionChanged.connect(self.__on_selection_changed)
        self._qwidget.setEditTriggers(QListView.NoEditTriggers)
        self._qwidget.doubleClicked.connect(self.__on_double_clicked)
        # self.returnPressed.connect(self.__double_clicked)
        # self.activated.connect(self.__double_clicked)
        self._row_height = 26

    def add_item(self, label, icon=None, bold=False):
        item = QStandardItem(label)
        if icon:
            try:
                item.setIcon(icon.qicon(16))
            except TypeError:
                item.setIcon(icon.qicon)
        item.setSizeHint(QSize(-1, self._row_height))
        if bold:
            font = self._qwidget.font()
            font.setWeight(QFont.Bold)
            item.setFont(font)
        self._model.appendRow(item)

    def clear(self):
        self._model.clear()

    def get_item(self, index):
        return self._model.item(index).text()

    def get_item_count(self):
        return self._model.rowCount()

    # FIXME:
    # def keyPressEvent(self, event):
    #     if event.key() == Qt.Key_Return:
    #         self.__double_clicked()
    #     else:
    #         super().keyPressEvent(event)

    def on_activate_item(self, index):
        pass

    def __on_double_clicked(self):
        index = self.index()
        if index is not None:
            self.on_activate_item(index)
            self.item_activated.emit(index)

    def __on_selection_changed(self):
        index = self.index()
        self.on_select_item(index)
        self.item_selected.emit(index)

    def set_default_icon(self, image):
        pass

    def set_items(self, items):
        self._model.clear()
        for item in items:
            if isinstance(item, str):
                self.add_item(item)
            elif isinstance(item, dict):
                label = item["label"]
                icon = item.get("icon", None)
                bold = item.get("bold", False)
                self.add_item(label, icon, bold)
            else:
                label, icon = item
                self.add_item(label, icon)

    def index(self):
        indices = self._qwidget.selectionModel().selectedIndexes()
        if len(indices) == 0:
            return None
        return indices[0].row()

    def on_select_item(self, index):
        print("calling item_selected.emit")
        self.item_selected.emit(index)

    def select_item(self, index):
        self.set_index(index)

    def set_index(self, index):
        if index is None:
            index = -1
        idx = self._model.index(index, 0)
        self._qwidget.scrollTo(idx)
        self._qwidget.setCurrentIndex(idx)

    def set_row_height(self, height):
        self._row_height = height