qpuiq 0.13__tar.gz → 0.15__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 (109) hide show
  1. {qpuiq-0.13 → qpuiq-0.15}/PKG-INFO +12 -4
  2. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/base.py +3 -3
  3. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/canvas.py +38 -2
  4. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/combobox.py +13 -3
  5. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/image.py +20 -2
  6. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/layout.py +8 -8
  7. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/tree.py +25 -15
  8. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/window.py +1 -0
  9. {qpuiq-0.13 → qpuiq-0.15}/PUI/__init__.py +1 -1
  10. {qpuiq-0.13 → qpuiq-0.15}/PUI/common.py +6 -0
  11. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/__init__.py +1 -0
  12. qpuiq-0.15/PUI/textual/base.py +148 -0
  13. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/label.py +2 -2
  14. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/layout.py +4 -0
  15. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/scroll.py +6 -4
  16. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/canvas.py +3 -0
  17. qpuiq-0.15/PUI/wx/base.py +246 -0
  18. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/canvas.py +2 -2
  19. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/combobox.py +14 -5
  20. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/layout.py +6 -0
  21. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/scroll.py +13 -2
  22. {qpuiq-0.13 → qpuiq-0.15}/README.md +3 -2
  23. {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.15/qpuiq.egg-info}/PKG-INFO +12 -4
  24. {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.15/qpuiq.egg-info}/SOURCES.txt +0 -4
  25. qpuiq-0.13/PUI/textual/base.py +0 -113
  26. qpuiq-0.13/PUI/wx/base.py +0 -202
  27. {qpuiq-0.13 → qpuiq-0.15}/LICENSE.txt +0 -0
  28. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/__init__.py +0 -0
  29. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/application.py +0 -0
  30. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/button.py +0 -0
  31. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/checkbox.py +0 -0
  32. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/dialog.py +0 -0
  33. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/divider.py +0 -0
  34. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/label.py +0 -0
  35. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/matplotlib.py +0 -0
  36. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/mdi.py +0 -0
  37. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/menu.py +0 -0
  38. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/modal.py +0 -0
  39. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/progressbar.py +0 -0
  40. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/radiobutton.py +0 -0
  41. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/scroll.py +0 -0
  42. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/splitter.py +0 -0
  43. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/tab.py +0 -0
  44. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/table.py +0 -0
  45. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/text.py +0 -0
  46. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/textfield.py +0 -0
  47. {qpuiq-0.13 → qpuiq-0.15}/PUI/PySide6/toolbar.py +0 -0
  48. {qpuiq-0.13 → qpuiq-0.15}/PUI/decorator.py +0 -0
  49. {qpuiq-0.13 → qpuiq-0.15}/PUI/dom.py +0 -0
  50. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/__init__.py +0 -0
  51. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/application.py +0 -0
  52. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/base.py +0 -0
  53. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/button.py +0 -0
  54. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/canvas.py +0 -0
  55. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/checkbox.py +0 -0
  56. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/label.py +0 -0
  57. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/layout.py +0 -0
  58. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/progressbar.py +0 -0
  59. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/radiobutton.py +0 -0
  60. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/scroll.py +0 -0
  61. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/tab.py +0 -0
  62. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/text.py +0 -0
  63. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/textfield.py +0 -0
  64. {qpuiq-0.13 → qpuiq-0.15}/PUI/flet/window.py +0 -0
  65. {qpuiq-0.13 → qpuiq-0.15}/PUI/interfaces.py +0 -0
  66. {qpuiq-0.13 → qpuiq-0.15}/PUI/node.py +0 -0
  67. {qpuiq-0.13 → qpuiq-0.15}/PUI/state.py +0 -0
  68. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/application.py +0 -0
  69. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/button.py +0 -0
  70. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/checkbox.py +0 -0
  71. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/progressbar.py +0 -0
  72. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/radiobutton.py +0 -0
  73. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/tab.py +0 -0
  74. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/text.py +0 -0
  75. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/textfield.py +0 -0
  76. {qpuiq-0.13 → qpuiq-0.15}/PUI/textual/window.py +0 -0
  77. {qpuiq-0.13 → qpuiq-0.15}/PUI/timeline.py +0 -0
  78. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/__init__.py +0 -0
  79. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/application.py +0 -0
  80. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/base.py +0 -0
  81. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/button.py +0 -0
  82. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/checkbox.py +0 -0
  83. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/label.py +0 -0
  84. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/layout.py +0 -0
  85. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/progressbar.py +0 -0
  86. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/radiobutton.py +0 -0
  87. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/scroll.py +0 -0
  88. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/tab.py +0 -0
  89. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/text.py +0 -0
  90. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/textfield.py +0 -0
  91. {qpuiq-0.13 → qpuiq-0.15}/PUI/tkinter/window.py +0 -0
  92. {qpuiq-0.13 → qpuiq-0.15}/PUI/utils.py +0 -0
  93. {qpuiq-0.13 → qpuiq-0.15}/PUI/view.py +0 -0
  94. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/__init__.py +0 -0
  95. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/application.py +0 -0
  96. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/button.py +0 -0
  97. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/checkbox.py +0 -0
  98. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/dialog.py +0 -0
  99. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/divider.py +0 -0
  100. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/label.py +0 -0
  101. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/progressbar.py +0 -0
  102. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/radiobutton.py +0 -0
  103. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/text.py +0 -0
  104. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/textfield.py +0 -0
  105. {qpuiq-0.13 → qpuiq-0.15}/PUI/wx/window.py +0 -0
  106. {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.15/qpuiq.egg-info}/dependency_links.txt +0 -0
  107. {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.15/qpuiq.egg-info}/top_level.txt +0 -0
  108. {qpuiq-0.13 → qpuiq-0.15}/setup.cfg +0 -0
  109. {qpuiq-0.13 → qpuiq-0.15}/setup.py +0 -0
