Skip to content

Helper Widgets

Color Button

ColorButton

Bases: QPushButton

Custom button to allow the user to select a color. The default color is a random bright color.

Left-clicking opens a color dialog box to choose a color. Right-clicking resets the color to the default.

Parameters

color : QColor or str, optional Default color for the button to use, by default None index : int, optional A value used in determining a default color, by default -1

Source code in trace/widgets/color_button.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class ColorButton(QPushButton):
    """Custom button to allow the user to select a color. The default
    color is a random bright color.

    Left-clicking opens a color dialog box to choose a color.
    Right-clicking resets the color to the default.

    Parameters
    ----------
    color : QColor or str, optional
        Default color for the button to use, by default None
    index : int, optional
        A value used in determining a default color, by default -1
    """

    color_changed = Signal(QColor)

    def __init__(self, *args: Any, color: QColor | str = None, index: int = -1, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        if not color:
            if index >= 0:
                color = self.index_color(index)
            else:
                color = self.random_color()
        elif not isinstance(color, QColor):
            color = QColor(color)

        self._color = None
        self._default = color
        self.dialog_box = QColorDialog(self)
        self.dialog_box.setCurrentColor(color)

        self.pressed.connect(self.dialog_box.show)
        self.dialog_box.colorSelected.connect(lambda c: setattr(self, "color", c))

        self.color = self._default

    @property
    def color(self) -> QColor:
        """Return the current color."""
        return self._color

    @color.setter
    def color(self, color: QColor) -> None:
        """Set the background color of the button to the selected color."""
        if color == self._color:
            return

        self._color = color
        style_str = "ColorButton {background-color: " + self._color.name() + "};"
        self.setStyleSheet(style_str)

        self.color_changed.emit(color)

    def mousePressEvent(self, e: QMouseEvent) -> None:
        """Set the color to the default on right-click."""
        if e.button() == Qt.RightButton:
            self.color = self._default
            return

        return super().mousePressEvent(e)

    @staticmethod
    def random_color() -> QColor:
        """Pick a random color for the default color of each PV. This
        function ensures that the color is bright, since it will be on a
        black background."""
        hue = int(360 * random())
        saturation = int(256 * (0.5 + random() / 2.0))
        lightness = int(256 * (0.4 + random() / 5.0))
        color = QColor()
        color.setHsl(hue, saturation, lightness)
        return color

    @staticmethod
    def index_color(index: int) -> QColor:
        """Returns the color in the color palette at index."""
        modded_index = index % len(color_palette)
        color = color_palette[modded_index]

        dark_factor = (index // len(color_palette)) * 35
        return color.darker(100 + dark_factor)

color property writable

Return the current color.

index_color(index) staticmethod

Returns the color in the color palette at index.

Source code in trace/widgets/color_button.py
85
86
87
88
89
90
91
92
@staticmethod
def index_color(index: int) -> QColor:
    """Returns the color in the color palette at index."""
    modded_index = index % len(color_palette)
    color = color_palette[modded_index]

    dark_factor = (index // len(color_palette)) * 35
    return color.darker(100 + dark_factor)

mousePressEvent(e)

Set the color to the default on right-click.

Source code in trace/widgets/color_button.py
65
66
67
68
69
70
71
def mousePressEvent(self, e: QMouseEvent) -> None:
    """Set the color to the default on right-click."""
    if e.button() == Qt.RightButton:
        self.color = self._default
        return

    return super().mousePressEvent(e)

random_color() staticmethod

Pick a random color for the default color of each PV. This function ensures that the color is bright, since it will be on a black background.

Source code in trace/widgets/color_button.py
73
74
75
76
77
78
79
80
81
82
83
@staticmethod
def random_color() -> QColor:
    """Pick a random color for the default color of each PV. This
    function ensures that the color is bright, since it will be on a
    black background."""
    hue = int(360 * random())
    saturation = int(256 * (0.5 + random() / 2.0))
    lightness = int(256 * (0.4 + random() / 5.0))
    color = QColor()
    color.setHsl(hue, saturation, lightness)
    return color

Frozen Table View

FrozenTableView

Bases: QTableView

QTableView with the leftmost column frozen so it always shows while the rest of the table is horizontally scrollable.

Python version of Qt FreezeTableWidget example: https://doc.qt.io/qt-6/qtwidgets-itemviews-frozencolumn-example.html

Source code in trace/widgets/frozen_table_view.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class FrozenTableView(QTableView):
    """QTableView with the leftmost column frozen so it always shows while the
    rest of the table is horizontally scrollable.

    Python version of Qt FreezeTableWidget example:
    https://doc.qt.io/qt-6/qtwidgets-itemviews-frozencolumn-example.html
    """

    def __init__(self, model):
        super(FrozenTableView, self).__init__()
        self.setModel(model)
        self.frozenTableView = QTableView(self)
        self.init()
        self.horizontalHeader().sectionResized.connect(self.updateSectionWidth)
        self.verticalHeader().hide()
        self.frozenTableView.verticalScrollBar().valueChanged.connect(self.verticalScrollBar().setValue)
        self.verticalScrollBar().valueChanged.connect(self.frozenTableView.verticalScrollBar().setValue)

    def init(self):
        self.frozenTableView.setModel(self.model())
        self.frozenTableView.setFocusPolicy(Qt.NoFocus)
        self.frozenTableView.verticalHeader().hide()
        self.frozenTableView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.viewport().stackUnder(self.frozenTableView)

        self.setAlternatingRowColors(True)
        self.frozenTableView.setAlternatingRowColors(True)
        self.frozenTableView.setStyleSheet("QTableView {border: none; border-right: 1px solid lightGray}")

        self.setSelectionBehavior(QTableView.SelectRows)
        self.frozenTableView.setSelectionBehavior(QTableView.SelectRows)
        self.frozenTableView.setSelectionModel(self.selectionModel())
        for col in range(1, self.model().columnCount()):
            self.frozenTableView.setColumnHidden(col, True)
        self.frozenTableView.setColumnWidth(0, self.columnWidth(0))
        self.frozenTableView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.frozenTableView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.frozenTableView.show()
        self.updateFrozenTableGeometry()
        self.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
        self.frozenTableView.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)

    def updateSectionWidth(self, logicalIndex, oldSize, newSize):
        if logicalIndex == 0:
            self.frozenTableView.setColumnWidth(0, newSize)
            self.updateFrozenTableGeometry()

    def updateSectionHeight(self, logicalIndex, oldSize, newSize):
        self.frozenTableView.setRowHeight(logicalIndex, newSize)

    def resizeEvent(self, event):
        super(FrozenTableView, self).resizeEvent(event)
        self.updateFrozenTableGeometry()

    def moveCursor(self, cursorAction, modifiers):
        current = super(FrozenTableView, self).moveCursor(cursorAction, modifiers)
        if (
            cursorAction == self.MoveLeft
            and current.column() > 0
            and self.visualRect(current).topLeft().x() < self.frozenTableView.columnWidth(0)
        ):
            newValue = (
                self.horizontalScrollBar().value()
                + self.visualRect(current).topLeft().x()
                - self.frozenTableView.columnWidth(0)
            )
            self.horizontalScrollBar().setValue(newValue)
        return current

    def scrollTo(self, index, hint):
        if index.column() > 0:
            super(FrozenTableView, self).scrollTo(index, hint)

    def updateFrozenTableGeometry(self):
        self.frozenTableView.setGeometry(
            self.verticalHeader().width() + self.frameWidth(),
            self.frameWidth(),
            self.columnWidth(0),
            self.viewport().height() + self.horizontalHeader().height(),
        )