Skip to content

Commit d49729b

Browse files
committed
[DEBUG] Implement canvas item enumeration and configuration
1 parent e8dc055 commit d49729b

5 files changed

Lines changed: 284 additions & 26 deletions

File tree

studio/debugtools/debugger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def handle_msg(self, msg):
165165
widget.deleted = True
166166
if event == "<<SelectionChanged>>":
167167
self.elements.element_pane.on_widget_tap(
168-
widget, msg.payload["event_obj"]
168+
widget, msg.payload["event_obj"], msg.payload.get("data")
169169
)
170170
suppress = True
171171
if event == "<<WidgetModified>>":

studio/debugtools/defs.py

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from dataclasses import dataclass, field
33

44
import studio.lib.menu as menu_lib
5+
import studio.lib.canvas as canvas_lib
56
from studio.debugtools.common import get_studio_equiv, get_root_id
67

78

@@ -74,6 +75,93 @@ def __init__(self, event):
7475
self.y_root = event.y_root
7576

7677

78+
class RemoteCanvasItem:
79+
DEF_OVERRIDES = dict(canvas_lib.CANVAS_PROPERTIES)
80+
_item_type_map = {
81+
"arc": canvas_lib.Arc,
82+
"bitmap": canvas_lib.Bitmap,
83+
"image": canvas_lib.Image,
84+
"line": canvas_lib.Line,
85+
"oval": canvas_lib.Oval,
86+
"polygon": canvas_lib.Polygon,
87+
"rectangle": canvas_lib.Rectangle,
88+
"text": canvas_lib.Text,
89+
"window": canvas_lib.Window,
90+
}
91+
92+
def __init__(self, canvas, item_id):
93+
self.canvas = canvas
94+
self.item_id = item_id
95+
self._attr_cache = None
96+
self._name = None
97+
self._equiv_class = None
98+
self.deleted = False
99+
self._dbg_node = None
100+
self.extra_items = []
101+
self._class = RemoteCanvasItem
102+
103+
@property
104+
def id(self):
105+
return f"{self.canvas.id}!{self.item_id}"
106+
107+
@property
108+
def equiv_class(self):
109+
if self._equiv_class is None:
110+
self._equiv_class = self._item_type_map.get(self.type(), canvas_lib.CanvasItem)
111+
return self._equiv_class
112+
113+
@property
114+
def name(self):
115+
if self._name is None:
116+
self._name = f"{self.type()}_{self.item_id}"
117+
return self._name
118+
119+
def _call(self, meth, *args, **kwargs):
120+
return self.canvas.debugger.transmit(
121+
Message(
122+
"WIDGET",
123+
payload={
124+
"id": self.canvas.id,
125+
"root": self.canvas.root,
126+
"meth": meth,
127+
"args": (self.item_id, *args),
128+
"kwargs": kwargs,
129+
}
130+
), response=True
131+
)
132+
133+
def configure(self, **kwargs):
134+
ret = self._call("itemconfigure", **kwargs)
135+
if not kwargs and isinstance(ret, dict):
136+
self._attr_cache = {k: v[-1] if isinstance(v, (tuple, list, set)) else v for k, v in ret.items()}
137+
return ret
138+
139+
config = configure
140+
141+
def cget(self, key):
142+
if self._attr_cache is not None:
143+
if key in self._attr_cache:
144+
return self._attr_cache[key]
145+
return self._call("itemcget", key)
146+
147+
__getitem__ = cget
148+
149+
def __setitem__(self, key, value):
150+
return self._call("itemconfigure", key, value)
151+
152+
def invalidate_conf(self):
153+
self._attr_cache = None
154+
155+
def type(self):
156+
return self._call("type")
157+
158+
def winfo_children(self):
159+
return []
160+
161+
def winfo_ismapped(self):
162+
return False
163+
164+
77165
class RemoteMenuItem:
78166

79167
_pool = []
@@ -96,6 +184,7 @@ def __init__(self, menu, index):
96184
self._attr_cache = None
97185
self._class = RemoteMenuItem
98186
self.menu_items = []
187+
self.extra_items = []
99188

100189
@property
101190
def id(self):
@@ -158,7 +247,7 @@ def __setitem__(self, key, value):
158247
return self._call("entryconfigure", key, value)
159248

160249
def invalidate_conf(self):
161-
pass
250+
self._attr_cache = None
162251