@@ -1,12 +1,19 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: qpuiq
3
- Version: 0.13
3
+ Version: 0.15
4
4
  Summary: "PUI" Python Declarative UI Framework
5
5
  Home-page: https://github.com/buganini/PUI
6
6
  Author: Buganini Chiu
7
7
  Author-email: buganini@b612.tw
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE.txt
10
+ Dynamic: author
11
+ Dynamic: author-email
12
+ Dynamic: description
13
+ Dynamic: description-content-type
14
+ Dynamic: home-page
15
+ Dynamic: license-file
16
+ Dynamic: summary
10
17
 
11
18
  # What is PUI
12
19
  PUI is a reactive/declarative UI framework with two-way data binding.
@@ -205,8 +212,9 @@ Then PUI will take care of view update [(code)](https://github.com/buganini/PUI/
205
212
  * textual (Text Mode)
206
213
  * no canvas
207
214
 
208
- # Components
209
- [Reference](REFERENCE.md)
215
+ # Documents
216
+ * [Components Reference](REFERENCE.md)
217
+ * [Sizing Strategy](https://html-preview.github.io/?url=https://github.com/buganini/PUI/blob/main/doc/Sizing.html)
210
218
 
211
219
  # Used by
212
220
  * https://github.com/buganini/kikit-ui
@@ -193,20 +193,20 @@ class QtBaseLayout(PUINode):
193
193
  from .modal import Modal
194
194
  from .layout import Spacer
195
195
  if isinstance(child, Spacer):
196
- self.layout.insertItem(idx, child.outer)
196
+ self.qtlayout.insertItem(idx, child.outer)
197
197
  elif isinstance(child, Modal):
198
198
  pass
199
199
  elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
200
200
  params = {}
201
201
  if not child.layout_weight is None:
202
202
  params["stretch"] = child.layout_weight
203
- self.layout.insertWidget(idx, child.outer, **params)
203
+ self.qtlayout.insertWidget(idx, child.outer, **params)
204
204
 
205
205
  def removeChild(self, idx, child):
206
206
  from .modal import Modal
207
207
  from .layout import Spacer
208
208
  if isinstance(child, Spacer):
209
- self.layout.removeItem(child.outer)
209
+ self.qtlayout.removeItem(child.outer)
210
210
  elif isinstance(child, Modal):
211
211
  pass
212
212
  elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
@@ -2,8 +2,7 @@ from .. import *
2
2
  from .base import *
3
3
 
4
4
  from PySide6 import QtWidgets, QtGui
5
- from PySide6.QtGui import QPainter, QColor, QPainterPath
6
- from PySide6.QtCore import QPoint
5
+ from PySide6.QtGui import QPainter, QColor, QPainterPath, QImage
7
6
 
8
7
  class PUIQtCanvas(QtWidgets.QWidget):
9
8
  def __init__(self, node, width=None, height=None):
@@ -46,6 +45,17 @@ class PUIQtCanvas(QtWidgets.QWidget):
46
45
  e.x_delta = event.pixelDelta().x()
47
46
  e.v_delta = event.angleDelta().y()
48
47
  e.h_delta = event.angleDelta().x()
48
+ modifier = 0
49
+ emodifiers = event.modifiers()
50
+ if emodifiers & QtCore.Qt.ShiftModifier:
51
+ modifier |= KeyModifier.SHIFT
52
+ if emodifiers & QtCore.Qt.ControlModifier:
53
+ modifier |= KeyModifier.CTRL
54
+ if emodifiers & QtCore.Qt.AltModifier:
55
+ modifier |= KeyModifier.ALT
56
+ if emodifiers & QtCore.Qt.MetaModifier:
57
+ modifier |= KeyModifier.META
58
+ e.modifiers = modifier
49
59
  self.node._wheel(e)
50
60
 
51
61
  def paintEvent(self, event):
@@ -61,6 +71,8 @@ class PUIQtCanvas(QtWidgets.QWidget):
61
71
  rect = QtCore.QRect(0, 0, self.width or self.geometry().width(), self.height or self.geometry().height())
62
72
  node.qpainter.fillRect(rect, bgBrush)
63
73
 
74
+ node.width = self.geometry().width()
75
+ node.height = self.geometry().height()
64
76
  node.painter(node, *node.args)
65
77
 
66
78
  node.qpainter.end()
@@ -286,3 +298,27 @@ class Canvas(QtBaseWidget):
286
298
  self.drawPolyline(shape.coords, color=stroke, width=width)
287
299
  else:
288
300
  raise RuntimeError(f"Not implemented: drawShapely({type(shape).__name__}) {dir(shape)}")
301
+
302
+ def loadImage(self, image_path):
303
+ return QImage(image_path)
304
+
305
+ def drawImage(self, image, x=0, y=0, width=None, height=None, src_x=0, src_y=0, src_width=None, src_height=None, opacity=1.0):
306
+ if image.isNull():
307
+ return
308
+
309
+ if src_width is None:
310
+ src_width = image.width() - src_x
311
+ if src_height is None:
312
+ src_height = image.height() - src_y
313
+
314
+ source_rect = QtCore.QRect(src_x, src_y, src_width, src_height)
315
+
316
+ if width is None:
317
+ width = src_width
318
+ if height is None:
319
+ height = src_height
320
+
321
+ dest_rect = QtCore.QRect(x, y, width, height)
322
+ self.qpainter.setOpacity(opacity)
323
+ self.qpainter.drawImage(dest_rect, image, source_rect)
324
+ self.qpainter.setOpacity(1.0)
@@ -24,15 +24,18 @@ class ComboBox(QtBaseWidget):
24
24
  super().update(prev)
25
25
 
26
26
  def postSync(self):
27
+ index = -1
28
+ text = ""
29
+
27
30
  if self.index_model:
28
31
  index = self.index_model.value
29
32
  text = self.children[index].text
30
33
  elif self.text_model:
31
34
  text = str(self.text_model.value)
32
35
  try:
33
- index = [c.text for c in self.children].index(text)
36
+ index = [c.value for c in self.children].index(text)
34
37
  except:
35
- index = 0
38
+ index = -1
36
39
 
37
40
  if self.signal_connected:
38
41
  self.ui.currentIndexChanged.disconnect()
@@ -51,11 +54,15 @@ class ComboBox(QtBaseWidget):
51
54
  def on_currentIndexChanged(self, idx):
52
55
  if self.index_model:
53
56
  self.index_model.value = idx
57
+ if self.text_model:
58
+ self.text_model.value = self.children[idx].value
54
59
  e = PUIEvent()
55
60
  e.value = idx
56
61
  self._change(e)
57
62
 
58
63
  def on_currentTextChanged(self, text):
64
+ if not self.editable:
65
+ return
59
66
  if self.text_model:
60
67
  self.text_model.value = text
61
68
  e = PUIEvent()
@@ -69,7 +76,10 @@ class ComboBox(QtBaseWidget):
69
76
  self.ui.removeItem(idx)
70
77
 
71
78
  class ComboBoxItem(PUINode):
72
- def __init__(self, text):
79
+ def __init__(self, text, value=None):
73
80
  super().__init__()
74
81
  self.id(text)
75
82
  self.text = text
83
+ if value is None:
84
+ value = text
85
+ self.value = value
@@ -1,5 +1,8 @@
1
1
  from .. import *
2
2
  from .base import *
3
+ from .label import ClickableQLabel
4
+ from PySide6.QtWidgets import QSizePolicy
5
+ import os
3
6
 
4
7
  class Image(QtBaseWidget):
5
8
  def __init__(self, path):
@@ -10,13 +13,28 @@ class Image(QtBaseWidget):
10
13
  def update(self, prev):
11
14
  if prev and prev.ui:
12
15
  self.ui = prev.ui
16
+ self.eventFilter = prev.eventFilter
13
17
  self.curr_path = prev.curr_path
18
+ self.curr_path_mtime = prev.curr_path_mtime
14
19
  self.pixmap = prev.pixmap
15
20
  else:
16
- self.ui = QtWidgets.QLabel()
21
+ self.ui = ClickableQLabel()
22
+ self.ui.clicked.connect(self._clicked)
17
23
  self.curr_path = Prop()
24
+ self.curr_path_mtime = Prop()
18
25
 
19
- if self.curr_path.set(self.path):
26
+ if self._onClicked:
27
+ self.ui.setCursor(QtCore.Qt.PointingHandCursor)
28
+
29
+ if self.layout_weight:
30
+ # XXX keep aspect ratio
31
+ self.ui.setScaledContents(True)
32
+ self.ui.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored))
33
+ else:
34
+ self.ui.setScaledContents(False)
35
+ self.ui.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred))
36
+
37
+ if self.curr_path.set(self.path) or self.curr_path_mtime.set(os.path.getmtime(self.path)):
20
38
  self.pixmap = QtGui.QPixmap(self.path)
