Remove debugging.
[dom-editor.git] / Dome / Display2.py
blobe1a8c4d7d467cdd6e59000f4b846f043076352bc
1 from __future__ import generators
3 import rox
4 from rox import g
5 from xml.dom import Node
6 import pango
7 from constants import XMLNS_NAMESPACE
9 import __main__
10 default_font = __main__.default_font
12 def calc_node(display, node, pos):
13 attribs = []
14 if node.nodeType == Node.TEXT_NODE:
15 text = node.nodeValue.strip()
16 elif node.nodeType == Node.ELEMENT_NODE:
17 if node.namespaceURI:
18 text = display.view.model.namespaces.prefix.get(node.namespaceURI, 'ERROR') + \
19 ':' + node.localName
20 else:
21 text = node.localName
22 elif node.nodeType == Node.ATTRIBUTE_NODE:
23 if node.namespaceURI:
24 text = display.view.model.namespaces.prefix.get(node.namespaceURI, 'ERROR') + \
25 ':' + node.localName
26 else:
27 text = node.localName
28 text = ' %s=%s' % (unicode(text), unicode(node.value))
29 elif node.nodeType == Node.COMMENT_NODE:
30 text = node.nodeValue.strip()
31 elif node.nodeName:
32 text = node.nodeName
33 elif node.nodeValue:
34 text = '<noname>' + node.nodeValue
35 else:
36 text = '<unknown>'
38 # PyGtk leaks PangoLayouts, so just reuse a single one
39 layout = display.surface_layout
40 layout.set_text(text, -1)
41 width, height = layout.get_pixel_size()
42 x, y = pos
44 text_x = x
45 if node.nodeType != Node.ATTRIBUTE_NODE:
46 text_x += 12
48 def draw_fn():
49 surface = display.pm
50 style = display.surface.style # Different surface ;-)
51 fg = style.fg_gc
52 bg = style.bg_gc
54 if node.nodeType != Node.ATTRIBUTE_NODE:
55 marker = True
56 surface.draw_rectangle(fg[g.STATE_NORMAL], True,
57 x, y, 8, height - 1)
58 if node.nodeType == Node.ELEMENT_NODE and node.hasAttributeNS(None, 'hidden'):
59 surface.draw_layout(fg[g.STATE_PRELIGHT], text_x + width + 2, y,
60 display.create_pango_layout('(hidden)'))
61 else:
62 marker = False
64 if node in display.selection:
65 if marker:
66 surface.draw_rectangle(style.bg_gc[g.STATE_SELECTED], True,
67 x + 1, y + 1, 6, height - 3)
68 surface.draw_rectangle(bg[g.STATE_SELECTED], True,
69 text_x, y, width - 1, height - 1)
70 surface.draw_layout(fg[g.STATE_SELECTED], text_x, y, layout)
71 else:
72 if marker:
73 surface.draw_rectangle(style.white_gc, True, x + 1, y + 1, 6, height - 3)
74 if node.nodeType == Node.TEXT_NODE:
75 gc = style.text_gc[g.STATE_NORMAL]
76 elif node.nodeType == Node.ATTRIBUTE_NODE:
77 gc = style.fg_gc[g.STATE_INSENSITIVE]
78 elif node.nodeType == Node.COMMENT_NODE:
79 gc = style.text_gc[g.STATE_INSENSITIVE]
80 else:
81 gc = style.fg_gc[g.STATE_NORMAL]
82 surface.draw_layout(gc, text_x, y, layout)
84 if node in display.view.marked:
85 surface.draw_rectangle(style.text_gc[g.STATE_PRELIGHT], False,
86 x - 1, y - 1, width + (text_x - x), height)
88 bbox = (x, y, text_x + width, y + height)
89 return bbox, draw_fn
91 class Display(g.HBox):
92 visible = 1 # Always visible
94 def __init__(self, window, view):
95 g.HBox.__init__(self, False, 0)
97 self.surface = g.EventBox()
98 self.surface_layout = self.surface.create_pango_layout('')
99 self.pack_start(self.surface, True, True, 0)
100 self.surface.show()
101 self.surface.set_app_paintable(True)
102 self.surface.set_double_buffered(False)
103 self.update_timeout = 0
104 self.cached_nodes = None
106 self.scroll_adj = g.Adjustment(lower = 0, upper = 100, step_incr = 1)
107 self.scroll_adj.connect('value-changed', self.scroll_to)
108 scale = g.VScale(self.scroll_adj)
109 scale.unset_flags(g.CAN_FOCUS)
110 scale.set_draw_value(False)
111 self.pack_start(scale, False, True, 0)
113 self.view = None
114 self.parent_window = window
115 self.pm = None
117 s = self.surface.get_style().copy()
118 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('old lace')
119 s.text[g.STATE_NORMAL] = g.gdk.color_parse('blue')
120 s.text[g.STATE_PRELIGHT] = g.gdk.color_parse('orange') # Mark
121 s.text[g.STATE_INSENSITIVE] = g.gdk.color_parse('dark green')# Comment
122 s.fg[g.STATE_PRELIGHT] = g.gdk.color_parse('red') # Hidden
123 self.surface.set_style(s)
125 self.signals = [self.connect('destroy', self.destroyed)]
126 self.surface.connect('button-press-event', self.bg_event)
127 self.surface.connect('button-release-event', self.bg_event)
129 # Display is relative to this node, which is the highest
130 # displayed node (possibly off the top of the screen)
131 self.ref_node = view.root
132 self.ref_pos = (0, 0)
134 self.last_alloc = None
135 self.surface.connect('size-allocate', lambda w, a: self.size_allocate(a))
136 self.surface.connect('size-request', lambda w, r: self.size_request(r))
137 self.surface.connect('expose-event', lambda w, e: 1)
139 self.pan_timeout = None
140 self.h_limits = (0, 0)
141 self.set_view(view)
143 def destroyed(self, widget):
144 self.view.remove_display(self)
145 for s in self.signals:
146 self.disconnect(s)
147 if self.update_timeout:
148 g.timeout_remove(self.update_timeout)
149 self.update_timeout = 0
151 #del self.selection
152 del self.view
153 del self.parent_window
154 del self.ref_node
155 del self.surface_layout
156 del self.surface
157 del self.scroll_adj
158 del self.drawn
159 del self.pm
161 def size_allocate(self, alloc):
162 new = (alloc.width, alloc.height)
163 if self.last_alloc == new:
164 return
165 self.last_alloc = new
166 assert self.window
167 #print "Alloc", alloc.width, alloc.height
168 pm = g.gdk.Pixmap(self.surface.window, alloc.width, alloc.height, -1)
169 self.surface.window.set_back_pixmap(pm, False)
170 self.pm = pm
172 if self.update_timeout:
173 g.timeout_remove(self.update_timeout)
174 self.update_timeout = 0
175 self.update()
177 def update(self):
178 # Must be called either:
179 # - With no update_timeout running, or
180 # - From the timeout callback
182 self.update_timeout = 0
184 if not self.pm: return 0
185 #print "update"
187 self.pm.draw_rectangle(self.surface.style.bg_gc[g.STATE_NORMAL], True,
188 0, 0, self.last_alloc[0], self.last_alloc[1])
190 self.drawn = {} # xmlNode -> ((x1, y1, y2, y2), attrib_parent)
192 n = self.ref_node
193 p = self.view.root.parentNode
194 while n is not p:
195 n = n.parentNode
196 if not n:
197 print "(lost root)"
198 self.ref_node = self.view.root
199 self.ref_pos = (0, 0)
200 break
202 if self.view.current_attrib:
203 self.selection = {self.view.current_attrib: None}
204 else:
205 self.selection = {}
206 for n in self.view.current_nodes:
207 self.selection[n] = None
209 pos = list(self.ref_pos)
210 self.h_limits = (self.ref_pos[0], self.ref_pos[0]) # Left, Right
211 node = self.ref_node
212 attr_parent = None
213 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
214 if bbox[1] > self.last_alloc[1]: break # Off-screen
215 if bbox[1] > -self.last_alloc[1]:
216 draw_fn()
217 else:
218 pass#print 'Warning: Ref node way off:', bbox[1]
219 if node.nodeType == Node.ATTRIBUTE_NODE:
220 self.drawn[node] = (bbox, attr_parent)
221 else:
222 attr_parent = node
223 self.drawn[node] = (bbox, None)
225 if bbox[1] < 0 and node.nodeType != Node.ATTRIBUTE_NODE:
226 self.ref_node = node
227 self.ref_pos = bbox[:2]
228 self.h_limits = (min(self.h_limits[0], bbox[0]),
229 max(self.h_limits[1], bbox[2]))
231 self.surface.window.clear()
233 # Update adjustment
234 self.ensure_cache()
235 try:
236 pos = self.cached_nodes.index(self.ref_node)
237 except:
238 pos = 0
239 print "Missing ref node!!"
240 self.scroll_adj.value = float(pos)
241 self.scroll_adj.upper = float(len(self.cached_nodes))
243 return 0
245 def ensure_cache(self):
246 "Find all the nodes in the document, in document order. Not attributes."
247 if self.cached_nodes is not None:
248 return
249 nodes = [self.view.root.parentNode]
250 node = self.view.root
251 while node:
252 nodes.append(node)
253 if node.childNodes:
254 node = node.childNodes[0]
255 else:
256 while not node.nextSibling:
257 node = node.parentNode
258 if not node:
259 self.cached_nodes = nodes
260 return
261 node = node.nextSibling
262 self.cached_nodes = nodes
264 def walk_tree(self, node, pos):
265 """Yield this (node, bbox), and all following ones in document order."""
266 pos = list(pos)
267 while node:
268 bbox, draw_fn = calc_node(self, node, pos)
269 yield (node, bbox, draw_fn)
271 hidden = False
272 if node.nodeType == Node.ELEMENT_NODE:
273 hidden = node.hasAttributeNS(None, 'hidden')
274 if not hidden:
275 apos = [bbox[2] + 4, bbox[1]]
276 for key in node.attributes:
277 a = node.attributes[key]
278 if a.namespaceURI == XMLNS_NAMESPACE:
279 continue
280 abbox, draw_fn = calc_node(self, a, apos)
281 apos[0] = abbox[2] + 4
282 yield (a, abbox, draw_fn)
284 pos[1] = bbox[3] + 2
285 if node.childNodes and not hidden:
286 node = node.childNodes[0]
287 pos[0] += 16
288 else:
289 while not node.nextSibling:
290 node = node.parentNode
291 if not node: return
292 pos[0] -= 16
293 node = node.nextSibling
295 def size_request(self, req):
296 req.width = 4
297 req.height = 4
299 def do_update_now(self):
300 # Update now, if we need to
301 if self.update_timeout:
302 g.timeout_remove(self.update_timeout)
303 self.update_timeout = 0
304 self.update()
306 def update_all(self, node = None):
307 self.cached_nodes = None
309 if self.update_timeout:
310 return # Going to update anyway...
312 if self.view.running():
313 self.update_timeout = g.timeout_add(2000, self.update)
314 else:
315 self.update_timeout = g.timeout_add(10, self.update)
317 def move_from(self, old = []):
318 if not self.pm: return
319 if self.view.current_nodes:
320 selection = {}
321 for n in self.view.current_nodes:
322 selection[n] = None
323 shown = False
324 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
325 if bbox[1] > self.last_alloc[1]: break # Off-screen
326 if bbox[3] > 0 and node in selection:
327 shown = True
328 break # A selected node is shown
329 if not shown:
330 #print "(selected nodes not shown)"
331 self.ref_node = self.view.current_nodes[0]
332 self.ref_pos = (40, self.last_alloc[1] / 2)
333 self.backup_ref_node()
334 self.update_all()
336 def set_view(self, view):
337 if self.view:
338 self.view.remove_display(self)
339 self.view = view
340 self.view.add_display(self)
341 self.update_all()
343 def show_menu(self, bev):
344 pass
346 def node_clicked(self, node, event):
347 pass
349 def xy_to_node(self, x, y):
350 "Return the node at this point and, if it's an attribute, its parent."
351 for (n, ((x1, y1, x2, y2), attrib_parent)) in self.drawn.iteritems():
352 if x >= x1 and x <= x2 and y >= y1 and y <= y2:
353 return n, attrib_parent
354 return None, None
356 def pan(self):
357 def scale(x):
358 val = (float(abs(x)) ** 1.4)
359 if x < 0:
360 return -val
361 else:
362 return val
363 def chop(x):
364 if x > 10: return x - 10
365 if x < -10: return x + 10
366 return 0
367 x, y, mask = self.surface.window.get_pointer()
368 sx, sy = self.pan_start
369 dx, dy = scale(chop(x - sx)) / 20, scale(chop(y - sy))
370 dx = max(dx, 10 - self.h_limits[1])
371 dx = min(dx, self.last_alloc[0] - 10 - self.h_limits[0])
372 new = [self.ref_pos[0] + dx, self.ref_pos[1] + dy]
374 if new == self.ref_pos:
375 return 1
377 self.ref_pos = new
379 self.backup_ref_node()
381 if self.update_timeout:
382 g.timeout_remove(self.update_timeout)
383 self.update_timeout = 0
384 self.update()
386 return 1
388 def backup_ref_node(self):
389 self.ref_pos = list(self.ref_pos)
390 # Walk up the parents until we get a ref node above the start of the screen
391 # (redraw will come back down)
392 while self.ref_pos[1] > 0:
393 src = self.ref_node
395 if self.ref_node.previousSibling:
396 self.ref_node = self.ref_node.previousSibling
397 elif self.ref_node.parentNode:
398 self.ref_node = self.ref_node.parentNode
399 else:
400 break
402 # Walk from the parent node to find how far it is to this node...
403 for node, bbox, draw_fn in self.walk_tree(self.ref_node, (0, 0)):
404 if node is src: break
405 else:
406 assert 0
408 self.ref_pos[0] -= bbox[0]
409 self.ref_pos[1] -= bbox[1]
411 #print "(start from %s at (%d,%d))" % (self.ref_node, self.ref_pos[0], self.ref_pos[1])
413 if self.ref_pos[1] > 10:
414 self.ref_pos[1] = 10
415 elif self.ref_pos[1] < -100:
416 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
417 if bbox[3] > 10: break # Something is visible
418 else:
419 self.ref_pos[1] = -100
421 def bg_event(self, widget, event):
422 if event.type == g.gdk.BUTTON_PRESS and event.button == 3:
423 self.show_menu(event)
424 elif event.type == g.gdk.BUTTON_PRESS or event.type == g.gdk._2BUTTON_PRESS:
425 self.do_update_now()
426 node, attr_parent = self.xy_to_node(event.x, event.y)
427 if event.button == 1:
428 if node:
429 if attr_parent:
430 self.attrib_clicked(attr_parent, node, event)
431 else:
432 self.node_clicked(node, event)
433 elif event.button == 2:
434 assert self.pan_timeout is None
435 self.pan_start = (event.x, event.y)
436 self.pan_timeout = g.timeout_add(100, self.pan)
437 elif event.type == g.gdk.BUTTON_RELEASE and event.button == 2:
438 assert self.pan_timeout is not None
439 g.timeout_remove(self.pan_timeout)
440 self.pan_timeout = None
441 else:
442 return 0
443 return 1
445 def marked_changed(self, nodes):
446 "nodes is a list of nodes to be rechecked."
447 self.update_all()
449 def options_changed(self):
450 if default_font.has_changed:
451 #self.modify_font(pango.FontDescription(default_font.value))
452 self.update_all()
454 def scroll_to(self, adj):
455 n = int(adj.value)
456 self.ensure_cache()
457 try:
458 node = self.cached_nodes[n]
459 except:
460 node = self.cached_nodes[-1]
461 if self.ref_node == node:
462 return
463 self.ref_node = node
464 x = 0
465 while node.parentNode:
466 x += 16
467 node = node.parentNode
468 self.ref_pos = (x, 0)
469 self.update_all()
471 def set_status(self, message):
472 self.parent_window.set_status(message)