163252
def type(self):
164253
return self._call("type")
@@ -197,6 +286,7 @@ def __init__(self, id_, debugger, root=0):
197286
self._prop_map = {}
198287
self._attr_cache = None
199288
self._menu_items = None
289+
self._canvas_items = None
200290
self.deleted = False
201291
self.root = root
202292
self.equiv_class = get_studio_equiv(self)
@@ -332,6 +422,53 @@ def menu_items(self):
332422
self._init_menu_items()
333423
return self._menu_items
334424

425+
def _init_canvas_items(self):
426+
self._canvas_items = {i: RemoteCanvasItem(self, i) for i in self.find_all()}
427+
428+
def get_canvas_item_from_id(self, item_id):
429+
if self._canvas_items is None:
430+
self._init_canvas_items()
431+
return self._canvas_items.get(item_id)
432+
433+
def delete_canvas_ids(self, *ids):
434+
if self._canvas_items is None:
435+
return []
436+
removed = []
437+
for id in ids:
438+
item = self._canvas_items.pop(int(id), None)
439+
if item:
440+
item.deleted = True
441+
removed.append(item)
442+
return removed
443+
444+
def add_canvas_item(self, id):
445+
if not self._canvas_items:
446+
self._canvas_items = {}
447+
if id in self._canvas_items:
448+
return self._canvas_items[id]
449+
item = RemoteCanvasItem(self, id)
450+
self._canvas_items[id] = item
451+
return item
452+
453+
@property
454+
def canvas_items(self):
455+
if self._class != tkinter.Canvas:
456+
return []
457+
if self._canvas_items is None:
458+
self._init_canvas_items()
459+
return list(self._canvas_items.values())
460+
461+
@property
462+
def extra_items(self):
463+
if self._class == tkinter.Menu:
464+
return self.menu_items
465+
if self._class == tkinter.Canvas:
466+
return self.canvas_items
467+
return []
468+
469+
def find_all(self):
470+
return self._call("find_all")
471+
335472
def keys(self):
336473
return self._call("keys")
337474

studio/debugtools/element_pane.py

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from hoverset.ui.icons import get_icon_image
44
from hoverset.ui.widgets import Button, ToggleButton, Label
5-
from studio.debugtools.defs import RemoteWidget, RemoteMenuItem
5+
from studio.debugtools.defs import RemoteWidget
66
from studio.feature.component_tree import ComponentTreeView
77
from studio.i18n import _
88
from studio.ui.tree import MalleableTreeView
@@ -49,7 +49,7 @@ def loaded(self):
4949
def update_preload_status(self, added):
5050
if self._loaded or self.widget.deleted:
5151
return
52-
if added or self.widget.winfo_children() or self.widget.menu_items:
52+
if added or self.widget.winfo_children() or self.widget.extra_items:
5353
# widget can expand
5454
self._set_expander(self.COLLAPSED_ICON)
5555
else:
@@ -58,9 +58,7 @@ def update_preload_status(self, added):
5858
def extract_name(self, widget):
5959
if isinstance(widget, RemoteWidget):
6060
return str(widget._name).strip("!")
61-
if isinstance(widget, RemoteMenuItem):
62-
return widget.name or "-"
63-
return 'root'
61+
return widget.name or "-"
6462

6563
def load(self):
6664
# lazy loading
@@ -71,14 +69,14 @@ def load(self):
7169
if getattr(child, "_dbg_ignore", False):
7270
continue
7371
self.add_as_node(widget=child).update_preload_status(False)
74-
if self.widget._class == tkinter.Menu:
75-
for item in self.widget.menu_items:
76-
if item._dbg_node:
77-
self.add(item._dbg_node)
78-
self.set_widget(item)
79-
item._dbg_node.update_preload_status(False)
80-
else:
81-
self.add_as_node(widget=item).update_preload_status(False)
72+
73+
for item in self.widget.extra_items:
74+
if item._dbg_node:
75+
self.add(item._dbg_node)
76+
self.set_widget(item)
77+
item._dbg_node.update_preload_status(False)
78+
else:
79+
self.add_as_node(widget=item).update_preload_status(False)
8280
self._loaded = True
8381

8482
def expand(self):
@@ -158,6 +156,8 @@ def __init__(self, master, debugger):
158156
self.debugger.bind("<<MenuItemModified>>", self.on_menu_item_modified, add=True)
159157
self.debugger.bind("<<MenuItemAdded>>", self.on_menu_item_added)
160158
self.debugger.bind("<<MenuItemRemoved>>", self.on_menu_items_removed)
159+
self.debugger.bind("<<CanvasItemCreated>>", self.on_canvas_item_created, add=True)
160+
self.debugger.bind("<<CanvasItemsDeleted>>", self.on_canvas_items_deleted, add=True)
161161