21
39
  if self.layout_width is not None and self.layout_height is not None:
22
40
  self.pixmap = self.pixmap.scaled(self.layout_width, self.layout_height, QtCore.Qt.KeepAspectRatio, mode=QtCore.Qt.SmoothTransformation)
@@ -5,24 +5,24 @@ class HBox(QtBaseLayout):
5
5
  def update(self, prev):
6
6
  if prev and prev.ui:
7
7
  self.ui = prev.ui
8
- self.layout = prev.layout
8
+ self.qtlayout = prev.qtlayout
9
9
  else:
10
10
  self.ui = QtWidgets.QWidget()
11
- self.layout = QtWidgets.QHBoxLayout()
12
- self.layout.setContentsMargins(0,0,0,0)
13
- self.ui.setLayout(self.layout)
11
+ self.qtlayout = QtWidgets.QHBoxLayout()
12
+ self.qtlayout.setContentsMargins(0,0,0,0)
13
+ self.ui.setLayout(self.qtlayout)
14
14
  super().update(prev)
15
15
 
16
16
  class VBox(QtBaseLayout):
17
17
  def update(self, prev):
18
18
  if prev and prev.ui:
19
19
  self.ui = prev.ui
20
- self.layout = prev.layout
20
+ self.qtlayout = prev.qtlayout
21
21
  else:
