예제 #1
0
class RegexPatternValidator(gui.Validator):
    error_occured = core.Signal(str)
    pattern_updated = core.Signal(object)

    def __repr__(self):
        return f"{type(self).__name__}()"

    def __eq__(self, other: object):
        return isinstance(other, type(self))

    def validate(  # type: ignore
            self,
            text: str,
            pos: int = 0) -> tuple[QtGui.QValidator.State, str, int]:
        # if text == "":
        #     self.compiled = None
        #     return (self.Intermediate, text, pos)
        try:
            compiled = re.compile(text)
        except sre_constants.error as e:
            self.error_occured.emit(str(e))
            self.pattern_updated.emit(None)
            return self.State.Intermediate, text, pos
        except re._regex_core.error as e:
            self.error_occured.emit(str(e))
            self.pattern_updated.emit(None)
            return self.State.Intermediate, text, pos
        else:
            self.error_occured.emit("")
            self.pattern_updated.emit(compiled)
            return self.State.Acceptable, text, pos
예제 #2
0
class ScrollBar(QtWidgets.QScrollBar):

    value_changed = core.Signal(int)

    def __init__(self, orientation="horizontal", parent=None):
        super().__init__(ORIENTATIONS[orientation], parent)
        self.valueChanged.connect(self.on_value_change)
예제 #3
0
class TimeEdit(QtWidgets.QTimeEdit):

    value_changed = core.Signal(datetime.datetime)

    def __getstate__(self):
        return dict(calendar_popup=self.calendarPopup(),
                    time=self.get_time(),
                    display_format=self.displayFormat(),
                    range=(self.min_time(), self.max_time()),
                    tooltip=self.toolTip(),
                    statustip=self.statusTip(),
                    enabled=self.isEnabled())

    def __setstate__(self, state):
        self.__init__(state["time"])
        self.setEnabled(state.get("enabled", True))
        self.setDisplayFormat(state["display_format"])
        self.set_range(*state["range"])
        self.setToolTip(state.get("tooltip", ""))
        self.setStatusTip(state.get("statustip", ""))

    def set_range(self, lower: datetime.time, upper: datetime.time):
        self.setToolTip(f"{lower} <= x <= {upper}")
        self.setTimeRange(lower, upper)

    def get_value(self) -> datetime.time:
        return self.get_time()

    def set_value(self, value: datetime.time):
        return self.setTime(value)
예제 #4
0
class DetachedTab(widgets.MainWindow):
    """window containing a detached tab

    When a tab is detached, the contents are placed into this QMainWindow.
    The tab can be re-attached by closing the dialog

    Attributes:
        on_close: signal, emitted when window is closed (widget, title, icon)
    """
    on_close = core.Signal(QtWidgets.QWidget, str, QtGui.QIcon)

    def __init__(self, name, widget):
        super().__init__(None)

        self.id = name
        self.title = name

        self.widget = widget
        self.setCentralWidget(self.widget)
        self.widget.show()

    #  If the window is closed, emit the on_close and give the
    #  content widget back to the DetachableTabWidget
    def closeEvent(self, event):
        self.on_close.emit(self.widget, self.id, self.windowIcon())
예제 #5
0
class RadioButton(QtWidgets.QRadioButton):

    value_changed = core.Signal(bool)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.toggled.connect(self.value_changed)
예제 #6
0
class SingleApplication(widgets.Application):

    messageReceived = core.Signal(str)

    def __init__(self, app_id: str):

        super().__init__(sys.argv)
        self.app_id = app_id
        self._activate_on_message = True

        # Is there another instance running?
        out_socket = network.LocalSocket()
        out_socket.connect_to_server(self.app_id)
        self._is_running = out_socket.waitForConnected()
        self._in_socket = None
        self._in_stream = None
        if not self._is_running:
            self._out_socket = None
            self._out_stream = None
            self._server = network.LocalServer()
            self._server.listen(self.app_id)
            self._server.newConnection.connect(self._on_new_connection)
        else:
            self._out_socket = out_socket
            self._out_stream = core.TextStream(self._out_socket)
            self._out_stream.set_codec("UTF-8")

    def is_running(self) -> bool:
        return self._is_running

    def activate_window(self):
        window = self.get_mainwindow()
        if window is None:
            return
        window.raise_to_top()

    def send_message(self, msg: str) -> bool:
        if self._out_stream is None or self._out_socket is None:
            return False
        self._out_stream << msg << "\n"
        self._out_stream.flush()
        return self._out_socket.waitForBytesWritten()

    def _on_new_connection(self):
        if self._in_socket:
            self._in_socket.readyRead.disconnect(self._on_ready_read)
        self._in_socket = self._server.nextPendingConnection()  # type: ignore
        if self._in_socket is None:
            return
        self._in_stream = core.TextStream(self._in_socket)
        self._in_stream.set_codec("UTF-8")
        self._in_socket.readyRead.connect(self._on_ready_read)
        if self._activate_on_message:
            self.activate_window()

    def _on_ready_read(self):
        if self._in_stream is None:
            raise RuntimeError()
        for msg in self._in_stream.read_lines():
            self.messageReceived.emit(msg)
