michis-python-sammlung 0.8__tar.gz → 0.8.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.
Files changed (23) hide show
  1. michis_python_sammlung-0.8.1/PKG-INFO +184 -0
  2. michis_python_sammlung-0.8.1/README.md +167 -0
  3. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/pyproject.toml +1 -1
  4. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung/pyqt/pyqt_util.py +23 -4
  5. michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets/__init__.py +15 -0
  6. {michis_python_sammlung-0.8/src/michis_python_sammlung/pyqt → michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets}/clickable_label.py +14 -5
  7. {michis_python_sammlung-0.8/src/michis_python_sammlung/pyqt → michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets}/expandable_widget.py +13 -17
  8. michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets/flow_layout.py +126 -0
  9. michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets/password_widget.py +83 -0
  10. michis_python_sammlung-0.8.1/src/michis_python_sammlung/widgets/progress_widget.py +135 -0
  11. michis_python_sammlung-0.8.1/src/michis_python_sammlung.egg-info/PKG-INFO +184 -0
  12. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung.egg-info/SOURCES.txt +7 -3
  13. michis_python_sammlung-0.8/PKG-INFO +0 -16
  14. michis_python_sammlung-0.8/README.md +0 -0
  15. michis_python_sammlung-0.8/src/michis_python_sammlung.egg-info/PKG-INFO +0 -16
  16. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/setup.cfg +0 -0
  17. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung/__init__.py +0 -0
  18. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung/pyqt/__init__.py +0 -0
  19. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung/pyqt/icons/__init__.py +0 -0
  20. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung/pyqt/image_paths.py +0 -0
  21. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung.egg-info/dependency_links.txt +0 -0
  22. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung.egg-info/requires.txt +0 -0
  23. {michis_python_sammlung-0.8 → michis_python_sammlung-0.8.1}/src/michis_python_sammlung.egg-info/top_level.txt +0 -0
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: michis_python_sammlung
3
+ Version: 0.8.1
4
+ Summary: Personal collection of mostly QT related stuff
5
+ Author: Michael Mischko
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
+ Project-URL: Repository, https://github.com/mischkomichael/Michis_python_sammlung
9
+ Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
10
+ Keywords: pyqt6,widgets,gui
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: PyQt6<7,>=6.11
17
+
18
+ # Publishing a New Release
19
+
20
+ ## Prerequisites
21
+
22
+ * Maintain project metadata in `pyproject.toml`.
23
+ * Keep the project in a dedicated virtual environment (`.venv`).
24
+ * Runtime dependencies are declared in `pyproject.toml` under `dependencies`, **not** by freezing the virtual environment.
25
+ * The GitHub repository is connected to the existing PyPI project via **Trusted Publishing**.
26
+
27
+ ## Local Build
28
+
29
+ Build the package locally before creating a release:
30
+
31
+ ```bash
32
+ python -m build
33
+ ```
34
+
35
+ This creates:
36
+
37
+ ```
38
+ dist/
39
+ package_name-x.y.z.tar.gz
40
+ package_name-x.y.z-py3-none-any.whl
41
+ ```
42
+
43
+ Optionally verify the package:
44
+
45
+ ```bash
46
+ python -m twine check dist/*
47
+ ```
48
+
49
+ ## Releasing
50
+
51
+ 1. Update the version in `pyproject.toml`.
52
+
53
+ Example:
54
+
55
+ ```toml
56
+ version = "1.2.3"
57
+ ```
58
+
59
+ 2. Commit and push the changes.
60
+
61
+ ```bash
62
+ git add .
63
+ git commit -m "Release 1.2.3"
64
+ git push
65
+ ```
66
+
67
+ 3. Create a matching Git tag.
68
+
69
+ ```bash
70
+ git tag v1.2.3
71
+ git push origin v1.2.3
72
+ ```
73
+
74
+ 4. GitHub Actions builds the package and publishes it to PyPI.
75
+
76
+ ---
77
+
78
+ # Important Caveats
79
+
80
+ ## Always increase the version
81
+
82
+ PyPI does **not** allow uploading a package with the same version twice.
83
+
84
+ Every release must have a unique version number.
85
+
86
+ ---
87
+
88
+ ## If the GitHub Action fails because the version was wrong
89
+
90
+ Simply pressing **Re-run jobs** is **not sufficient** if the commit itself is incorrect.
91
+
92
+ Instead:
93
+
94
+ 1. Fix `pyproject.toml`.
95
+
96
+ 2. Commit and push the fix.
97
+
98
+ 3. Delete the old tag locally.
99
+
100
+ ```bash
101
+ git tag -d v1.2.3
102
+ ```
103
+
104
+ 4. Delete the tag on GitHub.
105
+
106
+ ```bash
107
+ git push origin :refs/tags/v1.2.3
108
+ ```
109
+
110
+ 5. Create the tag again on the corrected commit.
111
+
112
+ ```bash
113
+ git tag v1.2.3
114
+ git push origin v1.2.3
115
+ ```
116
+
117
+ This triggers the workflow again using the corrected source.
118
+
119
+ ---
120
+
121
+ # Common Build Issues
122
+
123
+ ### Missing README
124
+
125
+ If `pyproject.toml` contains
126
+
127
+ ```toml
128
+ readme = "README.md"
129
+ ```
130
+
131
+ then `README.md` must exist.
132
+
133
+ ---
134
+
135
+ ### License configuration
136
+
137
+ Use the modern license expression, for example:
138
+
139
+ ```toml
140
+ license = "MIT"
141
+ ```
142
+
143
+ Do **not** also include the old license classifier:
144
+
145
+ ```toml
146
+ "License :: OSI Approved :: MIT License"
147
+ ```
148
+
149
+ Modern versions of `setuptools` reject this combination.
150
+
151
+ ---
152
+
153
+ # Useful Commands
154
+
155
+ Build:
156
+
157
+ ```bash
158
+ python -m build
159
+ ```
160
+
161
+ Check package:
162
+
163
+ ```bash
164
+ python -m twine check dist/*
165
+ ```
166
+
167
+ Delete local release tag:
168
+
169
+ ```bash
170
+ git tag -d vX.Y.Z
171
+ ```
172
+
173
+ Delete remote release tag:
174
+
175
+ ```bash
176
+ git push origin :refs/tags/vX.Y.Z
177
+ ```
178
+
179
+ Create and push release tag:
180
+
181
+ ```bash
182
+ git tag vX.Y.Z
183
+ git push origin vX.Y.Z
184
+ ```
@@ -0,0 +1,167 @@
1
+ # Publishing a New Release
2
+
3
+ ## Prerequisites
4
+
5
+ * Maintain project metadata in `pyproject.toml`.
6
+ * Keep the project in a dedicated virtual environment (`.venv`).
7
+ * Runtime dependencies are declared in `pyproject.toml` under `dependencies`, **not** by freezing the virtual environment.
8
+ * The GitHub repository is connected to the existing PyPI project via **Trusted Publishing**.
9
+
10
+ ## Local Build
11
+
12
+ Build the package locally before creating a release:
13
+
14
+ ```bash
15
+ python -m build
16
+ ```
17
+
18
+ This creates:
19
+
20
+ ```
21
+ dist/
22
+ package_name-x.y.z.tar.gz
23
+ package_name-x.y.z-py3-none-any.whl
24
+ ```
25
+
26
+ Optionally verify the package:
27
+
28
+ ```bash
29
+ python -m twine check dist/*
30
+ ```
31
+
32
+ ## Releasing
33
+
34
+ 1. Update the version in `pyproject.toml`.
35
+
36
+ Example:
37
+
38
+ ```toml
39
+ version = "1.2.3"
40
+ ```
41
+
42
+ 2. Commit and push the changes.
43
+
44
+ ```bash
45
+ git add .
46
+ git commit -m "Release 1.2.3"
47
+ git push
48
+ ```
49
+
50
+ 3. Create a matching Git tag.
51
+
52
+ ```bash
53
+ git tag v1.2.3
54
+ git push origin v1.2.3
55
+ ```
56
+
57
+ 4. GitHub Actions builds the package and publishes it to PyPI.
58
+
59
+ ---
60
+
61
+ # Important Caveats
62
+
63
+ ## Always increase the version
64
+
65
+ PyPI does **not** allow uploading a package with the same version twice.
66
+
67
+ Every release must have a unique version number.
68
+
69
+ ---
70
+
71
+ ## If the GitHub Action fails because the version was wrong
72
+
73
+ Simply pressing **Re-run jobs** is **not sufficient** if the commit itself is incorrect.
74
+
75
+ Instead:
76
+
77
+ 1. Fix `pyproject.toml`.
78
+
79
+ 2. Commit and push the fix.
80
+
81
+ 3. Delete the old tag locally.
82
+
83
+ ```bash
84
+ git tag -d v1.2.3
85
+ ```
86
+
87
+ 4. Delete the tag on GitHub.
88
+
89
+ ```bash
90
+ git push origin :refs/tags/v1.2.3
91
+ ```
92
+
93
+ 5. Create the tag again on the corrected commit.
94
+
95
+ ```bash
96
+ git tag v1.2.3
97
+ git push origin v1.2.3
98
+ ```
99
+
100
+ This triggers the workflow again using the corrected source.
101
+
102
+ ---
103
+
104
+ # Common Build Issues
105
+
106
+ ### Missing README
107
+
108
+ If `pyproject.toml` contains
109
+
110
+ ```toml
111
+ readme = "README.md"
112
+ ```
113
+
114
+ then `README.md` must exist.
115
+
116
+ ---
117
+
118
+ ### License configuration
119
+
120
+ Use the modern license expression, for example:
121
+
122
+ ```toml
123
+ license = "MIT"
124
+ ```
125
+
126
+ Do **not** also include the old license classifier:
127
+
128
+ ```toml
129
+ "License :: OSI Approved :: MIT License"
130
+ ```
131
+
132
+ Modern versions of `setuptools` reject this combination.
133
+
134
+ ---
135
+
136
+ # Useful Commands
137
+
138
+ Build:
139
+
140
+ ```bash
141
+ python -m build
142
+ ```
143
+
144
+ Check package:
145
+
146
+ ```bash
147
+ python -m twine check dist/*
148
+ ```
149
+
150
+ Delete local release tag:
151
+
152
+ ```bash
153
+ git tag -d vX.Y.Z
154
+ ```
155
+
156
+ Delete remote release tag:
157
+
158
+ ```bash
159
+ git push origin :refs/tags/vX.Y.Z
160
+ ```
161
+
162
+ Create and push release tag:
163
+
164
+ ```bash
165
+ git tag vX.Y.Z
166
+ git push origin vX.Y.Z
167
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "michis_python_sammlung"
7
- version = "0.8"
7
+ version = "0.8.1"
8
8
  description = "Personal collection of mostly QT related stuff"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
- from PyQt6.QtWidgets import QWidget, QGraphicsOpacityEffect, QLayout, QMainWindow
1
+ from PyQt6.QtWidgets import QWidget, QGraphicsOpacityEffect, QLayout, QMainWindow, QVBoxLayout
2
2
  from PyQt6.QtGui import QPainter, QColor, QColorConstants, QPen, QGuiApplication
3
- from PyQt6.QtCore import QRect, pyqtBoundSignal
3
+ from PyQt6.QtCore import QRect, pyqtBoundSignal, QPoint
4
4
  from contextlib import contextmanager
5
5
  from typing import Generator, TypeVar
6
6
 
@@ -86,12 +86,31 @@ def add_rect_paint_to_widget(widget: QWidget, thickness: int = 2, color: QColor
86
86
  paintEvent(event)
87
87
  widget.paintEvent = paintRect
88
88
 
89
- def clear_layout(layout: QLayout) -> None:
89
+ def clear_layout(layout: QLayout, delete_widgets: bool = False, of_type = None) -> None:
90
90
  while layout.count() > 0:
91
91
  item = layout.takeAt(0)
92
92
  if item.widget():
93
- item.widget().deleteLater()
93
+ item.widget().setParent(None)
94
+ if delete_widgets:
95
+ item.widget().deleteLater()
94
96
  elif item.layout():
95
97
  clear_layout(item.layout())
96
98
 
99
+ def index_at_v_pos(layout: QVBoxLayout, pos: QPoint) -> int:
100
+ # this is assuming NOT involved widgets at first and last position in the layout
101
+ category_item_index = 1
102
+ for i in range(1, layout.count()-1):
103
+ item = layout.itemAt(i)
104
+ w = item.widget()
105
+
106
+ if w is None or not w.isVisible():
107
+ continue
108
+
109
+ geo = w.geometry()
110
+
111
+ if pos.y() < geo.bottom():
112
+ return category_item_index
113
+ category_item_index += 1
114
+ return category_item_index-1 # before final stretch, if you have one
115
+
97
116
 
@@ -0,0 +1,15 @@
1
+ from .flow_layout import FlowLayout
2
+ from .clickable_label import Clickable_label, Togglable_clickable_label
3
+ from .expandable_widget import Spoiler_widget, Expandable_widget
4
+ from .password_widget import PasswordDialog
5
+ from .progress_widget import ProgressWidget
6
+
7
+ __all__ = [
8
+ "FlowLayout",
9
+ "Clickable_label",
10
+ "Togglable_clickable_label",
11
+ "Spoiler_widget",
12
+ "Expandable_widget",
13
+ "PasswordDialog",
14
+ "ProgressWidget"
15
+ ]
@@ -30,10 +30,12 @@ class Clickable_label(QLabel):
30
30
  cursor: QPixmap | Qt.CursorShape = Qt.CursorShape.ArrowCursor,
31
31
  scaled: float = None,
32
32
  heuristic_mask: bool = True,
33
- parent: QWidget = None
33
+ parent: QWidget = None,
34
+ press: bool = False
34
35
  ):
35
36
  super().__init__(parent)
36
37
  self.setAlignment(Qt.AlignmentFlag.AlignCenter)
38
+ self._press = press
37
39
  self._scaled = scaled
38
40
  self.configure_pixmaps(pixmap, pixmap_hover, scaled=scaled)
39
41
  self._hovering = False
@@ -65,7 +67,7 @@ class Clickable_label(QLabel):
65
67
  self,
66
68
  widget_to_replace: QWidget,
67
69
  delete_old: bool = True
68
- ) -> Clickable_label | tuple[Clickable_label]:
70
+ ) -> "Clickable_label":
69
71
  """
70
72
  Replace an existing QWidget with self. Return self if delete_old is True or a tuple (self, replaced_widget) if not.
71
73
  """
@@ -101,9 +103,16 @@ class Clickable_label(QLabel):
101
103
  super().leaveEvent(a0)
102
104
 
103
105
  def mouseReleaseEvent(self, ev):
104
- self._on_click_action()
105
- self.sg_clicked.emit(ev)
106
- return super().mouseReleaseEvent(ev)
106
+ if not self._press:
107
+ self._on_click_action()
108
+ self.sg_clicked.emit(ev)
109
+ return super().mouseReleaseEvent(ev)
110
+
111
+ def mousePressEvent(self, ev):
112
+ if self._press:
113
+ self._on_click_action()
114
+ self.sg_clicked.emit(ev)
115
+ return super().mousePressEvent(ev)
107
116
 
108
117
  def set_mask(self, new_size: QSize | None = None, heuristic_mask: bool=True) -> None:
109
118
  new_size = new_size if new_size is not None else self._pixmap_hover.size()
@@ -3,7 +3,7 @@ from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout, QLa
3
3
  from PyQt6.QtGui import QTransform, QPixmap, QPainter, QPen
4
4
  import PyQt6.sip as sip
5
5
 
6
- from .pyqt_util import clear_layout
6
+ from ..pyqt.pyqt_util import clear_layout
7
7
  from .clickable_label import Clickable_label, Togglable_clickable_label, Image_paths
8
8
 
9
9
  import faulthandler
@@ -14,6 +14,7 @@ class Expandable_widget(QWidget):
14
14
  ANIMATION_DURATION = 150
15
15
  sg_animation_finished = pyqtSignal()
16
16
  sg_animation_started = pyqtSignal()
17
+ sg_toggle = pyqtSignal(bool)
17
18
 
18
19
  _horizontal_directions = ("left", "right")
19
20
  _vertical_directions = ("up", "down")
@@ -161,12 +162,12 @@ class Expandable_widget(QWidget):
161
162
  g.moveLeft(right)
162
163
  self.setGeometry(g)
163
164
 
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)
165
+ def _set_outer_height_anchor_bottom(self, outer_h: int) -> None:
166
+ geometry = self.geometry()
167
+ bottom = geometry.y() + geometry.height()
168
+ geometry.setHeight(outer_h)
169
+ geometry.moveTop(bottom - outer_h)
170
+ self.setGeometry(geometry)
170
171
 
171
172
  @_content_maximum_width.setter
172
173
  def _content_maximum_width(self, new_width: int) -> None:
@@ -213,8 +214,10 @@ class Expandable_widget(QWidget):
213
214
 
214
215
  def _setup_widget(self) -> None:
215
216
  self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
216
- self.setWindowFlags(Qt.WindowType.Widget)
217
- self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
217
+ self.setWindowFlags(
218
+ Qt.WindowType.Widget |
219
+ Qt.WindowType.FramelessWindowHint
220
+ )
218
221
 
219
222
  def _execute_toggle(self, animated=False):
220
223
  if animated:
@@ -248,6 +251,7 @@ class Expandable_widget(QWidget):
248
251
  self._execute_toggle(animated)
249
252
  if button_toggle is not None:
250
253
  self.toggle_button.toggle(button_toggle)
254
+ self.sg_toggle.emit(expand)
251
255
 
252
256
  def _animation_value_changed(self, new_max_heigt: QSize) -> None: ...
253
257
  # self.layout().activate()
@@ -466,14 +470,6 @@ class Spoiler_widget(Expandable_widget):
466
470
  self.content_widget.setMaximumHeight(self.content_widget.sizeHint().height())
467
471
  self.update()
468
472
 
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
473
  def main():
478
474
  import sys
479
475
  app = QApplication(sys.argv)
@@ -0,0 +1,126 @@
1
+ from PyQt6.QtWidgets import QVBoxLayout, QSizePolicy, QLayout, QWidgetItem
2
+ from PyQt6.QtCore import Qt, QSize, QPoint, QRect, QMargins
3
+
4
+ class FlowLayout(QLayout):
5
+ def __init__(self, parent=None):
6
+ super().__init__(parent)
7
+
8
+ if parent is not None:
9
+ self.setContentsMargins(QMargins(0, 0, 0, 0))
10
+
11
+ self._item_list = []
12
+
13
+ def __del__(self):
14
+ item = self.takeAt(0)
15
+ while item:
16
+ item = self.takeAt(0)
17
+
18
+ def addItem(self, item):
19
+ self._item_list.append(item)
20
+
21
+ def count(self):
22
+ return len(self._item_list)
23
+
24
+ def itemAt(self, index):
25
+ if 0 <= index < len(self._item_list):
26
+ return self._item_list[index]
27
+
28
+ return None
29
+
30
+ def takeAt(self, index):
31
+ if 0 <= index < len(self._item_list):
32
+ return self._item_list.pop(index)
33
+
34
+ return None
35
+
36
+ def expandingDirections(self):
37
+ return Qt.Orientation(0)
38
+
39
+ def hasHeightForWidth(self):
40
+ return True
41
+
42
+ def heightForWidth(self, width):
43
+ height = self._do_layout(QRect(0, 0, width, 0), True)
44
+ return height
45
+
46
+ def setGeometry(self, rect):
47
+ super(FlowLayout, self).setGeometry(rect)
48
+ self._do_layout(rect, False)
49
+
50
+ def sizeHint(self):
51
+ return self.minimumSize()
52
+
53
+ def minimumSize(self):
54
+ size = QSize()
55
+
56
+ for item in self._item_list:
57
+ size = size.expandedTo(item.minimumSize())
58
+
59
+ size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
60
+ return size
61
+
62
+ def insertWidget(self, index, widget):
63
+ if index < 0:
64
+ index = 0
65
+ elif index > len(self._item_list):
66
+ index = len(self._item_list)
67
+
68
+ self.addChildWidget(widget)
69
+
70
+ item = QWidgetItem(widget)
71
+ self._item_list.insert(index, item)
72
+
73
+ self.invalidate()
74
+
75
+ def _do_layout(self, rect, test_only):
76
+ x = rect.x()
77
+ y = rect.y()
78
+ line_height = 0
79
+ spacing = self.spacing()
80
+
81
+ for item in self._item_list:
82
+ style = item.widget().style()
83
+ layout_spacing_x = style.layoutSpacing(
84
+ QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton,
85
+ Qt.Orientation.Horizontal
86
+ )
87
+ layout_spacing_y = style.layoutSpacing(
88
+ QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton,
89
+ Qt.Orientation.Vertical
90
+ )
91
+ space_x = spacing + layout_spacing_x
92
+ space_y = spacing + layout_spacing_y
93
+ next_x = x + item.sizeHint().width() + space_x
94
+ if next_x - space_x > rect.right() and line_height > 0:
95
+ x = rect.x()
96
+ y = y + line_height + space_y
97
+ next_x = x + item.sizeHint().width() + space_x
98
+ line_height = 0
99
+
100
+ if not test_only:
101
+ item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
102
+
103
+ x = next_x
104
+ line_height = max(line_height, item.sizeHint().height())
105
+
106
+ return y + line_height - rect.y()
107
+
108
+ def index_at_cursor_pos(self, pos: QPoint) -> int:
109
+ # this is assuming NOT involved widgets at first postion in the layout
110
+ image_item_index = 1
111
+ for i in range(1, self.count()):
112
+ item = self.itemAt(i)
113
+ w = item.widget()
114
+
115
+ if w is None or not w.isVisible():
116
+ continue
117
+
118
+ geo = w.geometry()
119
+
120
+ if pos.y() < geo.bottom() and pos.x() < geo.right():
121
+ return image_item_index
122
+ image_item_index += 1
123
+ return image_item_index
124
+
125
+
126
+
@@ -0,0 +1,83 @@
1
+ from PyQt6.QtWidgets import QVBoxLayout, QDialog, QLabel, QDialogButtonBox, QLineEdit
2
+
3
+ class PasswordDialog(QDialog):
4
+
5
+ def __init__(self, parent=None):
6
+ super().__init__(parent)
7
+
8
+ self.setWindowTitle("Authentication")
9
+ self.setModal(True)
10
+ self.setFixedWidth(420)
11
+
12
+ layout = QVBoxLayout(self)
13
+ layout.setContentsMargins(28, 24, 28, 24)
14
+ layout.setSpacing(18)
15
+
16
+ self.title_label = QLabel("Password authentication")
17
+ self.title_label.setObjectName("title")
18
+
19
+ self.description_label = QLabel(
20
+ "Enter password: "
21
+ )
22
+ self.description_label.setWordWrap(True)
23
+ self.description_label.setObjectName("description")
24
+
25
+ self.password_edit = QLineEdit()
26
+ self.password_edit.setEchoMode(QLineEdit.EchoMode.Password)
27
+ self.password_edit.setPlaceholderText("Password")
28
+
29
+ buttons = QDialogButtonBox(
30
+ QDialogButtonBox.StandardButton.Ok
31
+ | QDialogButtonBox.StandardButton.Cancel
32
+ )
33
+
34
+ buttons.accepted.connect(self.accept)
35
+ buttons.rejected.connect(self.reject)
36
+
37
+ layout.addWidget(self.title_label)
38
+ layout.addWidget(self.description_label)
39
+ layout.addWidget(self.password_edit)
40
+ layout.addStretch()
41
+ layout.addWidget(buttons)
42
+
43
+ self.setStyleSheet("""
44
+ PasswordDialog {
45
+ background: #f4f4f4;
46
+ }
47
+
48
+ QLabel#title_label {
49
+ font-size: 20px;
50
+ font-weight: 600;
51
+ color: #202020;
52
+ }
53
+
54
+ QLabel#description_label {
55
+ color: #606060;
56
+ font-size: 14px;
57
+ }
58
+
59
+ QLineEdit {
60
+ padding: 8px;
61
+ border: 1px solid #b8b8b8;
62
+ border-radius: 6px;
63
+ background: white;
64
+ font-size: 14px;
65
+ }
66
+
67
+ QPushButton {
68
+ min-width: 90px;
69
+ padding: 6px 12px;
70
+ }
71
+ """)
72
+
73
+ self.password_edit.setFocus()
74
+
75
+ @property
76
+ def password(self):
77
+ return self.password_edit.text()
78
+
79
+ def set_title(self, title: str) -> None:
80
+ self.title_label.setText(title)
81
+
82
+ def set_description(self, desc: str) -> None:
83
+ self.description_label.setText(desc)
@@ -0,0 +1,135 @@
1
+ from PyQt6.QtWidgets import QVBoxLayout, QWidget, QLabel, QSizePolicy, QProgressBar, QHBoxLayout, QApplication, QPushButton
2
+ from PyQt6.QtCore import Qt
3
+
4
+ class ProgressWidget(QWidget):
5
+
6
+ def __init__(self, parent=None):
7
+ super().__init__(parent, flags=Qt.WindowType.Window)
8
+
9
+ self.setWindowTitle("Progress")
10
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
11
+
12
+ self._setup_ui()
13
+ self._apply_style()
14
+
15
+ def _setup_ui(self) -> None:
16
+ self.setMinimumWidth(420)
17
+ self.setFixedHeight(300)
18
+
19
+ main_layout = QVBoxLayout(self)
20
+ main_layout.setContentsMargins(28, 24, 28, 24)
21
+ main_layout.setSpacing(16)
22
+
23
+ self.title_label = QLabel("Progress of Task")
24
+ self.title_label.setObjectName("titleLabel")
25
+
26
+ self.task_description_label = QLabel()
27
+ self.task_description_label.setObjectName("taskDescriptionLabel")
28
+ self.task_description_label.setWordWrap(True)
29
+ self.task_description_label.setAlignment(
30
+ Qt.AlignmentFlag.AlignLeft
31
+ | Qt.AlignmentFlag.AlignTop
32
+ )
33
+
34
+ self.current_task_info_label = QLabel(
35
+ "Preparing deployment..."
36
+ )
37
+ self.current_task_info_label.setObjectName(
38
+ "currentTaskLabel"
39
+ )
40
+ self.current_task_info_label.setWordWrap(True)
41
+ self.current_task_info_label.setAlignment(
42
+ Qt.AlignmentFlag.AlignLeft
43
+ | Qt.AlignmentFlag.AlignVCenter
44
+ )
45
+
46
+ self.progress_bar = QProgressBar()
47
+ self.progress_bar.setObjectName("progressBar")
48
+ self.progress_bar.setMinimum(0)
49
+ self.progress_bar.setMaximum(100)
50
+ self.progress_bar.setValue(0)
51
+ self.progress_bar.setTextVisible(True)
52
+ self.progress_bar.setMinimumHeight(24)
53
+ self.progress_bar.setSizePolicy(
54
+ QSizePolicy.Policy.Expanding,
55
+ QSizePolicy.Policy.Fixed,
56
+ )
57
+
58
+ main_layout.addWidget(self.title_label)
59
+ main_layout.addWidget(self.task_description_label)
60
+ main_layout.addStretch()
61
+ main_layout.addWidget(self.current_task_info_label)
62
+ main_layout.addWidget(self.progress_bar)
63
+ self.ok_button = QPushButton("Schließen")
64
+ button_layout = QHBoxLayout()
65
+ button_layout.addWidget(self.ok_button)
66
+ button_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
67
+ self.ok_button.setMaximumWidth(80)
68
+ self.ok_button.clicked.connect(self.hide)
69
+ main_layout.addLayout(button_layout)
70
+
71
+ def _apply_style(self) -> None:
72
+ self.setStyleSheet("""
73
+ ProgressWidget {
74
+ background-color: #f4f4f4;
75
+ }
76
+
77
+ QLabel#titleLabel {
78
+ font-size: 20px;
79
+ font-weight: 600;
80
+ color: #202020;
81
+ }
82
+
83
+ QLabel#taskDescriptionLabel {
84
+ font-size: 14px;
85
+ color: #505050;
86
+ line-height: 1.4;
87
+ }
88
+
89
+ QLabel#currentTaskLabel {
90
+ font-size: 13px;
91
+ color: #707070;
92
+ }
93
+
94
+ QProgressBar#progressBar {
95
+ border: 1px solid #b8b8b8;
96
+ border-radius: 7px;
97
+ background-color: #ffffff;
98
+ text-align: center;
99
+ min-height: 22px;
100
+ }
101
+
102
+ QProgressBar#progressBar::chunk {
103
+ background-color: #3c8dbc;
104
+ border-radius: 6px;
105
+ }
106
+ """)
107
+
108
+ def show_ok_button(self, show: bool) -> None:
109
+ self.ok_button.setVisible(show)
110
+
111
+ def set_title(self, title: str) -> None:
112
+ self.title_label.setText(title)
113
+
114
+ def reset_progress(self) -> None:
115
+ self.progress_bar.reset()
116
+
117
+ def set_progress(
118
+ self,
119
+ info: str,
120
+ step_count: int,
121
+ ) -> None:
122
+ self.current_task_info_label.setText(info)
123
+ self.progress_bar.setValue(step_count)
124
+ QApplication.processEvents()
125
+
126
+ def set_maximum_steps(self, maximum: int) -> None:
127
+ self.progress_bar.setMaximum(maximum)
128
+
129
+ def set_current_task_info(self, info: str) -> None:
130
+ self.current_task_info_label.setText(info)
131
+ QApplication.processEvents()
132
+
133
+ def set_task_desc(self, desc: str) -> None:
134
+ self.task_description_label.setText(desc)
135
+ QApplication.processEvents()
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: michis_python_sammlung
3
+ Version: 0.8.1
4
+ Summary: Personal collection of mostly QT related stuff
5
+ Author: Michael Mischko
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
+ Project-URL: Repository, https://github.com/mischkomichael/Michis_python_sammlung
9
+ Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
10
+ Keywords: pyqt6,widgets,gui
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: PyQt6<7,>=6.11
17
+
18
+ # Publishing a New Release
19
+
20
+ ## Prerequisites
21
+
22
+ * Maintain project metadata in `pyproject.toml`.
23
+ * Keep the project in a dedicated virtual environment (`.venv`).
24
+ * Runtime dependencies are declared in `pyproject.toml` under `dependencies`, **not** by freezing the virtual environment.
25
+ * The GitHub repository is connected to the existing PyPI project via **Trusted Publishing**.
26
+
27
+ ## Local Build
28
+
29
+ Build the package locally before creating a release:
30
+
31
+ ```bash
32
+ python -m build
33
+ ```
34
+
35
+ This creates:
36
+
37
+ ```
38
+ dist/
39
+ package_name-x.y.z.tar.gz
40
+ package_name-x.y.z-py3-none-any.whl
41
+ ```
42
+
43
+ Optionally verify the package:
44
+
45
+ ```bash
46
+ python -m twine check dist/*
47
+ ```
48
+
49
+ ## Releasing
50
+
51
+ 1. Update the version in `pyproject.toml`.
52
+
53
+ Example:
54
+
55
+ ```toml
56
+ version = "1.2.3"
57
+ ```
58
+
59
+ 2. Commit and push the changes.
60
+
61
+ ```bash
62
+ git add .
63
+ git commit -m "Release 1.2.3"
64
+ git push
65
+ ```
66
+
67
+ 3. Create a matching Git tag.
68
+
69
+ ```bash
70
+ git tag v1.2.3
71
+ git push origin v1.2.3
72
+ ```
73
+
74
+ 4. GitHub Actions builds the package and publishes it to PyPI.
75
+
76
+ ---
77
+
78
+ # Important Caveats
79
+
80
+ ## Always increase the version
81
+
82
+ PyPI does **not** allow uploading a package with the same version twice.
83
+
84
+ Every release must have a unique version number.
85
+
86
+ ---
87
+
88
+ ## If the GitHub Action fails because the version was wrong
89
+
90
+ Simply pressing **Re-run jobs** is **not sufficient** if the commit itself is incorrect.
91
+
92
+ Instead:
93
+
94
+ 1. Fix `pyproject.toml`.
95
+
96
+ 2. Commit and push the fix.
97
+
98
+ 3. Delete the old tag locally.
99
+
100
+ ```bash
101
+ git tag -d v1.2.3
102
+ ```
103
+
104
+ 4. Delete the tag on GitHub.
105
+
106
+ ```bash
107
+ git push origin :refs/tags/v1.2.3
108
+ ```
109
+
110
+ 5. Create the tag again on the corrected commit.
111
+
112
+ ```bash
113
+ git tag v1.2.3
114
+ git push origin v1.2.3
115
+ ```
116
+
117
+ This triggers the workflow again using the corrected source.
118
+
119
+ ---
120
+
121
+ # Common Build Issues
122
+
123
+ ### Missing README
124
+
125
+ If `pyproject.toml` contains
126
+
127
+ ```toml
128
+ readme = "README.md"
129
+ ```
130
+
131
+ then `README.md` must exist.
132
+
133
+ ---
134
+
135
+ ### License configuration
136
+
137
+ Use the modern license expression, for example:
138
+
139
+ ```toml
140
+ license = "MIT"
141
+ ```
142
+
143
+ Do **not** also include the old license classifier:
144
+
145
+ ```toml
146
+ "License :: OSI Approved :: MIT License"
147
+ ```
148
+
149
+ Modern versions of `setuptools` reject this combination.
150
+
151
+ ---
152
+
153
+ # Useful Commands
154
+
155
+ Build:
156
+
157
+ ```bash
158
+ python -m build
159
+ ```
160
+
161
+ Check package:
162
+
163
+ ```bash
164
+ python -m twine check dist/*
165
+ ```
166
+
167
+ Delete local release tag:
168
+
169
+ ```bash
170
+ git tag -d vX.Y.Z
171
+ ```
172
+
173
+ Delete remote release tag:
174
+
175
+ ```bash
176
+ git push origin :refs/tags/vX.Y.Z
177
+ ```
178
+
179
+ Create and push release tag:
180
+
181
+ ```bash
182
+ git tag vX.Y.Z
183
+ git push origin vX.Y.Z
184
+ ```
@@ -7,8 +7,12 @@ src/michis_python_sammlung.egg-info/dependency_links.txt
7
7
  src/michis_python_sammlung.egg-info/requires.txt
8
8
  src/michis_python_sammlung.egg-info/top_level.txt
9
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
10
  src/michis_python_sammlung/pyqt/image_paths.py
13
11
  src/michis_python_sammlung/pyqt/pyqt_util.py
14
- src/michis_python_sammlung/pyqt/icons/__init__.py
12
+ src/michis_python_sammlung/pyqt/icons/__init__.py
13
+ src/michis_python_sammlung/widgets/__init__.py
14
+ src/michis_python_sammlung/widgets/clickable_label.py
15
+ src/michis_python_sammlung/widgets/expandable_widget.py
16
+ src/michis_python_sammlung/widgets/flow_layout.py
17
+ src/michis_python_sammlung/widgets/password_widget.py
18
+ src/michis_python_sammlung/widgets/progress_widget.py
@@ -1,16 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: michis_python_sammlung
3
- Version: 0.8
4
- Summary: Personal collection of mostly QT related stuff
5
- Author: Michael Mischko
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
- Project-URL: Repository, https://github.com/mischkomichael/Michis_python_sammlung
9
- Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
10
- Keywords: pyqt6,widgets,gui
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: PyQt6<7,>=6.11
File without changes
@@ -1,16 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: michis_python_sammlung
3
- Version: 0.8
4
- Summary: Personal collection of mostly QT related stuff
5
- Author: Michael Mischko
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/mischkomichael/Michis_python_sammlung
8
- Project-URL: Repository, https://github.com/mischkomichael/Michis_python_sammlung
9
- Project-URL: Issues, https://github.com/mischkomichael/Michis_python_sammlung/issues
10
- Keywords: pyqt6,widgets,gui
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: PyQt6<7,>=6.11