QT-PyQt-PySide-Custom-Widgets-Pro 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. custom_widgets_pro/__init__.py +73 -0
  2. custom_widgets_pro/_catalog.py +25 -0
  3. custom_widgets_pro/_cli.py +132 -0
  4. custom_widgets_pro/_license.py +419 -0
  5. custom_widgets_pro/_version.py +14 -0
  6. custom_widgets_pro/datatable/__init__.py +17 -0
  7. custom_widgets_pro/datatable/datatable_pro.py +319 -0
  8. custom_widgets_pro/datatable/export.py +143 -0
  9. custom_widgets_pro/datatable/frozen_view.py +112 -0
  10. custom_widgets_pro/datatable/grouping.py +309 -0
  11. custom_widgets_pro/datatable/pivot.py +100 -0
  12. custom_widgets_pro/datatable/provider.py +98 -0
  13. custom_widgets_pro/datatable/virtual_model.py +451 -0
  14. custom_widgets_pro/widgets/__init__.py +7 -0
  15. custom_widgets_pro/widgets/charts/QCustomBeeswarm.py +317 -0
  16. custom_widgets_pro/widgets/charts/QCustomBubbleChart.py +655 -0
  17. custom_widgets_pro/widgets/charts/QCustomCandlestickChart.py +522 -0
  18. custom_widgets_pro/widgets/charts/QCustomDivergingBarChart.py +430 -0
  19. custom_widgets_pro/widgets/charts/QCustomDotMatrix.py +266 -0
  20. custom_widgets_pro/widgets/charts/QCustomFunnelChart.py +474 -0
  21. custom_widgets_pro/widgets/charts/QCustomGanttChart.py +376 -0
  22. custom_widgets_pro/widgets/charts/QCustomHeatmap.py +555 -0
  23. custom_widgets_pro/widgets/charts/QCustomRadarChart.py +600 -0
  24. custom_widgets_pro/widgets/charts/QCustomRadialBars.py +441 -0
  25. custom_widgets_pro/widgets/charts/QCustomRadialLines.py +554 -0
  26. custom_widgets_pro/widgets/charts/QCustomRangeBarChart.py +504 -0
  27. custom_widgets_pro/widgets/charts/QCustomSankey.py +539 -0
  28. custom_widgets_pro/widgets/charts/QCustomScatterChart.py +623 -0
  29. custom_widgets_pro/widgets/charts/__init__.py +1 -0
  30. custom_widgets_pro/widgets/data/QCustomCodeEditor.py +391 -0
  31. custom_widgets_pro/widgets/data/QCustomNodeGraph.py +1030 -0
  32. custom_widgets_pro/widgets/data/QCustomRichTextEditor.py +189 -0
  33. custom_widgets_pro/widgets/data/QCustomTableToolbar.py +566 -0
  34. custom_widgets_pro/widgets/data/__init__.py +1 -0
  35. custom_widgets_pro/widgets/media/QCustomImageViewer.py +300 -0
  36. custom_widgets_pro/widgets/media/QCustomMediaGrid.py +197 -0
  37. custom_widgets_pro/widgets/media/QCustomMediaTimeline.py +607 -0
  38. custom_widgets_pro/widgets/media/QCustomVideoPlayer.py +326 -0
  39. custom_widgets_pro/widgets/media/__init__.py +1 -0
  40. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/METADATA +112 -0
  41. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/RECORD +45 -0
  42. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/WHEEL +5 -0
  43. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/entry_points.txt +2 -0
  44. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/licenses/LICENSE +91 -0
  45. qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,441 @@