22
22
  self.ui = QtWidgets.QWidget()
23
- self.layout = QtWidgets.QVBoxLayout()
24
- self.layout.setContentsMargins(0,0,0,0)
25
- self.ui.setLayout(self.layout)
23
+ self.qtlayout = QtWidgets.QVBoxLayout()
24
+ self.qtlayout.setContentsMargins(0,0,0,0)
25
+ self.ui.setLayout(self.qtlayout)
26
26
  super().update(prev)
27
27
 
28
28
  class Spacer(PUINode):
@@ -2,6 +2,9 @@ from .. import *
2
2
  from .base import *
3
3
  from PySide6.QtCore import Qt, QModelIndex, QAbstractItemModel
4
4
 
5
+ # XXX
6
+ # If click handler triggers a model reset, dblclick handler will not be called
7
+
5
8
  class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
6
9
  def __init__(self, model: "BaseTreeAdapter"):
7
10
  super().__init__()
@@ -77,13 +80,12 @@ class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
77
80
  self.model.collapsed(node)
78
81
 
79
82
  class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
80
- def __init__(self, rootnode):
83
+ def __init__(self):
81
84
  super().__init__()
82
85
  self.node = None
83
- self.rootnode = rootnode
84
86
 
85
87
  def index(self, row, column, parent = QtCore.QModelIndex()):
