Renamed a 'with' variable; will be a keyword in Python 2.6.
[dom-editor.git] / Dome / Display2.py
bloba1221503903da30de380c90a7d11b46a4de10d6d
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
8 import gobject
10 import __main__
11 default_font = __main__.default_font
13 drag_cursor = g.gdk.Cursor(g.gdk.HAND1)
15 def calc_node(display, node, pos):
16 attribs = []
17 if node.nodeType == Node.TEXT_NODE:
18 text = node.nodeValue.strip()
19 elif node.nodeType == Node.ELEMENT_NODE:
20 if node.namespaceURI:
21 text = display.view.model.namespaces.prefix.get(node.namespaceURI, 'ERROR') + \
22 ':' + node.localName
23 else:
24 text = node.localName
25 elif node.nodeType == Node.ATTRIBUTE_NODE:
26 if node.namespaceURI:
27 text = display.view.model.namespaces.prefix.get(node.namespaceURI, 'ERROR') + \
28 ':' + node.localName
29 else:
30 text = node.localName
31 text = ' %s=%s' % (unicode(text), unicode(node.value))
32 elif node.nodeType == Node.COMMENT_NODE:
33 text = node.nodeValue.strip()
34 elif node.nodeType == Node.DOCUMENT_NODE:
35 chroots = len(display.view.chroots)
36 if chroots > 1:
37 text = "subtree (%d levels)" % chroots
38 elif chroots == 1:
39 text = 'subtree (one level)'
40 else:
41 text = display.view.model.uri
42 elif node.nodeName:
43 text = node.nodeName
44 elif node.nodeValue:
45 text = '<noname>' + node.nodeValue
46 else:
47 text = '<unknown>'
49 # PyGtk leaks PangoLayouts, so just reuse a single one
50 layout = display.surface_layout
51 layout.set_text(text)
52 width, height = layout.get_pixel_size()
53 x, y = map(int, pos)
55 text_x = x
56 if node.nodeType != Node.ATTRIBUTE_NODE:
57 text_x += 12
59 def draw_fn():
60 surface = display.pm
61 style = display.surface.style # Different surface ;-)
62 fg = style.fg_gc
63 bg = style.bg_gc
65 if node in display.selection:
66 state = g.STATE_SELECTED
67 else:
68 state = g.STATE_NORMAL
70 if node.nodeType != Node.ATTRIBUTE_NODE:
71 if node.nodeType == Node.ELEMENT_NODE:
72 surface.draw_rectangle(style.fg_gc[state], False, x, y, 7, height - 2)
73 surface.draw_rectangle(style.bg_gc[state], True, x + 1, y + 1, 6, height - 3)
74 elif node.nodeType == Node.DOCUMENT_NODE:
75 surface.draw_arc(style.fg_gc[state], False, x, y, 7, height - 2, 0, 64 * 360)
76 else:
77 # Text, etc
78 surface.draw_rectangle(style.text_gc[state], False, x, y, 7, height - 2)
79 surface.draw_rectangle(style.base_gc[state], True, x + 1, y + 1, 6, height - 3)
81 if node in display.view.model.hidden:
82 surface.draw_layout(fg[g.STATE_PRELIGHT], text_x + width + 2, y,
83 display.create_pango_layout('(%s)' % display.view.model.hidden[node]))
85 if node in display.selection:
86 surface.draw_rectangle(bg[g.STATE_SELECTED], True,
87 text_x, y, width - 1, height - 1)
88 surface.draw_layout(fg[g.STATE_SELECTED], text_x, y, layout)
89 else:
90 if node.nodeType == Node.TEXT_NODE:
91 gc = style.text_gc[g.STATE_NORMAL]
92 elif node.nodeType == Node.ATTRIBUTE_NODE:
93 gc = style.fg_gc[g.STATE_INSENSITIVE]
94 elif node.nodeType == Node.COMMENT_NODE:
95 gc = style.text_gc[g.STATE_INSENSITIVE]
96 else:
97 gc = style.fg_gc[g.STATE_NORMAL]
98 surface.draw_layout(gc, text_x, y, layout)
100 if node in display.view.marked:
101 surface.draw_rectangle(style.text_gc[g.STATE_PRELIGHT], False,
102 x - 1, y - 1, width + (text_x - x), height)
104 bbox = (x, y, text_x + width, y + height)
105 return bbox, draw_fn
107 class Display(g.HBox):
108 visible = 1 # Always visible
110 def __init__(self, window, view):
111 g.HBox.__init__(self, False, 0)
113 self.surface = g.EventBox()
114 self.surface_layout = self.surface.create_pango_layout('')
115 self.pack_start(self.surface, True, True, 0)
116 self.surface.show()
117 self.surface.set_app_paintable(True)
118 self.surface.set_double_buffered(False)
119 self.update_timeout = 0
120 self.cached_nodes = None
122 self.scroll_adj = g.Adjustment(lower = 0, upper = 100, step_incr = 1)
123 self.scroll_adj.connect('value-changed', self.scroll_to)
124 scale = g.VScrollbar(self.scroll_adj)
125 scale.unset_flags(g.CAN_FOCUS)
126 #scale.set_draw_value(False)
127 self.pack_start(scale, False, True, 0)
129 self.view = None
130 self.parent_window = window
131 self.pm = None
133 s = self.surface.get_style().copy()
134 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('old lace')
135 s.text[g.STATE_NORMAL] = g.gdk.color_parse('blue')
136 s.text[g.STATE_PRELIGHT] = g.gdk.color_parse('orange') # Mark
137 s.text[g.STATE_INSENSITIVE] = g.gdk.color_parse('dark green')# Comment
138 s.fg[g.STATE_PRELIGHT] = g.gdk.color_parse('red') # Hidden
139 self.surface.set_style(s)
141 self.signals = [self.connect('destroy', self.destroyed)]
142 self.surface.connect('button-press-event', self.bg_event)
143 self.surface.connect('motion-notify-event', self.bg_motion)
144 self.surface.connect('button-release-event', self.bg_event)
146 # Display is relative to this node, which is the highest
147 # displayed node (possibly off the top of the screen)
148 self.ref_node = view.root
149 self.ref_pos = (0, 0)
151 self.drag_info = None
153 self.last_alloc = None
154 self.surface.connect('size-allocate', lambda w, a: self.size_allocate(a))
155 self.surface.connect('size-request', lambda w, r: self.size_request(r))
156 def expose(w, e):
157 area = e.area
158 w.window.clear_area(area.x, area.y, area.width, area.height)
159 return True
160 self.surface.connect('expose-event', expose)
162 self.pan_timeout = None
163 self.h_limits = (0, 0)
164 self.set_view(view)
166 def destroyed(self, widget):
167 self.view.remove_display(self)
168 for s in self.signals:
169 self.disconnect(s)
170 if self.update_timeout:
171 g.timeout_remove(self.update_timeout)
172 self.update_timeout = 0
174 #del self.selection
175 del self.view
176 del self.parent_window
177 del self.ref_node
178 del self.surface_layout
179 del self.surface
180 del self.scroll_adj
181 del self.drawn
182 del self.pm
184 def size_allocate(self, alloc):
185 new = (alloc.width, alloc.height)
186 if self.last_alloc == new:
187 return
188 self.last_alloc = new
189 assert self.window
190 #print "Alloc", alloc.width, alloc.height
191 pm = g.gdk.Pixmap(self.surface.window, alloc.width, alloc.height, -1)
192 self.surface.window.set_back_pixmap(pm, False)
193 self.pm = pm
195 if self.update_timeout:
196 g.timeout_remove(self.update_timeout)
197 self.update_timeout = 0
198 self.update()
200 def update(self):
201 # Must be called either:
202 # - With no update_timeout running, or
203 # - From the timeout callback
205 self.update_timeout = 0
207 if not self.pm: return 0
208 #print "update"
210 self.pm.draw_rectangle(self.surface.style.bg_gc[g.STATE_NORMAL], True,
211 0, 0, self.last_alloc[0], self.last_alloc[1])
213 self.drawn = {} # xmlNode -> ((x1, y1, y2, y2), attrib_parent)
215 n = self.ref_node
216 p = self.view.root.parentNode
217 while n is not p:
218 n = n.parentNode
219 if not n:
220 print "(lost root)"
221 self.ref_node = self.view.root
222 self.ref_pos = (0, 0)
223 break
225 if self.view.current_attrib:
226 self.selection = {self.view.current_attrib: None}
227 else:
228 self.selection = {}
229 for n in self.view.current_nodes:
230 self.selection[n] = None
232 pos = list(self.ref_pos)
233 self.h_limits = (self.ref_pos[0], self.ref_pos[0]) # Left, Right
234 node = self.ref_node
235 attr_parent = None
236 drawn = 0
237 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
238 if bbox[1] > self.last_alloc[1]: break # Off-screen
239 if bbox[1] > -self.last_alloc[1]:
240 draw_fn()
241 drawn += 1
242 else:
243 pass#print 'Warning: Ref node way off:', bbox[1]
244 if node.nodeType == Node.ATTRIBUTE_NODE:
245 self.drawn[node] = (bbox, attr_parent)
246 else:
247 attr_parent = node
248 self.drawn[node] = (bbox, None)
250 if bbox[1] < 0 and node.nodeType != Node.ATTRIBUTE_NODE:
251 self.ref_node = node
252 self.ref_pos = bbox[:2]
253 self.h_limits = (min(self.h_limits[0], bbox[0]),
254 max(self.h_limits[1], bbox[2]))
255 else:
256 # Didn't have enough nodes to fill the screen
257 frac_filled = float(bbox[3]) / self.last_alloc[1]
258 if frac_filled:
259 drawn /= frac_filled
261 self.surface.window.clear()
263 # Update adjustment
264 self.ensure_cache()
265 try:
266 pos = self.cached_nodes.index(self.ref_node)
267 except:
268 pos = 0
269 print "Missing ref node!!"
270 self.scroll_adj.value = float(pos)
271 self.scroll_adj.upper = float(len(self.cached_nodes) + drawn)
273 self.scroll_adj.page_size = float(drawn)
274 self.scroll_adj.page_increment = float(drawn)
276 return 0
278 def ensure_cache(self):
279 "Find all the nodes in the document, in document order. Not attributes."
280 if self.cached_nodes is not None:
281 return
282 nodes = [self.view.root.parentNode]
283 node = self.view.root
284 hidden = self.view.model.hidden
285 while node:
286 nodes.append(node)
287 if node.childNodes and node not in hidden:
288 node = node.childNodes[0]
289 else:
290 while not node.nextSibling:
291 node = node.parentNode
292 if not node:
293 self.cached_nodes = nodes
294 return
295 node = node.nextSibling
296 self.cached_nodes = nodes
298 def walk_tree(self, node, pos):
299 """Yield this (node, bbox), and all following ones in document order."""
300 pos = list(pos)
301 hidden = self.view.model.hidden
302 while node:
303 bbox, draw_fn = calc_node(self, node, pos)
304 yield (node, bbox, draw_fn)
306 if node.nodeType == Node.ELEMENT_NODE:
307 if node not in hidden:
308 apos = [bbox[2] + 4, bbox[1]]
309 for key in node.attributes:
310 a = node.attributes[key]
311 if a.namespaceURI == XMLNS_NAMESPACE:
312 continue
313 abbox, draw_fn = calc_node(self, a, apos)
314 apos[0] = abbox[2] + 4
315 yield (a, abbox, draw_fn)
317 pos[1] = bbox[3] + 2
318 if node.childNodes and node not in hidden:
319 node = node.childNodes[0]
320 pos[0] += 16
321 else:
322 while not node.nextSibling:
323 node = node.parentNode
324 if not node: return
325 pos[0] -= 16
326 node = node.nextSibling
328 def size_request(self, req):
329 req.width = 4
330 req.height = 4
332 def do_update_now(self):
333 # Update now, if we need to
334 if self.update_timeout:
335 gobject.source_remove(self.update_timeout)
336 self.update_timeout = 0
337 self.update()
339 def update_all(self, node = None):
340 self.cached_nodes = None
342 if self.update_timeout:
343 return # Going to update anyway...
345 if self.view.running():
346 self.update_timeout = gobject.timeout_add(2000, self.update)
347 else:
348 self.update_timeout = gobject.timeout_add(10, self.update)
350 def move_from(self, old = []):
351 if not self.pm: return
352 if self.view.current_nodes:
353 selection = {}
354 for n in self.view.current_nodes:
355 selection[n] = None
356 shown = False
357 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
358 if bbox[1] > self.last_alloc[1]: break # Off-screen
359 if bbox[3] > 0 and node in selection:
360 shown = True
361 break # A selected node is shown
362 if not shown:
363 #print "(selected nodes not shown)"
364 self.ref_node = node = self.view.current_nodes[0]
365 self.ref_pos = (40, self.last_alloc[1] / 2)
367 all_hidden = self.view.model.hidden
368 while node:
369 node = node.parentNode
370 if node in all_hidden:
371 self.ref_node = node
373 self.backup_ref_node()
374 self.update_all()
376 def set_view(self, view):
377 if self.view:
378 self.view.remove_display(self)
379 self.view = view
380 self.view.add_display(self)
381 self.update_all()
383 def show_menu(self, bev):
384 pass
386 def node_clicked(self, node, event):
387 pass
389 def xy_to_node(self, x, y):
390 "Return the node at this point and, if it's an attribute, its parent."
391 for (n, ((x1, y1, x2, y2), attrib_parent)) in self.drawn.iteritems():
392 if x >= x1 and x <= x2 and y >= y1 and y <= y2:
393 return n, attrib_parent
394 return None, None
396 def pan(self):
397 def scale(x):
398 val = (float(abs(x)) ** 1.4)
399 if x < 0:
400 return -val
401 else:
402 return val
403 def chop(x):
404 if x > 10: return x - 10
405 if x < -10: return x + 10
406 return 0
407 x, y, mask = self.surface.window.get_pointer()
408 sx, sy = self.pan_start
409 dx, dy = scale(chop(x - sx)) / 20, scale(chop(y - sy))
410 dx = max(dx, 10 - self.h_limits[1])
411 dx = min(dx, self.last_alloc[0] - 10 - self.h_limits[0])
412 new = [self.ref_pos[0] + dx, self.ref_pos[1] + dy]
414 if new == self.ref_pos:
415 return 1
417 self.ref_pos = new
419 self.backup_ref_node()
421 if self.update_timeout:
422 g.timeout_remove(self.update_timeout)
423 self.update_timeout = 0
424 self.update()
426 return 1
428 def backup_ref_node(self):
429 self.ref_pos = list(self.ref_pos)
430 # Walk up the parents until we get a ref node above the start of the screen
431 # (redraw will come back down)
432 while self.ref_pos[1] > 0:
433 src = self.ref_node
435 if self.ref_node.previousSibling:
436 self.ref_node = self.ref_node.previousSibling
437 elif self.ref_node.parentNode:
438 self.ref_node = self.ref_node.parentNode
439 else:
440 break
442 # Walk from the parent node to find how far it is to this node...
443 for node, bbox, draw_fn in self.walk_tree(self.ref_node, (0, 0)):
444 if node is src: break
445 else:
446 assert 0
448 self.ref_pos[0] -= bbox[0]
449 self.ref_pos[1] -= bbox[1]
451 #print "(start from %s at (%d,%d))" % (self.ref_node, self.ref_pos[0], self.ref_pos[1])
453 if self.ref_pos[1] > 10:
454 self.ref_pos[1] = 10
455 elif self.ref_pos[1] < -100:
456 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
457 if bbox[3] > 10: break # Something is visible
458 else:
459 self.ref_pos[1] = -100
461 def bg_motion(self, widget, event):
462 if not self.drag_info:
463 return
464 node, attr_parent, x, y, in_progress = self.drag_info
465 if not in_progress:
466 if abs(event.x - x) > 5 or abs(event.y - y) > 5:
467 self.drag_info = (node, attr_parent, event.x, event.y, True)
468 self.window.set_cursor(drag_cursor)
469 else:
470 return False
471 return False
473 def do_drag(self, src, dst):
474 pass
476 def bg_event(self, widget, event):
477 if event.type == g.gdk.BUTTON_PRESS and event.button == 3:
478 self.show_menu(event)
479 elif event.type == g.gdk.BUTTON_PRESS or event.type == g.gdk._2BUTTON_PRESS:
480 self.do_update_now()
481 node, attr_parent = self.xy_to_node(event.x, event.y)
482 if event.button == 1:
483 self.drag_info = (node, attr_parent, event.x, event.y, False)
484 if node:
485 if attr_parent:
486 self.attrib_clicked(attr_parent, node, event)
487 else:
488 self.node_clicked(node, event)
489 elif event.button == 2:
490 assert self.pan_timeout is None
491 self.pan_start = (event.x, event.y)
492 self.pan_timeout = gobject.timeout_add(100, self.pan)
493 elif event.type == g.gdk.BUTTON_RELEASE:
494 if event.button == 2:
495 assert self.pan_timeout is not None
496 gobject.source_remove(self.pan_timeout)
497 self.pan_timeout = None
498 elif event.button == 1 and self.drag_info:
499 src_node, src_attr_parent, x, y, in_progress = self.drag_info
500 if not in_progress:
501 return True
502 dst_node, dst_attr_parent = self.xy_to_node(event.x, event.y)
503 self.window.set_cursor(None)
504 if not dst_node or (dst_node == src_node and dst_attr_parent == src_attr_parent):
505 return True
506 self.do_drag((src_node, src_attr_parent), (dst_node, dst_attr_parent))
507 else:
508 return False
509 return True
511 def marked_changed(self, nodes):
512 "nodes is a list of nodes to be rechecked."
513 self.update_all()
515 def options_changed(self):
516 if default_font.has_changed:
517 #self.modify_font(pango.FontDescription(default_font.value))
518 self.update_all()
520 def scroll_to(self, adj):
521 n = int(adj.value)
522 self.ensure_cache()
523 try:
524 node = self.cached_nodes[n]
525 except:
526 node = self.cached_nodes[-1]
527 if self.ref_node == node:
528 return
529 self.ref_node = node
530 x = 0
531 while node.parentNode:
532 x += 16
533 node = node.parentNode
534 self.ref_pos = (x, 0)
535 self.update_all()
537 def set_status(self, message):
538 self.parent_window.set_status(message)