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,376 @@
1
+ ########################################################################
2
+ ## SPINN DESIGN CODE
3
+ # YOUTUBE: (SPINN TV) https://www.youtube.com/@SpinnTV
4
+ # WEBSITE: customwidgets.org
5
+ ########################################################################
6
+
7
+ ########################################################################
8
+ ## QCustomGanttChart - a horizontal timeline / gantt of rounded pill bars.
9
+ ##
10
+ ## Every ROW is one rounded "pill" bar placed on a shared numeric x-axis by
11
+ ## its `start` and `length`; the row LABEL (e.g. a date) sits on the left,
12
+ ## the row VALUE is printed at the right end of the bar, and an optional
13
+ ## leading circular ICON (a QPixmap clipped to a circle) or coloured dot marks
14
+ ## the bar's start. Bars are coloured by CATEGORY (`colorsCsv`). A light x-grid
15
+ ## with tick labels runs underneath. This is the "projects timeline" viz.
16
+ ## Painted (no QtCharts) so it is crisp and self-contained.
17
+ ##
18
+ ## Give rows in code with setData([...]) — each row a dict
19
+ ## {"label","start","length","category","value","icon"} (icon = a path or
20
+ ## QPixmap, optional) — or in Qt Designer with `dataCsv` (rows by ';', fields
21
+ ## "label,start,length,category,value"). `xMax`/`gridStep` set the axis;
22
+ ## colours are qproperties so a theme flips them on switch.
23
+ ########################################################################
24
+ import os
25
+ from qtpy.QtCore import Qt, Property, QRectF, QPointF
26
+ from qtpy.QtGui import (QColor, QPainter, QBrush, QPen, QFont, QFontMetrics,
27
+ QPixmap, QPainterPath)
28
+ from qtpy.QtWidgets import QWidget, QSizePolicy
29
+
30
+
31
+ class QCustomGanttChart(QWidget):
32
+
33
+ WIDGET_ICON = "components/icons/bar_chart.png"
34
+ WIDGET_TOOLTIP = "A horizontal timeline / gantt of rounded pill bars"
35
+ WIDGET_MODULE = "Custom_Widgets.QCustomGanttChart"
36
+ WIDGET_DOM_XML = """
37
+ <ui language='c++'>
38
+ <widget class='QCustomGanttChart' name='customGanttChart'>
39
+ <property name='geometry'><rect><x>0</x><y>0</y><width>560</width><height>360</height></rect></property>
40
+ </widget>
41
+ </ui>
42
+ """
43
+ __catalog__ = {
44
+ "name": "QCustomGanttChart",
45
+ "props": {"dataCsv": {"type": "string", "default": ""},
46
+ "colorsCsv": {"type": "string", "default": "#8fe36b,#f6912b,#ffffff"},
47
+ "textColorsCsv": {"type": "string", "default": "#1c1c20,#1c1c20,#1c1c20"},
48
+ "xMax": {"type": "float", "default": 30.0},
49
+ "gridStep": {"type": "float", "default": 5.0},
50
+ "barHeight": {"type": "int", "default": 30},
51
+ "labelColor": {"type": "color", "default": "#c7c9cf"},
52
+ "axisTextColor": {"type": "color", "default": "#7d7f88"},
53
+ "gridColor": {"type": "color", "default": "#2f2f35"},
54
+ "showGrid": {"type": "bool", "default": True},
55
+ "showMarkers": {"type": "bool", "default": True}},
56
+ "signals": [],
57
+ # Intentionally no tokens_used: this widget resolves no token roles.
58
+ }
59
+
60
+ # Rich editors for the Designer "Custom Properties" dock (see
61
+ # DesignerTools.CustomPropertiesDock).
62
+ DESIGNER_CUSTOM_PROPS = [
63
+ {"name": "dataCsv", "kind": "str", "group": "General"},
64
+ {"name": "colorsCsv", "kind": "str", "group": "Colors"},
65
+ {"name": "textColorsCsv", "kind": "str", "group": "Colors"},
66
+ {"name": "xMax", "kind": "float", "group": "General"},
67
+ {"name": "gridStep", "kind": "float", "group": "General"},
68
+ {"name": "barHeight", "kind": "int", "group": "General"},
69
+ {"name": "labelColor", "kind": "color", "group": "Colors"},
70
+ {"name": "axisTextColor", "kind": "color", "group": "Colors"},
71
+ {"name": "gridColor", "kind": "color", "group": "Colors"},
72
+ {"name": "showGrid", "kind": "bool", "group": "General"},
73
+ {"name": "showMarkers", "kind": "bool", "group": "General"},
74
+ ]
75
+
76
+ # deterministic demo rows (label, start, length, category, value)
77
+ _DEMO = [
78
+ {"label": "30.09", "start": 2, "length": 8, "category": 0, "value": 16},
79
+ {"label": "29.09", "start": 18, "length": 8, "category": 1, "value": 29},
80
+ {"label": "28.09", "start": 8, "length": 6, "category": 2, "value": 15},
81
+ {"label": "27.09", "start": 9, "length": 9, "category": 0, "value": 21},
82
+ {"label": "26.09", "start": 6, "length": 4, "category": 2, "value": 10},
83
+ {"label": "25.09", "start": 8, "length": 5, "category": 1, "value": 15},
84
+ {"label": "25.09", "start": 12, "length": 9, "category": 0, "value": 19},
85
+ {"label": "24.09", "start": 6, "length": 4, "category": 2, "value": 8},
86
+ ]
87
+
88
+ def __init__(self, parent=None, rows=None):
89
+ super().__init__(parent)
90
+ self.setObjectName("QCustomGanttChart")
91
+ self._rows = [dict(r) for r in rows] if rows else []
92
+ self._colors = [QColor("#8fe36b"), QColor("#f6912b"), QColor("#ffffff")]
93
+ self._text_colors = [QColor("#1c1c20"), QColor("#1c1c20"), QColor("#1c1c20")]
94
+ self._x_max = 30.0
95
+ self._grid_step = 5.0
96
+ self._bar_h = 30
97
+ self._label_color = QColor("#c7c9cf")
98
+ self._axis_text_color = QColor("#7d7f88")
99
+ self._grid_color = QColor("#2f2f35")
100
+ self._show_grid = True
101
+ self._show_markers = True
102
+ self._label_w = 46 # left gutter for row labels
103
+ self._pix_cache = {}
104
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
105
+ self.setMinimumSize(200, 160)
106
+ if not self._rows:
107
+ self._rows = [dict(r) for r in self._DEMO]
108
+
109
+ # ------------------------------------------------------------------ #
110
+ ## Data API
111
+ # ------------------------------------------------------------------ #
112
+ def setData(self, rows):
113
+ self._rows = [dict(r) for r in (rows or [])]
114
+ self._pix_cache.clear()
115
+ self.update()
116
+
117
+ def data(self):
118
+ return [dict(r) for r in self._rows]
119
+
120
+ def setColors(self, fills, texts=None):
121
+ self._colors = [QColor(c) for c in fills if c]
122
+ if texts:
123
+ self._text_colors = [QColor(c) for c in texts if c]
124
+ self.update()
125
+
126
+ def _fill(self, cat):
127
+ return QColor(self._colors[int(cat) % len(self._colors)]) if self._colors else QColor("#ffffff")
128
+
129
+ def _text(self, cat):
130
+ if self._text_colors:
131
+ return QColor(self._text_colors[int(cat) % len(self._text_colors)])
132
+ return QColor("#1c1c20")
133
+
134
+ def _pixmap(self, icon):
135
+ if icon is None or icon == "":
136
+ return None
137
+ if isinstance(icon, QPixmap):
138
+ return icon if not icon.isNull() else None
139
+ key = str(icon)
140
+ if key in self._pix_cache:
141
+ return self._pix_cache[key]
142
+ pm = QPixmap(key) if os.path.exists(key) else QPixmap()
143
+ pm = pm if not pm.isNull() else None
144
+ self._pix_cache[key] = pm
145
+ return pm
146
+
147
+ # ------------------------------------------------------------------ #
148
+ ## Painting
149
+ # ------------------------------------------------------------------ #
150
+ def paintEvent(self, e):
151
+ if not self._rows:
152
+ return
153
+ p = QPainter(self)
154
+ p.setRenderHint(QPainter.Antialiasing, True)
155
+ p.setRenderHint(QPainter.SmoothPixmapTransform, True)
156
+ w, h = self.width(), self.height()
157
+ n = len(self._rows)
158
+
159
+ axis_font = QFont(self.font()); axis_font.setPointSize(9)
160
+ afm = QFontMetrics(axis_font)
161
+ x_room = afm.height() + 8 # bottom band for x tick labels
162
+
163
+ left = float(self._label_w)
164
+ plot_x0 = left
165
+ plot_w = max(1.0, w - left - 6)
166
+ plot_h = max(1.0, h - x_room)
167
+ xmax = self._x_max if self._x_max > 0 else 1.0
168
+
169
+ def x_of(v):
170
+ return plot_x0 + (float(v) / xmax) * plot_w
171
+
172
+ # --- x grid + tick labels ---------------------------------------- #
173
+ if self._show_grid and self._grid_step > 0:
174
+ p.setFont(axis_font)
175
+ t = 0.0
176
+ while t <= xmax + 1e-9:
177
+ gx = x_of(t)
178
+ p.setPen(QPen(self._grid_color, 1, Qt.DashLine))
179
+ p.drawLine(int(gx), 0, int(gx), int(plot_h))
180
+ p.setPen(self._axis_text_color)
181
+ p.drawText(QRectF(gx - 16, plot_h + 4, 32, x_room - 4),
182
+ Qt.AlignHCenter | Qt.AlignVCenter, ("%g" % t))
183
+ t += self._grid_step
184
+
185
+ row_pitch = plot_h / n
186
+ bh = float(min(self._bar_h, row_pitch * 0.8))
187
+ lbl_font = QFont(self.font()); lbl_font.setPointSize(10)
188
+ val_font = QFont(self.font()); val_font.setPointSize(9); val_font.setBold(True)
189
+
190
+ for i, row in enumerate(self._rows):
191
+ cy = row_pitch * (i + 0.5)
192
+ start = float(row.get("start", 0))
193
+ length = float(row.get("length", 0))
194
+ cat = row.get("category", 0)
195
+ value = row.get("value", "")
196
+ x0 = x_of(start)
197
+ x1 = x_of(start + length)
198
+ bar_w = max(bh, x1 - x0) # never thinner than a circle
199
+ rect = QRectF(x0, cy - bh / 2.0, bar_w, bh)
200
+ r = bh / 2.0
201
+
202
+ # bar
203
+ p.setPen(Qt.NoPen)
204
+ p.setBrush(QBrush(self._fill(cat)))
205
+ p.drawRoundedRect(rect, r, r)
206
+
207
+ # row label (left gutter)
208
+ p.setFont(lbl_font)
209
+ p.setPen(self._label_color)
210
+ p.drawText(QRectF(0, cy - bh / 2.0, left - 8, bh),
211
+ Qt.AlignRight | Qt.AlignVCenter, str(row.get("label", "")))
212
+
213
+ # leading circular marker / icon
214
+ if self._show_markers:
215
+ d = bh - 6
216
+ mrect = QRectF(x0 + 3, cy - d / 2.0, d, d)
217
+ pm = self._pixmap(row.get("icon"))
218
+ if pm is not None:
219
+ pr = pm.devicePixelRatio() or 1.0
220
+ aspect = (pm.width() / pr) / max(1.0, (pm.height() / pr))
221
+ if aspect > 1.3:
222
+ # a WIDE marker (e.g. an avatar group) — draw it as-is,
223
+ # left-aligned at bar height, NO circular clip.
224
+ wrect = QRectF(x0 + 3, cy - d / 2.0, d * aspect, d)
225
+ p.drawPixmap(wrect, pm, QRectF(0, 0, pm.width(), pm.height()))
226
+ else:
227
+ p.save()
228
+ clip = QPainterPath()
229
+ clip.addEllipse(mrect)
230
+ p.setClipPath(clip)
231
+ scaled = pm.scaled(int(d), int(d), Qt.KeepAspectRatioByExpanding,
232
+ Qt.SmoothTransformation)
233
+ p.drawPixmap(mrect.topLeft(), scaled)
234
+ p.restore()
235
+ p.setBrush(Qt.NoBrush)
236
+ p.setPen(QPen(QColor(0, 0, 0, 40), 1))
237
+ p.drawEllipse(mrect)
238
+ else:
239
+ p.setPen(Qt.NoPen)
240
+ p.setBrush(QBrush(QColor("#ffffff")))
241
+ p.drawEllipse(mrect)
242
+ dot = mrect.adjusted(d * 0.28, d * 0.28, -d * 0.28, -d * 0.28)
243
+ p.setBrush(QBrush(self._text(cat)))
244
+ p.drawEllipse(dot)
245
+
246
+ # trailing value
247
+ if value != "" and value is not None:
248
+ p.setFont(val_font)
249
+ p.setPen(self._text(cat))
250
+ vw = 30.0
251
+ p.drawText(QRectF(rect.right() - vw - 8, cy - bh / 2.0, vw, bh),
252
+ Qt.AlignRight | Qt.AlignVCenter, str(value))
253
+ p.end()
254
+
255
+ # ------------------------------------------------------------------ #
256
+ ## Designer properties
257
+ # ------------------------------------------------------------------ #
258
+ @Property(str)
259
+ def dataCsv(self):
260
+ return ";".join(
261
+ "%s,%g,%g,%d,%s" % (r.get("label", ""), float(r.get("start", 0)),
262
+ float(r.get("length", 0)), int(r.get("category", 0)),
263
+ str(r.get("value", "")))
264
+ for r in self._rows)
265
+
266
+ @dataCsv.setter
267
+ def dataCsv(self, text):
268
+ rows = []
269
+ for chunk in str(text).replace("|", ";").split(";"):
270
+ chunk = chunk.strip()
271
+ if not chunk:
272
+ continue
273
+ f = [t.strip() for t in chunk.split(",")]
274
+ f += [""] * (5 - len(f))
275
+ try:
276
+ rows.append({"label": f[0], "start": float(f[1] or 0),
277
+ "length": float(f[2] or 0), "category": int(float(f[3] or 0)),
278
+ "value": f[4]})
279
+ except ValueError:
280
+ pass
281
+ if rows:
282
+ self.setData(rows)
283
+
284
+ @Property(str)
285
+ def colorsCsv(self):
286
+ return ",".join(c.name() for c in self._colors)
287
+
288
+ @colorsCsv.setter
289
+ def colorsCsv(self, text):
290
+ cols = [t.strip() for t in str(text).replace(";", ",").split(",") if t.strip()]
291
+ if cols:
292
+ self._colors = [QColor(c) for c in cols]
293
+ self.update()
294
+
295
+ @Property(str)
296
+ def textColorsCsv(self):
297
+ return ",".join(c.name() for c in self._text_colors)
298
+
299
+ @textColorsCsv.setter
300
+ def textColorsCsv(self, text):
301
+ cols = [t.strip() for t in str(text).replace(";", ",").split(",") if t.strip()]
302
+ if cols:
303
+ self._text_colors = [QColor(c) for c in cols]
304
+ self.update()
305
+
306
+ @Property(float)
307
+ def xMax(self):
308
+ return self._x_max
309
+
310
+ @xMax.setter
311
+ def xMax(self, v):
312
+ self._x_max = max(1.0, float(v))
313
+ self.update()
314
+
315
+ @Property(float)
316
+ def gridStep(self):
317
+ return self._grid_step
318
+
319
+ @gridStep.setter
320
+ def gridStep(self, v):
321
+ self._grid_step = max(0.0, float(v))
322
+ self.update()
323
+
324
+ @Property(int)
325
+ def barHeight(self):
326
+ return self._bar_h
327
+
328
+ @barHeight.setter
329
+ def barHeight(self, v):
330
+ self._bar_h = max(6, int(v))
331
+ self.update()
332
+
333
+ @Property(QColor)
334
+ def labelColor(self):
335
+ return self._label_color
336
+
337
+ @labelColor.setter
338
+ def labelColor(self, c):
339
+ self._label_color = QColor(c)
340
+ self.update()
341
+
342
+ @Property(QColor)
343
+ def axisTextColor(self):
344
+ return self._axis_text_color
345
+
346
+ @axisTextColor.setter
347
+ def axisTextColor(self, c):
348
+ self._axis_text_color = QColor(c)
349
+ self.update()
350
+
351
+ @Property(QColor)
352
+ def gridColor(self):
353
+ return self._grid_color
354
+
355
+ @gridColor.setter
356
+ def gridColor(self, c):
357
+ self._grid_color = QColor(c)
358
+ self.update()
359
+
360
+ @Property(bool)
361
+ def showGrid(self):
362
+ return self._show_grid
363
+
364
+ @showGrid.setter
365
+ def showGrid(self, v):
366
+ self._show_grid = bool(v)
367
+ self.update()
368
+
369
+ @Property(bool)
370
+ def showMarkers(self):
371
+ return self._show_markers
372
+
373
+ @showMarkers.setter
374
+ def showMarkers(self, v):
375
+ self._show_markers = bool(v)
376
+ self.update()