86
- parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
88
+ parent_node = parent.internalPointer() if parent.isValid() else self.node
87
89
  if 0 <= row and row < len(parent_node.children):
88
90
  child = parent_node.children[row]
89
91
  return self.createIndex(row, column, child)
@@ -130,14 +132,14 @@ class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
130
132
  return None
131
133
 
132
134
  def rowCount(self, parent):
133
- parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
135
+ parent_node = parent.internalPointer() if parent.isValid() else self.node
134
136
  return len(parent_node.children)
135
137
 
136
138
  def columnCount(self, parent):
137
139
  return 1
138
140
 
139
141
  def hasChildren(self, parent):
140
- parent_node = parent.internalPointer() if parent.isValid() else self.rootnode
142
+ parent_node = parent.internalPointer() if parent.isValid() else self.node
141
143
  return len(parent_node.children) > 0
142
144
 
143
145
  def clicked(self, node):
@@ -167,15 +169,21 @@ class Tree(QtBaseWidget):
167
169
  self.ui = prev.ui
168
170
  self.qt_model = prev.qt_model
169
171
  self.curr_model = prev.curr_model
172
+
173
+ self.ui.clicked.disconnect()
174
+ self.ui.doubleClicked.disconnect()
175
+ self.ui.expanded.disconnect()
176
+ self.ui.collapsed.disconnect()
170
177
  else:
171
178
  self.qt_model = None
172
179
  self.curr_model = Prop()
173
180
  self.ui = QtWidgets.QTreeView()
174
181
  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)
182
+
183
+ self.ui.clicked.connect(self.on_item_clicked)
184
+ self.ui.doubleClicked.connect(self.on_item_double_clicked)
185
+ self.ui.expanded.connect(self.on_item_expanded)
186
+ self.ui.collapsed.connect(self.on_item_collapsed)
179
187
 
180
188
  if self.model:
181
189
  if self.curr_model.set(self.model):
@@ -186,11 +194,13 @@ class Tree(QtBaseWidget):
186
194
  self.qt_model.modelReset.emit()
187
195
  else:
188
196
  if not self.qt_model:
189
- self.qt_model = QTreeNodeModelAdapter(self)
197
+ self.qt_model = QTreeNodeModelAdapter()
190
198
  self.qt_model.node = self
