jbqt 0.1.29__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 (47) hide show
  1. jbqt-0.1.29/PKG-INFO +18 -0
  2. jbqt-0.1.29/README.md +0 -0
  3. jbqt-0.1.29/jbqt/__init__.py +9 -0
  4. jbqt-0.1.29/jbqt/common/__init__.py +9 -0
  5. jbqt-0.1.29/jbqt/common/__pycache__/__init__.cpython-312.pyc +0 -0
  6. jbqt-0.1.29/jbqt/common/__pycache__/qt_utils.cpython-312.pyc +0 -0
  7. jbqt-0.1.29/jbqt/common/qt_utils.py +133 -0
  8. jbqt-0.1.29/jbqt/consts/__init__.py +239 -0
  9. jbqt-0.1.29/jbqt/consts/__pycache__/__init__.cpython-312.pyc +0 -0
  10. jbqt-0.1.29/jbqt/dialogs/__init__.py +8 -0
  11. jbqt-0.1.29/jbqt/dialogs/__pycache__/__init__.cpython-312.pyc +0 -0
  12. jbqt-0.1.29/jbqt/dialogs/__pycache__/file_dialog.cpython-312.pyc +0 -0
  13. jbqt-0.1.29/jbqt/dialogs/__pycache__/input_form.cpython-312.pyc +0 -0
  14. jbqt-0.1.29/jbqt/dialogs/__pycache__/single_input.cpython-312.pyc +0 -0
  15. jbqt-0.1.29/jbqt/dialogs/__pycache__/text_preview.cpython-312.pyc +0 -0
  16. jbqt-0.1.29/jbqt/dialogs/file_dialog.py +60 -0
  17. jbqt-0.1.29/jbqt/dialogs/input_form.py +140 -0
  18. jbqt-0.1.29/jbqt/dialogs/single_input.py +85 -0
  19. jbqt-0.1.29/jbqt/dialogs/text_preview.py +42 -0
  20. jbqt-0.1.29/jbqt/models/__init__.py +18 -0
  21. jbqt-0.1.29/jbqt/models/__pycache__/__init__.cpython-312.pyc +0 -0
  22. jbqt-0.1.29/jbqt/models/__pycache__/chip_button.cpython-312.pyc +0 -0
  23. jbqt-0.1.29/jbqt/models/__pycache__/chips.cpython-312.pyc +0 -0
  24. jbqt-0.1.29/jbqt/models/__pycache__/dialog_options.cpython-312.pyc +0 -0
  25. jbqt-0.1.29/jbqt/models/__pycache__/model_consts.cpython-312.pyc +0 -0
  26. jbqt-0.1.29/jbqt/models/__pycache__/model_utils.cpython-312.pyc +0 -0
  27. jbqt-0.1.29/jbqt/models/__pycache__/toolbar_button.cpython-312.pyc +0 -0
  28. jbqt-0.1.29/jbqt/models/chip_button.py +10 -0
  29. jbqt-0.1.29/jbqt/models/chips.py +29 -0
  30. jbqt-0.1.29/jbqt/models/dialog_options.py +28 -0
  31. jbqt-0.1.29/jbqt/models/model_consts.py +9 -0
  32. jbqt-0.1.29/jbqt/models/model_utils.py +27 -0
  33. jbqt-0.1.29/jbqt/models/toolbar_button.py +60 -0
  34. jbqt-0.1.29/jbqt/types/__init__.py +24 -0
  35. jbqt-0.1.29/jbqt/types/__pycache__/__init__.cpython-312.pyc +0 -0
  36. jbqt-0.1.29/jbqt/view_icons.py +209 -0
  37. jbqt-0.1.29/jbqt/widgets/__init__.py +20 -0
  38. jbqt-0.1.29/jbqt/widgets/__pycache__/__init__.cpython-312.pyc +0 -0
  39. jbqt-0.1.29/jbqt/widgets/__pycache__/chip_button.cpython-312.pyc +0 -0
  40. jbqt-0.1.29/jbqt/widgets/__pycache__/simple.cpython-312.pyc +0 -0
  41. jbqt-0.1.29/jbqt/widgets/chip_button.py +212 -0
  42. jbqt-0.1.29/jbqt/widgets/chips.py +261 -0
  43. jbqt-0.1.29/jbqt/widgets/multiselect.py +219 -0
  44. jbqt-0.1.29/jbqt/widgets/simple.py +71 -0
  45. jbqt-0.1.29/jbqt/widgets/toast.py +35 -0
  46. jbqt-0.1.29/jbqt/widgets/widget_utils.py +90 -0
  47. jbqt-0.1.29/pyproject.toml +19 -0