예제 #7
0
class DateEdit(QtWidgets.QDateEdit):

    value_changed = core.Signal(datetime.datetime)

    def __setstate__(self, state):
        super().__setstate__(state)
        self.setDate(state["date"])
        self.set_range(*state["range"])

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    def serialize_fields(self):
        return dict(
            date=self.get_date(),
            range=(self.min_date(), self.max_date()),
        )

    def set_value(self, value: types.DateType):
        if isinstance(value, str):
            value = QtCore.QDate.fromString(value)
        self.setDate(value)  # type: ignore

    def set_range(self, lower: types.DateType, upper: types.DateType):
        if isinstance(lower, str):
            lower = QtCore.QDate.fromString(lower)
        if isinstance(upper, str):
            upper = QtCore.QDate.fromString(upper)
        self.setToolTip(f"{lower} <= x <= {upper}")
        self.setDateRange(lower, upper)  # type: ignore

    def get_value(self) -> datetime.date:
        return self.get_date()
예제 #8
0
class FontChooserButton(widgets.Widget):

    value_changed = core.Signal(gui.Font)

    def __init__(
        self,
        font: QtGui.QFont | None = None,
        parent: QtWidgets.QWidget | None = None,
    ):
        super().__init__(parent)
        self._current_font = font
        layout = widgets.BoxLayout("horizontal", self)
        layout.set_margin(0)
        self.lineedit = widgets.LineEdit()
        self.lineedit.set_read_only()
        layout.add(self.lineedit)
        action = widgets.Action()
        action.triggered.connect(self.choose_font)
        self.button = widgets.ToolButton()
        self.button.setDefaultAction(action)
        layout.add(self.button)

    def __repr__(self):
        return f"{type(self).__name__}({self._current_font})"

    def serialize_fields(self):
        return dict(current_font=self._current_font)

    def __setstate__(self, state):
        super().__setstate__(state)
        if state["current_font"]:
            self.set_value(state["current_font"])
        self.set_enabled(state.get("enabled", True))

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    @core.Slot()
    def choose_font(self):
        dlg = widgets.FontDialog()
        if self._current_font:
            dlg.setCurrentFont(self._current_font)

        if dlg.main_loop():
            self.set_current_font(dlg.current_font())
            self.value_changed.emit(dlg.current_font())

    def set_current_font(self, font: str | QtGui.QFont):
        if isinstance(font, str):
            self._current_font = gui.Font(font)
        else:
            self._current_font = font
        self.lineedit.setText(self._current_font.family())

    def set_value(self, value: str | QtGui.QFont):
        self.set_current_font(value)

    def get_value(self):
        return self._current_font
예제 #9
0
class ColorChooserButton(widgets.Widget):

    value_changed = core.Signal(gui.Color)

    def __init__(self, color=None, parent=None):
        super().__init__(parent)
        layout = widgets.BoxLayout("horizontal", self)
        layout.set_margin(0)
        self.lineedit = widgets.LineEdit()
        self.lineedit.set_regex_validator(r"^#(?:[0-9a-fA-F]{6})$")
        layout += self.lineedit
        action = widgets.Action(icon="mdi.format-color-fill")
        action.triggered.connect(self.choose_color)
        self.button = widgets.ToolButton()
        self.button.setDefaultAction(action)
        layout += self.button
        if color is not None:
            self.set_color(color)

    def __repr__(self):
        return f"ColorChooserButton({self.current_color})"

    def __getstate__(self):
        return dict(color=self.current_color, enabled=self.isEnabled())

    def __setstate__(self, state):
        self.__init__()
        if state["color"]:
            self.set_color(state["color"])
        self.setEnabled(state.get("enabled", True))

    @core.Slot()
    def choose_color(self):
        dlg = widgets.ColorDialog()
        if self.current_color:
            dlg.setCurrentColor(self.current_color)

        if dlg.exec_():
            self.set_color(dlg.current_color())
            self.value_changed.emit(dlg.current_color())

    def set_color(self, color):
        if isinstance(color, str):
            self.current_color = gui.Color(color)
        else:
            self.current_color = color
        self.lineedit.set_text(self.current_color.name().upper())
        icon = gui.Icon.for_color(self.current_color)
        self.button.set_icon(icon)

    def is_valid(self) -> bool:
        return self.lineedit.is_valid()

    def get_value(self):
        return self.current_color

    def set_value(self, value):
        self.set_color(value)