191
199
  self.ui.setModel(self.qt_model)
192
200
  else:
193
- self.qt_model.modelReset.emit()
201
+ self.qt_model.beginResetModel()
202
+ self.qt_model.node = self
203
+ self.qt_model.endResetModel()
194
204
 
195
205
  for pending in self.pendings:
196
206
  pending[0](*pending[1:])
@@ -239,19 +249,19 @@ class Tree(QtBaseWidget):
239
249
 
240
250
  def on_item_clicked(self, index):
241
251
  treenode = index.internalPointer()
242
- self.qt_model.clicked(treenode)
252
+ self.get_node().qt_model.clicked(treenode)
243
253
 
244
254
  def on_item_double_clicked(self, index):
245
255
  treenode = index.internalPointer()
246
- self.qt_model.dblclicked(treenode)
256
+ self.get_node().qt_model.dblclicked(treenode)
247
257
 
248
258
  def on_item_expanded(self, index):
249
259
  treenode = index.internalPointer()
250
- self.qt_model.expanded(treenode)
260
+ self.get_node().qt_model.expanded(treenode)
251
261
 
252
262
  def on_item_collapsed(self, index):
253
263
  treenode = index.internalPointer()
254
- self.qt_model.collapsed(treenode)
264
+ self.get_node().qt_model.collapsed(treenode)
255
265
 
256
266
  class TreeNode(PUINode):
257
267
  def __init__(self, data=""):
@@ -49,6 +49,7 @@ class Window(QtBaseWidget):
49
49
  if self.curr_size.set(self.size):
50
50
  self.ui.resize(*self.size)
51
51
  if self.curr_maximize.set(self.maximize):
52
+ self.ui.resize(800, 600) # workaround on windows ref: https://stackoverflow.com/questions/27157312/qt-showmaximized-not-working-in-windows
52
53
  self.ui.showMaximized()
53
54
  if self.curr_fullscreen.set(self.fullscreen):
54
55
  self.ui.showFullScreen()
@@ -1,4 +1,4 @@
1
- __version__ = "0.13"
1
+ __version__ = "0.15"
2
2
 
3
3
  from .node import *
4
4
  from .view import *
@@ -18,3 +18,9 @@ class MouseButton(IntEnum):
18
18
  MIDDLE = 4
19
19
  X1 = 8
20
20
  X2 = 16
21
+
22
+ class KeyModifier(IntEnum):
23
+ SHIFT = 1
24
+ CTRL = 2
25
+ ALT = 4
26
+ META = 8
@@ -30,5 +30,6 @@ class DummyWidget(TBase):
30
30
  Canvas = DummyWidget
31
31
  Combobox = DummyWidget
32
32
  ComboboxItem = DummyWidget
33
+ Divider = lambda: None
33
34
 
34
35
  PUI_BACKEND = "textual"
