qpuiq 0.10__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.

Files changed (108) hide show
  1. {qpuiq-0.10 → qpuiq-0.12}/PKG-INFO +17 -2
  2. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/base.py +42 -0
  3. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/table.py +71 -13
  4. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/textfield.py +4 -2
  5. qpuiq-0.12/PUI/PySide6/tree.py +277 -0
  6. {qpuiq-0.10 → qpuiq-0.12}/PUI/__init__.py +1 -1
  7. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/textfield.py +3 -1
  8. {qpuiq-0.10 → qpuiq-0.12}/PUI/node.py +12 -0
  9. {qpuiq-0.10 → qpuiq-0.12}/PUI/state.py +6 -3
  10. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/textfield.py +4 -2
  11. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/textfield.py +3 -1
  12. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/textfield.py +3 -2
  13. {qpuiq-0.10 → qpuiq-0.12}/QPUIQ.egg-info/PKG-INFO +17 -2
  14. {qpuiq-0.10 → qpuiq-0.12}/README.md +16 -1
  15. qpuiq-0.10/PUI/PySide6/tree.py +0 -120
  16. {qpuiq-0.10 → qpuiq-0.12}/LICENSE.txt +0 -0
  17. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/__init__.py +0 -0
  18. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/application.py +0 -0
  19. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/button.py +0 -0
  20. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/canvas.py +0 -0
  21. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/checkbox.py +0 -0
  22. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/combobox.py +0 -0
  23. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/dialog.py +0 -0
  24. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/divider.py +0 -0
  25. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/image.py +0 -0
  26. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/label.py +0 -0
  27. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/layout.py +0 -0
  28. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/matplotlib.py +0 -0
  29. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/mdi.py +0 -0
  30. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/menu.py +0 -0
  31. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/modal.py +0 -0
  32. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/progressbar.py +0 -0
  33. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/radiobutton.py +0 -0
  34. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/scroll.py +0 -0
  35. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/splitter.py +0 -0
  36. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/tab.py +0 -0
  37. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/text.py +0 -0
  38. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/toolbar.py +0 -0
  39. {qpuiq-0.10 → qpuiq-0.12}/PUI/PySide6/window.py +0 -0
  40. {qpuiq-0.10 → qpuiq-0.12}/PUI/common.py +0 -0
  41. {qpuiq-0.10 → qpuiq-0.12}/PUI/decorator.py +0 -0
  42. {qpuiq-0.10 → qpuiq-0.12}/PUI/dom.py +0 -0
  43. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/__init__.py +0 -0
  44. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/application.py +0 -0
  45. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/base.py +0 -0
  46. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/button.py +0 -0
  47. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/canvas.py +0 -0
  48. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/checkbox.py +0 -0
  49. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/label.py +0 -0
  50. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/layout.py +0 -0
  51. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/progressbar.py +0 -0
  52. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/radiobutton.py +0 -0
  53. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/scroll.py +0 -0
  54. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/tab.py +0 -0
  55. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/text.py +0 -0
  56. {qpuiq-0.10 → qpuiq-0.12}/PUI/flet/window.py +0 -0
  57. {qpuiq-0.10 → qpuiq-0.12}/PUI/interfaces.py +0 -0
  58. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/__init__.py +0 -0
  59. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/application.py +0 -0
  60. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/base.py +0 -0
  61. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/button.py +0 -0
  62. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/checkbox.py +0 -0
  63. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/label.py +0 -0
  64. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/layout.py +0 -0
  65. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/progressbar.py +0 -0
  66. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/radiobutton.py +0 -0
  67. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/scroll.py +0 -0
  68. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/tab.py +0 -0
  69. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/text.py +0 -0
  70. {qpuiq-0.10 → qpuiq-0.12}/PUI/textual/window.py +0 -0
  71. {qpuiq-0.10 → qpuiq-0.12}/PUI/timeline.py +0 -0
  72. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/__init__.py +0 -0
  73. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/application.py +0 -0
  74. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/base.py +0 -0
  75. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/button.py +0 -0
  76. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/canvas.py +0 -0
  77. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/checkbox.py +0 -0
  78. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/label.py +0 -0
  79. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/layout.py +0 -0
  80. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/progressbar.py +0 -0
  81. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/radiobutton.py +0 -0
  82. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/scroll.py +0 -0
  83. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/tab.py +0 -0
  84. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/text.py +0 -0
  85. {qpuiq-0.10 → qpuiq-0.12}/PUI/tkinter/window.py +0 -0
  86. {qpuiq-0.10 → qpuiq-0.12}/PUI/utils.py +0 -0
  87. {qpuiq-0.10 → qpuiq-0.12}/PUI/view.py +0 -0
  88. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/__init__.py +0 -0
  89. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/application.py +0 -0
  90. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/base.py +0 -0
  91. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/button.py +0 -0
  92. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/canvas.py +0 -0
  93. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/checkbox.py +0 -0
  94. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/combobox.py +0 -0
  95. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/dialog.py +0 -0
  96. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/divider.py +0 -0
  97. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/label.py +0 -0
  98. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/layout.py +0 -0
  99. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/progressbar.py +0 -0
  100. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/radiobutton.py +0 -0
  101. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/scroll.py +0 -0
  102. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/text.py +0 -0
  103. {qpuiq-0.10 → qpuiq-0.12}/PUI/wx/window.py +0 -0
  104. {qpuiq-0.10 → qpuiq-0.12}/QPUIQ.egg-info/SOURCES.txt +0 -0
  105. {qpuiq-0.10 → qpuiq-0.12}/QPUIQ.egg-info/dependency_links.txt +0 -0
  106. {qpuiq-0.10 → qpuiq-0.12}/QPUIQ.egg-info/top_level.txt +0 -0
  107. {qpuiq-0.10 → qpuiq-0.12}/setup.cfg +0 -0
  108. {qpuiq-0.10 → qpuiq-0.12}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qpuiq
