11from typing import Callable
22from typing import Optional
33
4- from PySide6 .QtGui import QColor
4+ from PySide6 .QtCore import Qt
55from PySide6 .QtWidgets import QTreeWidget
66from PySide6 .QtWidgets import QTreeWidgetItem
77
8- from compas .scene import Scene
9-
108
119class Sceneform (QTreeWidget ):
1210 """
@@ -15,128 +13,115 @@ class Sceneform(QTreeWidget):
1513 Parameters
1614 ----------
1715 scene : :class:`compas.scene.Scene`
18- The tree to be displayed. An typical example is the scene
19- object tree: :attr:`compas_viewer.viewer.Viewer._tree`.
20- columns : dict[str, callable]
16+ The scene to be displayed.
17+ columns : list[dict]
2118 A dictionary of column names and their corresponding attributes.
22- Example: ``{"Name": (lambda o: o.name), "Object": (lambda o: o)}``
23- column_editable : list, optional
24- A list of booleans indicating whether the corresponding column is editable.
25- Defaults to ``[False]``.
19+ Example: {"Name": lambda o: o.name, "Object": lambda o: o}
20+ column_editable : list[bool], optional
21+ A list of booleans indicating whether the corresponding column is editable. Defaults to [False].
2622 show_headers : bool, optional
27- Show the header of the tree.
28- Defaults to ``True``.
29- stretch : int, optional
30- Stretch factor of the tree in the grid layout.
31- Defaults to ``2``.
32- backgrounds : dict[str, callable], optional
33- A dictionary of column names and their corresponding color.
34- Example: ``{"Object-Color": (lambda o: o.surfacecolor)}``
23+ Show the header of the tree. Defaults to True.
24+ callback : Callable, optional
25+ Callback function to execute when an item is clicked or selected.
3526
3627 Attributes
3728 ----------
38- tree : :class:`compas.datastructures.Tree`
39- The tree to be displayed.
40-
41- See Also
42- --------
43- :class:`compas.datastructures.Tree`
44- :class:`compas.datastructures.tree.TreeNode`
45- :class:`compas_viewer.layout.SidedockLayout`
46-
47- References
48- ----------
49- :PySide6:`PySide6/QtWidgets/QTreeWidget`
50-
51- Examples
52- --------
53- .. code-block:: python
54-
55- from compas_viewer import Viewer
56-
57- viewer = Viewer()
58-
59- for i in range(10):
60- for j in range(10):
61- sp = viewer.scene.add(Sphere(0.1, Frame([i, j, 0], [1, 0, 0], [0, 1, 0])), name=f"Sphere_{i}_{j}")
62-
63- viewer.layout.sidedock.add_element(Treeform(viewer._tree, {"Name": (lambda o: o.object.name), "Object": (lambda o: o.object)}))
64-
65- viewer.show()
66-
29+ scene : :class:`compas.scene.Scene`
30+ The scene to be displayed.
31+ columns : list[dict]
32+ A dictionary of column names and their corresponding function.
33+ checkbox_columns : dict[int, dict[str, Callable]]
34+ A dictionary of column indices and their corresponding attributes.
6735 """
6836
6937 def __init__ (
7038 self ,
71- scene : Scene ,
72- columns : dict [str , Callable ],
73- column_editable : list [bool ] = [False ],
39+ columns : list [dict ],
40+ column_editable : Optional [list [bool ]] = None ,
7441 show_headers : bool = True ,
75- stretch : int = 2 ,
76- backgrounds : Optional [dict [str , Callable ]] = None ,
7742 callback : Optional [Callable ] = None ,
7843 ):
7944 super ().__init__ ()
8045 self .columns = columns
81- self .column_editable = column_editable + [False ] * (len (columns ) - len (column_editable ))
46+ self .checkbox_columns : dict [int , str ] = {}
47+ self .column_editable = (column_editable or [False ]) + [False ] * (len (columns ) - len (column_editable or [False ]))
8248 self .setColumnCount (len (columns ))
83- self .setHeaderLabels (list ( self .columns . keys ()) )
49+ self .setHeaderLabels (col [ "title" ] for col in self .columns )
8450 self .setHeaderHidden (not show_headers )
85- self .stretch = stretch
86- self ._backgrounds = backgrounds
8751
88- self .scene = scene
8952 self .callback = callback
90- self .itemClicked .connect (self .on_item_clickded )
53+
54+ self .itemClicked .connect (self .on_item_clicked )
9155 self .itemSelectionChanged .connect (self .on_item_selection_changed )
9256
9357 @property
94- def scene (self ) -> Scene :
95- return self ._scene
96-
97- @scene .setter
98- def scene (self , scene : Scene ):
99- self .clear ()
100- for node in scene .traverse ("breadthfirst" ):
101- if node .is_root :
102- continue
103-
104- strings = [str (c (node )) for _ , c in self .columns .items ()]
105-
106- if node .parent .is_root : # type: ignore
107- node .attributes ["widget" ] = QTreeWidgetItem (self , strings ) # type: ignore
108- else :
109- node .attributes ["widget" ] = QTreeWidgetItem (
110- node .parent .attributes ["widget" ],
111- strings , # type: ignore
112- )
113-
114- node .attributes ["widget" ].node = node
115- node .attributes ["widget" ].setSelected (node .is_selected )
58+ def viewer (self ):
59+ from compas_viewer import Viewer
11660
117- if self ._backgrounds :
118- for col , background in self ._backgrounds .items ():
119- node .attributes ["widget" ].setBackground (list (self .columns .keys ()).index (col ), QColor (* background (node ).rgb255 ))
61+ return Viewer ()
12062
121- self ._scene = scene
63+ @property
64+ def scene (self ):
65+ return self .viewer .scene
12266
12367 def update (self ):
124- from compas_viewer import Viewer
125-
126- self .scene = Viewer ().scene
127-
128- def on_item_clickded (self ):
129- selected_nodes = [item .node for item in self .selectedItems ()]
130- for node in self .scene .objects :
131- node .is_selected = node in selected_nodes
132- if self .callback and node .is_selected :
133- self .callback (node )
68+ self .clear ()
69+ self .checkbox_columns = {}
13470
135- from compas_viewer import Viewer
71+ for node in self .scene .traverse ("breadthfirst" ):
72+ if node .is_root :
73+ continue
13674
137- Viewer ().renderer .update ()
75+ strings = []
76+
77+ for i , column in enumerate (self .columns ):
78+ type = column .get ("type" , None )
79+ if type == "checkbox" :
80+ action = column .get ("action" )
81+ checked = column .get ("checked" )
82+ if not action or not checked :
83+ raise ValueError ("Both action and checked must be provided for checkbox" )
84+ self .checkbox_columns [i ] = {"action" : action , "checked" : checked }
85+ strings .append ("" )
86+ elif type == "label" :
87+ text = column .get ("text" )
88+ if not text :
89+ raise ValueError ("Text must be provided for label" )
90+ strings .append (text (node ))
91+
92+ parent_widget = self if node .parent .is_root else node .parent .attributes ["widget" ]
93+ widget = QTreeWidgetItem (parent_widget , strings )
94+ widget .node = node
95+ widget .setSelected (node .is_selected )
96+ widget .setFlags (widget .flags () | Qt .ItemIsUserCheckable | Qt .ItemIsSelectable | Qt .ItemIsEnabled )
97+
98+ for col , col_data in self .checkbox_columns .items ():
99+ widget .setCheckState (col , Qt .Checked if col_data ["checked" ](node ) else Qt .Unchecked )
100+
101+ node .attributes ["widget" ] = widget
102+
103+ self .adjust_column_widths ()
104+
105+ def on_item_clicked (self , item , column ):
106+ if column in self .checkbox_columns :
107+ check = self .checkbox_columns [column ]["action" ]
108+ check (item .node , item .checkState (column ) == Qt .Checked )
109+
110+ if self .selectedItems ():
111+ selected_nodes = {item .node for item in self .selectedItems ()}
112+ for node in self .scene .objects :
113+ node .is_selected = node in selected_nodes
114+ if self .callback and node .is_selected :
115+ self .callback (node )
116+
117+ self .viewer .renderer .update ()
138118
139119 def on_item_selection_changed (self ):
140120 for item in self .selectedItems ():
141121 if self .callback :
142122 self .callback (item .node )
123+
124+ def adjust_column_widths (self ):
125+ for i in range (self .columnCount ()):
126+ if i in self .checkbox_columns :
127+ self .setColumnWidth (i , 50 )
0 commit comments