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

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 

5 

6from graphica.gui import theme 

7 

8GLYPHS = ("line", "scatter", "line_scatter", "bar") 

9 

10 

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() 

40 

41 

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

55 

56 def sizeHint(self): 

57 return QSize(56, 48) 

58 

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() 

67 

68 

69class TypeTileGroup(QWidget): 

70 """QComboBox と同じ呼び方ができるタイルの列(ComboMirror で今の「種別」のコンボと同期させる)。""" 

71 

72 currentIndexChanged = Signal(int) 

73 

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 

84 

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 

96 

97 def count(self): 

98 return len(self._items) 

99 

100 def itemText(self, index): 

101 return self._items[index][0] if 0 <= index < len(self._items) else "" 

102 

103 def itemData(self, index): 

104 return self._items[index][1] if 0 <= index < len(self._items) else None 

105 

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 

111 

112 def currentIndex(self): 

113 return self._current 

114 

115 def currentData(self): 

116 return self.itemData(self._current) 

117 

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)