1
+ ########################################################################
2
+ ## SPINN DESIGN CODE
3
+ # YOUTUBE: (SPINN TV) https://www.youtube.com/@SpinnTV
4
+ # WEBSITE: customwidgets.org
5
+ ########################################################################
6
+
7
+ ########################################################################
8
+ ## QCustomRadialBars - a painted radial bar chart.
9
+ ##
10
+ ## Concentric arcs, one per category, each sweeping in proportion to its
11
+ ## value. The "activity rings" form: compact, reads at a glance, and unlike a
12
+ ## pie it compares values that do NOT sum to a whole.
13
+ ##
14
+ ## QPainter only, NO QtCharts (see docs/design/mui-charts-gap.md). Angles come
15
+ ## from the shared _chart_axis polar helpers, so this winds the same way as
16
+ ## QCustomRadarChart and QCustomRadialLines.
17
+ ##
18
+ ## Data goes in with setBars([...]) in code, or barsCsv in Qt Designer:
19
+ ##
20
+ ## barsCsv = "Move=82,Exercise=64,Stand=95"
21
+ ##
22
+ ## Emits barHovered(int) and barClicked(int); -1 means "nothing".
23
+ ########################################################################
24
+ import math
25
+
26
+ from qtpy.QtCore import Qt, Signal, Property, QRectF, QSize, QPointF
27
+ from qtpy.QtGui import QColor, QPainter, QPen, QBrush, QFontMetrics
28
+ from qtpy.QtWidgets import QWidget, QSizePolicy
29
+
30
+
31
+ class QCustomRadialBars(QWidget):
32
+ barHovered = Signal(int)
33
+ barClicked = Signal(int)
34
+
35
+ WIDGET_ICON = "components/icons/donut_large.png"
36
+ WIDGET_TOOLTIP = "A painted radial bar chart (activity rings)"
37
+ WIDGET_MODULE = "Custom_Widgets.QCustomRadialBars"
38
+ WIDGET_DOM_XML = """
39
+ <ui language='c++'>
40
+ <widget class='QCustomRadialBars' name='customRadialBars'>
41
+ <property name='geometry'><rect><x>0</x><y>0</y><width>300</width><height>300</height></rect></property>
42
+ <property name='barsCsv'><string>Move=82,Exercise=64,Stand=95</string></property>
43
+ </widget>
44
+ </ui>
45
+ """
46
+ __catalog__ = {
47
+ "name": "QCustomRadialBars",
48
+ "props": {"barsCsv": {"type": "string", "default": ""},
49
+ "colorsCsv": {"type": "string", "default": ""},
50
+ "maxValue": {"type": "float", "default": 100.0},
51
+ "startAngle": {"type": "int", "default": 90},
52
+ "clockwise": {"type": "bool", "default": True},
53
+ "thickness": {"type": "int", "default": 18},
54
+ "spacing": {"type": "int", "default": 6},
55
+ "holeRatio": {"type": "float", "default": 0.35},
56
+ "rounded": {"type": "bool", "default": True},
57
+ "showTrack": {"type": "bool", "default": True},
58
+ "showLabels": {"type": "bool", "default": True},
59
+ "showValues": {"type": "bool", "default": True},
60
+ "trackColor": {"type": "color", "default": "#e2e8f0"},
61
+ "labelColor": {"type": "color", "default": "#0f172a"}},
62
+ "signals": ["barHovered", "barClicked"],
63
+ "tokens_used": ["surface-muted", "on-surface"],
64
+ }
65
+
66
+ # Rich editors for the Designer "Custom Properties" dock (see
67
+ # DesignerTools.CustomPropertiesDock).
68
+ DESIGNER_CUSTOM_PROPS = [
69
+ {"name": "barsCsv", "kind": "str", "group": "General"},
70
+ {"name": "colorsCsv", "kind": "str", "group": "Colors"},
71
+ {"name": "maxValue", "kind": "float", "group": "General"},
72
+ {"name": "startAngle", "kind": "int", "group": "General"},
73
+ {"name": "clockwise", "kind": "bool", "group": "General"},
74
+ {"name": "thickness", "kind": "int", "group": "General"},
75
+ {"name": "spacing", "kind": "int", "group": "General"},
76
+ {"name": "holeRatio", "kind": "float", "group": "General"},
77
+ {"name": "rounded", "kind": "bool", "group": "General"},
78
+ {"name": "showTrack", "kind": "bool", "group": "General"},
79
+ {"name": "showLabels", "kind": "bool", "group": "General"},
80
+ {"name": "showValues", "kind": "bool", "group": "General"},
81
+ {"name": "trackColor", "kind": "color", "group": "Colors"},
82
+ {"name": "labelColor", "kind": "color", "group": "Colors"},
83
+ ]
84
+
85
+ _DEFAULT_COLORS = ["#2563eb", "#16a34a", "#f59e0b", "#dc2626", "#7c3aed",
86
+ "#0891b2"]
87
+
88
+ def __init__(self, parent=None, bars=None):
89
+ super().__init__(parent)
90
+ self.setObjectName("QCustomRadialBars")
91
+ self._bars = [] # list of (label, value)
92
+ self._colors = []
93
+ self._maxValue = 100.0
94
+ self._startAngle = 90
95
+ self._clockwise = True
96
+ self._thickness = 18
97
+ self._spacing = 6
98
+ self._holeRatio = 0.35
99
+ self._rounded = True
100
+ self._showTrack = True
101
+ self._showLabels = True
102
+ self._showValues = True
103
+ self._hover = -1
104
+
105
+ self._trackColor = QColor("#e2e8f0")
106
+ self._labelColor = QColor("#0f172a")
107
+
108
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
109
+ self.setMouseTracking(True)
110
+ if bars:
111
+ self.setBars(bars)
112
+
113
+ # ------------------------------------------------------------------ #
114
+ ## Data
115
+ # ------------------------------------------------------------------ #
116
+ @staticmethod
117
+ def _coerce(bar):
118
+ if isinstance(bar, dict):
119
+ label, value = bar.get("label"), bar.get("value")
120
+ elif isinstance(bar, (tuple, list)) and len(bar) >= 2:
121
+ label, value = bar[0], bar[1]
122
+ else:
123
+ return None
124
+ try:
125
+ return (str(label), max(0.0, float(value)))
126
+ except (TypeError, ValueError):
127
+ return None
128
+
129
+ def setBars(self, bars):
130
+ self._bars = [b for b in (self._coerce(x) for x in (bars or [])) if b]
131
+ self._hover = -1
132
+ self.update()
133
+
134
+ def bars(self):
135
+ return list(self._bars)
136
+
137
+ def barCount(self):
138
+ return len(self._bars)
139
+
140
+ def clearBars(self):
141
+ self._bars = []
142
+ self._hover = -1
143
+ self.update()
144
+
145
+ def maximum(self):
146
+ if self._maxValue > 0:
147
+ return self._maxValue
148
+ return max((v for _l, v in self._bars), default=0.0) or 1.0
149
+
150
+ def barColor(self, index):
151
+ if 0 <= index < len(self._colors) and self._colors[index] is not None:
152
+ return QColor(self._colors[index])
153
+ return QColor(self._DEFAULT_COLORS[index % len(self._DEFAULT_COLORS)])
154
+
155
+ def fractionFor(self, index):
156
+ """0..1 of the maximum. Values above the maximum are clamped so a bar
157
+ cannot wrap past its own start and read as a smaller number."""
158
+ if not (0 <= index < len(self._bars)):
159
+ return 0.0
160
+ return max(0.0, min(1.0, self._bars[index][1] / self.maximum()))
161
+
162
+ # ------------------------------------------------------------------ #
163
+ ## Geometry
164
+ # ------------------------------------------------------------------ #
165
+ def sizeHint(self):
166
+ return QSize(300, 300)
167
+
168
+ def minimumSizeHint(self):
169
+ return QSize(110, 110)
170
+
171
+ def _legendHeight(self):
172
+ if not (self._showLabels or self._showValues) or not self._bars:
173
+ return 0.0
174
+ return QFontMetrics(self.font()).height() + 8
175
+
176
+ def _plotRect(self):
177
+ bottom = self._legendHeight()
178
+ side = max(10.0, min(self.width(), self.height() - bottom) - 16)
179
+ return QRectF((self.width() - side) / 2.0,
180
+ (self.height() - bottom - side) / 2.0, side, side)
181
+
182
+ def ringRect(self, index):
183
+ """Bounding rect of ring `index`, outermost first."""
184
+ rect = self._plotRect()
185
+ inset = index * (self._thickness + self._spacing) + self._thickness / 2.0
186
+ limit = rect.width() / 2.0 * (1.0 - max(0.0, min(0.95, self._holeRatio)))
187
+ inset = min(inset, max(1.0, limit))
188
+ return rect.adjusted(inset, inset, -inset, -inset)
189
+
190
+ def _sweepFor(self, index):
191
+ """(startAngle, spanAngle) in Qt's 1/16-degree units."""
192
+ span = self.fractionFor(index) * 360.0
193
+ if self._clockwise:
194
+ span = -span
195
+ return int(self._startAngle * 16), int(span * 16)
196
+
197
+ # ------------------------------------------------------------------ #
198
+ ## Painting
199
+ # ------------------------------------------------------------------ #
200
+ def paintEvent(self, e):
201
+ p = QPainter(self)
202
+ p.setRenderHint(QPainter.Antialiasing)
203
+ p.setFont(self.font())
204
+ if not self._bars:
205
+ return
206
+
207
+ cap = Qt.RoundCap if self._rounded else Qt.FlatCap
208
+ for index in range(len(self._bars)):
209
+ ring = self.ringRect(index)
210
+ if self._showTrack:
211
+ pen = QPen(self._trackColor, self._thickness)
212
+ pen.setCapStyle(Qt.FlatCap)
213
+ p.setPen(pen)
214
+ p.setBrush(Qt.NoBrush)
215
+ p.drawArc(ring, 0, 360 * 16)
216
+
217
+ colour = self.barColor(index)
218
+ if index == self._hover:
219
+ colour = colour.lighter(115)
220
+ pen = QPen(colour, self._thickness)
221
+ pen.setCapStyle(cap)
222
+ p.setPen(pen)
223
+ start, span = self._sweepFor(index)
224
+ if span:
225
+ p.drawArc(ring, start, span)
226
+
227
+ if self._showLabels or self._showValues:
228
+ self._paintLabels(p)
229
+
230
+ def _paintLabels(self, p):
231
+ """A legend strip below the rings.
232
+
233
+ Anchoring each label to its own ring's start angle put the text
234
+ straight over the arcs — every ring starts at the same angle, so the
235
+ labels stacked on top of each other and on the bars underneath. A
236
+ legend also matches the other charts in the family.
237
+ """
238
+ fm = QFontMetrics(self.font())
239
+ swatch, gap = 9.0, 14.0
240
+ entries = []
241
+ for index, (label, value) in enumerate(self._bars):
242
+ parts = []
243
+ if self._showLabels:
244
+ parts.append(label)
245
+ if self._showValues:
246
+ parts.append("%g" % value)
247
+ text = " ".join(parts)
248
+ if text:
249
+ entries.append((index, text))
250
+ if not entries:
251
+ return
252
+
253
+ widths = [swatch + 5 + fm.horizontalAdvance(text) for _i, text in entries]
254
+ x = (self.width() - (sum(widths) + gap * (len(widths) - 1))) / 2.0
255
+ y = self.height() - self._legendHeight() + 4
256
+ for slot, (index, text) in enumerate(entries):
257
+ p.setPen(Qt.NoPen)
258
+ p.setBrush(QBrush(self.barColor(index)))
259
+ p.drawEllipse(QRectF(x, y + (fm.height() - swatch) / 2.0,
260
+ swatch, swatch))
261
+ p.setPen(QPen(self._labelColor))
262
+ p.drawText(QRectF(x + swatch + 5, y, widths[slot], fm.height()),
263
+ int(Qt.AlignLeft | Qt.AlignVCenter), text)
264
+ x += widths[slot] + gap
265
+
266
+ # ------------------------------------------------------------------ #
267
+ ## Interaction
268
+ # ------------------------------------------------------------------ #
269
+ def barAt(self, pos):
270
+ """Index of the ring under a point, or -1.
271
+
272
+ Tests the radius band only: a point anywhere on the ring counts, filled
273
+ or not, so hovering the empty part of a track still identifies it.
274
+ """
275
+ point = QPointF(pos)
276
+ for index in range(len(self._bars)):
277
+ ring = self.ringRect(index)
278
+ centre = ring.center()
279
+ distance = math.hypot(point.x() - centre.x(), point.y() - centre.y())
280
+ radius = ring.width() / 2.0
281
+ if abs(distance - radius) <= self._thickness / 2.0:
282
+ return index
283
+ return -1
284
+
285
+ def mouseMoveEvent(self, e):
286
+ index = self.barAt(e.pos())
287
+ if index != self._hover:
288
+ self._hover = index
289
+ self.barHovered.emit(index)
290
+ self.update()
291
+ super().mouseMoveEvent(e)
292
+
293
+ def leaveEvent(self, e):
294
+ if self._hover != -1:
295
+ self._hover = -1
296
+ self.barHovered.emit(-1)
297
+ self.update()
298
+ super().leaveEvent(e)
299
+
300
+ def mouseReleaseEvent(self, e):
301
+ if e.button() == Qt.LeftButton:
302
+ index = self.barAt(e.pos())
303
+ if index >= 0:
304
+ self.barClicked.emit(index)
305
+ super().mouseReleaseEvent(e)
306
+
307
+ # ------------------------------------------------------------------ #
308
+ ## Designer properties
309
+ # ------------------------------------------------------------------ #
310
+ @Property(str)
311
+ def barsCsv(self):
312
+ return ",".join("%s=%g" % (label, value) for label, value in self._bars)
313
+
314
+ @barsCsv.setter
315
+ def barsCsv(self, text):
316
+ bars = []
317
+ for chunk in str(text).replace(";", ",").split(","):
318
+ chunk = chunk.strip()
319
+ if not chunk:
320
+ continue
321
+ if "=" in chunk:
322
+ label, _, raw = chunk.partition("=")
323
+ else:
324
+ label, raw = "Bar %d" % (len(bars) + 1), chunk
325
+ try:
326
+ bars.append((label.strip() or "Bar %d" % (len(bars) + 1),
327
+ max(0.0, float(raw.strip()))))
328
+ except ValueError:
329
+ continue
330
+ self.setBars(bars)
331
+
332
+ @Property(str)
333
+ def colorsCsv(self):
334
+ return ",".join(c.name() if c is not None else "" for c in self._colors)
335
+
336
+ @colorsCsv.setter
337
+ def colorsCsv(self, text):
338
+ colors = []
339
+ for token in str(text).replace(";", ",").split(","):
340
+ token = token.strip()
341
+ colour = QColor(token) if token else QColor()
342
+ colors.append(colour if colour.isValid() else None)
343
+ self._colors = colors
344
+ self.update()
345
+
346
+ @Property(float)
347
+ def maxValue(self):
348
+ return self._maxValue
349
+
350
+ @maxValue.setter
351
+ def maxValue(self, value):
352
+ self._maxValue = max(0.0, float(value)); self.update()
353
+
354
+ @Property(int)
355
+ def startAngle(self):
356
+ return self._startAngle
357
+
358
+ @startAngle.setter
359
+ def startAngle(self, value):
360
+ self._startAngle = int(value) % 360; self.update()
361
+
362
+ @Property(bool)
363
+ def clockwise(self):
364
+ return self._clockwise
365
+
366
+ @clockwise.setter
367
+ def clockwise(self, value):
368
+ self._clockwise = bool(value); self.update()
369
+
370
+ @Property(int)
371
+ def thickness(self):
372
+ return self._thickness
373
+
374
+ @thickness.setter
375
+ def thickness(self, value):
376
+ self._thickness = max(1, int(value)); self.update()
377
+
378
+ @Property(int)
379
+ def spacing(self):
380
+ return self._spacing
381
+
382
+ @spacing.setter
383
+ def spacing(self, value):
384
+ self._spacing = max(0, int(value)); self.update()
385
+
386
+ @Property(float)
387
+ def holeRatio(self):
388
+ return self._holeRatio
389
+
390
+ @holeRatio.setter
391
+ def holeRatio(self, value):
392
+ self._holeRatio = max(0.0, min(0.95, float(value))); self.update()
393
+
394
+ @Property(bool)
395
+ def rounded(self):
396
+ return self._rounded
397
+
398
+ @rounded.setter
399
+ def rounded(self, value):
400
+ self._rounded = bool(value); self.update()
401
+
402
+ @Property(bool)
403
+ def showTrack(self):
404
+ return self._showTrack
405
+
406
+ @showTrack.setter
407
+ def showTrack(self, value):
408
+ self._showTrack = bool(value); self.update()
409
+
410
+ @Property(bool)
411
+ def showLabels(self):
412
+ return self._showLabels
413
+
414
+ @showLabels.setter
415
+ def showLabels(self, value):
416
+ self._showLabels = bool(value); self.update()
417
+
418
+ @Property(bool)
419
+ def showValues(self):
420
+ return self._showValues
421
+
422
+ @showValues.setter
423
+ def showValues(self, value):
424
+ self._showValues = bool(value); self.update()
425
+
426
+ # -- colours (from tokens via qproperty) --
427
+ @Property(QColor)
428
+ def trackColor(self):
429
+ return self._trackColor
430
+
431
+ @trackColor.setter
432
+ def trackColor(self, c):
433
+ self._trackColor = QColor(c); self.update()
434
+
435
+ @Property(QColor)
436
+ def labelColor(self):
437
+ return self._labelColor
438
+
439
+ @labelColor.setter
440
+ def labelColor(self, c):
441
+ self._labelColor = QColor(c); self.update()