jbqt-0.1.29/PKG-INFO ADDED
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: jbqt
3
+ Version: 0.1.29
4
+ Summary:
5
+ Author: Joseph Bochinski
6
+ Author-email: stirgejr@gmail.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: fuzzywuzzy (>=0.18.0,<0.19.0)
11
+ Requires-Dist: jbconsts (>=0.1.1,<0.2.0)
12
+ Requires-Dist: jbutils (>=0.2.2,<0.3.0)
13
+ Requires-Dist: pyqt6 (>=6.9.0,<7.0.0)
14
+ Requires-Dist: python-levenshtein (>=0.27.1,<0.28.0)
15
+ Requires-Dist: rich (>=14.0.0,<15.0.0)
16
+ Description-Content-Type: text/markdown
17
+
18
+
jbqt-0.1.29/README.md ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ """Common exports for jbqt"""
2
+
3
+ from jbqt.common import qt_utils
4
+ from jbqt.common.qt_utils import register_app
5
+
6
+ __all__ = [
7
+ "qt_utils",
8
+ "register_app",
9
+ ]
@@ -0,0 +1,9 @@
1
+ """Const exports"""
2
+
3
+ from jbqt.common import qt_utils
4
+ from jbqt.common.qt_utils import register_app
5
+
6
+ __all__ = [
7
+ "qt_utils",
8
+ "register_app",
9
+ ]
@@ -0,0 +1,133 @@
1
+ from datetime import date
2
+ from typing import Any
3
+
4
+ from jbutils import JbuConsole
5
+ from PyQt6.QtCore import Qt, QDate
6
+ from PyQt6.QtWidgets import (
7
+ QListWidgetItem,
8
+ QCalendarWidget,
9
+ QApplication,
10
+ QWidget,
11
+ QLineEdit,
12
+ QTextEdit,
13
+ QSpinBox,
14
+ QCheckBox,
15
+ QDoubleSpinBox,
16
+ )
17
+
18
+ import jbqt.consts as consts
19
+ from jbqt.models import IChipsWidget
20
+
21
+
22
+ def get_item_value(item: QListWidgetItem) -> str:
23
+ """Retrieves the value from a list widget item
24
+
25
+ Args:
26
+ item (QListWidgetItem): Item object to retrieve data from
27
+
28
+ Returns:
29
+ str: Value contained in the item
30
+ """
31
+
32
+ value = item.data(consts.LIST_ITEM_ROLE) or item.text()
33
+ return value.strip()
34
+
35
+
36
+ def register_app(app: QApplication, icon_dir: str = "") -> None:
37
+ """Registers the PyQt app with the JbQt global config reference
38
+
39
+ Args:
40
+ app (QApplication): The PyQt application
41
+ icon_dir (str, optional): Directory containing custom icons. Defaults to "".
42
+ """
43
+
44
+ consts.QtGlobalRefs.app = app
45
+ consts.set_icon_dir(icon_dir)
46
+
47
+
48
+ def get_widget_value(widget: QWidget, key: str = "") -> Any:
49
+ """Convenience function to retrieve the value from a variety of
50
+ common widgets
51
+
52
+ Args:
53
+ widget (QWidget): Widget to get the value from
54
+ key (str, optional): A key to retrieve a specific value for
55
+ certain widget types. Defaults to "".
56
+
57
+ Returns:
58
+ Any: Value contained in the widget, if any.
59
+ """
60
+
61
+ if isinstance(widget, QLineEdit):
62
+ return widget.text()
63
+ if isinstance(widget, QTextEdit):
64
+ return widget.toPlainText().strip()
65
+ if isinstance(widget, (QSpinBox, QDoubleSpinBox)):
66
+ return widget.value()
67
+ if isinstance(widget, IChipsWidget):
68
+ return widget.values
69
+ if isinstance(widget, QCheckBox):
70
+ return widget.checkState == Qt.CheckState.Checked
71
+ if isinstance(widget, QCalendarWidget):
72
+ return widget.selectedDate().toString()
73
+
74
+ JbuConsole.warn(f"No handler defined for {key} of type `{type(widget)}`")
75
+
76
+
77
+ def set_widget_value(widget: QWidget, value: Any) -> None:
78
+ """Set the value of a one of variety of common widgets
79
+
80
+ Currently Supported Widgets:
81
+ - QLineEdit
82
+ - QTextEdit
83
+ - QSpinBox
84
+ - QDoubleSpinBox
85
+ - ChipsWidget
86
+ - QCheckBox
87
+ - QCalendarWidget
88
+
89
+ Args:
90
+ widget (QWidget): Widget to assign value to
91
+ value (Any): Value to assign it
92
+ """
93
+
94
+ if isinstance(widget, (QLineEdit, QTextEdit)):
95
+ widget.setText(str(value))
96
+ elif isinstance(widget, QSpinBox):
97
+ try:
98
+ widget.setValue(int(value))
99
+ except Exception as e:
100
+ print(e)
101
+ print(type(e))
102
+
103
+ elif isinstance(widget, QDoubleSpinBox):
104
+ try:
105
+ widget.setValue(float(value))
106
+ except Exception as e:
107
+ print(e)
108
+ print(type(e))
109
+
110
+ elif isinstance(widget, IChipsWidget) and isinstance(value, list):
111
+ widget.add_chips(value)
112
+ elif isinstance(widget, QCheckBox):
113
+ state = Qt.CheckState.Unchecked
114
+ if isinstance(value, Qt.CheckState):
115
+ state = value
116
+ elif value is True:
117
+ state = Qt.CheckState.Checked
118
+ elif value is False:
119
+ state = Qt.CheckState.Unchecked
120
+
121
+ widget.setCheckState(state)
122
+
123
+ elif isinstance(widget, QCalendarWidget):
124
+ if isinstance(value, (date, QDate)):
125
+ widget.setSelectedDate(value)
126
+
127
+
128
+ def set_item_disabled(item: QListWidgetItem, disabled: bool = True) -> None:
129
+ if disabled:
130
+ item.setFlags(Qt.ItemFlag.NoItemFlags)
131
+ item.setCheckState(Qt.CheckState.Unchecked)
132
+ else:
133
+ item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
@@ -0,0 +1,239 @@
1
+ """Collection of constants and global references for the jbqt package."""
2
+
3
+ import os
4
+ import re
5
+
6
+ from typing import Optional
7
+
8
+
9
+ from PyQt6.QtWidgets import QApplication, QMainWindow, QScrollArea, QWidget
10
+ from PyQt6.QtCore import Qt, QSize
11
+ from PyQt6.QtGui import QIcon, QPixmap, QPainter, QColor
12
+
13
+ from jbqt.types import IconGetter
14
+
15
+ TAG_RE = re.compile(r"^\((.+)\)(\d+\.\d+)$")
16
+ LIST_ITEM_ROLE = Qt.ItemDataRole.UserRole - 1
17
+
18
+
19
+ class QtGlobalRefs:
20
+ """Class that tracks various values globally for a given application"""
21
+
22
+ app: QApplication | None = None
23
+ main_window: QMainWindow | None = None
24
+ scroll_area: QScrollArea | None = None
25
+ server_active: bool = False
26
+ debug_set: bool = False
27
+ seed_widget: QWidget | None = None
28
+
29
+
30
+ class QtPaths:
31
+ """Global path reference storage for JbQt projects"""
32
+
33
+ icon_dir: str | None = None
34
+
35
+
36
+ THEME_ICONS: dict[str, QIcon] = {
37
+ "save": QIcon.fromTheme("media-floppy-symbolic"),
38
+ "new_file": QIcon.fromTheme("document-new-symbolic"),
39
+ "open": QIcon.fromTheme("folder-open-symbolic"),
40
+ "copy": QIcon.fromTheme("edit-paste-symbolic"),
41
+ "inspect": QIcon.fromTheme("docviewer-app-symbolic"),
42
+ "clone": QIcon.fromTheme("edit-copy-symbolic"),
43
+ "plus": QIcon.fromTheme("list-add-symbolic"),
44
+ "minus": QIcon.fromTheme("list-remove-symbolic"),
45
+ "times": QIcon.fromTheme("cancel-operation-symbolic"),
46
+ "refresh": QIcon.fromTheme("view-refresh-symbolic"),
47
+ "reload": QIcon.fromTheme("update-symbolic"),
48
+ "circle_check": QIcon.fromTheme("selection-checked"),
49
+ "circle_x": QIcon.fromTheme("application-exit"),
50
+ "code": QIcon.fromTheme("input-tablet-symbolic"),
51
+ "font-selection-editor": QIcon.fromTheme("font-select-symbolic"),
52
+ "trash": QIcon.fromTheme("trash-symbolic"),
53
+ "edit_data": QIcon.fromTheme("document-edit-symbolic"),
54
+ "preview": QIcon.fromTheme("view-layout-symbolic"),
55
+ "sync": QIcon.fromTheme("mail-send-receive-symbolic"),
56
+ }
57
+ """ Mapping of system fallback icons if STD_ICONS doesn't have one"""
58
+
59
+ STD_ICONS: dict[str, QIcon] = {
60
+ "save": QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave),
61
+ "new_file": QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew),
62
+ "open": QIcon.fromTheme(QIcon.ThemeIcon.FolderOpen),
63
+ "copy": QIcon.fromTheme(QIcon.ThemeIcon.EditPaste),
64
+ "inspect": QIcon.fromTheme(QIcon.ThemeIcon.DocumentPageSetup),
65
+ "clone": QIcon.fromTheme(QIcon.ThemeIcon.EditCopy),
66
+ "plus": QIcon.fromTheme(QIcon.ThemeIcon.ListAdd),
67
+ "minus": QIcon.fromTheme(QIcon.ThemeIcon.DialogError),
68
+ "times": QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
69
+ "refresh": QIcon.fromTheme(QIcon.ThemeIcon.SystemReboot),
70
+ "reload": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaylistRepeat),
71
+ "circle_check": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart),
72
+ "circle_x": QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop),
73
+ "code": QIcon.fromTheme(QIcon.ThemeIcon.Computer),
74
+ "font-selection-editor": QIcon.fromTheme(QIcon.ThemeIcon.EditFind),
75
+ "trash": QIcon.fromTheme(QIcon.ThemeIcon.EditDelete),
76
+ "edit_data": QIcon.fromTheme(QIcon.ThemeIcon.InputTablet),
77
+ "preview": QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn),
78
+ "sync": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaylistShuffle),
79
+ }
80
+ """ Mapping of preferred icon definitions """
81
+
82
+
83
+ def set_icon_dir(path: str) -> None:
84
+ """Convenience function to set the global icon directory"""
85
+ QtPaths.icon_dir = path
86
+
87
+
88
+ class QtIconSizes:
89
+ """Reference class to retrieve common PyQt icon size classes"""
90
+
91
+ ICON_XS = QSize(16, 16)
92
+ ICON_SM = QSize(20, 20)
93
+ ICON_MD = QSize(22, 22)
94
+ ICON_LG = QSize(24, 24)
95
+ ICON_XL = QSize(32, 32)
96
+ ICON_XXL = QSize(48, 48)
97
+
98
+ @classmethod
99
+ def get(cls, key: str, default: QSize | None = None) -> QSize:
100
+ if hasattr(cls, key):
101
+ return getattr(cls, key)
102
+ return default or cls.ICON_MD
103
+
104
+
105
+ def icon_dir(file_name: str) -> str:
106
+ """Retrieve the filepath to an icon image
107
+
108
+ Args:
109
+ file_name (str): File name of the icon image to retrieve
110
+
111
+ Returns:
112
+ str: Either the global icon directory joined with file_name if
113
+ the global icon directory has been defined, otherwise just
114
+ the file_name
115
+ """
116
+
117
+ if QtPaths.icon_dir:
118
+ return os.path.join(QtPaths.icon_dir, file_name)
119
+ return file_name
120
+
121
+
122
+ def recolor_icon(
123
+ image_path: str, color: str | QColor, scale: QSize | None = None
124
+ ) -> QIcon:
125
+ """Define QIcon instance and adjust the color accordingly
126
+
127
+ Args:
128
+ image_path (str): Path to the image file to use, or a standard
129
+ system/theme reference string
130
+ color (str | QColor): Color value to set the icon to
131
+ scale (QSize | None, optional): Size to make the icon. If None,
132
+ gets set 0to 22px. Defaults to None.
133
+
134
+ Returns:
135
+ QIcon: QIcon instance with the provided size and color
136
+ """
137
+
138
+ if not color:
139
+ color = QColor("black")
140
+ if isinstance(color, str):
141
+ color = QColor(color)
142
+
143
+ if scale is None:
144
+ scale = QtIconSizes.ICON_MD
145
+ pixmap = QPixmap(image_path)
146
+ icon_key = os.path.splitext(os.path.basename(image_path))[0]
147
+
148
+ if pixmap.isNull():
149
+ fallback = THEME_ICONS.get(icon_key, STD_ICONS.get(icon_key))
150
+ if fallback:
151
+ pixmap = fallback.pixmap(scale)
152
+
153
+ recolored_pixmap = QPixmap(pixmap.size())
154
+
155
+ recolored_pixmap.fill(
156
+ QColor("transparent")
157
+ ) # Ensure the background is transparent
158
+ painter = QPainter(recolored_pixmap)
159
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
160
+
161
+ # Draw the original pixmap
162
+ painter.drawPixmap(0, 0, pixmap)
163
+
164
+ # Apply the desired color
165
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
166
+ painter.fillRect(pixmap.rect(), color)
167
+
168
+ painter.end()
169
+ return QIcon(recolored_pixmap.scaled(scale, Qt.AspectRatioMode.KeepAspectRatio))
170
+
171
+
172
+ def get_icon(file_name: str) -> IconGetter:
173
+ """Wrapper function that returns a function to build a QIcon instance
174
+
175
+ Args:
176
+ file_name (str): Icon image file name in the global icon directory,
177
+ or a standard system/theme icon string
178
+
179
+ Returns:
180
+ IconGetter: Function that builds the actual QIcon instance based
181
+ on the provided file name
182
+ """
183
+
184
+ def getter(
185
+ color: Optional[str | QColor] = "", size: QSize | int | None = None
186
+ ) -> QIcon:
187
+ color = color or ""
188
+ if isinstance(size, int):
189
+ size = QSize(size, size)
190
+
191
+ return recolor_icon(icon_dir(file_name), color, size)
192
+
193
+ return getter
194
+
195
+
196
+ class ICONS:
197
+ """Reference class that provides common predefined IconGetter functions"""
198
+
199
+ SAVE = get_icon("save.svg")
200
+ NEW = get_icon("new_file.svg")
201
+ OPEN = get_icon("open.png")
202
+ COPY = get_icon("copy.png")
203
+ INSPECT = get_icon("inspect.svg")
204
+ CLONE = get_icon("clone.svg")
205
+ PLUS = get_icon("plus.png")
206
+ MINUS = get_icon("minus.png")
207
+ TIMES = get_icon("times.svg")
208
+ REFRESH = get_icon("refresh.svg")
209
+ RELOAD = get_icon("reload.png")
210
+ CIRCLE_CHECK = get_icon("circle_check.svg")
211
+ CIRCLE_TIMES = get_icon("circle_x.svg")
212
+ CODE = get_icon("code.png")
213
+ EDIT = get_icon("font-selection-editor.png")
214
+ TRASH = get_icon("trash.png")
215
+ EDIT_DATA = get_icon("edit_data.svg")
216
+ PREVIEW = get_icon("preview.png")
217
+ SYNC = get_icon("sync.png")
218
+
219
+ @classmethod
220
+ def get(cls, name: str, default: str = "") -> IconGetter:
221
+ """Function to retrieve an IconGetter reference by name.
222
+
223
+ Args:
224
+ name (str): _description_
225
+ default (str, optional): _description_. Defaults to "".
226
+
227
+ Returns:
228
+ IconGetter | str: _description_
229
+ """
230
+ if hasattr(cls, name):
231
+ return getattr(cls, name)
232
+ else:
233
+ return get_icon(default)
234
+
235
+
236
+ __all__ = [
237
+ "ICONS",
238
+ "QtIconSizes",
239
+ ]
@@ -0,0 +1,8 @@
1
+ """jbqt Dialog exports"""
2
+
3
+ from jbqt.dialogs.input_form import InputFormDialog
4
+ from jbqt.dialogs.single_input import InputDialog
5
+ from jbqt.dialogs.text_preview import TextPreviewDialog
6
+ from jbqt.dialogs.file_dialog import JbFileDialog
7
+
8
+ __all__ = ["InputDialog", "InputFormDialog", "TextPreviewDialog", "JbFileDialog"]
@@ -0,0 +1,60 @@
1
+ from PyQt6.QtCore import pyqtSignal
2
+ from PyQt6.QtWidgets import QWidget, QPushButton, QVBoxLayout, QFileDialog
3
+
4
+
5
+ class JbFileDialog(QWidget):
6
+ """A simple widget that opens a file dialog in either 'open' or 'save' mode.
7
+
8
+ Emits:
9
+ selectedFile (str): Emitted when a file path is chosen.
10
+
11
+ Args:
12
+ title (str): The dialog window title.
13
+ directory (str): The starting directory.
14
+ mode (str): 'open' to open an existing file, 'save' to choose a save location.
15
+ """
16
+
17
+ selectedFile = pyqtSignal(str)
18
+
19
+ def __init__(
20
+ self, title: str = "File Dialog", directory: str = "", mode: str = "open"
21
+ ) -> None:
22
+ super().__init__()
23
+
24
+ self.title = title
25
+ self.directory = directory
26
+ self.mode = mode.lower()
27
+
28
+ if self.mode not in {"open", "save"}:
29
+ raise ValueError("mode must be either 'open' or 'save'")
30
+
31
+ self.setWindowTitle(title)
32
+
33
+ self.button = QPushButton(
34
+ "Open File" if self.mode == "open" else "Save File", self
35
+ )
36
+ self.button.clicked.connect(self.open_file_dialog)
37
+
38
+ layout = QVBoxLayout()
39
+ layout.addWidget(self.button)
40
+ self.setLayout(layout)
41
+
42
+ def open_file_dialog(self) -> None:
43
+ """Open a file dialog based on the selected mode."""
44
+ if self.mode == "open":
45
+ file_name, _ = QFileDialog.getOpenFileName(
46
+ self,
47
+ self.title,
48
+ self.directory,
49
+ "All Files (*);;Text Files (*.txt)",
50
+ )
51
+ else:
52
+ file_name, _ = QFileDialog.getSaveFileName(
53
+ self,
54
+ self.title,
55
+ self.directory,
56
+ "All Files (*);;Text Files (*.txt)",
57
+ )
58
+
59
+ if file_name:
60
+ self.selectedFile.emit(file_name)
@@ -0,0 +1,140 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Callable
3
+
4
+ from PyQt6.QtCore import pyqtSignal
5
+ from PyQt6.QtWidgets import (
6
+ QDialog,
7
+ QDialogButtonBox,
8
+ QDoubleSpinBox,
9
+ QHBoxLayout,
10
+ QLabel,
11
+ QLayout,
12
+ QLineEdit,
13
+ QPushButton,
14
+ QSpinBox,
15
+ QVBoxLayout,
16
+ QWidget,
17
+ )
18
+
19
+ from PyQt6 import QtWidgets
20
+
21
+ from jbqt.models import DialogOptions
22
+
23
+
24
+ def get_widget(name: str) -> QWidget | None:
25
+ widget: QWidget | None = None
26
+ if hasattr(QtWidgets, name):
27
+ widget = getattr(QtWidgets, name)
28
+ """ elif hasattr(widgets, name):
29
+ widget = getattr(widgets, name) """
30
+
31
+ if widget:
32
+ if not callable(widget):
33
+ return
34
+
35
+ widget = widget()
36
+ if isinstance(widget, QWidget):
37
+ return widget
38
+ return None
39
+
40
+
41
+ def get_widget_val(widget: QWidget) -> str | int | float | None:
42
+ # Emit the custom signal with data when the dialog is accepted
43
+ value = None
44
+ if isinstance(widget, QLineEdit):
45
+ value = widget.text()
46
+ elif isinstance(widget, QSpinBox):
47
+ value = widget.value()
48
+ elif isinstance(widget, QDoubleSpinBox):
49
+ value = widget.value()
50
+
51
+ return value
52
+
53
+
54
+ class InputFormDialog(QDialog):
55
+ """reference value for external use"""
56
+
57
+ value = pyqtSignal(dict)
58
+
59
+ def __init__(
60
+ self,
61
+ parent=None,
62
+ form_data: list[dict] | None = None,
63
+ opts: DialogOptions | None = None,
64
+ ):
65
+ super().__init__(parent)
66
+
67
+ opts = opts or DialogOptions(title="Input")
68
+ opts.apply(self)
69
+
70
+ self.form_data: list[dict] = [obj.copy() for obj in form_data or []]
71
+ self.input_widgets: dict[str, QWidget] = {}
72
+
73
+ self.main_layout = QVBoxLayout()
74
+ self.construct_widgets()
75
+
76
+ button_box = QHBoxLayout()
77
+ submit_btn = QPushButton("Submit", parent=self)
78
+ submit_btn.clicked.connect(self.submit)
79
+
80
+ cancel_btn = QPushButton("Cancel", parent=self)
81
+ cancel_btn.clicked.connect(self.close)
82
+
83
+ button_box.addWidget(submit_btn)
84
+ button_box.addWidget(cancel_btn)
85
+ self.main_layout.addLayout(button_box)
86
+
87
+ self.setLayout(self.main_layout)
88
+
89
+ def construct_widgets(self) -> None:
90
+ for widget_def in self.form_data:
91
+ if not "type" in widget_def:
92
+ continue
93
+
94
+ layout = QHBoxLayout()
95
+
96
+ w_type = widget_def.pop("type")
97
+ label = widget_def.pop("label", "")
98
+ key = widget_def.pop("key", "")
99
+ if not key:
100
+ continue
101
+
102
+ widget = get_widget(w_type)
103
+ if not widget:
104
+ continue
105
+ if label and isinstance(label, str):
106
+ layout.addWidget(QLabel(label))
107
+
108
+ if widget_def:
109
+ self._init_widget(widget, **widget_def)
110
+ layout.addWidget(widget)
111
+ self.main_layout.addLayout(layout)
112
+ self.input_widgets[key] = widget
113
+
114
+ def get_form_data(self) -> dict:
115
+ form_values: dict[str, Any] = {}
116
+
117
+ for key, widget in self.input_widgets.items():
118
+ value = get_widget_val(widget)
119
+ if value:
120
+ form_values[key] = value
121
+
122
+ return form_values
123
+
124
+ def submit(self) -> None:
125
+ form_data = self.get_form_data()
126
+ self.value.emit(form_data)
127
+ self.close()
128
+
129
+ def close(self) -> bool:
130
+ self.deleteLater()
131
+
132
+ return super().close()
133
+
134
+ def _init_widget(self, widget: QWidget, **opts) -> None:
135
+ for key, value in opts.items():
136
+ setter_key = f"set{key[0].upper() + key[1:]}"
137
+ if hasattr(widget, setter_key):
138
+ setter = getattr(widget, setter_key)
139
+ if setter:
140
+ setter(value)