Coverage for graphica/gui/widgets/type_tiles.py: 99.2%
102 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""グラフの種類のタイル(線・点・線+点・棒)。シンプル表示のプロパティで「種別」のコンボの代わりに出す。"""
2from PySide6.QtCore import QPointF, QRectF, QSize, Qt, Signal
3from PySide6.QtGui import QColor, QPainter, QPen
4from PySide6.QtWidgets import QButtonGroup, QHBoxLayout, QSizePolicy, QToolButton, QWidget
6from graphica.gui import theme
8GLYPHS = ("line", "scatter", "line_scatter", "bar")
11def draw_glyph(painter, glyph, rect, color):
12 """種類の小さな絵。rect の中に収める。"""
13 painter.save()
14 painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
15 pen = QPen(color)
16 pen.setWidthF(1.7)
17 pen.setCapStyle(Qt.PenCapStyle.RoundCap)
18 pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
19 painter.setPen(pen)
20 painter.setBrush(Qt.BrushStyle.NoBrush)
21 x, y, w, h = rect.x(), rect.y(), rect.width(), rect.height()
22 # 右下がりから持ち上がる、測定データらしい形
23 points = [QPointF(x + w * fx, y + h * fy) for fx, fy in
24 ((0.05, 0.85), (0.3, 0.45), (0.55, 0.6), (0.8, 0.2), (0.95, 0.3))]
25 if glyph in ("line", "line_scatter"):
26 for a, b in zip(points, points[1:]):
27 painter.drawLine(a, b)
28 if glyph in ("scatter", "line_scatter"):
29 painter.setBrush(color)
30 radius = 1.9 if glyph == "line_scatter" else 2.2
31 for point in points:
32 painter.drawEllipse(point, radius, radius)
33 if glyph == "bar":
34 painter.setBrush(color)
35 painter.setPen(Qt.PenStyle.NoPen)
36 bar_w = w * 0.16
37 for fx, fh in ((0.12, 0.45), (0.42, 0.85), (0.72, 0.6)):
38 painter.drawRoundedRect(QRectF(x + w * fx, y + h * (1 - fh), bar_w, h * fh), 1, 1)
39 painter.restore()
42class _TypeTile(QToolButton):
43 def __init__(self, glyph, text, parent=None):
44 super().__init__(parent)
45 self._glyph = glyph
46 self.setObjectName("type_tile")
47 self.setText(text)
48 self.setCheckable(True)
49 self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextOnly)
50 self.setCursor(Qt.CursorShape.PointingHandCursor)
51 self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
52 self.setFixedHeight(48)
53 # 文字は下に寄せ、上の空きに絵を描く
54 self.setStyleSheet("padding-top: 22px;")
56 def sizeHint(self):
57 return QSize(56, 48)
59 def paintEvent(self, event):
60 super().paintEvent(event)
61 tokens = theme.current_tokens()
62 color = QColor(tokens["accent_fg"] if self.isChecked() else tokens["text_secondary"])
63 painter = QPainter(self)
64 size = 20
65 draw_glyph(painter, self._glyph, QRectF((self.width() - size) / 2, 5, size, 16), color)
66 painter.end()
69class TypeTileGroup(QWidget):
70 """QComboBox と同じ呼び方ができるタイルの列(ComboMirror で今の「種別」のコンボと同期させる)。"""
72 currentIndexChanged = Signal(int)
74 def __init__(self, parent=None):
75 super().__init__(parent)
76 layout = QHBoxLayout(self)
77 layout.setContentsMargins(0, 0, 0, 0)
78 layout.setSpacing(6)
79 self._group = QButtonGroup(self)
80 self._group.setExclusive(True)
81 self._group.idClicked.connect(self.setCurrentIndex)
82 self._items = []
83 self._current = -1
85 def addTile(self, glyph, text, data):
86 if glyph not in GLYPHS:
87 raise ValueError(f"知らない絵: {glyph}")
88 tile = _TypeTile(glyph, text, self)
89 index = len(self._items)
90 self._items.append((text, data))
91 self._group.addButton(tile, index)
92 self.layout().addWidget(tile)
93 if self._current < 0:
94 self.setCurrentIndex(0)
95 return index
97 def count(self):
98 return len(self._items)
100 def itemText(self, index):
101 return self._items[index][0] if 0 <= index < len(self._items) else ""
103 def itemData(self, index):
104 return self._items[index][1] if 0 <= index < len(self._items) else None
106 def findData(self, data):
107 for index, (_, item_data) in enumerate(self._items):
108 if item_data == data:
109 return index
110 return -1
112 def currentIndex(self):
113 return self._current
115 def currentData(self):
116 return self.itemData(self._current)
118 def setCurrentIndex(self, index):
119 if index < -1 or index >= len(self._items) or index == self._current:
120 return
121 self._current = index
122 if index >= 0:
123 self._group.button(index).setChecked(True)
124 else:
125 # 4つに無い種類(面・ステップ・プラグインの種類など)のときは、どれも選ばない
126 self._group.setExclusive(False)
127 for button in self._group.buttons():
128 button.setChecked(False)
129 self._group.setExclusive(True)
130 self.currentIndexChanged.emit(index)