@@ -0,0 +1,148 @@
1
+ from .. import *
2
+ from textual import widgets, containers
3
+
4
+ class TBase(PUINode):
5
+ scroll = False
6
+ container_x = False # axis
7
+ container_y = False # axis
8
+ expand_x_prio = 0
9
+ expand_y_prio = 0
10
+ expand_x1_children = 0
11
+ expand_x2_children = 0
12
+ expand_x3_children = 0
13
+ expand_x4_children = 0
14
+ expand_y1_children = 0
15
+ expand_y2_children = 0
16
+ expand_y3_children = 0
17
+ expand_y4_children = 0
18
+ cached_tparent = None
19
+
20
+ @property
21
+ def expand_x(self):
22
+ parent = self.cached_tparent
23
+ expand = self.expand_x_prio
24
+ if not parent:
25
+ return False
26
+
27
+ # textual handles 1fr as shrinkable, but we need scrolller's content not to shrink
28
+ # See Exp.1 in refs/textual_layout.py
29
+ if parent.scroll:
30
+ return False
31
+
32
+ # textual populates auto(1fr) to be 1fr(1fr), but we require expanding not to go over the container
33
+ # See Exp.2 in refs/textual_layout.py
34
+ if not parent.expand_x and expand < 3:
35
+ return False
36
+
37
+ return expand
38
+
39
+ @property
40
+ def expand_y(self):
41
+ parent = self.cached_tparent
42
+ expand = self.expand_y_prio
43
+ if not parent:
44
+ return False
45
+
46
+ # textual handles 1fr as shrinkable, but we need scrolller's content not to shrink
47
+ # See Exp.1 in refs/textual_layout.py
48
+ if parent.scroll:
49
+ return False
50
+
51
+ # textual populates auto(1fr) to be 1fr(1fr), but we require expanding not to go over the container
52
+ # See Exp.2 in refs/textual_layout.py
53
+ if not parent.expand_y and expand < 3:
54
+ return False
55
+
56
+ return expand
57
+
58
+ def tremove(self):
59
+ self.ui.remove()
60
+
61
+ def destroy(self, direct):
62
+ self.ui.remove()
63
+ return super().destroy(direct)
64
+
65
+ def update(self, prev):
66
+ super().update(prev)
67
+
68
+ self.cached_tparent = parent = self.tparent
69
+ if parent:
70
+ if self.layout_weight:
71
+ if parent.container_x:
72
+ self.expand_x_prio = 4
73
+ if parent.container_y:
74
+ self.expand_y_prio = 4
75
+
76
+ if self.expand_x_prio >= 1:
77
+ parent.expand_x1_children += 1
78
+ if self.expand_x_prio >= 2:
79
+ parent.expand_x2_children += 1
80
+ if self.expand_x_prio >= 3:
81
+ parent.expand_x3_children += 1
82
+ if self.expand_x_prio >= 4:
83
+ parent.expand_x4_children += 1
84
+
85
+ if self.expand_y_prio >= 1:
86
+ parent.expand_y1_children += 1
87
+ if self.expand_y_prio >= 2:
88
+ parent.expand_y2_children += 1
89
+ if self.expand_y_prio >= 3:
90
+ parent.expand_y3_children += 1
91
+ if self.expand_y_prio >= 4:
92
+ parent.expand_y4_children += 1
93
+
94
+ def postUpdate(self):
95
+ super().postUpdate()
96
+ parent = self.cached_tparent
97
+ if parent:
98
+ if parent.container_x:
99
+ if self.expand_x_prio < 1 and parent.expand_x1_children > 0:
100
+ self.expand_x_prio = 0
101
+ if self.expand_x_prio < 2 and parent.expand_x2_children > 0:
102
+ self.expand_x_prio = 0
103
+ if self.expand_x_prio < 3 and parent.expand_x3_children > 0:
104
+ self.expand_x_prio = 0
105
+ if self.expand_x_prio < 4 and parent.expand_x4_children > 0:
106
+ self.expand_x_prio = 0
107
+
108
+ if parent.container_y:
109
+ if self.expand_y_prio < 1 and parent.expand_y1_children > 0:
110
+ self.expand_y_prio = 0
111
+ if self.expand_y_prio < 2 and parent.expand_y2_children > 0:
112
+ self.expand_y_prio = 0
113
+ if self.expand_y_prio < 3 and parent.expand_y3_children > 0:
114
+ self.expand_y_prio = 0
115
+ if self.expand_y_prio < 4 and parent.expand_y4_children > 0:
116
+ self.expand_y_prio = 0
117
+
118
+ self.t_update_layout()
119
+
120
+ @property
121
+ def tparent(self):
122
+ parent = self.parent
123
+ while not isinstance(parent, TBase):
124
+ if parent==parent.parent:
125
+ parent = None
126
+ break
127
+ parent = parent.parent
128
+ return parent
129
+
130
+ def t_update_layout(self):
131
+ if not self.ui:
132
+ return
133
+
134
+ width = "auto"
135
+ if self.expand_x:
136
+ width = "1fr"
137
+
138
+ height = "auto"
139
+ if self.expand_y:
140
+ height = "1fr"
141
+
142
+ if self._debug:
143
+ print("layout", self.key, f"{width}:{height} expand_x={self.expand_x}", f"expand_y={self.expand_y}", f"expand_x_prio={self.expand_x_prio}", f"expand_y_prio={self.expand_y_prio}")
144
+ self.ui.styles.width = width
145
+ self.ui.styles.height = height
146
+
147
+ class TPUIView(PUIView):
148
+ pui_virtual = True
@@ -13,13 +13,12 @@ class Label(TBase):
13
13
  self.widget = prev.widget
