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()
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()
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()
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()
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))
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()
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; }} """)
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()
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()
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