162162
@property
163163
def selected(self):
@@ -166,14 +166,20 @@ def selected(self):
166166
def on_select(self):
167167
self.debugger.selection.set(map(lambda node: node.widget, self._tree.get()))
168168

169-
def on_widget_tap(self, widget, event):
169+
def on_widget_tap(self, widget, event, data=None):
170170
self._select_btn.toggle()
171171
if widget:
172172
# bring debugger to front
173173
self.debugger.attributes('-topmost', True)
174174
self.debugger.attributes('-topmost', False)
175175
self.debugger.focus_force()
176176
node = self._tree.expand_to(widget)
177+
if widget._class == tkinter.Canvas and data:
178+
item_id = int(data)
179+
node.expand()
180+
item = widget.get_canvas_item_from_id(item_id)
181+
node = item._dbg_node
182+
177183
if node:
178184
self._tree.see(node)
179185
node.select(event)
@@ -206,10 +212,14 @@ def on_menu_item_added(self, event):
206212
widget, root, index = event.user_data.split(" ")
207213
widget = self.debugger.widget_from_id(widget, int(root))
208214
parent_node = getattr(widget, "_dbg_node", None)
209-
if not parent_node or not parent_node.loaded:
215+
if not parent_node:
210216
return
211217

212218
item = widget._add_menu_item(int(index))
219+
if not parent_node.loaded:
220+
parent_node.update_preload_status(True)
221+
return
222+
213223
node = parent_node.add_as_node(widget=item) if item._dbg_node is None else item._dbg_node
214224
if not item._dbg_node:
215225
# remove node if it was just created
@@ -225,18 +235,22 @@ def on_menu_items_removed(self, event):
225235
parent_node = getattr(widget, "_dbg_node", None)
226236

227237
items = widget._remove_menu_items(int(index1), int(index2))
238+
if not parent_node:
239+
return
240+
241+
if not parent_node.loaded:
242+
parent_node.update_preload_status(False)
243+
return
244+
228245
had_selection = False
229246
for item in items:
230-
if parent_node.loaded:
231-
if item._dbg_node in self.selected:
232-
had_selection = True
233-
self._tree.toggle_from_selection(item._dbg_node)
234-
parent_node.remove(item._dbg_node)
235-
else:
236-
parent_node.update_preload_status(False)
247+
if item._dbg_node in self.selected:
248+
had_selection = True
249+
self._tree.toggle_from_selection(item._dbg_node)
250+
parent_node.remove(item._dbg_node)
237251

238252
if had_selection and not self.selected:
239-
self._tree.see(self.debugger.root._dbg_node)
253+
self._tree.see(parent_node)
240254

241255
def on_widget_modified(self, _):
242256
if self.debugger.active_widget not in self.selected:
@@ -252,6 +266,45 @@ def on_menu_item_modified(self, event):
252266
if item._dbg_node:
253267
item._dbg_node.set_widget(item)
254268

269+
def on_canvas_item_created(self, event):
270+
widget, root, id = event.user_data.split(" ")
271+
widget = self.debugger.widget_from_id(widget, int(root))
272+
parent_node = getattr(widget, "_dbg_node", None)
273+
if not parent_node:
274+
return
275+
276+
item = widget.add_canvas_item(int(id))
277+
if not parent_node.loaded:
278+
parent_node.update_preload_status(True)
279+
return
280+
281+
node = parent_node.add_as_node(widget=item)
282+
node.set_widget(item)
283+
item._dbg_node = node
284+
285+
def on_canvas_items_deleted(self, event):
286+
widget, root, *ids = event.user_data.split(" ")
287+
widget = self.debugger.widget_from_id(widget, int(root))
288+
parent_node = getattr(widget, "_dbg_node", None)
289+
items = widget.delete_canvas_ids(*ids)
290+
291+
if not parent_node:
292+
return
293+
294+
if not parent_node.loaded:
295+
parent_node.update_preload_status(False)
296+
return
297+
298+
had_selection = False
299+
for item in items:
300+
if item._dbg_node in self.selected:
301+
had_selection = True
302+
self._tree.toggle_from_selection(item._dbg_node)
303+
parent_node.remove(item._dbg_node)
304+
305+
if had_selection and not self.selected:
306+
self._tree.see(parent_node)
307+
255308
def on_widget_map(self, _):
256309
pass
257310

0 commit comments

Comments
 (0)