qpuiq 0.11__tar.gz → 0.13__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.11 → qpuiq-0.13}/PKG-INFO +17 -2
  2. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/base.py +42 -0
  3. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/table.py +71 -13
  4. qpuiq-0.13/PUI/PySide6/tree.py +280 -0
  5. {qpuiq-0.11 → qpuiq-0.13}/PUI/__init__.py +1 -1
  6. {qpuiq-0.11 → qpuiq-0.13}/PUI/node.py +10 -0
  7. {qpuiq-0.11 → qpuiq-0.13}/PUI/state.py +6 -3
  8. {qpuiq-0.11 → qpuiq-0.13}/QPUIQ.egg-info/PKG-INFO +17 -2
  9. {qpuiq-0.11 → qpuiq-0.13}/README.md +16 -1
  10. qpuiq-0.11/PUI/PySide6/tree.py +0 -120
  11. {qpuiq-0.11 → qpuiq-0.13}/LICENSE.txt +0 -0
  12. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/__init__.py +0 -0
  13. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/application.py +0 -0
  14. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/button.py +0 -0
  15. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/canvas.py +0 -0
  16. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/checkbox.py +0 -0
  17. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/combobox.py +0 -0
  18. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/dialog.py +0 -0
  19. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/divider.py +0 -0
  20. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/image.py +0 -0
  21. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/label.py +0 -0
  22. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/layout.py +0 -0
  23. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/matplotlib.py +0 -0
  24. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/mdi.py +0 -0
  25. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/menu.py +0 -0
  26. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/modal.py +0 -0
  27. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/progressbar.py +0 -0
  28. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/radiobutton.py +0 -0
  29. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/scroll.py +0 -0
  30. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/splitter.py +0 -0
  31. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/tab.py +0 -0
  32. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/text.py +0 -0
  33. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/textfield.py +0 -0
  34. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/toolbar.py +0 -0
  35. {qpuiq-0.11 → qpuiq-0.13}/PUI/PySide6/window.py +0 -0
  36. {qpuiq-0.11 → qpuiq-0.13}/PUI/common.py +0 -0
  37. {qpuiq-0.11 → qpuiq-0.13}/PUI/decorator.py +0 -0
  38. {qpuiq-0.11 → qpuiq-0.13}/PUI/dom.py +0 -0
  39. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/__init__.py +0 -0
  40. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/application.py +0 -0
  41. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/base.py +0 -0
  42. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/button.py +0 -0
  43. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/canvas.py +0 -0
  44. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/checkbox.py +0 -0
  45. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/label.py +0 -0
  46. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/layout.py +0 -0
  47. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/progressbar.py +0 -0
  48. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/radiobutton.py +0 -0
  49. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/scroll.py +0 -0
  50. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/tab.py +0 -0
  51. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/text.py +0 -0
  52. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/textfield.py +0 -0
  53. {qpuiq-0.11 → qpuiq-0.13}/PUI/flet/window.py +0 -0
  54. {qpuiq-0.11 → qpuiq-0.13}/PUI/interfaces.py +0 -0
  55. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/__init__.py +0 -0
  56. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/application.py +0 -0
  57. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/base.py +0 -0
  58. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/button.py +0 -0
  59. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/checkbox.py +0 -0
  60. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/label.py +0 -0
  61. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/layout.py +0 -0
  62. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/progressbar.py +0 -0
  63. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/radiobutton.py +0 -0
  64. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/scroll.py +0 -0
  65. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/tab.py +0 -0
  66. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/text.py +0 -0
  67. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/textfield.py +0 -0
  68. {qpuiq-0.11 → qpuiq-0.13}/PUI/textual/window.py +0 -0
  69. {qpuiq-0.11 → qpuiq-0.13}/PUI/timeline.py +0 -0
  70. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/__init__.py +0 -0
  71. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/application.py +0 -0
  72. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/base.py +0 -0
  73. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/button.py +0 -0
  74. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/canvas.py +0 -0
  75. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/checkbox.py +0 -0
  76. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/label.py +0 -0
  77. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/layout.py +0 -0
  78. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/progressbar.py +0 -0
  79. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/radiobutton.py +0 -0
  80. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/scroll.py +0 -0
  81. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/tab.py +0 -0
  82. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/text.py +0 -0
  83. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/textfield.py +0 -0
  84. {qpuiq-0.11 → qpuiq-0.13}/PUI/tkinter/window.py +0 -0
  85. {qpuiq-0.11 → qpuiq-0.13}/PUI/utils.py +0 -0
  86. {qpuiq-0.11 → qpuiq-0.13}/PUI/view.py +0 -0
  87. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/__init__.py +0 -0
  88. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/application.py +0 -0
  89. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/base.py +0 -0
  90. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/button.py +0 -0
  91. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/canvas.py +0 -0
  92. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/checkbox.py +0 -0
  93. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/combobox.py +0 -0
  94. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/dialog.py +0 -0
  95. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/divider.py +0 -0
  96. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/label.py +0 -0
  97. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/layout.py +0 -0
  98. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/progressbar.py +0 -0
  99. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/radiobutton.py +0 -0
  100. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/scroll.py +0 -0
  101. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/text.py +0 -0
  102. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/textfield.py +0 -0
  103. {qpuiq-0.11 → qpuiq-0.13}/PUI/wx/window.py +0 -0
  104. {qpuiq-0.11 → qpuiq-0.13}/QPUIQ.egg-info/SOURCES.txt +0 -0
  105. {qpuiq-0.11 → qpuiq-0.13}/QPUIQ.egg-info/dependency_links.txt +0 -0
  106. {qpuiq-0.11 → qpuiq-0.13}/QPUIQ.egg-info/top_level.txt +0 -0
  107. {qpuiq-0.11 → qpuiq-0.13}/setup.cfg +0 -0
  108. {qpuiq-0.11 → qpuiq-0.13}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qpuiq
