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