예제 #10
0
class LineSignalLogger(logging.Handler, core.Object):
    log_line = core.Signal(str)

    def __init__(self):
        super().__init__()
        core.Object.__init__(self)

    def emit(self, record):
        msg = self.format(record)
        self.log_line.emit(msg)
예제 #11
0
class SelectionWidget(widgets.GroupBox):
    option_changed = core.Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.layout = widgets.BoxLayout("horizontal")
        self.rb_other = widgets.RadioButton("Other")
        self.buttons = dict()
        # self.rb_comma.setChecked(True)

        self.setLayout(self.layout)

    def add_items(self, dct):
        for k, v in dct.items():
            self.add_item(k, v)

    def select_radio_by_data(self, value):
        for rb, data in self.buttons.items():
            if data == value:
                rb.setChecked(True)

    def add_item(self, title, data=None):
        rb = widgets.RadioButton(title)
        rb.toggled.connect(self.update_choice)
        self.buttons[rb] = data
        if len(self.buttons) == 1:
            rb.setChecked(True)
        self.layout.addWidget(rb)

    def add_custom(self, regex):
        self.lineedit_custom_sep = widgets.LineEdit(self)
        # TODO: Enable this or add BAR radio and option.
        self.lineedit_custom_sep.setEnabled(False)
        self.rb_other.toggled.connect(self.lineedit_custom_sep.setEnabled)
        self.lineedit_custom_sep.textChanged.connect(lambda: self.update_choice(True))
        self.lineedit_custom_sep.set_regex_validator(regex)
        self.layout.addWidget(self.rb_other)
        self.layout.addWidget(self.lineedit_custom_sep)

    def current_choice(self):
        for k, v in self.buttons.items():
            if k.isChecked():
                return v
        if self.rb_other.isChecked():
            return self.lineedit_custom_sep.text()
        return

    @core.Slot(bool)
    def update_choice(self, checked):
        if not checked:
            return None
        choice = self.current_choice()
        if len(choice) > 0:
            self.option_changed.emit(choice)
예제 #12
0
class FontComboBox(QtWidgets.QFontComboBox):

    value_changed = core.Signal(object)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.currentIndexChanged.connect(self.index_changed)

    def serialize_fields(self):
        return dict(
            current_font=self.get_current_font(),
            font_filters=self.get_font_filters(),
        )

    def __setstate__(self, state):
        self.set_font_filters(*state.get("font_filters", []))
        self.setCurrentFont(state["current_font"])

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    def set_font_filters(self, *filters: FontFilterStr):
        """Set font filters.

        Args:
            filters: font filters to use

        Raises:
            InvalidParamError: invalid font filters
        """
        if not filters:
            filters = ("all",)
        for item in filters:
            if item not in FONT_FILTERS:
                raise InvalidParamError(item, FONT_FILTERS)
        flags = helpers.merge_flags(filters, FONT_FILTERS)
        self.setFontFilters(flags)

    def get_font_filters(self) -> list[FontFilterStr]:
        """Return list of font filters.

        Returns:
            font filter list
        """
        return [k for k, v in FONT_FILTERS.items() if v & self.fontFilters()]

    def set_value(self, value: QtGui.QFont):
        self.setCurrentFont(value)

    def get_value(self) -> gui.Font:
        return self.get_current_font()

    def get_current_font(self) -> gui.Font:
        return gui.Font(self.currentFont())