14
14
  else:
15
15
  self.ui = containers.Container()
16
- self.ui.set_styles("width: auto; height: auto;")
17
16
  if self._onClicked:
18
17
  if self.widget is None or not isinstance(self.widget, widgets.Button):
19
18
  if self.widget:
20
19
  self.widget.remove()
21
20
  self.widget = widgets.Button(self.text)
22
- self.widget.set_styles("height: auto; border-top: none; border-bottom: none;")
21
+ self.widget.set_styles("border-top: none; border-bottom: none;")
23
22
  self.widget.puinode = self
24
23
  else:
25
24
  self.widget.label = self.text
@@ -33,4 +32,5 @@ class Label(TBase):
33
32
  super().update(prev)
34
33
 
35
34
  def postUpdate(self):
35
+ super().postUpdate()
36
36
  self.ui.mount(self.widget)
@@ -3,6 +3,8 @@ from .base import *
3
3
 
4
4
  class VBox(TBase):
5
5
  container_y = True
6
+ expand_x_prio = 1
7
+ expand_y_prio = 2
6
8
  def update(self, prev):
7
9
  if prev and prev.ui:
8
10
  self.ui = prev.ui
@@ -20,6 +22,8 @@ class VBox(TBase):
20
22
 
21
23
  class HBox(TBase):
22
24
  container_x = True
25
+ expand_x_prio = 2
26
+ expand_y_prio = 1
23
27
  def update(self, prev):
24
28
  if prev and prev.ui:
25
29
  self.ui = prev.ui
@@ -4,8 +4,6 @@ import math
4
4
 
5
5
  class Scroll(TBase):
6
6
  END = -0.0
7
- weak_expand_x = True
8
- weak_expand_y = True
9
7
  scroll = True
10
8
  def __init__(self, vertical=None, horizontal=False):
11
9
  self.vertical = vertical
@@ -19,23 +17,27 @@ class Scroll(TBase):
19
17
  self.ui = prev.ui
20
18
  else:
21
19
  self.ui = containers.ScrollableContainer()
20
+
22
21
  v = "auto"
23
22
  self.container_y = True
23
+ self.expand_y_prio = 3
24
24
  if self.vertical is True:
25
25
  v = "scroll"
26
26
  elif self.vertical is False:
27
27
  v = "hidden"
28
28
  self.container_y = False
29
- self.nweak_expand_y = True # discard weak_expand_x from self
29
+ self.expand_y_prio = 1
30
30
 
31
31
  h = "auto"
32
32
  self.container_x = True
33
+ self.expand_x_prio = 3
33
34
  if self.horizontal is True:
34
35
  h = "scroll"
35
36
  elif self.horizontal is False:
36
37
  h = "hidden"
37
38
  self.container_x = False
38
- self.nweak_expand_x = True # discard weak_expand_y from self
39
+ self.expand_x_prio = 1
40
+
39
41
  self.ui.set_styles(f"overflow-x: {h}; overflow-y: {v};")
40
42
 
41
43
  super().update(prev)
@@ -3,6 +3,9 @@ from .base import *
3
3
  import itertools
4
4
  class Canvas(TkBaseWidget):
5
5
  pui_terminal = True
6
+ expand_x_prio = 2
7
+ expand_y_prio = 2
8
+
6
9
  def __init__(self, painter, *args):
7
10
  super().__init__()
8
11
  self.painter = painter