jbqt 0.1.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.

Potentially problematic release.


This version of jbqt might be problematic. Click here for more details.

jbqt-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: jbqt
3
+ Version: 0.1.1
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: jb-utils (>=0.1.2,<0.2.0)
12
+ Requires-Dist: pyqt6 (>=6.9.0,<7.0.0)
13
+ Requires-Dist: python-levenshtein (>=0.27.1,<0.28.0)
14
+ Description-Content-Type: text/markdown
15
+
16
+
jbqt-0.1.1/README.md ADDED
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ """Const exports"""
2
+
3
+ from jbqt.common.consts import COLORS, STYLES, SIZES, GlobalRefs, ICONS
4
+ from jbqt.common.qt_utils import register_app
5
+
6
+ __all__ = ["COLORS", "STYLES", "SIZES", "GlobalRefs", "ICONS", "register_app"]
@@ -0,0 +1,167 @@
1
+ import os
2
+ import re
3
+
4
+ import jb_utils
5
+
6
+ from PyQt6.QtWidgets import QApplication, QMainWindow, QScrollArea, QWidget
7
+ from PyQt6.QtCore import Qt, QSize
8
+ from PyQt6.QtGui import QIcon, QPixmap, QPainter, QColor
9
+
10
+ TAG_RE = re.compile(r"^\((.+)\)(\d+\.\d+)$")
11
+ LIST_ITEM_ROLE = Qt.ItemDataRole.UserRole - 1
12
+
13
+ STYLES = jb_utils.STYLES
14
+ COLORS = jb_utils.COLORS
15
+
16
+
17
+ class GlobalRefs:
18
+ app: QApplication = None
19
+ main_window: QMainWindow = None
20
+ scroll_area: QScrollArea = None
21
+ server_active: bool = False
22
+ debug_set: bool = False
23
+ seed_widget: QWidget = None
24
+
25
+
26
+ class QtPaths:
27
+ icon_dir: str = None
28
+
29
+
30
+ THEME_ICONS: dict[str, QIcon] = {
31
+ "save": QIcon.fromTheme("media-floppy-symbolic"),
32
+ "new_file": QIcon.fromTheme("document-new-symbolic"),
33
+ "open": QIcon.fromTheme("folder-open-symbolic"),
34
+ "copy": QIcon.fromTheme("edit-paste-symbolic"),
35
+ "inspect": QIcon.fromTheme("docviewer-app-symbolic"),
36
+ "clone": QIcon.fromTheme("edit-copy-symbolic"),
37
+ "plus": QIcon.fromTheme("list-add-symbolic"),
38
+ "minus": QIcon.fromTheme("list-remove-symbolic"),
39
+ "times": QIcon.fromTheme("cancel-operation-symbolic"),
40
+ "refresh": QIcon.fromTheme("view-refresh-symbolic"),
41
+ "reload": QIcon.fromTheme("update-symbolic"),
42
+ "circle_check": QIcon.fromTheme("selection-checked"),
43
+ "circle_x": QIcon.fromTheme("application-exit"),
44
+ "code": QIcon.fromTheme("input-tablet-symbolic"),
45
+ "font-selection-editor": QIcon.fromTheme("font-select-symbolic"),
46
+ "trash": QIcon.fromTheme("trash-symbolic"),
47
+ "edit_data": QIcon.fromTheme("document-edit-symbolic"),
48
+ "preview": QIcon.fromTheme("view-layout-symbolic"),
49
+ "sync": QIcon.fromTheme("mail-send-receive-symbolic"),
50
+ }
51
+ STD_ICONS: dict[str, QIcon] = {
52
+ "save": QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave),
53
+ "new_file": QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew),
54
+ "open": QIcon.fromTheme(QIcon.ThemeIcon.FolderOpen),
55
+ "copy": QIcon.fromTheme(QIcon.ThemeIcon.EditPaste),
56
+ "inspect": QIcon.fromTheme(QIcon.ThemeIcon.DocumentPageSetup),
57
+ "clone": QIcon.fromTheme(QIcon.ThemeIcon.EditCopy),
58
+ "plus": QIcon.fromTheme(QIcon.ThemeIcon.ListAdd),
59
+ "minus": QIcon.fromTheme(QIcon.ThemeIcon.DialogError),
60
+ "times": QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
61
+ "refresh": QIcon.fromTheme(QIcon.ThemeIcon.SystemReboot),
62
+ "reload": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaylistRepeat),
63
+ "circle_check": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart),
64
+ "circle_x": QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop),
65
+ "code": QIcon.fromTheme(QIcon.ThemeIcon.Computer),
66
+ "font-selection-editor": QIcon.fromTheme(QIcon.ThemeIcon.EditFind),
67
+ "trash": QIcon.fromTheme(QIcon.ThemeIcon.EditDelete),
68
+ "edit_data": QIcon.fromTheme(QIcon.ThemeIcon.InputTablet),
69
+ "preview": QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn),
70
+ "sync": QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaylistShuffle),
71
+ }
72
+
73
+
74
+ def set_icon_dir(path: str) -> None:
75
+ QtPaths.icon_dir = path
76
+
77
+
78
+ class SIZES:
79
+ ICON_XS = QSize(16, 16)
80
+ ICON_SM = QSize(20, 20)
81
+ ICON_MD = QSize(22, 22)
82
+ ICON_LG = QSize(24, 24)
83
+ ICON_XL = QSize(32, 32)
84
+ ICON_XXL = QSize(48, 48)
85
+
86
+
87
+ def icon_dir(file_name: str) -> str:
88
+ if QtPaths.icon_dir:
89
+ return os.path.join(QtPaths.icon_dir, file_name)
90
+ return file_name
91
+
92
+
93
+ def recolor_icon(
94
+ image_path: str, color: str | QColor, scale: QSize = None
95
+ ) -> QIcon:
96
+ if not color:
97
+ color = QColor("black")
98
+ if isinstance(color, str):
99
+ color = QColor(color)
100
+
101
+ if not scale:
102
+ scale = SIZES.ICON_MD
103
+ pixmap = QPixmap(image_path)
104
+ icon_key = os.path.splitext(os.path.basename(image_path))[0]
105
+
106
+ if pixmap.isNull():
107
+ fallback = THEME_ICONS.get(icon_key, STD_ICONS.get(icon_key))
108
+ if fallback:
109
+ pixmap = fallback.pixmap()
110
+
111
+ recolored_pixmap = QPixmap(pixmap.size())
112
+
113
+ recolored_pixmap.fill(
114
+ QColor("transparent")
115
+ ) # Ensure the background is transparent
116
+ painter = QPainter(recolored_pixmap)
117
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
118
+
119
+ # Draw the original pixmap
120
+ painter.drawPixmap(0, 0, pixmap)
121
+
122
+ # Apply the desired color
123
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
124
+ painter.fillRect(pixmap.rect(), color)
125
+
126
+ painter.end()
127
+ return QIcon(recolored_pixmap.scaled(scale, Qt.AspectRatioMode.KeepAspectRatio))
128
+
129
+
130
+ def get_icon(file_name: str) -> QIcon:
131
+ def getter(color: str = "", size: QSize | int = None):
132
+ if isinstance(size, int):
133
+ size = QSize(size, size)
134
+
135
+ # return QIcon(QPixmap(_icon_dir(file_name)))
136
+ return recolor_icon(icon_dir(file_name), color, size)
137
+
138
+ return getter
139
+
140
+
141
+ class ICONS:
142
+ SAVE = get_icon("save.svg")
143
+ NEW = get_icon("new_file.svg")
144
+ OPEN = get_icon("open.png")
145
+ COPY = get_icon("copy.png")
146
+ INSPECT = get_icon("inspect.svg")
147
+ CLONE = get_icon("clone.svg")
148
+ PLUS = get_icon("plus.png")
149
+ MINUS = get_icon("minus.png")
150
+ TIMES = get_icon("times.svg")
151
+ REFRESH = get_icon("refresh.svg")
152
+ RELOAD = get_icon("reload.png")
153
+ CIRCLE_CHECK = get_icon("circle_check.svg")
154
+ CIRCLE_TIMES = get_icon("circle_x.svg")
155
+ CODE = get_icon("code.png")
156
+ EDIT = get_icon("font-selection-editor.png")
157
+ TRASH = get_icon("trash.png")
158
+ EDIT_DATA = get_icon("edit_data.svg")
159
+ PREVIEW = get_icon("preview.png")
160
+ SYNC = get_icon("sync.png")
161
+
162
+ @classmethod
163
+ def get(cls, name: str, default: str = "") -> str:
164
+ if hasattr(cls, name):
165
+ return getattr(cls, name)
166
+ else:
167
+ return default
@@ -0,0 +1,81 @@
1
+ from datetime import date
2
+ from typing import Any
3
+
4
+ from PyQt6.QtCore import Qt, QDate
5
+ from PyQt6.QtWidgets import (
6
+ QListWidgetItem,
7
+ QCalendarWidget,
8
+ QApplication,
9
+ QWidget,
10
+ QLineEdit,
11
+ QTextEdit,
12
+ QSpinBox,
13
+ QCheckBox,
14
+ QDoubleSpinBox,
15
+ )
16
+
17
+ from jbqt.common import consts
18
+ from jbqt.models import IChipsWidget
19
+
20
+
21
+ def get_item_value(item: QListWidgetItem) -> str:
22
+ value = item.data(consts.LIST_ITEM_ROLE) or item.text()
23
+ return value.strip()
24
+
25
+
26
+ def register_app(app: QApplication, icon_dir: str = "") -> None:
27
+ consts.GlobalRefs.app = app
28
+ consts.set_icon_dir(icon_dir)
29
+
30
+
31
+ def get_widget_value(widget: QWidget, key: str = "") -> Any:
32
+
33
+ if isinstance(widget, QLineEdit):
34
+ return widget.text()
35
+ if isinstance(widget, QTextEdit):
36
+ return widget.toPlainText().strip()
37
+ if isinstance(widget, (QSpinBox, QDoubleSpinBox)):
38
+ return widget.value()
39
+ if isinstance(widget, IChipsWidget):
40
+ return widget.values
41
+ if isinstance(widget, QCheckBox):
42
+ return widget.checkState == Qt.CheckState.Checked
43
+ if isinstance(widget, QCalendarWidget):
44
+ return widget.selectedDate().toString()
45
+
46
+ print(f"No handler defined for {key} of type `{type(widget)}`")
47
+
48
+
49
+ def set_widget_value(widget: QWidget, value: Any) -> None:
50
+ if isinstance(widget, (QLineEdit, QTextEdit)):
51
+ widget.setText(str(value))
52
+ elif isinstance(widget, QSpinBox):
53
+ try:
54
+ widget.setValue(int(value))
55
+ except Exception as e:
56
+ print(e)
57
+ print(type(e))
58
+
59
+ elif isinstance(widget, QDoubleSpinBox):
60
+ try:
61
+ widget.setValue(float(value))
62
+ except Exception as e:
63
+ print(e)
64
+ print(type(e))
65
+
66
+ elif isinstance(widget, IChipsWidget) and isinstance(value, list):
67
+ widget.add_chips(value)
68
+ elif isinstance(widget, QCheckBox):
69
+ state = Qt.CheckState.Unchecked
70
+ if isinstance(value, Qt.CheckState):
71
+ state = value
72
+ elif value is True:
73
+ state = Qt.CheckState.Checked
74
+ elif value is False:
75
+ state = Qt.CheckState.Unchecked
76
+
77
+ widget.setCheckState(state)
78
+
79
+ elif isinstance(widget, QCalendarWidget):
80
+ if isinstance(value, (date, QDate)):
81
+ widget.selectedDate(value)
@@ -0,0 +1,7 @@
1
+ """jbqt Dialog exports"""
2
+
3
+ from jbqt.dialogs.input_form import InputDialog
4
+ from jbqt.dialogs.text_preview import TextPreviewDialog
5
+ from jbqt.dialogs.file_dialog import JbFileDialog
6
+
7
+ __all__ = ["InputDialog", "TextPreviewDialog", "JbFileDialog"]
@@ -0,0 +1,27 @@
1
+ from PyQt6.QtCore import pyqtSignal
2
+ from PyQt6.QtWidgets import QWidget, QPushButton, QVBoxLayout, QFileDialog
3
+
4
+
5
+ class JbFileDialog(QWidget):
6
+ selectedFile = pyqtSignal(str)
7
+
8
+ def __init__(self, title: str = "File Dialog", directory: str = "") -> None:
9
+ """Initialize the main window with a button to open a file dialog."""
10
+ super().__init__()
11
+ self.setWindowTitle("QFileDialog Example")
12
+ self.directory = directory
13
+
14
+ self.button = QPushButton("Open File", self)
15
+ self.button.clicked.connect(self.open_file_dialog)
16
+
17
+ layout = QVBoxLayout()
18
+ layout.addWidget(self.button)
19
+ self.setLayout(layout)
20
+
21
+ def open_file_dialog(self) -> None:
22
+ """Open a file dialog to select a file."""
23
+ file_name, _ = QFileDialog.getOpenFileName(
24
+ self, "Open File", self.directory, "All Files (*);;Text Files (*.txt)"
25
+ )
26
+ if file_name:
27
+ self.selectedFile.emit(file_name)
@@ -0,0 +1,85 @@
1
+ from typing import Any, Callable
2
+
3
+ from PyQt6.QtCore import pyqtSignal
4
+ from PyQt6.QtWidgets import (
5
+ QDialog,
6
+ QDialogButtonBox,
7
+ QDoubleSpinBox,
8
+ QLabel,
9
+ QLineEdit,
10
+ QSpinBox,
11
+ QVBoxLayout,
12
+ )
13
+
14
+
15
+ INPUT_SIGNAL_TYPES: tuple[Any] = (str, int, float)
16
+
17
+
18
+ class InputDialog(QDialog):
19
+ signal_types: tuple[Any] = INPUT_SIGNAL_TYPES
20
+ """ reference value for external use """
21
+
22
+ value = pyqtSignal(*([ptype] for ptype in INPUT_SIGNAL_TYPES))
23
+
24
+ def __init__(
25
+ self,
26
+ parent=None,
27
+ title: str = "Input",
28
+ msg_str: str = "Enter Input:",
29
+ input_type: type = str,
30
+ **opts,
31
+ ):
32
+ super().__init__(parent)
33
+
34
+ self.setWindowTitle(title)
35
+
36
+ QBtn = (
37
+ QDialogButtonBox.StandardButton.Cancel
38
+ | QDialogButtonBox.StandardButton.Open
39
+ )
40
+
41
+ self.buttonBox = QDialogButtonBox(QBtn)
42
+ self.buttonBox.accepted.connect(self.accept)
43
+ self.buttonBox.rejected.connect(self.reject)
44
+
45
+ layout = QVBoxLayout()
46
+ message = QLabel(msg_str)
47
+ layout.addWidget(message)
48
+
49
+ self.input_widget = None
50
+ if input_type is str:
51
+ self.input_widget = QLineEdit()
52
+ elif input_type is int:
53
+ self.input_widget = QSpinBox()
54
+ elif input_type is float:
55
+ self.input_widget = QDoubleSpinBox()
56
+
57
+ self._init_widget(**opts)
58
+
59
+ layout.addWidget(self.input_widget)
60
+ layout.addWidget(self.buttonBox)
61
+ self.setLayout(layout)
62
+
63
+ def _init_widget(self, **opts) -> None:
64
+ for key, value in opts.items():
65
+ setter_key = f"set{key[0].upper() + key[1:]}"
66
+ if hasattr(self.input_widget, setter_key):
67
+ setter = getattr(self.input_widget, setter_key)
68
+ if setter:
69
+ setter(value)
70
+
71
+ def connect(self, callback: Callable) -> None:
72
+ for parm_type in self.signal_types:
73
+ self.value[parm_type].connect(callback)
74
+
75
+ def accept(self):
76
+ # Emit the custom signal with data when the dialog is accepted
77
+
78
+ if isinstance(self.input_widget, QLineEdit):
79
+ self.value[str].emit(self.input_widget.text())
80
+ elif isinstance(self.input_widget, QSpinBox):
81
+ self.value[int].emit(self.input_widget.value())
82
+ elif isinstance(self.input_widget, QDoubleSpinBox):
83
+ self.value[float].emit(self.input_widget.value())
84
+
85
+ super().accept()
@@ -0,0 +1,42 @@
1
+ from PyQt6.QtGui import QFontMetrics
2
+ from PyQt6.QtWidgets import (
3
+ QDialog,
4
+ QVBoxLayout,
5
+ QTextEdit,
6
+ QPushButton,
7
+ )
8
+
9
+
10
+ class TextPreviewDialog(QDialog):
11
+ def __init__(self, text: str, title: str = "", parent=None):
12
+ super().__init__(parent)
13
+
14
+ self.setWindowTitle("Text Preview")
15
+ if title:
16
+ self.setWindowTitle(f"Text Preview [{title}]")
17
+
18
+ layout = QVBoxLayout(self)
19
+
20
+ # QTextEdit to display the text
21
+ self.text_edit = QTextEdit(self)
22
+ self.text_edit.setPlainText(text)
23
+ self.text_edit.setReadOnly(True)
24
+ layout.addWidget(self.text_edit)
25
+
26
+ # Close button
27
+ close_button = QPushButton("Close", self)
28
+ close_button.clicked.connect(self.accept)
29
+ layout.addWidget(close_button)
30
+
31
+ # Adjust the dialog size based on the text
32
+ self.adjust_size_based_on_text(text)
33
+
34
+ def adjust_size_based_on_text(self, text):
35
+ # Calculate text size
36
+ font_metrics = QFontMetrics(self.text_edit.font())
37
+ text_size = font_metrics.size(0, text)
38
+
39
+ # Adjust size with some padding
40
+ h_padding = 20
41
+ v_padding = 100
42
+ self.resize(text_size.width() + h_padding, text_size.height() + v_padding)
@@ -0,0 +1,6 @@
1
+ """Model exports"""
2
+
3
+ from jbqt.models.chips import IChipsWidget
4
+ from jbqt.models.chip_button import IChipButton
5
+
6
+ __all__ = ["IChipButton", "IChipsWidget"]
@@ -0,0 +1,13 @@
1
+ """Model for chips widget"""
2
+
3
+ from abc import abstractmethod, ABC
4
+
5
+ from PyQt6.QtCore import QObject
6
+ from PyQt6.QtWidgets import QWidget
7
+
8
+
9
+ class IChipButton(QWidget):
10
+ """ChipButton model"""
11
+
12
+ def __init__(self, parent: QObject = None) -> None:
13
+ super().__init__(parent)
@@ -0,0 +1,31 @@
1
+ """Model for chips widget"""
2
+
3
+ from abc import abstractmethod, ABC
4
+
5
+ from PyQt6.QtCore import QObject
6
+ from PyQt6.QtWidgets import QWidget
7
+
8
+ from jbqt.models.chip_button import IChipButton
9
+
10
+
11
+ class IChipsWidget(QWidget):
12
+ """ChipsWidget model"""
13
+
14
+ def __init__(self, parent: QObject = None) -> None:
15
+ super().__init__(parent)
16
+ self.values: list = None
17
+
18
+ def add_chip(self):
19
+ pass
20
+
21
+ def remove_chip(self, button: IChipButton):
22
+ pass
23
+
24
+ def add_chips(self, items: list[str]) -> None:
25
+ pass
26
+
27
+ def remove_chips(self, items: list[str]) -> None:
28
+ pass
29
+
30
+ def remove_all(self, *_) -> None:
31
+ pass