예제 #13
0
파일: slider.py 프로젝트: phil65/PrettyQt
class Slider(QtWidgets.QSlider):

    value_changed = core.Signal(int)

    def __init__(
        self,
        orientation: (constants.OrientationStr
                      | QtCore.Qt.Orientation) = "horizontal",
        parent: QtWidgets.QWidget | None = None,
    ):
        if isinstance(orientation, QtCore.Qt.Orientation):
            ori = orientation
        else:
            ori = constants.ORIENTATION[orientation]
        super().__init__(ori, parent)
        self.valueChanged.connect(self.on_value_change)

    def serialize_fields(self):
        return dict(
            tick_position=self.get_tick_position(),
            tick_interval=self.tickInterval(),
        )

    def __setstate__(self, state):
        super().__setstate__(state)
        self.set_tick_position(state["tick_position"])
        self.setTickInterval(state["tick_interval"])

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    def set_tick_position(self, position: TickPositionAllStr):
        """Set the tick position for the slider.

        For vertical orientation, "above" equals to "left" and "below" to "right".

        Args:
            position: position for the ticks
        """
        if position == "left":
            position = "above"
        elif position == "right":
            position = "below"
        elif position not in TICK_POSITION:
            raise InvalidParamError(position, TICK_POSITION)
        self.setTickPosition(TICK_POSITION[position])

    def get_tick_position(self) -> TickPositionStr:
        """Return tick position.

        Returns:
            tick position
        """
        return TICK_POSITION.inverse[self.tickPosition()]
예제 #14
0
class FlagSelectionWidget(widgets.GroupBox):
    value_changed = core.Signal(int)

    def __init__(
        self,
        label: str = "",
        layout: Literal["horizontal", "vertical"] = "vertical",
        parent: QtWidgets.QWidget | None = None,
    ):
        super().__init__(title=label, parent=parent)
        self.box = widgets.BoxLayout(layout)
        self.buttons: dict[widgets.CheckBox, int] = {}
        self.set_layout(self.box)

    def __iter__(self) -> Iterator[tuple[widgets.CheckBox, int]]:
        return iter(self.buttons.items())

    def add_items(self, items: Iterable | Mapping):
        if isinstance(items, Mapping):
            for k, v in items.items():
                self.add(v, k)
        else:
            for i in items:
                if isinstance(i, Iterable):
                    self.add(*i)
                else:
                    raise TypeError("Invalid item type")

    def add(self, title: str, flag: int):
        checkbox = widgets.CheckBox(title)
        checkbox.toggled.connect(self.update_choice)
        self.buttons[checkbox] = flag
        self.box.add(checkbox)

    def current_choice(self) -> int:
        ret_val = 0
        for btn, flag in self.buttons.items():
            if btn.isChecked():
                ret_val |= flag
        return int(ret_val)

    @core.Slot(bool)
    def update_choice(self, checked: bool):
        choice = self.current_choice()
        self.value_changed.emit(choice)

    def set_value(self, value: int):
        value = int(value)
        for btn, flag in self.buttons.items():
            btn.setChecked(bool(value & flag))

    def get_value(self) -> int:
        return self.current_choice()
예제 #15
0
class PlaceManager(core.Object):
    on_finished = core.Signal(location.PlaceSearchReply)

    def __init__(self, item: QtLocation.QPlaceManager):
        super().__init__()
        self.item = item
        self.finished.connect(self._on_finished)

    def __getattr__(self, val):
        return getattr(self.item, val)

    def _on_finished(self, reply: QtLocation.QPlaceSearchReply):
        reply = location.PlaceSearchReply.clone_from(reply)
        self.on_finished.emit(reply)

    def get_category(self, cat_id: str) -> location.PlaceCategory:
        return location.PlaceCategory(self.item.category(cat_id))

    def get_child_categories(self,
                             cat_id: str) -> list[location.PlaceCategory]:
        return [
            location.PlaceCategory(i)
            for i in self.item.childCategories(cat_id)
        ]

    def get_locales(self) -> list[core.Locale]:
        return [core.Locale(i) for i in self.locales()]

    def search_place(
        self,
        search_term: str,
        coord: tuple[float, float] | QtPositioning.QGeoCoordinate,
        radius: float | None = None,
        limit: int | None = None,
        relevance: location.placesearchrequest.RelevanceHintStr | None = None,
        categories: list[str] | None = None,
    ):
        request = location.PlaceSearchRequest()
        request.setSearchTerm(search_term)
        if radius is None:
            radius = -1
        if isinstance(coord, tuple):
            coord = positioning.GeoCoordinate(*coord)
        circle = positioning.GeoCircle(coord, radius)
        request.setSearchArea(circle)
        if limit is not None:
            request.setLimit(limit)
        if relevance is not None:
            request.set_relevance_hint(relevance)
        if categories is not None:
            self.setCategories(categories)
        return self.search(request)
