jbqt 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jbqt might be problematic. Click here for more details.
- jbqt/__init__.py +0 -0
- jbqt/common/__init__.py +6 -0
- jbqt/common/consts.py +167 -0
- jbqt/common/qt_utils.py +81 -0
- jbqt/dialogs/__init__.py +7 -0
- jbqt/dialogs/file_dialog.py +27 -0
- jbqt/dialogs/input_form.py +85 -0
- jbqt/dialogs/text_preview.py +42 -0
- jbqt/models/__init__.py +6 -0
- jbqt/models/chip_button.py +13 -0
- jbqt/models/chips.py +31 -0
- jbqt/view_icons.py +208 -0
- jbqt/widgets/__init__.py +16 -0
- jbqt/widgets/chip_button.py +204 -0
- jbqt/widgets/chips.py +232 -0
- jbqt/widgets/multiselect.py +201 -0
- jbqt/widgets/simple.py +67 -0
- jbqt/widgets/toast.py +35 -0
- jbqt/widgets/widget_utils.py +77 -0
- jbqt-0.1.1.dist-info/METADATA +16 -0
- jbqt-0.1.1.dist-info/RECORD +22 -0
- jbqt-0.1.1.dist-info/WHEEL +4 -0
jbqt/__init__.py
ADDED
|
File without changes
|
jbqt/common/__init__.py
ADDED
jbqt/common/consts.py
ADDED
|
@@ -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
|
jbqt/common/qt_utils.py
ADDED
|
@@ -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)
|
jbqt/dialogs/__init__.py
ADDED
|
@@ -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)
|
jbqt/models/__init__.py
ADDED
|
@@ -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)
|
jbqt/models/chips.py
ADDED
|
@@ -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
|
jbqt/view_icons.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtCore import QSize, Qt, QTimer
|
|
6
|
+
from PyQt6.QtGui import QIcon
|
|
7
|
+
from PyQt6.QtWidgets import (
|
|
8
|
+
QApplication,
|
|
9
|
+
QGridLayout,
|
|
10
|
+
QLabel,
|
|
11
|
+
QLineEdit,
|
|
12
|
+
QMainWindow,
|
|
13
|
+
QPushButton,
|
|
14
|
+
QScrollArea,
|
|
15
|
+
QStyle,
|
|
16
|
+
QTableWidget,
|
|
17
|
+
QTableWidgetItem,
|
|
18
|
+
QVBoxLayout,
|
|
19
|
+
QWidget,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from fuzzywuzzy import process # Needs fuzzywuzzy + python-Levenshtein
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(description="Simple tool to display icons")
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--from-theme",
|
|
27
|
+
"-f",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Search and display all icons from the Ubuntu themes as opposed to the StandardIcon library in Qt6",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--theme-icons",
|
|
33
|
+
"-t",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="Pull icons from the QIcon.ThemeIcon list",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--add-quotes",
|
|
39
|
+
"-q",
|
|
40
|
+
action="store_true",
|
|
41
|
+
help="Wrap copied icon name with quotes",
|
|
42
|
+
)
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
THEMES_DIR = "/usr/share/icons"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Toast(QLabel):
|
|
49
|
+
def __init__(self, text: str, parent: QWidget = None, duration_ms: int = 1500):
|
|
50
|
+
super().__init__(parent)
|
|
51
|
+
|
|
52
|
+
self.setText(text)
|
|
53
|
+
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
54
|
+
self.setStyleSheet(
|
|
55
|
+
"""
|
|
56
|
+
background-color: rgba(50, 50, 50, 180);
|
|
57
|
+
color: white;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
padding: 8px 16px;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
"""
|
|
62
|
+
)
|
|
63
|
+
self.setWindowFlags(Qt.WindowType.ToolTip)
|
|
64
|
+
self.adjustSize()
|
|
65
|
+
|
|
66
|
+
# Center it over parent if available
|
|
67
|
+
if parent:
|
|
68
|
+
parent_rect = parent.rect()
|
|
69
|
+
toast_rect = self.rect()
|
|
70
|
+
self.move(
|
|
71
|
+
parent_rect.center().x() - toast_rect.width() // 2,
|
|
72
|
+
parent_rect.top() + 50, # appear slightly under the top
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
QTimer.singleShot(duration_ms, self.close)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_theme_icons(theme_dir: str) -> list[str]:
|
|
79
|
+
icon_names = set()
|
|
80
|
+
for root, _, files in os.walk(theme_dir):
|
|
81
|
+
for file in files:
|
|
82
|
+
if file.endswith((".svg", ".png")):
|
|
83
|
+
rel_path = os.path.relpath(os.path.join(root, file), theme_dir)
|
|
84
|
+
parts = rel_path.split(os.sep)
|
|
85
|
+
if len(parts) >= 2:
|
|
86
|
+
icon_name = os.path.splitext(parts[-1])[0]
|
|
87
|
+
icon_names.add(icon_name)
|
|
88
|
+
return sorted(icon_names)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Window(QMainWindow):
|
|
92
|
+
def __init__(self):
|
|
93
|
+
super().__init__()
|
|
94
|
+
|
|
95
|
+
self.setGeometry(1000, 400, 1600, 800)
|
|
96
|
+
|
|
97
|
+
central_widget = QWidget()
|
|
98
|
+
self.setCentralWidget(central_widget)
|
|
99
|
+
|
|
100
|
+
self.layout = QVBoxLayout()
|
|
101
|
+
central_widget.setLayout(self.layout)
|
|
102
|
+
|
|
103
|
+
self.search_bar = QLineEdit()
|
|
104
|
+
self.search_bar.setPlaceholderText("Search icons...")
|
|
105
|
+
self.search_bar.textChanged.connect(self.update_display)
|
|
106
|
+
|
|
107
|
+
self.table = QTableWidget()
|
|
108
|
+
self.table.setColumnCount(4) # 4 columns of icons
|
|
109
|
+
self.table.horizontalHeader().setVisible(False)
|
|
110
|
+
self.table.verticalHeader().setVisible(False)
|
|
111
|
+
self.table.setIconSize(QSize(64, 64))
|
|
112
|
+
self.table.setShowGrid(False)
|
|
113
|
+
self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
|
114
|
+
self.table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
|
|
115
|
+
self.table.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
116
|
+
self.table.cellClicked.connect(self.copy_icon_name_to_clipboard)
|
|
117
|
+
self.table.setStyleSheet(
|
|
118
|
+
"""
|
|
119
|
+
QTableWidget::item:hover {
|
|
120
|
+
background-color: #444444;
|
|
121
|
+
}
|
|
122
|
+
"""
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
self.layout.addWidget(self.search_bar)
|
|
126
|
+
self.layout.addWidget(self.table)
|
|
127
|
+
|
|
128
|
+
cur_theme = QIcon.themeName()
|
|
129
|
+
self.icon_src = "standard"
|
|
130
|
+
if args.from_theme:
|
|
131
|
+
self.icon_src = "from_theme"
|
|
132
|
+
elif args.theme_icons:
|
|
133
|
+
self.icon_src = "theme_icons"
|
|
134
|
+
|
|
135
|
+
match self.icon_src:
|
|
136
|
+
case "from_theme":
|
|
137
|
+
self.icons = get_theme_icons(os.path.join(THEMES_DIR, cur_theme))
|
|
138
|
+
case "theme_icons":
|
|
139
|
+
self.icons = [
|
|
140
|
+
i for i in dir(QIcon.ThemeIcon) if not i.startswith("_")
|
|
141
|
+
]
|
|
142
|
+
case _:
|
|
143
|
+
self.icons = sorted(
|
|
144
|
+
[
|
|
145
|
+
attr
|
|
146
|
+
for attr in dir(QStyle.StandardPixmap)
|
|
147
|
+
if attr.startswith("SP_")
|
|
148
|
+
]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self.update_display()
|
|
152
|
+
|
|
153
|
+
def copy_icon_name_to_clipboard(self, row: int, column: int) -> None:
|
|
154
|
+
item = self.table.item(row, column)
|
|
155
|
+
if item and item.text():
|
|
156
|
+
text = f'"{item.text()}"' if args.add_quotes else item.text()
|
|
157
|
+
QApplication.clipboard().setText(text)
|
|
158
|
+
print(f"Copied '{item.text()}' to clipboard")
|
|
159
|
+
Toast(f"Copied '{item.text()}'", parent=self).show()
|
|
160
|
+
|
|
161
|
+
def update_display(self):
|
|
162
|
+
text = self.search_bar.text().strip()
|
|
163
|
+
|
|
164
|
+
if text:
|
|
165
|
+
# Use fuzzy matching
|
|
166
|
+
matches = process.extract(
|
|
167
|
+
text,
|
|
168
|
+
self.icons,
|
|
169
|
+
limit=200, # Don't explode the table even if 2400+ icons exist
|
|
170
|
+
)
|
|
171
|
+
filtered_icons = [match[0] for match in matches if match[1] >= 50]
|
|
172
|
+
else:
|
|
173
|
+
filtered_icons = self.icons
|
|
174
|
+
|
|
175
|
+
self.table.setRowCount((len(filtered_icons) + 3) // 4)
|
|
176
|
+
|
|
177
|
+
for i in range(self.table.rowCount()):
|
|
178
|
+
for j in range(4):
|
|
179
|
+
idx = i * 4 + j
|
|
180
|
+
if idx >= len(filtered_icons):
|
|
181
|
+
self.table.setItem(i, j, QTableWidgetItem())
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
name = filtered_icons[idx]
|
|
185
|
+
|
|
186
|
+
match self.icon_src:
|
|
187
|
+
case "from_theme":
|
|
188
|
+
icon = QIcon.fromTheme(name)
|
|
189
|
+
case "theme_icons":
|
|
190
|
+
icon = QIcon.fromTheme(getattr(QIcon.ThemeIcon, name))
|
|
191
|
+
case _:
|
|
192
|
+
|
|
193
|
+
pixmapi = getattr(QStyle.StandardPixmap, name)
|
|
194
|
+
icon = self.style().standardIcon(pixmapi)
|
|
195
|
+
|
|
196
|
+
item = QTableWidgetItem(name)
|
|
197
|
+
item.setIcon(icon)
|
|
198
|
+
self.table.setItem(i, j, item)
|
|
199
|
+
|
|
200
|
+
self.table.resizeRowsToContents()
|
|
201
|
+
self.table.resizeColumnsToContents()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
app = QApplication(sys.argv)
|
|
205
|
+
w = Window()
|
|
206
|
+
w.show()
|
|
207
|
+
|
|
208
|
+
app.exec()
|