3
- Version: 0.10
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.columnHeader is None:
71
- self.ui.horizontalHeader().hide()
72
- else:
73
- self.ui.horizontalHeader().show()
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
- if self.model.rowHeader is None:
76
- self.ui.verticalHeader().hide()
77
- else:
78
- self.ui.verticalHeader().show()
113
+ if self.model.rowHeader is None:
114
+ self.ui.verticalHeader().hide()
115
+ else:
116
+ self.ui.verticalHeader().show()
79
117
 
80
- if self.curr_model.set(self.model):
81
- self.qt_model = QtTableModelAdapter(self.model)
82
- self.ui.setModel(self.qt_model)
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.qt_model.refresh()
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)
@@ -53,10 +53,12 @@ class TextField(QtBaseWidget):
53
53
 
54
54
  def on_textchanged(self):
55
55
  node = self.get_node()
56
- node.editing = True
57
56
  value = self.ui.text()
58
57
  if node.edit_model:
59
- node.edit_model.value = value
58
+ node.editing = True
59
+ node.edit_model.value = value
60
+ else:
61
+ node.model.value = value
60
62
  e = PUIEvent()
61
63
  e.value = value
62
64
  self._input(e)
@@ -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)
@@ -1,4 +1,4 @@
1
- __version__ = "0.10"
1
+ __version__ = "0.12"
2
2
 
3
3
  from .node import *
4
4
  from .view import *
@@ -32,10 +32,12 @@ class TextField(FBase):
32
32
 
33
33
  def on_textbox_changed(self, e):
34
34
  node = self.get_node()
35
- node.editing = True
36
35
  value = e.control.value
37
36
  if node.edit_model:
37
+ node.editing = True
38
38
  node.edit_model.value = value
39
+ else:
40
+ node.model.value = value
39
41
  e = PUIEvent()
40
42
  e.value = value
41
43
  self._input(e)
@@ -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
@@ -197,6 +199,8 @@ class PUINode():
197
199
  node = self
198
200
  while node.retired_by:
199
201
  node = node.retired_by
202
+ if node is not self:
203
+ self.retired_by = node
200
204
  return node
201
205
 
202
206
  def __repr__(self):
@@ -304,6 +308,14 @@ class PUINode():
304
308
  self.genKey()
305
309
  return self
306
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
+
307
319
  def click(self, callback, *cb_args, **cb_kwargs):
308
320
  self._onClicked = callback, cb_args, cb_kwargs
309
321
  return self
@@ -8,7 +8,10 @@ class DummyBinding():
8
8
  def __init__(self, value):
9
9
  self.value = value
10
10
 
11
- class AttrBinding():
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()
@@ -31,9 +31,11 @@ class TextField(TBase):
31
31
 
32
32
  def _changed(self, value):
33
33
  node = self.get_node()
34
- node.editing = True
35
34
  if node.edit_model:
36
- node.edit_model.value = value
35
+ node.editing = True
36
+ node.edit_model.value = value
37
+ else:
38
+ node.model.value = value
37
39
  e = PUIEvent()
38
40
  e.value = value
39
41
  self._input(e)
@@ -32,9 +32,11 @@ class TextField(TkBaseWidget):
32
32
 
33
33
  def on_variable_changed(self, var, index, mode):
34
34
  node = self.get_node()
35
- node.editing = True
36
35
  if node.edit_model:
36
+ node.editing = True
37
37
  node.edit_model.value = self.variable.get()
38
+ else:
39
+ node.model.value = self.variable.get()
38
40
  self._input()
39
41
 
40
42
  def on_change(self):
@@ -45,12 +45,13 @@ class TextField(WxBaseWidget):
45
45
 
46
46
  def on_kill_focus(self, *args):
47
47
  node = self.get_node()
48
- node.editing = True
49
-
50
48
  value = self.ui.GetValue()
51
49
  node.model.value = value
52
50
  if node.edit_model:
51
+ node.editing = True
53
52
  node.edit_model.value = value
53
+ else:
54
+ node.model.value = value
54
55
  e = PUIEvent()
55
56
  e.value = value
56
57
  self._change(e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qpuiq
3
- Version: 0.10
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
@@ -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