예제 #16
0
class FileChooserButton(widgets.Widget):

    value_changed = core.Signal(pathlib.Path)

    def __init__(self, extensions=None, mode="save", parent=None):
        super().__init__(parent)
        self.path = None
        self.extensions = extensions
        self.mode = mode
        layout = widgets.BoxLayout("horizontal", self)
        layout.set_margin(0)
        self.lineedit = widgets.LineEdit()
        self.lineedit.set_read_only()
        layout += self.lineedit
        action = widgets.Action()
        action.set_icon("mdi.file-outline")
        action.triggered.connect(self.open_file)

        self.button = widgets.ToolButton()
        self.button.setDefaultAction(action)
        layout += self.button

    def __getstate__(self):
        return dict(path=self.path,
                    extensions=self.extensions,
                    enabled=self.isEnabled())

    def __setstate__(self, state):
        self.__init__(state["extensions"])
        self.set_path(state["path"])
        self.setEnabled(state.get("enabled", True))

    @core.Slot()
    def open_file(self):
        dialog = widgets.FileDialog(parent=self, path_id="file_path")
        dialog.set_accept_mode(self.mode)
        if self.extensions:
            dialog.setNameFilter(self.extensions)
        if not dialog.open_file():
            return None
        self.set_path(dialog.selected_file())
        self.value_changed.emit(self.path)

    def set_path(self, path):
        self.path = path
        self.lineedit.setText(str(path))

    def get_value(self) -> Optional[pathlib.Path]:
        return self.path

    def set_value(self, value):
        self.set_path(value)
예제 #17
0
class ElidedLabel(widgets.Frame):
    elision_changed = core.Signal(bool)

    def __init__(
        self,
        text: str = "",
        parent: QtWidgets.QWidget | None = None,
    ):
        super().__init__(parent=parent)
        self.elided = False
        self.content = text
        self.set_size_policy("expanding", "preferred")

    def __repr__(self):
        return f"{type(self).__name__}({self.text()!r})"

    def set_text(self, text: str):
        self.content = text
        self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = gui.Painter(self)
        metrics = painter.get_font_metrics()
        did_elide = False
        line_spacing = metrics.lineSpacing()
        y = 0
        layout = gui.TextLayout(self.content, painter.font())
        with layout.process_layout():
            while True:
                line = layout.createLine()

                if not line.isValid():
                    break

                line.setLineWidth(self.width())
                next_line_y = y + line_spacing

                if self.height() >= next_line_y + line_spacing:
                    line.draw(painter, core.Point(0, y))
                    y = next_line_y
                else:
                    last_line = self.content[line.textStart():]
                    elided_line = metrics.elided_text(last_line, "right",
                                                      self.width())
                    painter.drawText(0, y + metrics.ascent(), elided_line)
                    line = layout.createLine()
                    did_elide = line.isValid()
                    break
        if did_elide != self.elided:
            self.elided = did_elide
            self.elision_changed.emit(did_elide)
예제 #18
0
파일: spinbox.py 프로젝트: phil65/PrettyQt
class SpinBox(QtWidgets.QSpinBox):

    value_changed = core.Signal(int)

    def __init__(
        self,
        parent: QtWidgets.QWidget | None = None,
        min_value: int | None = None,
        max_value: int | None = None,
        default_value: int | None = None,
    ):
        super().__init__(parent)
        self.valueChanged.connect(self.value_changed)
        self.set_range(min_value, max_value)
        if default_value is not None:
            self.set_value(default_value)

    def serialize_fields(self):
        return dict(
            range=(self.minimum(), self.maximum()),
            value=self.value(),
            prefix=self.prefix(),
            suffix=self.suffix(),
            step_type=self.get_step_type(),
            single_step=self.singleStep(),
            int_base=self.displayIntegerBase(),
        )

    def __setstate__(self, state):
        super().__setstate__(state)
        self.set_range(*state["range"])
        self.setValue(state["value"])
        self.setSingleStep(state["single_step"])
        self.setPrefix(state["prefix"])
        self.setSuffix(state["suffix"])
        self.setDisplayIntegerBase(state["int_base"])
        self.set_step_type(state["step_type"])

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    def set_range(self, start: int | None, end: int | None):
        if start is None:
            start = -2147483647
        if end is None:
            end = 2147483647
        self.setRange(start, end)

    def set_step_size(self, step_size):
        self.setSingleStep(step_size)
예제 #19
0
 def __new__(cls, name, bases, attrs):
     for key in list(attrs.keys()):
         attr = attrs[key]
         if not isinstance(attr, SyncedProperty):
             continue
         initial_value = attr.initial_value
         type_ = type(initial_value)
         notifier = core.Signal(type_)
         attrs[key] = PropertyImpl(initial_value,
                                   name=key,
                                   type_=type_,
                                   notify=notifier)
         attrs[signal_attribute_name(key)] = notifier
     return super().__new__(cls, name, bases, attrs)
