qpuiq 0.11__tar.gz → 0.12__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 qpuiq might be problematic. Click here for more details.
- {qpuiq-0.11 → qpuiq-0.12}/PKG-INFO +17 -2
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/base.py +42 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/table.py +71 -13
- qpuiq-0.12/PUI/PySide6/tree.py +277 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/__init__.py +1 -1
- {qpuiq-0.11 → qpuiq-0.12}/PUI/node.py +10 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/state.py +6 -3
- {qpuiq-0.11 → qpuiq-0.12}/QPUIQ.egg-info/PKG-INFO +17 -2
- {qpuiq-0.11 → qpuiq-0.12}/README.md +16 -1
- qpuiq-0.11/PUI/PySide6/tree.py +0 -120
- {qpuiq-0.11 → qpuiq-0.12}/LICENSE.txt +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/__init__.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/application.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/button.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/canvas.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/checkbox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/combobox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/dialog.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/divider.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/image.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/label.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/layout.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/matplotlib.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/mdi.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/menu.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/modal.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/progressbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/radiobutton.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/scroll.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/splitter.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/tab.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/text.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/textfield.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/toolbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/PySide6/window.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/common.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/decorator.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/dom.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/__init__.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/application.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/base.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/button.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/canvas.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/checkbox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/label.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/layout.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/progressbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/radiobutton.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/scroll.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/tab.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/text.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/textfield.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/flet/window.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/interfaces.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/__init__.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/application.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/base.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/button.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/checkbox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/label.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/layout.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/progressbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/radiobutton.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/scroll.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/tab.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/text.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/textfield.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/textual/window.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/timeline.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/__init__.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/application.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/base.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/button.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/canvas.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/checkbox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/label.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/layout.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/progressbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/radiobutton.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/scroll.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/tab.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/text.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/textfield.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/tkinter/window.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/utils.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/view.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/__init__.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/application.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/base.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/button.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/canvas.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/checkbox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/combobox.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/dialog.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/divider.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/label.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/layout.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/progressbar.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/radiobutton.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/scroll.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/text.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/textfield.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/PUI/wx/window.py +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/QPUIQ.egg-info/SOURCES.txt +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/QPUIQ.egg-info/dependency_links.txt +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/QPUIQ.egg-info/top_level.txt +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/setup.cfg +0 -0
- {qpuiq-0.11 → qpuiq-0.12}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qpuiq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: "PUI" Python Declarative UI Framework
|
|
5
5
|
Home-page: https://github.com/buganini/PUI
|
|
6
6
|
Author: Buganini Chiu
|
|
@@ -17,8 +17,23 @@ PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declara
|
|
|
17
17
|
[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)
|
|
18
18
|
|
|
19
19
|
# Installation
|
|
20
|
-
```
|
|
20
|
+
``` shell
|
|
21
21
|
pip install QPUIQ
|
|
22
|
+
|
|
23
|
+
# Optional, for hot-reload
|
|
24
|
+
pip install jurigged
|
|
25
|
+
|
|
26
|
+
# Optional, for PySide6 backend
|
|
27
|
+
pip install PySide6
|
|
28
|
+
|
|
29
|
+
# Optional, for textual backend
|
|
30
|
+
pip install textual
|
|
31
|
+
|
|
32
|
+
# Optional, for wxPython backend
|
|
33
|
+
pip install wxPython
|
|
34
|
+
|
|
35
|
+
# Optional, for flet backend
|
|
36
|
+
pip install flet
|
|
22
37
|
```
|
|
23
38
|
|
|
24
39
|
# Get Started
|
|
@@ -72,6 +72,19 @@ class QtPUIView(PUIView):
|
|
|
72
72
|
self.qt_params[k] = v
|
|
73
73
|
return self
|
|
74
74
|
|
|
75
|
+
class EventFilter(QtCore.QObject):
|
|
76
|
+
def __init__(self):
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.node = None
|
|
79
|
+
|
|
80
|
+
def eventFilter(self, obj, event):
|
|
81
|
+
node = self.node.get_node()
|
|
82
|
+
if event.type() == QtCore.QEvent.DragEnter:
|
|
83
|
+
return node.handleDragEnterEvent(event)
|
|
84
|
+
elif event.type() == QtCore.QEvent.Drop:
|
|
85
|
+
return node.handleDropEvent(event)
|
|
86
|
+
return super().eventFilter(obj, event)
|
|
87
|
+
|
|
75
88
|
class QtBaseWidget(PUINode):
|
|
76
89
|
pui_terminal = True
|
|
77
90
|
|
|
@@ -89,6 +102,12 @@ class QtBaseWidget(PUINode):
|
|
|
89
102
|
def update(self, prev=None):
|
|
90
103
|
super().update(prev)
|
|
91
104
|
|
|
105
|
+
if prev:
|
|
106
|
+
self.eventFilter = prev.eventFilter
|
|
107
|
+
else:
|
|
108
|
+
self.eventFilter = EventFilter()
|
|
109
|
+
self.eventFilter.node = self
|
|
110
|
+
|
|
92
111
|
sizePolicy = self.ui.sizePolicy()
|
|
93
112
|
if self.layout_width is not None:
|
|
94
113
|
sizePolicy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred)
|
|
@@ -102,6 +121,29 @@ class QtBaseWidget(PUINode):
|
|
|
102
121
|
|
|
103
122
|
_apply_params(self.ui, self)
|
|
104
123
|
|
|
124
|
+
def postUpdate(self):
|
|
125
|
+
if self.ui:
|
|
126
|
+
if self._onDropped:
|
|
127
|
+
self.ui.setAcceptDrops(True)
|
|
128
|
+
self.ui.installEventFilter(self.eventFilter)
|
|
129
|
+
else:
|
|
130
|
+
self.ui.setAcceptDrops(False)
|
|
131
|
+
super().postUpdate()
|
|
132
|
+
|
|
133
|
+
def handleDragEnterEvent(self, event):
|
|
134
|
+
if self._onDragEntered:
|
|
135
|
+
self._onDragEntered[0](event, *self._onDragEntered[1], **self._onDragEntered[2])
|
|
136
|
+
else:
|
|
137
|
+
event.setDropAction(QtCore.Qt.CopyAction)
|
|
138
|
+
event.accept()
|
|
139
|
+
|
|
140
|
+
def handleDropEvent(self, event):
|
|
141
|
+
if self._onDropped:
|
|
142
|
+
self._onDropped[0](event, *self._onDropped[1], **self._onDropped[2])
|
|
143
|
+
else:
|
|
144
|
+
print("Dropped", event)
|
|
145
|
+
event.ignore()
|
|
146
|
+
|
|
105
147
|
def qtSizeHint(self):
|
|
106
148
|
node = self.get_node()
|
|
107
149
|
if not node.ui:
|
|
@@ -49,8 +49,45 @@ class QtTableModelAdapter(QtCore.QAbstractTableModel):
|
|
|
49
49
|
return self.model.columnCount()
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
class QtTableNodeModelAdapter(QtCore.QAbstractTableModel):
|
|
53
|
+
def __init__(self, children):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.children = children
|
|
56
|
+
|
|
57
|
+
def data(self, index, role):
|
|
58
|
+
if role == QtCore.Qt.DisplayRole:
|
|
59
|
+
data = self.children[index.row()].children[index.column()].data
|
|
60
|
+
if isinstance(data, BaseBinding):
|
|
61
|
+
return str(data.value)
|
|
62
|
+
return data
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def setData(self, index, value, role):
|
|
66
|
+
if role == QtCore.Qt.EditRole:
|
|
67
|
+
n = self.children[index.row()].children[index.column()]
|
|
68
|
+
if isinstance(n.data, BaseBinding):
|
|
69
|
+
n.data.value = value
|
|
70
|
+
else:
|
|
71
|
+
n._set(value)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def flags(self, index):
|
|
75
|
+
flags = super().flags(index)
|
|
76
|
+
n = self.children[index.row()].children[index.column()]
|
|
77
|
+
if isinstance(n.data, BaseBinding) or n._set_callback:
|
|
78
|
+
flags |= QtCore.Qt.ItemIsEditable
|
|
79
|
+
return flags
|
|
80
|
+
|
|
81
|
+
def rowCount(self, index):
|
|
82
|
+
return len(self.children)
|
|
83
|
+
|
|
84
|
+
def columnCount(self, index):
|
|
85
|
+
if self.children:
|
|
86
|
+
return len(self.children[0].children)
|
|
87
|
+
return 0
|
|
88
|
+
|
|
52
89
|
class Table(QtBaseWidget):
|
|
53
|
-
def __init__(self, model, autofit=True):
|
|
90
|
+
def __init__(self, model=None, autofit=True):
|
|
54
91
|
super().__init__()
|
|
55
92
|
self.layout_weight = 1
|
|
56
93
|
self.model = model
|
|
@@ -67,23 +104,44 @@ class Table(QtBaseWidget):
|
|
|
67
104
|
self.curr_model = Prop()
|
|
68
105
|
self.ui = QtWidgets.QTableView()
|
|
69
106
|
|
|
70
|
-
if self.model
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
if self.model:
|
|
108
|
+
if self.model.columnHeader is None:
|
|
109
|
+
self.ui.horizontalHeader().hide()
|
|
110
|
+
else:
|
|
111
|
+
self.ui.horizontalHeader().show()
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
if self.model.rowHeader is None:
|
|
114
|
+
self.ui.verticalHeader().hide()
|
|
115
|
+
else:
|
|
116
|
+
self.ui.verticalHeader().show()
|
|
79
117
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
if self.curr_model.set(self.model):
|
|
119
|
+
self.qt_model = QtTableModelAdapter(self.model)
|
|
120
|
+
self.ui.setModel(self.qt_model)
|
|
121
|
+
else:
|
|
122
|
+
self.qt_model.refresh()
|
|
83
123
|
else:
|
|
84
|
-
self.
|
|
124
|
+
self.ui.horizontalHeader().hide()
|
|
125
|
+
self.ui.verticalHeader().hide()
|
|
126
|
+
self.qt_model = QtTableNodeModelAdapter(self.children)
|
|
127
|
+
self.ui.setModel(self.qt_model)
|
|
85
128
|
|
|
86
129
|
if self.autofit:
|
|
87
130
|
self.ui.resizeColumnsToContents()
|
|
88
131
|
|
|
89
132
|
super().update(prev)
|
|
133
|
+
|
|
134
|
+
class TableNode(PUINode):
|
|
135
|
+
def __init__(self, data=""):
|
|
136
|
+
super().__init__()
|
|
137
|
+
self._set_callback = None
|
|
138
|
+
self.data = data
|
|
139
|
+
|
|
140
|
+
def set(self, cb, *args, **kwargs):
|
|
141
|
+
self._set_callback = (cb, args, kwargs)
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def _set(self, data):
|
|
145
|
+
if self._set_callback:
|
|
146
|
+
cb, args, kwargs = self._set_callback
|
|
147
|
+
cb(data, *args, **kwargs)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from .base import *
|
|
3
|
+
from PySide6.QtCore import Qt, QModelIndex, QAbstractItemModel
|
|
4
|
+
|
|
5
|
+
class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
|
|
6
|
+
def __init__(self, model: "BaseTreeAdapter"):
|
|
7
|
+
super().__init__()
|
|
8
|
+
self.model = model
|
|
9
|
+
self.node = None
|
|
10
|
+
|
|
11
|
+
def index(self, row, column, parent = QtCore.QModelIndex()):
|
|
12
|
+
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
13
|
+
if 0 <= row and row < self.model.rowCount(parent_node):
|
|
14
|
+
child = self.model.child(parent_node, row)
|
|
15
|
+
return self.createIndex(row, column, child)
|
|
16
|
+
return QtCore.QModelIndex()
|
|
17
|
+
|
|
18
|
+
def flags(self, index):
|
|
19
|
+
if not index.isValid():
|
|
20
|
+
return QtCore.Qt.ItemIsDropEnabled
|
|
21
|
+
|
|
22
|
+
defaultFlags = super().flags(index)
|
|
23
|
+
|
|
24
|
+
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable
|
|
25
|
+
|
|
26
|
+
def canDropMimeData(self, data, action, row, column, parent):
|
|
27
|
+
if parent.isValid():
|
|
28
|
+
pass
|
|
29
|
+
else:
|
|
30
|
+
return bool(self.node._onDropped)
|
|
31
|
+
|
|
32
|
+
def dropMimeData(self, data, action, row, column, parent):
|
|
33
|
+
if parent.isValid():
|
|
34
|
+
pass
|
|
35
|
+
else:
|
|
36
|
+
event = QtGui.QDropEvent(QtCore.QPoint(0,0), action, data, QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.KeyboardModifier.NoModifier, QtCore.QEvent.Drop)
|
|
37
|
+
self.node._onDropped[0](event, *self.node._onDropped[1], **self.node._onDropped[2])
|
|
38
|
+
|
|
39
|
+
def parent(self, index):
|
|
40
|
+
if not index.isValid():
|
|
41
|
+
return QModelIndex()
|
|
42
|
+
node = index.internalPointer()
|
|
43
|
+
parent_node = self.model.parent(node)
|
|
44
|
+
if parent_node:
|
|
45
|
+
return self.createIndex(0, 0, parent_node)
|
|
46
|
+
return QModelIndex()
|
|
47
|
+
|
|
48
|
+
def data(self, index, role):
|
|
49
|
+
if not index.isValid():
|
|
50
|
+
return None
|
|
51
|
+
node = index.internalPointer()
|
|
52
|
+
if role == QtCore.Qt.DisplayRole:
|
|
53
|
+
return self.model.data(node)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def rowCount(self, parent):
|
|
57
|
+
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
58
|
+
return self.model.rowCount(parent_node)
|
|
59
|
+
|
|
60
|
+
def columnCount(self, parent):
|
|
61
|
+
return 1
|
|
62
|
+
|
|
63
|
+
def hasChildren(self, parent):
|
|
64
|
+
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
65
|
+
return self.model.rowCount(parent_node) > 0
|
|
66
|
+
|
|
67
|
+
def clicked(self, node):
|
|
68
|
+
self.model.clicked(node)
|
|
69
|
+
|
|
70
|
+
def dblclicked(self, node):
|
|
71
|
+
self.model.dblclicked(node)
|
|
72
|
+
|
|
73
|
+
def expanded(self, node):
|
|
74
|
+
self.model.expanded(node)
|
|
75
|
+
|
|
76
|
+
def collapsed(self, node):
|
|
77
|
+
self.model.collapsed(node)
|
|
78
|
+
|
|
79
|
+
class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
80
|
+
def __init__(self, rootnode):
|
|
81
|
+
super().__init__()
|
|
82
|
+
self.node = None
|
|
83
|
+
self.rootnode = rootnode
|
|
84
|
+
|
|
85
|
+
def index(self, row, column, parent = QtCore.QModelIndex()):
|
|
86
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
|
|
87
|
+
if 0 <= row and row < len(parent_node.children):
|
|
88
|
+
child = parent_node.children[row]
|
|
89
|
+
return self.createIndex(row, column, child)
|
|
90
|
+
return QtCore.QModelIndex()
|
|
91
|
+
|
|
92
|
+
def flags(self, index):
|
|
93
|
+
if not index.isValid():
|
|
94
|
+
return QtCore.Qt.ItemIsDropEnabled
|
|
95
|
+
|
|
96
|
+
defaultFlags = super().flags(index)
|
|
97
|
+
|
|
98
|
+
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable
|
|
99
|
+
|
|
100
|
+
def canDropMimeData(self, data, action, row, column, parent):
|
|
101
|
+
if parent.isValid():
|
|
102
|
+
pass
|
|
103
|
+
else:
|
|
104
|
+
return bool(self.node._onDropped)
|
|
105
|
+
|
|
106
|
+
def dropMimeData(self, data, action, row, column, parent):
|
|
107
|
+
if parent.isValid():
|
|
108
|
+
pass
|
|
109
|
+
else:
|
|
110
|
+
event = QtGui.QDropEvent(QtCore.QPoint(0,0), action, data, QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.KeyboardModifier.NoModifier, QtCore.QEvent.Drop)
|
|
111
|
+
self.node._onDropped[0](event, *self.node._onDropped[1], **self.node._onDropped[2])
|
|
112
|
+
|
|
113
|
+
def parent(self, index):
|
|
114
|
+
if not index.isValid():
|
|
115
|
+
return QModelIndex()
|
|
116
|
+
node = index.internalPointer()
|
|
117
|
+
parent_node = node.parent
|
|
118
|
+
if parent_node:
|
|
119
|
+
return self.createIndex(0, 0, parent_node)
|
|
120
|
+
return QModelIndex()
|
|
121
|
+
|
|
122
|
+
def data(self, index, role):
|
|
123
|
+
if not index.isValid():
|
|
124
|
+
return None
|
|
125
|
+
node = index.internalPointer()
|
|
126
|
+
if role == QtCore.Qt.DisplayRole:
|
|
127
|
+
return node.data
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def rowCount(self, parent):
|
|
131
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
|
|
132
|
+
return len(parent_node.children)
|
|
133
|
+
|
|
134
|
+
def columnCount(self, parent):
|
|
135
|
+
return 1
|
|
136
|
+
|
|
137
|
+
def hasChildren(self, parent):
|
|
138
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
|
|
139
|
+
return len(parent_node.children) > 0
|
|
140
|
+
|
|
141
|
+
def clicked(self, node):
|
|
142
|
+
node._clicked(None)
|
|
143
|
+
|
|
144
|
+
def dblclicked(self, node):
|
|
145
|
+
node._dblclicked(None)
|
|
146
|
+
|
|
147
|
+
def expanded(self, node):
|
|
148
|
+
node._expanded()
|
|
149
|
+
|
|
150
|
+
def collapsed(self, node):
|
|
151
|
+
node._collapsed()
|
|
152
|
+
|
|
153
|
+
class Tree(QtBaseWidget):
|
|
154
|
+
def __init__(self, model=None):
|
|
155
|
+
super().__init__()
|
|
156
|
+
self.layout_weight = 1
|
|
157
|
+
self.model = model
|
|
158
|
+
self.curr_model = None
|
|
159
|
+
self.pendings = []
|
|
160
|
+
|
|
161
|
+
def update(self, prev):
|
|
162
|
+
if prev and prev.ui:
|
|
163
|
+
self.ui = prev.ui
|
|
164
|
+
self.qt_model = prev.qt_model
|
|
165
|
+
self.curr_model = prev.curr_model
|
|
166
|
+
else:
|
|
167
|
+
self.qt_model = None
|
|
168
|
+
self.curr_model = Prop()
|
|
169
|
+
self.ui = QtWidgets.QTreeView()
|
|
170
|
+
self.ui.setHeaderHidden(True)
|
|
171
|
+
self.ui.clicked.connect(self.on_item_clicked)
|
|
172
|
+
self.ui.doubleClicked.connect(self.on_item_double_clicked)
|
|
173
|
+
self.ui.expanded.connect(self.on_item_expanded)
|
|
174
|
+
self.ui.collapsed.connect(self.on_item_collapsed)
|
|
175
|
+
|
|
176
|
+
if self.model:
|
|
177
|
+
if self.curr_model.set(self.model):
|
|
178
|
+
self.qt_model = QAbstractItemModelAdapter(self.model)
|
|
179
|
+
self.qt_model.node = self
|
|
180
|
+
self.ui.setModel(self.qt_model)
|
|
181
|
+
else:
|
|
182
|
+
self.qt_model.modelReset.emit()
|
|
183
|
+
else:
|
|
184
|
+
self.qt_model = QTreeNodeModelAdapter(self)
|
|
185
|
+
self.qt_model.node = self
|
|
186
|
+
self.ui.setModel(self.qt_model)
|
|
187
|
+
|
|
188
|
+
for pending in self.pendings:
|
|
189
|
+
pending[0](*pending[1:])
|
|
190
|
+
self.pendings = []
|
|
191
|
+
|
|
192
|
+
super().update(prev)
|
|
193
|
+
|
|
194
|
+
def expandAll(self):
|
|
195
|
+
if self.ui:
|
|
196
|
+
self.ui.expandAll()
|
|
197
|
+
else:
|
|
198
|
+
self.pendings.append([self.expandAll])
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def collapseAll(self):
|
|
202
|
+
if self.ui:
|
|
203
|
+
self.ui.collapseAll()
|
|
204
|
+
else:
|
|
205
|
+
self.pendings.append([self.collapseAll])
|
|
206
|
+
return self
|
|
207
|
+
|
|
208
|
+
def expandable(self, enabled):
|
|
209
|
+
if self.ui:
|
|
210
|
+
self.ui.setItemsExpandable(enabled)
|
|
211
|
+
else:
|
|
212
|
+
self.pendings.append([self.expandable, enabled])
|
|
213
|
+
return self
|
|
214
|
+
|
|
215
|
+
def expand(self, cb, *args, **kwargs):
|
|
216
|
+
self._expand_callback = (cb, args, kwargs)
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def _expanded(self):
|
|
220
|
+
if self._expand_callback:
|
|
221
|
+
cb, args, kwargs = self._expand_callback
|
|
222
|
+
cb(*args, **kwargs)
|
|
223
|
+
|
|
224
|
+
def collapse(self, cb, *args, **kwargs):
|
|
225
|
+
self._collapse_callback = (cb, args, kwargs)
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
def _collapsed(self):
|
|
229
|
+
if self._collapse_callback:
|
|
230
|
+
cb, args, kwargs = self._collapse_callback
|
|
231
|
+
cb(*args, **kwargs)
|
|
232
|
+
|
|
233
|
+
def on_item_clicked(self, index):
|
|
234
|
+
node = self.get_node()
|
|
235
|
+
treenode = index.internalPointer()
|
|
236
|
+
self.qt_model.clicked(treenode)
|
|
237
|
+
|
|
238
|
+
def on_item_double_clicked(self, index):
|
|
239
|
+
node = self.get_node()
|
|
240
|
+
treenode = index.internalPointer()
|
|
241
|
+
self.qt_model.dblclicked(treenode)
|
|
242
|
+
|
|
243
|
+
def on_item_expanded(self, index):
|
|
244
|
+
node = self.get_node()
|
|
245
|
+
treenode = index.internalPointer()
|
|
246
|
+
self.qt_model.expanded(treenode)
|
|
247
|
+
|
|
248
|
+
def on_item_collapsed(self, index):
|
|
249
|
+
node = self.get_node()
|
|
250
|
+
treenode = index.internalPointer()
|
|
251
|
+
self.qt_model.collapsed(treenode)
|
|
252
|
+
|
|
253
|
+
class TreeNode(PUINode):
|
|
254
|
+
def __init__(self, data=""):
|
|
255
|
+
super().__init__()
|
|
256
|
+
self._set_callback = None
|
|
257
|
+
self.data = data
|
|
258
|
+
self._expand_callback = None
|
|
259
|
+
self._collapse_callback = None
|
|
260
|
+
|
|
261
|
+
def expand(self, cb, *args, **kwargs):
|
|
262
|
+
self._expand_callback = (cb, args, kwargs)
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
def _expanded(self):
|
|
266
|
+
if self._expand_callback:
|
|
267
|
+
cb, args, kwargs = self._expand_callback
|
|
268
|
+
cb(*args, **kwargs)
|
|
269
|
+
|
|
270
|
+
def collapse(self, cb, *args, **kwargs):
|
|
271
|
+
self._collapse_callback = (cb, args, kwargs)
|
|
272
|
+
return self
|
|
273
|
+
|
|
274
|
+
def _collapsed(self):
|
|
275
|
+
if self._collapse_callback:
|
|
276
|
+
cb, args, kwargs = self._collapse_callback
|
|
277
|
+
cb(*args, **kwargs)
|
|
@@ -73,6 +73,8 @@ class PUINode():
|
|
|
73
73
|
self._onMouseUp = None
|
|
74
74
|
self._onMouseMove = None
|
|
75
75
|
self._onWheel = None
|
|
76
|
+
self._onDragEntered = None
|
|
77
|
+
self._onDropped = None
|
|
76
78
|
|
|
77
79
|
self.ui = None
|
|
78
80
|
self.args = args
|
|
@@ -306,6 +308,14 @@ class PUINode():
|
|
|
306
308
|
self.genKey()
|
|
307
309
|
return self
|
|
308
310
|
|
|
311
|
+
def dragEnter(self, callback, *cb_args, **cb_kwargs):
|
|
312
|
+
self._onDragEntered = callback, cb_args, cb_kwargs
|
|
313
|
+
return self
|
|
314
|
+
|
|
315
|
+
def drop(self, callback, *cb_args, **cb_kwargs):
|
|
316
|
+
self._onDropped = callback, cb_args, cb_kwargs
|
|
317
|
+
return self
|
|
318
|
+
|
|
309
319
|
def click(self, callback, *cb_args, **cb_kwargs):
|
|
310
320
|
self._onClicked = callback, cb_args, cb_kwargs
|
|
311
321
|
return self
|
|
@@ -8,7 +8,10 @@ class DummyBinding():
|
|
|
8
8
|
def __init__(self, value):
|
|
9
9
|
self.value = value
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class BaseBinding():
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class AttrBinding(BaseBinding):
|
|
12
15
|
def __init__(self, state, key):
|
|
13
16
|
try:
|
|
14
17
|
self.viewroot = find_puiview()
|
|
@@ -52,7 +55,7 @@ class AttrBinding():
|
|
|
52
55
|
listeners.update(l)
|
|
53
56
|
_notify(getattr(self.state, "_StateObject__pending"), listeners)
|
|
54
57
|
|
|
55
|
-
class ListBinding():
|
|
58
|
+
class ListBinding(BaseBinding):
|
|
56
59
|
def __init__(self, state, key):
|
|
57
60
|
try:
|
|
58
61
|
self.viewroot = find_puiview()
|
|
@@ -93,7 +96,7 @@ class ListBinding():
|
|
|
93
96
|
def emit(self):
|
|
94
97
|
_notify(getattr(self.state, "_StateList__pending"), getattr(self.state, "_StateList__listeners"))
|
|
95
98
|
|
|
96
|
-
class DictBinding():
|
|
99
|
+
class DictBinding(BaseBinding):
|
|
97
100
|
def __init__(self, state, key):
|
|
98
101
|
try:
|
|
99
102
|
self.viewroot = find_puiview()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qpuiq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: "PUI" Python Declarative UI Framework
|
|
5
5
|
Home-page: https://github.com/buganini/PUI
|
|
6
6
|
Author: Buganini Chiu
|
|
@@ -17,8 +17,23 @@ PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declara
|
|
|
17
17
|
[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)
|
|
18
18
|
|
|
19
19
|
# Installation
|
|
20
|
-
```
|
|
20
|
+
``` shell
|
|
21
21
|
pip install QPUIQ
|
|
22
|
+
|
|
23
|
+
# Optional, for hot-reload
|
|
24
|
+
pip install jurigged
|
|
25
|
+
|
|
26
|
+
# Optional, for PySide6 backend
|
|
27
|
+
pip install PySide6
|
|
28
|
+
|
|
29
|
+
# Optional, for textual backend
|
|
30
|
+
pip install textual
|
|
31
|
+
|
|
32
|
+
# Optional, for wxPython backend
|
|
33
|
+
pip install wxPython
|
|
34
|
+
|
|
35
|
+
# Optional, for flet backend
|
|
36
|
+
pip install flet
|
|
22
37
|
```
|
|
23
38
|
|
|
24
39
|
# Get Started
|
|
@@ -7,8 +7,23 @@ PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declara
|
|
|
7
7
|
[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)
|
|
8
8
|
|
|
9
9
|
# Installation
|
|
10
|
-
```
|
|
10
|
+
``` shell
|
|
11
11
|
pip install QPUIQ
|
|
12
|
+
|
|
13
|
+
# Optional, for hot-reload
|
|
14
|
+
pip install jurigged
|
|
15
|
+
|
|
16
|
+
# Optional, for PySide6 backend
|
|
17
|
+
pip install PySide6
|
|
18
|
+
|
|
19
|
+
# Optional, for textual backend
|
|
20
|
+
pip install textual
|
|
21
|
+
|
|
22
|
+
# Optional, for wxPython backend
|
|
23
|
+
pip install wxPython
|
|
24
|
+
|
|
25
|
+
# Optional, for flet backend
|
|
26
|
+
pip install flet
|
|
12
27
|
```
|
|
13
28
|
|
|
14
29
|
# Get Started
|
qpuiq-0.11/PUI/PySide6/tree.py
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
from .. import *
|
|
2
|
-
from .base import *
|
|
3
|
-
from PySide6.QtCore import Qt, QModelIndex, QAbstractItemModel
|
|
4
|
-
|
|
5
|
-
class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
|
|
6
|
-
def __init__(self, model: "BaseTreeAdapter"):
|
|
7
|
-
super().__init__()
|
|
8
|
-
self.model = model
|
|
9
|
-
|
|
10
|
-
def index(self, row, column, parent = QtCore.QModelIndex()):
|
|
11
|
-
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
12
|
-
if 0 <= row and row < self.model.rowCount(parent_node):
|
|
13
|
-
child = self.model.child(parent_node, row)
|
|
14
|
-
return self.createIndex(row, column, child)
|
|
15
|
-
return QtCore.QModelIndex()
|
|
16
|
-
|
|
17
|
-
def parent(self, index):
|
|
18
|
-
if not index.isValid():
|
|
19
|
-
return QModelIndex()
|
|
20
|
-
node = index.internalPointer()
|
|
21
|
-
parent_node = self.model.parent(node)
|
|
22
|
-
if parent_node:
|
|
23
|
-
return self.createIndex(0, 0, parent_node)
|
|
24
|
-
return QModelIndex()
|
|
25
|
-
|
|
26
|
-
def data(self, index, role):
|
|
27
|
-
if not index.isValid():
|
|
28
|
-
return None
|
|
29
|
-
node = index.internalPointer()
|
|
30
|
-
if role == QtCore.Qt.DisplayRole:
|
|
31
|
-
return self.model.data(node)
|
|
32
|
-
return None
|
|
33
|
-
|
|
34
|
-
def rowCount(self, parent):
|
|
35
|
-
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
36
|
-
return self.model.rowCount(parent_node)
|
|
37
|
-
|
|
38
|
-
def columnCount(self, parent):
|
|
39
|
-
return 1
|
|
40
|
-
|
|
41
|
-
def hasChildren(self, parent):
|
|
42
|
-
parent_node = parent.internalPointer() if parent.isValid() else None
|
|
43
|
-
return self.model.rowCount(parent_node) > 0
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class Tree(QtBaseWidget):
|
|
47
|
-
def __init__(self, model):
|
|
48
|
-
super().__init__()
|
|
49
|
-
self.layout_weight = 1
|
|
50
|
-
self.model = model
|
|
51
|
-
self.curr_model = None
|
|
52
|
-
self.pendings = []
|
|
53
|
-
|
|
54
|
-
def update(self, prev):
|
|
55
|
-
if prev and prev.ui:
|
|
56
|
-
self.ui = prev.ui
|
|
57
|
-
self.qt_model = prev.qt_model
|
|
58
|
-
self.curr_model = prev.curr_model
|
|
59
|
-
else:
|
|
60
|
-
self.qt_model = None
|
|
61
|
-
self.curr_model = Prop()
|
|
62
|
-
self.ui = QtWidgets.QTreeView()
|
|
63
|
-
self.ui.setHeaderHidden(True)
|
|
64
|
-
self.ui.clicked.connect(self.on_item_clicked)
|
|
65
|
-
self.ui.doubleClicked.connect(self.on_item_double_clicked)
|
|
66
|
-
self.ui.expanded.connect(self.on_item_expanded)
|
|
67
|
-
self.ui.collapsed.connect(self.on_item_collapsed)
|
|
68
|
-
|
|
69
|
-
if self.curr_model.set(self.model):
|
|
70
|
-
self.qt_model = QAbstractItemModelAdapter(self.model)
|
|
71
|
-
self.ui.setModel(self.qt_model)
|
|
72
|
-
else:
|
|
73
|
-
self.qt_model.modelReset.emit()
|
|
74
|
-
|
|
75
|
-
for pending in self.pendings:
|
|
76
|
-
pending[0](*pending[1:])
|
|
77
|
-
self.pendings = []
|
|
78
|
-
|
|
79
|
-
super().update(prev)
|
|
80
|
-
|
|
81
|
-
def expandAll(self):
|
|
82
|
-
if self.ui:
|
|
83
|
-
self.ui.expandAll()
|
|
84
|
-
else:
|
|
85
|
-
self.pendings.append([self.expandAll])
|
|
86
|
-
return self
|
|
87
|
-
|
|
88
|
-
def collapseAll(self):
|
|
89
|
-
if self.ui:
|
|
90
|
-
self.ui.collapseAll()
|
|
91
|
-
else:
|
|
92
|
-
self.pendings.append([self.collapseAll])
|
|
93
|
-
return self
|
|
94
|
-
|
|
95
|
-
def expandable(self, enabled):
|
|
96
|
-
if self.ui:
|
|
97
|
-
self.ui.setItemsExpandable(enabled)
|
|
98
|
-
else:
|
|
99
|
-
self.pendings.append([self.expandable, enabled])
|
|
100
|
-
return self
|
|
101
|
-
|
|
102
|
-
def on_item_clicked(self, index):
|
|
103
|
-
node = self.get_node()
|
|
104
|
-
treenode = index.internalPointer()
|
|
105
|
-
node.model.clicked(treenode)
|
|
106
|
-
|
|
107
|
-
def on_item_double_clicked(self, index):
|
|
108
|
-
node = self.get_node()
|
|
109
|
-
treenode = index.internalPointer()
|
|
110
|
-
node.model.dblclicked(treenode)
|
|
111
|
-
|
|
112
|
-
def on_item_expanded(self, index):
|
|
113
|
-
node = self.get_node()
|
|
114
|
-
treenode = index.internalPointer()
|
|
115
|
-
node.model.expanded(treenode)
|
|
116
|
-
|
|
117
|
-
def on_item_collapsed(self, index):
|
|
118
|
-
node = self.get_node()
|
|
119
|
-
treenode = index.internalPointer()
|
|
120
|
-
node.model.collapsed(treenode)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|