3
- Version: 0.11
3
+ Version: 0.13
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)
@@ -0,0 +1,280 @@
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
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
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 not isinstance(parent_node, TreeNode):
119
+ parent_node = None
120
+ if parent_node:
121
+ return self.createIndex(0, 0, parent_node)
122
+ return QModelIndex()
123
+
124
+ def data(self, index, role):
125
+ if not index.isValid():
126
+ return None
127
+ node = index.internalPointer()
128
+ if role == QtCore.Qt.DisplayRole:
129
+ return node.data
130
+ return None
131
+
132
+ def rowCount(self, parent):
133
+ parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
134
+ return len(parent_node.children)
135
+
136
+ def columnCount(self, parent):
137
+ return 1
138
+
139
+ def hasChildren(self, parent):
140
+ parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
141
+ return len(parent_node.children) > 0
142
+
143
+ def clicked(self, node):
144
+ node._clicked(None)
145
+
146
+ def dblclicked(self, node):
147
+ node._dblclicked(None)
148
+
149
+ def expanded(self, node):
150
+ node._expanded()
151
+
152
+ def collapsed(self, node):
153
+ node._collapsed()
154
+
155
+ class Tree(QtBaseWidget):
156
+ def __init__(self, model=None):
157
+ super().__init__()
158
+ self.layout_weight = 1
159
+ self.model = model
160
+ self.curr_model = None
161
+ self.pendings = []
162
+ self._expand_callback = None
163
+ self._collapse_callback = None
164
+
165
+ def update(self, prev):
166
+ if prev and prev.ui:
167
+ self.ui = prev.ui
168
+ self.qt_model = prev.qt_model
169
+ self.curr_model = prev.curr_model
170
+ else:
171
+ self.qt_model = None
172
+ self.curr_model = Prop()
173
+ self.ui = QtWidgets.QTreeView()
174
+ self.ui.setHeaderHidden(True)
175
+ self.ui.clicked.connect(self.on_item_clicked)
176
+ self.ui.doubleClicked.connect(self.on_item_double_clicked)
177
+ self.ui.expanded.connect(self.on_item_expanded)
178
+ self.ui.collapsed.connect(self.on_item_collapsed)
179
+
180
+ if self.model:
181
+ if self.curr_model.set(self.model):
182
+ self.qt_model = QAbstractItemModelAdapter(self.model)
183
+ self.qt_model.node = self
184
+ self.ui.setModel(self.qt_model)
185
+ else:
186
+ self.qt_model.modelReset.emit()
187
+ else:
188
+ if not self.qt_model:
189
+ self.qt_model = QTreeNodeModelAdapter(self)
190
+ self.qt_model.node = self
191
+ self.ui.setModel(self.qt_model)
192
+ else:
193
+ self.qt_model.modelReset.emit()
194
+
195
+ for pending in self.pendings:
196
+ pending[0](*pending[1:])
197
+ self.pendings = []
198
+
199
+ super().update(prev)
200
+
201
+ def expandAll(self):
202
+ if self.ui:
203
+ self.ui.expandAll()
204
+ else:
205
+ self.pendings.append([self.expandAll])
206
+ return self
207
+
208
+ def collapseAll(self):
209
+ if self.ui:
210
+ self.ui.collapseAll()
211
+ else:
212
+ self.pendings.append([self.collapseAll])
213
+ return self
214
+
215
+ def expandable(self, enabled):
216
+ if self.ui:
217
+ self.ui.setItemsExpandable(enabled)
218
+ else:
219
+ self.pendings.append([self.expandable, enabled])
220
+ return self
221
+
222
+ def expand(self, cb, *args, **kwargs):
223
+ self._expand_callback = (cb, args, kwargs)
224
+ return self
225
+
226
+ def _expanded(self):
227
+ if self._expand_callback:
228
+ cb, args, kwargs = self._expand_callback
229
+ cb(*args, **kwargs)
230
+
231
+ def collapse(self, cb, *args, **kwargs):
232
+ self._collapse_callback = (cb, args, kwargs)
233
+ return self
234
+
235
+ def _collapsed(self):
236
+ if self._collapse_callback:
237
+ cb, args, kwargs = self._collapse_callback
238
+ cb(*args, **kwargs)
239
+
240
+ def on_item_clicked(self, index):
241
+ treenode = index.internalPointer()
242
+ self.qt_model.clicked(treenode)
243
+
244
+ def on_item_double_clicked(self, index):
245
+ treenode = index.internalPointer()
246
+ self.qt_model.dblclicked(treenode)
247
+
248
+ def on_item_expanded(self, index):
249
+ treenode = index.internalPointer()
250
+ self.qt_model.expanded(treenode)
251
+
252
+ def on_item_collapsed(self, index):
253
+ treenode = index.internalPointer()
254
+ self.qt_model.collapsed(treenode)
255
+
256
+ class TreeNode(PUINode):
257
+ def __init__(self, data=""):
258
+ super().__init__()
259
+ self._set_callback = None
260
+ self.data = data
261
+ self._expand_callback = None
262
+ self._collapse_callback = None
263
+
264
+ def expand(self, cb, *args, **kwargs):
265
+ self._expand_callback = (cb, args, kwargs)
266
+ return self
267
+
268
+ def _expanded(self):
269
+ if self._expand_callback:
270
+ cb, args, kwargs = self._expand_callback
271
+ cb(*args, **kwargs)
272
+
273
+ def collapse(self, cb, *args, **kwargs):
274
+ self._collapse_callback = (cb, args, kwargs)
275
+ return self
276
+
277
+ def _collapsed(self):
278
+ if self._collapse_callback:
279
+ cb, args, kwargs = self._collapse_callback
280
+ cb(*args, **kwargs)
@@ -1,4 +1,4 @@
1
- __version__ = "0.11"
1
+ __version__ = "0.13"
2
2
 
3
3
  from .node import *
4
4
  from .view import *
@@ -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 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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qpuiq
3
- Version: 0.11
3
+ Version: 0.13
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
File without changes
File without changes
File without changes
File without changes
File without changes