예제 #20
0
class DoubleSpinBox(QtWidgets.QDoubleSpinBox):

    value_changed = core.Signal(float)

    def __init__(self,
                 parent=None,
                 min_value=None,
                 max_value=None,
                 default_value=None):
        super().__init__(parent)
        self.valueChanged.connect(self.value_changed)
        self.set_range(min_value, max_value)
        if default_value is not None:
            self.set_value(default_value)

    def __getstate__(self):
        return dict(range=(self.minimum(), self.maximum()),
                    value=super().value(),
                    enabled=self.isEnabled(),
                    tooltip=self.toolTip(),
                    statustip=self.statusTip(),
                    step_type=self.get_step_type(),
                    prefix=self.prefix(),
                    correction_mode=self.get_correction_mode(),
                    button_symbols=self.get_button_symbols(),
                    decimals=self.decimals(),
                    suffix=self.suffix(),
                    single_step=self.singleStep())

    def __setstate__(self, state):
        self.__init__()
        self.set_range(*state["range"])
        self.setValue(state["value"])
        self.setEnabled(state.get("enabled", True))
        self.setToolTip(state.get("tooltip", ""))
        self.setStatusTip(state.get("statustip", ""))
        self.setPrefix(state["prefix"])
        self.setSuffix(state["suffix"])
        self.setDecimals(state["decimals"])
        self.setSingleStep(state["single_step"])
        self.set_step_type(state["step_type"])
        self.set_correction_mode(state["correction_mode"])
        self.set_button_symbols(state["button_symbols"])

    def set_range(self, start, end):
        if start is None:
            start = -float("inf")
        if end is None:
            end = float("inf")
        self.setRange(start, end)
예제 #21
0
class StringOrNumberWidget(widgets.GroupBox):

    value_changed = core.Signal(object)

    def __init__(self,
                 title: str = "",
                 parent: QtWidgets.QWidget | None = None):
        super().__init__(checkable=False, title=title)
        self.set_layout("vertical")
        self.rb_lineedit = widgets.RadioButton("String")
        self.lineedit = widgets.LineEdit()
        self.rb_spinbox = widgets.RadioButton("Number")
        self.spinbox = widgets.DoubleSpinBox()
        layout_lineedit = widgets.BoxLayout("horizontal")
        layout_lineedit.add(self.rb_lineedit)
        layout_lineedit.add(self.lineedit)
        layout_spinbox = widgets.BoxLayout("horizontal")
        layout_spinbox.add(self.rb_spinbox)
        layout_spinbox.add(self.spinbox)
        self.box.add(layout_lineedit)
        self.box.add(layout_spinbox)
        self.rb_spinbox.toggled.connect(self.spinbox.setEnabled)
        self.rb_spinbox.toggled.connect(self.lineedit.setDisabled)
        self.rb_lineedit.toggled.connect(self.lineedit.setEnabled)
        self.rb_lineedit.toggled.connect(self.spinbox.setDisabled)
        self.spinbox.value_changed.connect(self.on_value_change)
        self.lineedit.value_changed.connect(self.on_value_change)
        self.rb_lineedit.setChecked(True)

    def on_value_change(self):
        value = self.get_value()
        self.value_changed.emit(value)

    def get_value(self) -> float | str:
        if self.rb_spinbox.isChecked():
            val = self.spinbox.get_value()
            return int(val) if val.is_integer() else val
        else:
            return self.lineedit.get_value()

    def set_value(self, value: float | str):
        if isinstance(value, str):
            self.rb_lineedit.setChecked(True)
            self.lineedit.set_value(value)
        elif isinstance(value, (int, float)):
            self.rb_spinbox.setChecked(True)
            self.spinbox.set_value(value)
        else:
            raise TypeError(f"Invalid Type for set_value: {type(value)}")
