michis-python-sammlung 0.0.1__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: michis_python_sammlung
3
+ Version: 0.0.1
4
+ Summary: Collection of mostly QT related stuff
5
+ Author-email: Michael Mischko <mischkom@web.de>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
+ Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
File without changes
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "michis_python_sammlung"
3
+ version = "0.0.1"
4
+ authors = [
5
+ { name="Michael Mischko", email="mischkom@web.de" },
6
+ ]
7
+ description = "Collection of mostly QT related stuff"
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/mischkomichael/Michis_python_sammlung"
19
+ Issues = "https://github.com/mischkomichael/Michis_python_sammlung/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ __all__ = ["..."]
2
+ __version__ = "1.0.1"
@@ -0,0 +1,210 @@
1
+ from __future__ import annotations
2
+
3
+ from PyQt6.QtCore import Qt, QSize, pyqtSignal, Qt, QPoint, QTimer, QRect
4
+ from PyQt6.QtWidgets import QLabel, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLayout, QSizePolicy, QStackedLayout
5
+ from PyQt6.QtGui import QPixmap, QResizeEvent, QColorConstants, QPainter, QRegion, QCursor, QMouseEvent, QColor, QBitmap, QTransform
6
+
7
+ from numbers import Number
8
+ from typing import Callable
9
+
10
+ from PyQt6.QtGui import QImage
11
+ from michis_python_sammlung.pyqt.image_paths import Image_paths
12
+
13
+ def create_alpha_mask(image: QImage | QPixmap) -> QBitmap:
14
+ # Ensure the image has an alpha channel
15
+ if isinstance(image, QPixmap):
16
+ image = image.toImage()
17
+ image_with_alpha = image.convertToFormat(QImage.Format.Format_ARGB32)
18
+ mask_image = image_with_alpha.createAlphaMask()
19
+ return QBitmap.fromImage(mask_image)
20
+
21
+ class Clickable_label(QLabel):
22
+
23
+ sg_clicked = pyqtSignal(QMouseEvent)
24
+
25
+ def __init__(
26
+ self,
27
+ pixmap: str | QPixmap,
28
+ pixmap_hover: str | QPixmap,
29
+ on_click_action: Callable = lambda: ...,
30
+ cursor: QPixmap | Qt.CursorShape = Qt.CursorShape.ArrowCursor,
31
+ scaled: float = None,
32
+ heuristic_mask: bool = True,
33
+ parent: QWidget = None
34
+ ):
35
+ super().__init__(parent)
36
+ self.setAlignment(Qt.AlignmentFlag.AlignCenter)
37
+ self._scaled = scaled
38
+ self.configure_pixmaps(pixmap, pixmap_hover, scaled=scaled)
39
+ self._hovering = False
40
+ #somehow, the sizes are too small for the mask to not clip from the top and bottom ...
41
+ self.setMinimumSize(self._pixmap_hover.size())
42
+ cursor = QCursor(cursor) if isinstance(cursor, QPixmap) else cursor
43
+ self.setCursor(cursor)
44
+ self._on_click_action: Callable = on_click_action
45
+ self._heuristic_mask = heuristic_mask
46
+ self._mask_initialized = True
47
+ # ### lazy mask evaluation to make sure Clickable_labels can be instantiated even without properly initialized paintDevice
48
+ #self.set_mask(heuristic_mask=heuristic_mask)
49
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
50
+ self.setAutoFillBackground(False)
51
+
52
+ def sizeHint(self):
53
+ return self._pixmap_hover.size()
54
+
55
+ def showEvent(self, a0):
56
+ if not self._mask_initialized:
57
+ QTimer.singleShot(0, lambda: self._lazy_mask_init(self._heuristic_mask))
58
+ return super().showEvent(a0)
59
+
60
+ def _lazy_mask_init(self, heuristic_mask: bool) -> None:
61
+ self.set_mask(heuristic_mask=heuristic_mask)
62
+ self._mask_initialized
63
+
64
+ def replace_widget(
65
+ self,
66
+ widget_to_replace: QWidget,
67
+ delete_old: bool = True
68
+ ) -> Clickable_label | tuple[Clickable_label]:
69
+ """
70
+ Replace an existing QWidget with self. Return self if delete_old is True or a tuple (self, replaced_widget) if not.
71
+ """
72
+ layouts_to_search: list[QLayout] = widget_to_replace.parent().findChildren(QVBoxLayout) + widget_to_replace.parent().findChildren(QHBoxLayout)
73
+ found_layout: None | QHBoxLayout | QVBoxLayout = None
74
+ for layout in layouts_to_search:
75
+ for index in range(layout.count()):
76
+ if widget_to_replace is layout.itemAt(index).widget():
77
+ found_layout = layout
78
+ break
79
+ if found_layout is None:
80
+ raise Exception("clickable_label.replace_widget_in_layout: widget has no layout !")
81
+ else:
82
+ found_layout.replaceWidget(widget_to_replace, self)
83
+ if delete_old:
84
+ widget_to_replace.deleteLater()
85
+ return self
86
+ else:
87
+ return (self, widget_to_replace)
88
+
89
+ def resizeEvent(self, a0: QResizeEvent):
90
+ super().resizeEvent(a0)
91
+ self.set_mask(a0.size(), self._heuristic_mask)
92
+
93
+ def enterEvent(self, event):
94
+ self._hovering = True
95
+ self.setPixmap(self._pixmap_hover)
96
+ super().enterEvent(event)
97
+
98
+ def leaveEvent(self, a0):
99
+ self._hovering = False
100
+ self.setPixmap(self._pixmap)
101
+ super().leaveEvent(a0)
102
+
103
+ def mouseReleaseEvent(self, ev):
104
+ self._on_click_action()
105
+ self.sg_clicked.emit(ev)
106
+ return super().mouseReleaseEvent(ev)
107
+
108
+ def set_mask(self, new_size: QSize | None = None, heuristic_mask: bool=True) -> None:
109
+ new_size = new_size if new_size is not None else self._pixmap_hover.size()
110
+ empty_mask = QPixmap(new_size)
111
+ empty_mask.fill(QColorConstants.Transparent)
112
+ painter = QPainter(empty_mask)
113
+ center_anker_x = (new_size.width()-self._pixmap_hover.size().width())//2
114
+ center_anker_y = (new_size.height()-self._pixmap_hover.size().height())//2
115
+ painter.drawPixmap(center_anker_x, center_anker_y, self._pixmap_hover)
116
+ painter.end()
117
+ if not heuristic_mask:
118
+ self._mask = create_alpha_mask(empty_mask)
119
+ else:
120
+ self._mask = empty_mask.createHeuristicMask(clipTight=False)
121
+ self.setMask(self._mask)
122
+ # self.setMask(empty_mask.createMaskFromColor(QColorConstants.Transparent, Qt.MaskMode.MaskInColor))
123
+
124
+ def replace_on_click_action(self, new_action: Callable) -> None:
125
+ if not isinstance(new_action, Callable):
126
+ raise ValueError(f"utility.custom_widgets.clickable_label.Cliclable_label.replace_on_click_action:\
127
+ Action must be of type 'Callable', is {type(new_action)} instead.")
128
+ self._on_click_action = new_action
129
+
130
+ def configure_pixmaps(self,
131
+ pixmap: str | QPixmap,
132
+ pixmap_hover: str | QPixmap,
133
+ scaled: float = None
134
+ ) -> None:
135
+ self._pixmap = QPixmap(pixmap) if isinstance(pixmap,str) else pixmap
136
+ self._pixmap_hover = QPixmap(pixmap_hover) if isinstance(pixmap_hover,str) else pixmap_hover
137
+ if self._pixmap.isNull() or self._pixmap_hover.isNull():
138
+ raise ValueError(f"Failed instantiating pixmap from path: {pixmap} / {pixmap_hover}")
139
+ if isinstance(scaled, Number):
140
+ self._scaled = scaled
141
+ self._pixmap = self._pixmap.scaled(self._pixmap.size()*self._scaled,transformMode=Qt.TransformationMode.SmoothTransformation)
142
+ self._pixmap_hover = self._pixmap_hover.scaled(self._pixmap_hover.size()*self._scaled,transformMode=Qt.TransformationMode.SmoothTransformation)
143
+ elif scaled is None:
144
+ self.setScaledContents(False)
145
+ self.setPixmap(self._pixmap)
146
+
147
+ def scale_pixmap(self, scaled: Number) -> None:
148
+ self._scaled = scaled
149
+ self._pixmap = self._pixmap.scaled(self.size()*self._scaled,transformMode=Qt.TransformationMode.SmoothTransformation)
150
+ self._pixmap_hover = self._pixmap_hover.scaled(self.size()*self._scaled,transformMode=Qt.TransformationMode.SmoothTransformation)
151
+
152
+ def reset(self) -> None:
153
+ self.setPixmap(self._pixmap)
154
+ self._hovering = False
155
+
156
+ class Togglable_clickable_label(QWidget):
157
+
158
+ def __init__(
159
+ self,
160
+ untoggled_label: Clickable_label,
161
+ toggled_label: Clickable_label,
162
+ toggle_on_click: bool = True,
163
+ parent=None,
164
+ allow_expansion=True
165
+ ):
166
+ super().__init__(parent=parent)
167
+ if not (isinstance(untoggled_label, Clickable_label) or isinstance(toggled_label, Clickable_label)):
168
+ raise ValueError(f"label_1 and label_2 need to be of type Clickable_label not {type(untoggled_label), type(toggled_label)}")
169
+ self.untoggled_label = untoggled_label
170
+ self.toggled_label = toggled_label
171
+ size_1 = self.untoggled_label._pixmap_hover
172
+ size_2 = self.toggled_label._pixmap_hover
173
+ if allow_expansion:
174
+ self.setMinimumSize(QSize(size_1.width() if size_1.width() > size_2.width() else size_2.width(),
175
+ size_1.height() if size_1.height() > size_2.height() else size_2.height()))
176
+ else:
177
+ self.setFixedSize(QSize(size_1.width() if size_1.width() > size_2.width() else size_2.width(),
178
+ size_1.height() if size_1.height() > size_2.height() else size_2.height()))
179
+ self._layout = QVBoxLayout(self)
180
+ self._layout.setContentsMargins(0,0,0,0)
181
+ self._layout.addWidget(untoggled_label)
182
+ self._layout.addWidget(toggled_label)
183
+ self.toggled = False
184
+ self.toggle(toggle=self.toggled)
185
+ if toggle_on_click:
186
+ print("TOGGLING")
187
+ self.untoggled_label.sg_clicked.connect(self.toggle)
188
+ self.toggled_label.sg_clicked.connect(self.toggle)
189
+
190
+ def toggle(self, mouse_event: QMouseEvent=None, toggle=None) -> None:
191
+ self.toggled = not self.toggled if toggle is None else toggle
192
+ if self.toggled:
193
+ self.untoggled_label.setVisible(False)
194
+ self.toggled_label.setVisible(True)
195
+ else:
196
+ self.toggled_label.setVisible(False)
197
+ self.untoggled_label.setVisible(True)
198
+
199
+ def replace_on_click_actions(self, action_1: Callable, action_2: Callable) -> None:
200
+ self.untoggled_label.replace_on_click_action(action_1)
201
+ self.toggled_label.replace_on_click_action(action_2)
202
+
203
+ def main():
204
+ import sys
205
+ app = QApplication(sys.argv)
206
+ label = Clickable_label(Image_paths.cogwheel, Image_paths.cogwheel_hover, heuristic_mask=False)
207
+ label.show()
208
+ sys.exit(app.exec())
209
+ if __name__ == "__main__":
210
+ main()
@@ -0,0 +1,494 @@
1
+ from PyQt6.QtCore import Qt, pyqtSignal, QPropertyAnimation, pyqtProperty, QSize, QPoint, QTimer, QCoreApplication, QEvent
2
+ from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QSizePolicy, QSpacerItem, QLayout
3
+ from PyQt6.QtGui import QTransform, QPixmap, QPainter, QPen
4
+ import PyQt6.sip as sip
5
+
6
+ from pyqt_util import clear_layout
7
+ from clickable_label import Clickable_label, Togglable_clickable_label, Image_paths
8
+
9
+ import faulthandler
10
+ faulthandler.enable()
11
+
12
+ class Expandable_widget(QWidget):
13
+
14
+ ANIMATION_DURATION = 150
15
+ sg_animation_finished = pyqtSignal()
16
+ sg_animation_started = pyqtSignal()
17
+
18
+ _horizontal_directions = ("left", "right")
19
+ _vertical_directions = ("up", "down")
20
+ _possible_directions = _horizontal_directions + _vertical_directions
21
+
22
+ def __init__(self,
23
+ direction: str = "left",
24
+ animate_button: bool = False,
25
+ parent=None
26
+ ):
27
+ super().__init__(parent=parent)
28
+ if not direction in self._possible_directions:
29
+ raise ValueError(f"Invalid directional argument: {direction}. Valid arguments are: {self._possible_directions} !")
30
+ self.direction = direction
31
+ self._content_widget = QWidget(parent=self)
32
+ self.animation = None
33
+ self._expanded = False
34
+ self._animate_button = animate_button
35
+ self._widget_position_at_animation_start = None
36
+ self.toggle_button = self._create_toggle_button()
37
+ self.header_widget = self._create_header_widget()
38
+ # self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
39
+ self._setup()
40
+
41
+ @property
42
+ def expanded(self) -> bool:
43
+ return self._expanded
44
+
45
+ @property
46
+ def content_widget(self) -> QWidget:
47
+ return self._content_widget
48
+
49
+ def set_content_widget(self, widget: QWidget) -> None:
50
+ layout = self.layout()
51
+ if self.content_widget is not None:
52
+ removed_widget = layout.replaceWidget(self.content_widget, widget).widget()
53
+ removed_widget.setParent(None)
54
+ removed_widget.deleteLater()
55
+ else:
56
+ layout.addWidget(widget)
57
+ self._content_widget = widget
58
+ self._setup_animation()
59
+ self._execute_toggle()
60
+
61
+ def _create_toggle_button(self) -> Togglable_clickable_label:
62
+ untoggled_pixmap, untoggled_hover_pixmap, toggled_pixmap, toggled_hover_pixmap = self._create_button_pixmaps()
63
+ expand_button = Clickable_label(untoggled_pixmap, untoggled_hover_pixmap, lambda: self.toggle(True, True), scaled=0.6)
64
+ shrink_button = Clickable_label(toggled_pixmap, toggled_hover_pixmap, lambda: self.toggle(False, True), scaled=0.6)
65
+ toggle_button = Togglable_clickable_label(expand_button, shrink_button)
66
+ return toggle_button
67
+
68
+ def _rotate_pixmaps(self,
69
+ base_pixmap: QPixmap,
70
+ base_hover_pixmap: QPixmap,
71
+ rotation_start: QTransform,
72
+ rotation_end: QTransform
73
+ ) -> tuple[QPixmap]:
74
+ untoggled_pixmap = base_pixmap.transformed(rotation_start)
75
+ untoggled_hover_pixmap = base_hover_pixmap.transformed(rotation_start)
76
+ toggled_pixmap = base_pixmap.transformed(rotation_end)
77
+ toggled_hover_pixmap = base_hover_pixmap.transformed(rotation_end)
78
+ return untoggled_pixmap, untoggled_hover_pixmap, toggled_pixmap, toggled_hover_pixmap
79
+
80
+ def _create_rotated_pixmaps_left(self,
81
+ base_pixmap: QPixmap,
82
+ base_hover_pixmap: QPixmap
83
+ ) -> tuple[QPixmap]:
84
+ if self._animate_button:
85
+ rotation_start = QTransform().rotate(180)
86
+ rotation_end = QTransform().rotate(0)
87
+ else:
88
+ rotation_start = QTransform().rotate(90)
89
+ rotation_end = QTransform().rotate(180)
90
+ return self._rotate_pixmaps(base_pixmap, base_hover_pixmap, rotation_start, rotation_end)
91
+
92
+ def _create_rotated_pixmaps_right(self,
93
+ base_pixmap: QPixmap,
94
+ base_hover_pixmap: QPixmap
95
+ ) -> tuple[QPixmap]:
96
+ if self._animate_button:
97
+ rotation_start = QTransform().rotate(0)
98
+ rotation_end = QTransform().rotate(180)
99
+ else:
100
+ rotation_start = QTransform().rotate(90)
101
+ rotation_end = QTransform().rotate(0)
102
+ return self._rotate_pixmaps(base_pixmap, base_hover_pixmap, rotation_start, rotation_end)
103
+
104
+ def _create_rotated_pixmaps_up(self,
105
+ base_pixmap: QPixmap,
106
+ base_hover_pixmap: QPixmap
107
+ ) -> tuple[QPixmap]:
108
+ if self._animate_button:
109
+ rotation_start = QTransform().rotate(270)
110
+ rotation_end = QTransform().rotate(90)
111
+ else:
112
+ rotation_start = QTransform().rotate(0)
113
+ rotation_end = QTransform().rotate(270)
114
+ return self._rotate_pixmaps(base_pixmap, base_hover_pixmap, rotation_start, rotation_end)
115
+
116
+ def _create_rotated_pixmaps_down(self,
117
+ base_pixmap: QPixmap,
118
+ base_hover_pixmap: QPixmap
119
+ ) -> tuple[QPixmap]:
120
+ if self._animate_button:
121
+ rotation_start = QTransform().rotate(90)
122
+ rotation_end = QTransform().rotate(270)
123
+ else:
124
+ rotation_start = QTransform().rotate(0)
125
+ rotation_end = QTransform().rotate(90)
126
+ return self._rotate_pixmaps(base_pixmap, base_hover_pixmap, rotation_start, rotation_end)
127
+
128
+ def _create_button_pixmaps(self) -> tuple[QPixmap]:
129
+ base_pixmap = QPixmap(Image_paths.play_right)
130
+ base_hover_pixmap = QPixmap(Image_paths.play_right_hover)
131
+ match self.direction:
132
+ case "left":
133
+ return self._create_rotated_pixmaps_left(base_pixmap, base_hover_pixmap)
134
+ case "right":
135
+ return self._create_rotated_pixmaps_right(base_pixmap, base_hover_pixmap)
136
+ case "up":
137
+ return self._create_rotated_pixmaps_up(base_pixmap, base_hover_pixmap)
138
+ case "down":
139
+ return self._create_rotated_pixmaps_down(base_pixmap, base_hover_pixmap)
140
+
141
+ def _setup_animation(self) -> None:
142
+ if self.animation is not None:
143
+ self.animation.setParent(None)
144
+ self.animation.deleteLater()
145
+ if self.direction in self._vertical_directions:
146
+ self.animation = QPropertyAnimation(self, b"_content_maximum_height")
147
+ else:
148
+ self.animation = QPropertyAnimation(self, b"_content_maximum_width")
149
+ self.animation.setDuration(self.ANIMATION_DURATION)
150
+ self.animation.finished.connect(self.sg_animation_finished)
151
+ self.animation.valueChanged.connect(self._animation_value_changed)
152
+
153
+ @pyqtProperty(int)
154
+ def _content_maximum_width(self) -> int:
155
+ return self.content_widget.maximumWidth()
156
+
157
+ def _set_outer_width_anchor_right(self, outer_w: int):
158
+ g = self.geometry()
159
+ right = g.x() + g.width()
160
+ g.setWidth(outer_w)
161
+ g.moveLeft(right)
162
+ self.setGeometry(g)
163
+
164
+ def _set_outer_height_achor_bottom(self, outer_h: int) -> None:
165
+ g = self.geometry()
166
+ bottom = g.y() + g.bottom()
167
+ g.setHeight(outer_h)
168
+ g.moveTop(bottom)
169
+ self.setGeometry(g)
170
+
171
+ @_content_maximum_width.setter
172
+ def _content_maximum_width(self, new_width: int) -> None:
173
+ self.content_widget.setMaximumWidth(new_width)
174
+
175
+ # Outer width = header + content (+ margins if any)
176
+ header_w = self.header_widget.sizeHint().width() # or actual header widget
177
+ outer_w = header_w + new_width
178
+
179
+ if self.direction == "left":
180
+ self._set_outer_width_anchor_right(outer_w)
181
+ else:
182
+ self.resize(outer_w, self.height()) # natural right-grow
183
+
184
+ @pyqtProperty(int)
185
+ def _content_maximum_height(self) -> int:
186
+ return self.content_widget.maximumHeight()
187
+
188
+ @_content_maximum_height.setter
189
+ def _content_maximum_height(self, new_height: int) -> None:
190
+ self.content_widget.setMaximumHeight(new_height)
191
+
192
+ header_h = self.header_widget.sizeHint().height()
193
+ outer_h = header_h + new_height
194
+
195
+ if self.direction == "up":
196
+ self._set_outer_height_achor_bottom(outer_h)
197
+ else:
198
+ self.resize(self.width(), outer_h)
199
+
200
+ def _create_header_widget(self) -> QWidget:
201
+ widget = QWidget(parent=self)
202
+ horizontal_layout = QHBoxLayout()
203
+ horizontal_layout.addWidget(self.toggle_button)
204
+ horizontal_layout.setContentsMargins(0,0,0,0)
205
+ horizontal_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
206
+ widget.setLayout(horizontal_layout)
207
+ return widget
208
+
209
+ def _setup(self) -> None:
210
+ self._setup_widget()
211
+ self._setup_layout()
212
+ self._setup_animation()
213
+
214
+ def _setup_widget(self) -> None:
215
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
216
+ self.setWindowFlags(Qt.WindowType.Widget)
217
+ self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
218
+
219
+ def _execute_toggle(self, animated=False):
220
+ if animated:
221
+ if self.direction in self._vertical_directions:
222
+ start = self.content_widget.maximumHeight()
223
+ end = max(0,self.content_widget.sizeHint().height()) if self._expanded else 0
224
+ else:
225
+ start = self.content_widget.maximumWidth()
226
+ end = max(0,self.content_widget.sizeHint().width()) if self._expanded else 0
227
+ self.animation.stop()
228
+ self.animation.setStartValue(start)
229
+ if self._expanded:
230
+ self._widget_position_at_animation_start = self.pos()
231
+ self.animation.setEndValue(end)
232
+ self.sg_animation_started.emit()
233
+ self.animation.start()
234
+ else:
235
+ if self.direction in self._vertical_directions:
236
+ height = 0 if not self._expanded else max(0,self.content_widget.sizeHint().height())
237
+ self.content_widget.setMaximumHeight(height)
238
+ else:
239
+ width = max(0,self.content_widget.sizeHint().width()) if self._expanded else 0
240
+ self.content_widget.setMaximumWidth(width)
241
+
242
+ def toggle(self,
243
+ expand: bool,
244
+ animated: bool = False,
245
+ button_toggle: bool | None = None
246
+ ) -> None:
247
+ self._expanded = expand
248
+ self._execute_toggle(animated)
249
+ if button_toggle is not None:
250
+ self.toggle_button.toggle(button_toggle)
251
+
252
+ def _animation_value_changed(self, new_max_heigt: QSize) -> None: ...
253
+ # self.layout().activate()
254
+ # self.adjustSize()
255
+
256
+ def _create_layout_left(self) -> QLayout:
257
+ layout = QHBoxLayout()
258
+ layout.setAlignment(Qt.AlignmentFlag.AlignRight)
259
+ if self._animate_button:
260
+ layout.addWidget(self.header_widget)
261
+ layout.addWidget(self.content_widget)
262
+ else:
263
+ layout.addWidget(self.content_widget)
264
+ layout.addWidget(self.header_widget)
265
+ return layout
266
+
267
+ def _create_layout_right(self) -> QLayout:
268
+ layout = QHBoxLayout()
269
+ layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
270
+ if self._animate_button:
271
+ layout.addWidget(self.content_widget)
272
+ layout.addWidget(self.header_widget)
273
+ else:
274
+ layout.addWidget(self.header_widget)
275
+ layout.addWidget(self.content_widget)
276
+ return layout
277
+
278
+ def _create_layout_up(self) -> QLayout:
279
+ layout = QVBoxLayout()
280
+ layout.setAlignment(Qt.AlignmentFlag.AlignBottom)
281
+ if self._animate_button:
282
+ layout.addWidget(self.header_widget)
283
+ layout.addWidget(self.content_widget)
284
+ else:
285
+ layout.addWidget(self.content_widget)
286
+ layout.addWidget(self.header_widget)
287
+ return layout
288
+
289
+ def _create_layout_down(self) -> QLayout:
290
+ layout = QVBoxLayout()
291
+ layout.setAlignment(Qt.AlignmentFlag.AlignTop)
292
+ if self._animate_button:
293
+ layout.addWidget(self.content_widget)
294
+ layout.addWidget(self.header_widget)
295
+ else:
296
+ layout.addWidget(self.header_widget)
297
+ layout.addWidget(self.content_widget)
298
+ return layout
299
+
300
+ def _setup_layout(self) -> None:
301
+ match self.direction:
302
+ case "left":
303
+ layout = self._create_layout_left()
304
+ case "right":
305
+ layout = self._create_layout_right()
306
+ case "up":
307
+ layout = self._create_layout_up()
308
+ case "down":
309
+ layout = self._create_layout_down()
310
+ layout.setSpacing(0)
311
+ layout.setContentsMargins(0,0,0,0)
312
+ self.setLayout(layout)
313
+ self._execute_toggle()
314
+
315
+
316
+ class Spoiler_content_widget(QWidget):
317
+
318
+ def __init__(self,
319
+ tree_disabled_widgets: list[QHBoxLayout],
320
+ parent=None
321
+ ):
322
+ super().__init__(parent=parent)
323
+ self._tree_disabled_widgets = tree_disabled_widgets
324
+ self._content_offset = 0
325
+ self._line_offset = 0
326
+ self._tree_rendering_enabled = True
327
+ self._setup()
328
+
329
+ def _setup(self) -> None:
330
+ vertical_layout = QVBoxLayout()
331
+ vertical_layout.setContentsMargins(0,0,0,0)
332
+ self.setLayout(vertical_layout)
333
+
334
+ def set_content_offset(self, offset: int) -> None:
335
+ self._content_offset = offset
336
+ self.update()
337
+
338
+ def enable_tree_rendering(self, enable: bool) -> None:
339
+ self._tree_rendering_enabled = enable
340
+ self.update()
341
+
342
+ def set_line_offset(self, offset: int) -> None:
343
+ self._line_offset = offset
344
+ self.update()
345
+
346
+ def paintEvent(self, event):
347
+ if self._tree_rendering_enabled:
348
+ pen = QPen(Qt.GlobalColor.black, 2)
349
+ painter = QPainter(self)
350
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
351
+ painter.setPen(pen)
352
+ line_start = QPoint(self._line_offset,0)
353
+ # get all child widgets inside the layout
354
+ for i in range(self.layout().count()):
355
+ l = self.layout().itemAt(i).layout()
356
+ if not l or l in self._tree_disabled_widgets:
357
+ continue
358
+
359
+ # get widget geometry relative to container
360
+ rect = l.geometry()
361
+ center_y = rect.top() + rect.height() // 2
362
+
363
+ # draw arrow from margin → widget
364
+ arrow_end_x = line_start.x()+ max(self._content_offset-self._line_offset,0)
365
+ painter.drawLine(line_start.x(), line_start.y(), line_start.x(), center_y)
366
+ painter.drawLine(line_start.x(), center_y, arrow_end_x, center_y)
367
+ else:
368
+ super().paintEvent(event)
369
+
370
+ class Spoiler_widget(Expandable_widget):
371
+
372
+ _content_widget: Spoiler_content_widget
373
+
374
+ def __init__(self,
375
+ tree_rendering: bool = True,
376
+ parent=None
377
+ ):
378
+ super().__init__(direction="down", parent=parent)
379
+ self.sg_animation_started.connect(lambda: setattr(self, "_animation_going", True))
380
+ self.sg_animation_finished.connect(lambda: setattr(self, "_animation_going", False))
381
+ self._layout_widget_mapping: dict[QWidget, QHBoxLayout] = {}
382
+ self._tree_disabled_content: list[QHBoxLayout] = []
383
+ self._animation_going = False
384
+ self._content_offset = 0
385
+
386
+ content_widget = Spoiler_content_widget(self._tree_disabled_content)
387
+ content_widget.set_line_offset(self.toggle_button.sizeHint().width()//2)
388
+ self.set_content_widget(content_widget)
389
+ self.enable_tree_rendering(tree_rendering)
390
+
391
+ def set_content_margins(self, left, top, right, bottom) -> None:
392
+ self.content_widget.layout().setContentsMargins(left, top, right, bottom)
393
+ self.update()
394
+
395
+ def enable_tree_rendering(self, enable: bool) -> None:
396
+ self._content_widget.enable_tree_rendering(enable)
397
+ self.set_content_offset(self.toggle_button.sizeHint().width()//2)
398
+ self.update()
399
+
400
+ def set_content_offset(self, offset: int) -> None:
401
+ self._content_offset = offset
402
+ self._content_widget.set_content_offset(offset)
403
+ self.update()
404
+
405
+ def __iter__(self):
406
+ return iter(self._layout_widget_mapping.keys())
407
+
408
+ def count(self) -> int:
409
+ return len(self._layout_widget_mapping)
410
+
411
+ def add_header_widget(self,
412
+ widget: QWidget
413
+ ) -> None:
414
+ self.header_widget.layout().addWidget(widget)
415
+
416
+ def _schedule_geo_update(self):
417
+ if not self._expanded:
418
+ return
419
+ QTimer.singleShot(0, self._run_geo_update)
420
+
421
+ def _run_geo_update(self) -> None:
422
+ if self._animation_going:
423
+ return
424
+ self._update_content_geometries()
425
+
426
+ def eventFilter(self, obj, event):
427
+ if event.type() in (QEvent.Type.LayoutRequest, QEvent.Type.Resize):
428
+ self._schedule_geo_update()
429
+ return super().eventFilter(obj, event)
430
+
431
+ def add_widget(self,
432
+ widget: QWidget,
433
+ at_index: int | None = None,
434
+ disable_tree: bool = False
435
+ ) -> None:
436
+ layout = QHBoxLayout()
437
+ # layout.insertItem(0, QSpacerItem(self.toggle_button.sizeHint().width()+15 , 0))
438
+ spacer = QSpacerItem(self._content_offset , 0)
439
+ layout.insertItem(0, spacer)
440
+ layout.addWidget(widget)
441
+ widget.installEventFilter(self)
442
+ if at_index is None:
443
+ self.content_widget.layout().addLayout(layout)
444
+ else:
445
+ self.content_widget.layout().insertLayout(at_index, layout)
446
+ if self._expanded:
447
+ QTimer.singleShot(0, self._update_content_geometries)
448
+ self._layout_widget_mapping[widget] = layout
449
+ if disable_tree:
450
+ self._tree_disabled_content.append(layout)
451
+
452
+ def remove_widget(self,
453
+ widget: QWidget
454
+ ) -> QWidget:
455
+ layout = self._layout_widget_mapping[widget]
456
+ del self._layout_widget_mapping[widget]
457
+ clear_layout(layout)
458
+ layout.setParent(None)
459
+ layout.deleteLater()
460
+ if layout in self._tree_disabled_content:
461
+ self._tree_disabled_content.remove(layout)
462
+
463
+ def _update_content_geometries(self) -> None:
464
+ #self.content_widget.updateGeometry()
465
+ # self.content_widget.adjustSize()
466
+ self.content_widget.setMaximumHeight(self.content_widget.sizeHint().height())
467
+ self.update()
468
+
469
+ # def sizeHint(self):
470
+ # header_h = self.header_widget.size().height()
471
+ # content_h = self.content_widget.maximumHeight()
472
+ # return QSize(
473
+ # max(self.header_widget.sizeHint().width(), self.content_widget.sizeHint().width()),
474
+ # header_h + content_h
475
+ # )
476
+
477
+ def main():
478
+ import sys
479
+ app = QApplication(sys.argv)
480
+ spoiler = Expandable_widget(direction="down", animate_button=False)
481
+ spoiler_2 = Spoiler_widget()
482
+ spoiler_2.add_widget(QLabel("JOOO"))
483
+ # spoiler = Spoiler_widget()
484
+ # spoiler.add_header_widget(QLabel("I bims 1 spoiler !"))
485
+ # for i in range(5):
486
+ # spoiler.add_content_widget(QLabel(f"Label_{i}"))
487
+ spoiler.set_content_widget(QLabel("I bims 1 Label"))
488
+ spoiler.show()
489
+ #spoiler_2.show()
490
+ sys.exit(app.exec())
491
+
492
+ if __name__ == "__main__":
493
+ main()
494
+
@@ -0,0 +1,130 @@
1
+ import os
2
+
3
+ class Image_paths:
4
+ __relative_source_path__ = os.path.join(os.path.dirname(__file__), "icons\\")
5
+ _green_plus_mask = __relative_source_path__+"green_plus_mask.png"
6
+ apply = __relative_source_path__+"apply_button.png"
7
+ apply_hover = __relative_source_path__+"apply_button_hover.png"
8
+ decline: str = __relative_source_path__+"cancel_button.png"
9
+ decline_hover: str = __relative_source_path__+"cancel_button_hover.png"
10
+ green_lamp: str = __relative_source_path__+"enabled_sending.png"
11
+ green_lamp_hover: str = __relative_source_path__+"endabled_sending_hover.png"
12
+ red_lamp: str = __relative_source_path__+"disabled_sending.png"
13
+ red_lamp_hover: str = __relative_source_path__+"disabled_sending_hover.png"
14
+ grey_lamp: str = __relative_source_path__+"enabled_notSending.png"
15
+ grey_lamp_hover: str = __relative_source_path__+"disabled_notSending.png"
16
+ yellow_lamp: str = __relative_source_path__+"connectionPending.png"
17
+ settings: str = __relative_source_path__+"settings.png"
18
+ load = __relative_source_path__+"load_button.png"
19
+ load_test = __relative_source_path__+"load_button_test.png"
20
+ load_hover = __relative_source_path__+"load_button_hover.png"
21
+ save = __relative_source_path__+"save_button.png"
22
+ save_hover = __relative_source_path__+"save_button_hover.png"
23
+ menu = __relative_source_path__+"menu.png"
24
+ menu_hover = __relative_source_path__+"menu_hover.png"
25
+ power_off = __relative_source_path__+"power_off.png"
26
+ power_off_small = __relative_source_path__+"power_off_small.png"
27
+ power_off_hover = __relative_source_path__+"power_off_hover.png"
28
+ power_off_small_hover = __relative_source_path__+"power_off_small_hover.png"
29
+ power_on_small = __relative_source_path__+"power_on_small.png"
30
+ power_on_hover = __relative_source_path__+"power_on_hover.png"
31
+ power_on_small_hover = __relative_source_path__+"power_on_small_hover.png"
32
+ power_on = __relative_source_path__+"power_on.png"
33
+ power_on_hover = __relative_source_path__+"power_on_hover.png"
34
+ plug_settings = __relative_source_path__+"plug_settings.png"
35
+ plug_settings_hover = __relative_source_path__+"plug_settings_hover.png"
36
+ drag_cursor = __relative_source_path__+"drag_series_device_ui_cursor.png"
37
+ cogwheel = __relative_source_path__+"cogwheel.png"
38
+ cogwheel_hover = __relative_source_path__+"cogwheel_hover.png"
39
+ BAR = __relative_source_path__+"dark_sun.png"
40
+ new_node = __relative_source_path__+"new_node_icon.png"
41
+ new_node_hover = __relative_source_path__+"new_node_icon_hover.png"
42
+ new_action_hover = __relative_source_path__+"new_action_hover.png"
43
+ new_node_plus = __relative_source_path__+"new_node_icon_plus.png"
44
+ new_node_plus_hover = __relative_source_path__+"new_node_icon_plus_hover.png"
45
+ trash = __relative_source_path__+"trash_icon.png"
46
+ trash_hover = __relative_source_path__+"trash_icon_hover.png"
47
+ m_button = __relative_source_path__+"m_button.png"
48
+ m_button_hover = __relative_source_path__+"m_button_hover.png"
49
+ blue_plus = __relative_source_path__+"blue_plus.png"
50
+ blue_plus_hover = __relative_source_path__+"blue_plus_hover.png"
51
+ green_plus = __relative_source_path__+"green_plus.png"
52
+ green_plus_hover = __relative_source_path__+"green_plus_hover.png"
53
+ red_plus = __relative_source_path__+"red_plus.png"
54
+ red_plus_hover = __relative_source_path__+"red_plus_hover.png"
55
+ blue_minus = __relative_source_path__+"blue_minus.png"
56
+ blue_minus_hover = __relative_source_path__+"blue_minus_hover.png"
57
+ green_minus = __relative_source_path__+"green_minus.png"
58
+ green_minus_hover = __relative_source_path__+"green_minus_hover.png"
59
+ red_minus = __relative_source_path__+"red_minus.png"
60
+ red_minus_hover = __relative_source_path__+"red_minus_hover.png"
61
+ question_mark = __relative_source_path__+"question_mark.png"
62
+ question_mark_hover = __relative_source_path__+"question_mark_hover.png"
63
+ eye_open = __relative_source_path__+"eye_open.png"
64
+ eye_open_hover = __relative_source_path__+"eye_open_hover.png"
65
+ eye_filled_open = __relative_source_path__+"filled_eye_open.png"
66
+ eye_filled_open_hover = __relative_source_path__+"filled_eye_open_hover.png"
67
+ eye_filled_crossed = __relative_source_path__+"filled_eye_crossed.png"
68
+ eye_filled_crossed_hover = __relative_source_path__+"filled_eye_crossed_hover.png"
69
+ color_wheel = __relative_source_path__+"color_wheel.png"
70
+ color_wheel_hover = __relative_source_path__+"color_wheel_hover.png"
71
+ m_button = __relative_source_path__+"m_button.png"
72
+ m_button_hover = __relative_source_path__+"m_button_hover.png"
73
+ blue_plus = __relative_source_path__+"blue_plus.png"
74
+ blue_plus_hover = __relative_source_path__+"blue_plus_hover.png"
75
+ question_mark = __relative_source_path__+"question_mark.png"
76
+ question_mark_hover = __relative_source_path__+"question_mark_hover.png"
77
+ eye_open = __relative_source_path__+"eye_open.png"
78
+ eye_open_hover = __relative_source_path__+"eye_open_hover.png"
79
+ eye_filled_open = __relative_source_path__+"filled_eye_open.png"
80
+ eye_filled_open_hover = __relative_source_path__+"filled_eye_open_hover.png"
81
+ eye_filled_crossed = __relative_source_path__+"filled_eye_crossed.png"
82
+ eye_filled_crossed_hover = __relative_source_path__+"filled_eye_crossed_hover.png"
83
+ color_wheel = __relative_source_path__+"color_wheel.png"
84
+ color_wheel_hover = __relative_source_path__+"color_wheel_hover.png"
85
+ thickness = __relative_source_path__+"thickness_icon.png"
86
+ thickness_hover = __relative_source_path__+"thickness_icon_hover.png"
87
+ left_sided_thickness_arrow = __relative_source_path__+"left_sided_thickness_arrow_icon.png"
88
+ right_sided_thickness_arrow = __relative_source_path__+"right_sided_thickness_arrow_icon.png"
89
+ circle_icon = __relative_source_path__+"circle_icon.png"
90
+ circle_icon_hover = __relative_source_path__+"circle_icon_hover.png"
91
+ line_icon = __relative_source_path__+"line_icon.png"
92
+ line_icon_hover = __relative_source_path__+"line_icon_hover.png"
93
+ crosshair_icon = __relative_source_path__+"crosshair_icon.png"
94
+ crosshair_icon_hover = __relative_source_path__+"crosshair_icon_hover.png"
95
+ align_center_icon = __relative_source_path__+"align_center_icon.png"
96
+ align_center_icon_hover = __relative_source_path__+"align_center_icon_hover.png"
97
+ inner_area = __relative_source_path__+"inner_background_2.png"
98
+ outer_ring = __relative_source_path__+"outer_ring_3.png"
99
+ house_icon = __relative_source_path__+"house_icon.png"
100
+ house_icon_hover = __relative_source_path__+"house_icon_hover.png"
101
+ play_icon = __relative_source_path__+"play.png"
102
+ play_icon_hover = __relative_source_path__+"play_hover.png"
103
+ pause_icon = __relative_source_path__+"pause.png"
104
+ pause_icon_hover = __relative_source_path__+"pause_hover.png"
105
+ stop_icon = __relative_source_path__+"stop.png"
106
+ stop_icon_hover = __relative_source_path__+"stop_hover.png"
107
+ stop_icon_2 = __relative_source_path__+"stop_icon.png"
108
+ stop_icon_2_hover = __relative_source_path__+"stop_icon_hover.png"
109
+ lcd_background = __relative_source_path__+"lcd_background.png"
110
+ magnifying_glass = __relative_source_path__+"magnifying_glass.png"
111
+ magnifying_glass_hover = __relative_source_path__+"magnifying_glass_hover.png"
112
+ play_right = __relative_source_path__+"play_right.png"
113
+ play_right_hover = __relative_source_path__+"play_right_hover.png"
114
+ play_left = __relative_source_path__+"play_left.png"
115
+ play_left_hover = __relative_source_path__+"play_left_hover.png"
116
+ starwars_stop = __relative_source_path__+"starwars_stop.png"
117
+ stop_3 = __relative_source_path__+"stop_3.png"
118
+ stop_3_hover = __relative_source_path__+"stop_3_hover.png"
119
+ arrows_outwards = __relative_source_path__+"arrows_outwards_icon.png"
120
+ arrows_outwards_hover = __relative_source_path__+"arrows_outwards_hover_icon.png"
121
+ green_circular_arrow = __relative_source_path__+"green_circular_arrow.png"
122
+ green_circular_arrow_hover = __relative_source_path__+"green_circular_arrow_hover.png"
123
+ grey_grid = __relative_source_path__+"grey_grid.png"
124
+ grey_grid_hover = __relative_source_path__+"grey_grid_hover.png"
125
+ green_grid = __relative_source_path__+"green_grid.png"
126
+ green_grid_hover = __relative_source_path__+"green_grid_hover.png"
127
+ eye_colorful = __relative_source_path__+"eye_colorful.png"
128
+ eye_colorful_hover = __relative_source_path__+"eye_colorful_hover.png"
129
+ eye_crossed = __relative_source_path__+"eye_crossed.png"
130
+ eye_crossed_hover = __relative_source_path__+"eye_crossed_hover.png"
@@ -0,0 +1,97 @@
1
+ from PyQt6.QtWidgets import QWidget, QGraphicsOpacityEffect, QLayout, QMainWindow
2
+ from PyQt6.QtGui import QPainter, QColor, QColorConstants, QPen, QGuiApplication
3
+ from PyQt6.QtCore import QRect, pyqtBoundSignal
4
+ from contextlib import contextmanager
5
+ from typing import Generator, TypeVar
6
+
7
+ def set_font_size(widget: QWidget, font_size: int) -> None:
8
+ font = widget.font()
9
+ font.setPointSize(font_size)
10
+ widget.setFont(font)
11
+
12
+ def center_window(win: QMainWindow):
13
+ screen = QGuiApplication.primaryScreen()
14
+ geo = screen.availableGeometry()
15
+ win_geo = win.frameGeometry()
16
+ win_geo.moveCenter(geo.center())
17
+ win.move(win_geo.topLeft())
18
+
19
+ def disconnect_all(signal: pyqtBoundSignal) -> None:
20
+ signals = True
21
+ while(signals):
22
+ try:
23
+ signal.disconnect()
24
+ except TypeError:
25
+ signals = False
26
+
27
+ def retain_size_when_hidden(widget: QWidget, retain_size: bool) -> None:
28
+ """
29
+ Determines whether a widget still occupies space when its visibility is set to False.
30
+
31
+ Args:
32
+ widget (QWidget): A QWidget.
33
+ retain_size (bool): Do you want the widget to occupy space when being invisibe ?.
34
+
35
+ Returns:
36
+ None
37
+ """
38
+ policy = widget.sizePolicy()
39
+ policy.setRetainSizeWhenHidden(retain_size)
40
+ widget.setSizePolicy(policy)
41
+
42
+ TWidget = TypeVar("TWidget", bound=QWidget)
43
+
44
+ @contextmanager
45
+ def inactive_signaling(
46
+ widget: TWidget,
47
+ ) -> Generator[TWidget, None, None]:
48
+ try:
49
+ widget.blockSignals(True)
50
+ yield widget
51
+ finally:
52
+ widget.blockSignals(False)
53
+
54
+ def set_widget_opacity(
55
+ widget: QWidget,
56
+ value: float
57
+ ) -> None:
58
+ """value between 0.0 (fully transparent) and 1.0 (fully opaque)."""
59
+ effect = widget.graphicsEffect()
60
+ if not isinstance(effect, QGraphicsOpacityEffect):
61
+ effect = QGraphicsOpacityEffect(widget)
62
+ widget.setGraphicsEffect(effect)
63
+ effect.setOpacity(value)
64
+
65
+ def widget_in_layout(widget, layout: QLayout) -> bool:
66
+ """Check if a widget is in the given layout."""
67
+ for i in range(layout.count()): # Iterate through the layout's items
68
+ item = layout.itemAt(i)
69
+ if item.widget() == widget: # Check if the widget matches
70
+ return True
71
+ return False
72
+
73
+ def add_rect_paint_to_widget(widget: QWidget, thickness: int = 2, color: QColor = QColorConstants.Red) -> None:
74
+ """
75
+ This does not have the expected effect on Clickable_labels or any other widget with custom masking.
76
+ """
77
+ paintEvent = widget.paintEvent
78
+ def paintRect(event):
79
+ painter = QPainter(widget)
80
+ pen = QPen()
81
+ pen.setWidth(thickness)
82
+ pen.setColor(color)
83
+ inset = int(pen.widthF() / 2.0)
84
+ painter.setPen(pen)
85
+ painter.drawRect(widget.rect().adjusted(inset, inset, -inset, -inset))
86
+ paintEvent(event)
87
+ widget.paintEvent = paintRect
88
+
89
+ def clear_layout(layout: QLayout) -> None:
90
+ while layout.count() > 0:
91
+ item = layout.takeAt(0)
92
+ if item.widget():
93
+ item.widget().deleteLater()
94
+ elif item.layout():
95
+ clear_layout(item.layout())
96
+
97
+
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: michis_python_sammlung
3
+ Version: 0.0.1
4
+ Summary: Collection of mostly QT related stuff
5
+ Author-email: Michael Mischko <mischkom@web.de>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
+ Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/michis_python_sammlung/__init__.py
5
+ src/michis_python_sammlung.egg-info/PKG-INFO
6
+ src/michis_python_sammlung.egg-info/SOURCES.txt
7
+ src/michis_python_sammlung.egg-info/dependency_links.txt
8
+ src/michis_python_sammlung.egg-info/top_level.txt
9
+ src/michis_python_sammlung/pyqt/__init__.py
10
+ src/michis_python_sammlung/pyqt/clickable_label.py
11
+ src/michis_python_sammlung/pyqt/expandable_widget.py
12
+ src/michis_python_sammlung/pyqt/image_paths.py
13
+ src/michis_python_sammlung/pyqt/pyqt_util.py
14
+ src/michis_python_sammlung/pyqt/icons/__init__.py