예제 #22
0
class InputAndSlider(widgets.Widget):

    value_changed = core.Signal(int)

    def __init__(
        self,
        bounds: tuple[int, int] | None = None,
        parent: QtWidgets.QWidget | None = None,
    ):
        super().__init__(parent)
        self.path = None
        layout = widgets.BoxLayout("horizontal", self)
        layout.set_margin(0)
        self.spinbox = widgets.SpinBox()
        layout.add(self.spinbox)
        self.slider = widgets.Slider()
        layout.add(self.slider)
        if bounds:
            self.set_range(*bounds)
        self.spinbox.valueChanged.connect(self.slider.set_value)
        self.slider.valueChanged.connect(self.spinbox.set_value)
        self.spinbox.valueChanged.connect(self.value_changed)

    def serialize_fields(self):
        return dict(path=self.path)

    # def __setstate__(self, state):
    #     self.__init__(state["extensions"])
    #     self.set_path(state["path"])
    #     self.set_enabled(state.get("enabled", True))

    def set_range(self, min_val: int, max_val: int):
        self.spinbox.set_range(min_val, max_val)
        self.slider.set_range(min_val, max_val)

    def get_value(self) -> int:
        return self.spinbox.get_value()

    def set_value(self, value: int):
        self.spinbox.set_value(value)
        self.slider.set_value(value)

    def is_valid(self) -> bool:
        return self.spinbox.is_valid()

    def set_step_size(self, step_size: int):
        self.spinbox.set_step_size(step_size)
        self.slider.set_step_size(step_size)
        self.slider.setTickInterval(step_size)
예제 #23
0
class ScrollBar(QtWidgets.QScrollBar):

    value_changed = core.Signal(int)

    def __init__(
        self,
        orientation: (QtCore.Qt.Orientation
                      | constants.OrientationStr) = "horizontal",
        parent: QtWidgets.QWidget | None = None,
    ):
        if isinstance(orientation, QtCore.Qt.Orientation):
            ori = orientation
        else:
            ori = constants.ORIENTATION[orientation]
        super().__init__(ori, parent)
        self.valueChanged.connect(self.on_value_change)
예제 #24
0
class PushButton(QtWidgets.QPushButton):

    value_changed = core.Signal(bool)

    def __init__(
        self,
        label: str | None = None,
        parent: QtWidgets.QWidget | None = None,
        callback: Callable | None = None,
    ):
        if label is None:
            label = ""
        super().__init__(label, parent)
        if callback:
            self.clicked.connect(callback)
        self.toggled.connect(self.value_changed)
예제 #25
0
class DoubleSpinBox(QtWidgets.QDoubleSpinBox):

    value_changed = core.Signal(float)

    def __init__(
        self,
        parent: QtWidgets.QWidget | None = None,
        min_value: float | None = None,
        max_value: float | None = None,
        default_value: float | None = None,
    ):
        super().__init__(parent)
        self.valueChanged.connect(self.value_changed)
        self.set_range(min_value, max_value)
        if default_value is not None:
            self.set_value(default_value)

    def __setstate__(self, state):
        super().__setstate__(state)
        self.set_range(*state["range"])
        self.setValue(state["value"])
        self.setPrefix(state["prefix"])
        self.setSuffix(state["suffix"])
        self.setDecimals(state["decimals"])
        self.setSingleStep(state["single_step"])
        self.set_step_type(state["step_type"])

    def __reduce__(self):
        return type(self), (), self.__getstate__()

    def serialize_fields(self):
        return dict(
            range=(self.minimum(), self.maximum()),
            prefix=self.prefix(),
            suffix=self.suffix(),
            step_type=self.get_step_type(),
            single_step=self.singleStep(),
            value=self.value(),
            decimals=self.decimals(),
        )

    def set_range(self, start: float | None, end: float | None):
        if start is None:
            start = -float("inf")
        if end is None:
            end = float("inf")
        self.setRange(start, end)
예제 #26
0
class FontChooserButton(widgets.Widget):

    value_changed = core.Signal(gui.Font)

    def __init__(self, font=None, parent=None):
        super().__init__(parent)
        self.current_font = font
        layout = widgets.BoxLayout("horizontal", self)
        layout.set_margin(0)
        self.lineedit = widgets.LineEdit()
        self.lineedit.set_read_only()
        layout += self.lineedit
        action = widgets.Action()
        action.triggered.connect(self.choose_font)
        self.button = widgets.ToolButton()
        self.button.setDefaultAction(action)
        layout += self.button

    def __repr__(self):
        return f"FontChooserButton({self.current_font})"

    def __getstate__(self):
        return dict(font=self.current_font,
                    enabled=self.isEnabled())

    def __setstate__(self, state):
        self.__init__()
        if state["font"]:
            self.set_font(state["font"])
        self.setEnabled(state.get("enabled", True))

    @core.Slot()
    def choose_font(self):
        dlg = widgets.FontDialog()
        if self.current_font:
            dlg.setCurrentFont(self.current_font)

        if dlg.exec_():
            self.set_font(dlg.current_font())
            self.value_changed.emit(dlg.current_font())

    def set_font(self, font):
        if isinstance(font, str):
            self.current_font = gui.Font(font)
        else:
            self.current_font = font
        self.lineedit.setText(self.current_font.family())
예제 #27
0
class StarEditor(widgets.Widget):
    """The custom editor for editing StarRatings."""

    # A signal to tell the delegate when we've finished editing.
    editing_finished = core.Signal()

    def __init__(self, parent: QtWidgets.QWidget | None = None):
        """Initialize the editor object, making sure we can watch mouse events."""
        super().__init__(parent)

        self.setMouseTracking(True)
        self.setAutoFillBackground(True)
        self.star_rating = StarRating()

    def sizeHint(self):
        """Tell the caller how big we are."""
        return self.star_rating.sizeHint()

    def paintEvent(self, event):
        """Paint the editor, offloading the work to the StarRating class."""
        painter = gui.Painter(self)
        self.star_rating.paint(painter, self.rect(), self.palette(), is_editable=True)

    def mouseMoveEvent(self, event):
        """Update stars on mouse move."""
        star = self.star_at_position(event.x())

        if star != -1:
            self.star_rating.star_count = star
            self.update()

    def mouseReleaseEvent(self, event):
        """Once star rating was clicked, tell the delegate we're done editing."""
        self.editing_finished.emit()

    def star_at_position(self, x: int) -> int:
        """Calculate which star the user's mouse cursor is currently hovering over."""
        val = x // (self.star_rating.sizeHint().width() // self.star_rating.max_stars) + 1
        if not 0 < val <= self.star_rating.max_stars:
            return -1
        return val

    def set_star_rating(self, rating: int):
        self.star_rating.star_count = rating
예제 #28
0
class KeySequenceEdit(QtWidgets.QKeySequenceEdit):

    value_changed = core.Signal(QtGui.QKeySequence)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.keySequenceChanged.connect(self.value_changed)

    def __repr__(self):
        return f"KeySequenceEdit({self.get_value()})"

    def set_value(self, value: str):
        seq = gui.KeySequence.fromString(value)
        self.setKeySequence(seq)

    def get_value(self) -> str:
        return self.keySequence().toString()

    def is_valid(self) -> bool:
        return True
예제 #29
0
class BoolDictToolButton(widgets.ToolButton):
    value_changed = core.Signal(dict)

    def __init__(
        self,
        title: str,
        icon: types.IconType = None,
        dct: dict[str, str] = None,
        parent: QtWidgets.QWidget | None = None,
    ):
        super().__init__(parent=parent)
        self.set_text(title)
        self.set_icon(icon)
        self.button_menu = widgets.Menu()
        self.setMenu(self.button_menu)
        self.set_popup_mode("instant")
        if dct:
            self.set_dict(dct)

    def __getitem__(self, key: str) -> bool:  # type: ignore
        return self.button_menu[key].isChecked()

    def __setitem__(self, key: str, value: bool):
        self.button_menu[key].setChecked(value)
        self.value_changed.emit(self.as_dict())

    def set_dict(self, dct: dict[str, str]):
        self.button_menu.clear()
        for k, v in dct.items():
            action = widgets.Action()
            action.set_text(v)
            action.setCheckable(True)
            action.set_id(k)
            action.triggered.connect(
                lambda: self.value_changed.emit(self.as_dict()))
            self.button_menu.add(action)
        self.value_changed.emit(self.as_dict())

    def as_dict(self) -> dict[str, bool]:
        return {act.get_id(): act.isChecked() for act in self.button_menu}
예제 #30
0
class TextEdit(QtWidgets.QTextEdit):

    value_changed = core.Signal(str)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.textChanged.connect(self.on_value_change)

    def on_value_change(self):
        self.value_changed.emit(self.text())

    def __getstate__(self):
        return dict(text=self.text(),
                    enabled=self.isEnabled(),
                    font=gui.Font(self.font()))

    def __setstate__(self, state):
        self.__init__()
        self.set_text(state["text"])
        self.setEnabled(state.get("enabled", True))
        self.setFont(state["font"])

    def __add__(self, other):
        if isinstance(other, str):
            self.append_text(other)
            return self

    def set_text(self, text: str):
        self.setPlainText(text)

    def append_text(self, text: str):
        self.append(text)

    def text(self) -> str:
        return self.toPlainText()

    def set_read_only(self, value: bool = True):
        self.setReadOnly(value)