Set background colour when hiding/showing.
[dom-editor.git] / Dome / Display.py
blob12a729ce9b9786a17aa196e39fd9d8ba746f5f2b
1 from rox import g, TRUE, FALSE
2 from gnome2 import canvas
3 from xml.dom import Node
4 from constants import *
5 import rox
7 watch_cursor = g.gdk.Cursor(g.gdk.WATCH)
8 no_cursor = g.gdk.Cursor(g.gdk.TCROSS)
10 def set_busy(widget, busy = TRUE):
11 w = widget.window
12 if not w:
13 return
14 if busy:
15 w.set_cursor(watch_cursor)
16 else:
17 w.set_cursor(no_cursor)
19 def wrap(str, width):
20 ret = ''
21 while len(str) > width:
22 i = str[:width].rfind(' ')
23 if i == -1:
24 i = width
25 ret = ret + str[:i + 1] + '\n'
26 str = str[i + 1:]
27 return ret + str
29 cramped_indent = 16
30 normal_indent = 24
32 #import __main__
33 #default_font = __main__.default_font
35 class Display(canvas.Canvas):
36 def __init__(self, window, view):
37 canvas.Canvas.__init__(self)
38 self.view = None
39 self.parent_window = window
40 self.root_group = None
41 self.update_timeout = 0
42 self.update_nodes = {} # Set of nodes to update on update_timeout
43 self.current_attrib = None # Canvas group
44 self.node_to_group = {}
46 self.visible = 1
48 s = self.get_style().copy()
49 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('old lace')
50 self.set_style(s)
52 self.connect('size-allocate', self.size_allocate)
53 self.connect('destroy', self.destroyed)
54 self.connect('button-press-event', self.bg_event)
56 self.set_view(view)
58 #rox.app_options.add_notify(self.options_changed)
60 def size_allocate(self, canvas, size):
61 x, y, width, height = self.get_allocation()
62 chains = self.parent_window.list.chains
63 if self.visible:
64 if width < 40:
65 self.visible = 0
66 chains.set_active(0)
67 print "hide"
68 else:
69 if width > 40:
70 self.visible = 1
71 self.update_all()
72 chains.set_active(1)
73 print "show"
75 def set_view(self, view):
76 if self.view:
77 self.view.remove_display(self)
78 self.view = view
79 self.view.add_display(self)
80 self.update_all()
82 def set_bounds(self):
83 m = 8
85 (x, y, w, h) = self.get_allocation()
86 w -= m * 2 + 1
87 h -= m * 2 + 1
89 #self.update_now() # GnomeCanvas bug?
90 min_x, min_y, max_x, max_y = self.root().get_bounds()
91 if max_x - min_x < w:
92 max_x = min_x + w
93 if max_y - min_y < h:
94 max_y = min_y + h
95 self.set_scroll_region(min_x - m, min_y - m, max_x + m, max_y + m)
96 self.root().move(0, 0) # Magic!
98 def update_record_state(self):
99 self.parent_window.update_title()
101 def update_all(self, node = None):
102 if not node:
103 node = self.view.model.doc.documentElement
105 if node == self.view.root:
106 self.update_nodes = {node: None}
107 elif node not in self.update_nodes:
108 # Note: we don't eliminate duplicates (parent and child) nodes
109 # here because it takes *ages*
110 self.update_nodes[node] = None
112 if self.update_timeout or not self.visible:
113 return # Going to update anyway...
115 if self.view.running():
116 self.update_timeout = g.timeout_add(2000, self.update_callback)
117 else:
118 self.update_timeout = g.timeout_add(10, self.update_callback)
120 def do_update_now(self):
121 # Update now, if we need to
122 if self.update_timeout:
123 g.timeout_remove(self.update_timeout)
124 self.update_callback()
125 #self.update_timeout = g.timeout_add(10, self.update_callback)
127 def update_callback(self):
128 print "update_callback"
129 self.update_timeout = 0
130 #print "Update...", self.update_nodes
131 set_busy(self)
132 try:
133 for node in self.update_nodes:
134 root = self.view.root
135 if node is not root and self.view.has_ancestor(node, root):
136 # The root is OK - the change is inside...
137 try:
138 group = self.node_to_group[node]
139 #if group.flags() & DESTROYED:
140 # print "(group destroyed)"
141 # raise Exception('Group destroyed')
142 except:
143 # Modified node not created yet.
144 # Don't worry, updating the parent later
145 # will fix it...
146 print "(node missing)"
147 pass
148 else:
149 # Can't delete all children, so create a new group
150 # instead.
151 parent = group.get_property('parent')
152 x = group.get_property('x')
153 y = group.get_property('y')
154 gr = parent.add(canvas.CanvasGroup, x = x, y = y)
155 group.destroy()
156 group.attrib_to_group = None
157 self.node_to_group[node] = gr
158 gr.connect('event', self.node_event, node)
160 self.create_tree(node, gr, cramped = group.cramped)
161 self.auto_highlight(node, rec = 1)
162 self.child_group_resized(node)
163 else:
164 # Need to rebuild everything...
165 if self.root_group:
166 self.root_group.destroy()
167 self.node_to_group = {}
168 self.root_group = self.root().add(canvas.CanvasGroup, x = 0, y = 0)
169 group = self.root_group
170 node = self.view.root
171 group.connect('event', self.node_event, node)
172 print "creating tree..."
173 self.create_tree(node, group)
174 print "highlighting..."
175 self.auto_highlight(node, rec = 1)
176 print "done"
177 self.move_from()
178 self.set_bounds()
179 if self.view.current_nodes:
180 self.scroll_to_show(self.view.current_nodes[0])
181 finally:
182 set_busy(self, FALSE)
183 self.update_nodes = {}
184 #print "<update_callback"
185 return 0
187 def child_group_resized(self, node):
188 "The group for this node has changed size. Work up the tree making room for "
189 "it (and put it in the right place)."
190 kids = []
191 if node == self.view.root or node not in self.node_to_group:
192 return
193 node = node.parentNode
194 if node not in self.node_to_group:
195 return
196 for n in node.childNodes:
197 try:
198 kids.append(self.node_to_group[n])
199 except KeyError:
200 pass
201 self.position_kids(self.node_to_group[node], kids)
202 self.child_group_resized(node)
204 def auto_highlight(self, node, rec = 0):
205 a = {}
206 for x in self.view.current_nodes:
207 a[x] = None
208 cattr = self.view.current_attrib
209 def do(node):
210 "After creating the tree, everything is highlighted..."
211 try:
212 self.highlight(self.node_to_group[node], cattr == None and node in a)
213 except KeyError:
214 return
215 if rec:
216 map(do, node.childNodes)
217 do(node)
219 def options_changed(self):
220 if default_font.has_changed:
221 self.update_all()
223 def destroyed(self, widget):
224 self.view.remove_display(self)
225 #rox.app_options.remove_notify(self.options_changed)
227 def create_attribs(self, attrib, group, cramped, parent):
228 group.text = group.add(canvas.CanvasText, x = 0, y = -6, anchor = g.ANCHOR_NW,
229 fill_color = 'grey40',
230 #font = default_font.value,
231 text = "%s=%s" % (str(attrib.name), str(attrib.value)))
232 group.connect('event', self.attrib_event, parent, attrib)
234 #self.update_now() # GnomeCanvas bug?
235 (lx, ly, hx, hy) = group.text.get_bounds()
236 group.rect = group.add(canvas.CanvasRect,
237 x1 = lx - 1, y1 = ly - 1, x2 = hx + 1, y2 = hy + 1,
238 fill_color = None)
239 group.rect.lower_to_bottom()
241 def create_tree(self, node, group, cramped = 0):
242 if node.nodeType == Node.ELEMENT_NODE:
243 hidden = node.hasAttributeNS(None, 'hidden')
244 else:
245 hidden = FALSE
247 group.node = node
248 group.cramped = cramped
249 if hidden:
250 c = 'red'
251 elif node.nodeType == Node.TEXT_NODE:
252 c = 'lightblue'
253 else:
254 c = 'yellow'
255 group.add(canvas.CanvasEllipse, x1 = -4, y1 = -4, x2 = 4, y2 = 4,
256 fill_color = c, outline_color = 'black')
257 text = self.get_text(node)
259 hbox = node.nodeName == 'tr'
260 if cramped:
261 text = wrap(text, 32)
262 else:
263 text = wrap(text, 1000)
264 group.text = group.add(canvas.CanvasText, x = 10 , y = -6, anchor = g.ANCHOR_NW,
265 #font = default_font.value,
266 fill_color = 'black', text = text)
267 self.node_to_group[node] = group
269 #self.update_now() # GnomeCanvas bug?
270 (lx, ly, hx, hy) = group.text.get_bounds()
271 group.rect = group.add(canvas.CanvasRect,
272 x1 = -8 , y1 = ly - 1, x2 = hx + 1, y2 = hy + 1,
273 fill_color = 'blue')
274 #group.rect.hide()
276 if hbox:
277 cramped = 1
279 if node.nodeType == Node.ELEMENT_NODE:
280 group.attrib_to_group = {}
281 if not hidden:
282 ax = hx + 8
283 ay = 0
284 if not cramped:
285 l = 0
286 for key in node.attributes:
287 a = node.attributes[key]
288 value = a.value or ''
289 l += len(a.name) + len(value)
290 acramped = l > 80
291 else:
292 acramped = cramped
293 for key in node.attributes:
294 a = node.attributes[key]
295 gr = group.add(canvas.CanvasGroup, x = ax, y = ay)
296 self.create_attribs(a, gr, cramped, node)
297 #self.update_now() # GnomeCanvas bug?
298 (alx, aly, ahx, ahy) = gr.get_bounds()
299 if acramped:
300 ay = ahy + 8
301 hy = ahy
302 else:
303 ax = ahx + 8
304 group.attrib_to_group[a] = gr
306 group.hy = hy
307 kids = []
308 if not hidden:
309 for n in node.childNodes:
310 gr = group.add(canvas.CanvasGroup, x = 0, y = 0)
311 gr.connect('event', self.node_event, n)
312 self.create_tree(n, gr, cramped)
313 kids.append(gr)
315 self.position_kids(group, kids)
316 group.rect.lower_to_bottom()
318 def position_kids(self, group, kids):
319 if not kids:
320 return
322 #assert not group.flags() & DESTROYED
323 #for k in kids:
324 # assert not k.flags() & DESTROYED
326 if group.cramped:
327 indent = cramped_indent
328 else:
329 indent = normal_indent
331 node = group.node
332 hy = group.hy
334 if hasattr(group, 'lines'):
335 for l in group.lines:
336 l.destroy()
337 group.lines = []
339 if node.nodeName != 'tr':
340 y = hy + 4
341 top = None
342 for gr in kids:
343 gr.set(x = 0, y = 0)
344 #self.update_now() # GnomeCanvas bug?
345 (lx, ly, hx, hy) = gr.get_bounds()
346 y -= ly
347 lowest_child = y
348 gr.set(x = indent, y = y)
349 if not top:
350 top = y
351 y = y + hy + 4
352 diag = min(top, indent)
354 max_segment = 16000
355 points = (4, 4, diag, diag, indent, top, indent,
356 min(lowest_child, top + max_segment))
357 group.lines.append(group.add(canvas.CanvasLine,
358 points = points, fill_color = 'black', width_pixels = 1))
359 group.lines[-1].lower_to_bottom()
360 while points[-1] < lowest_child:
361 old_y = points[-1]
362 points = (indent, old_y, indent,
363 min(old_y + max_segment, lowest_child))
364 group.lines.append(group.add(canvas.CanvasLine,
365 points = points, fill_color = 'black', width_pixels = 1))
366 group.lines[-1].lower_to_bottom()
367 else:
368 y = hy + 8
369 x = indent
370 for gr in kids:
371 gr.set(x = 0, y = 0)
372 #self.update_now() # GnomeCanvas bug?
373 (lx, ly, hx, hy) = gr.get_bounds()
374 x -= lx
375 gr.set(x = x, y = y - ly)
376 group.lines.append(group.add(canvas.CanvasLine,
377 points = (x, y - 4, x, y - ly - 4),
378 fill_color = 'black',
379 width_pixels = 1))
380 right_child = x
381 x += hx - lx + 8
382 group.lines.append(group.add(canvas.CanvasLine,
383 points = (0, 4, 0, y - 4, right_child, y - 4),
384 fill_color = 'black',
385 width_pixels = 1))
386 group.lines[-1].lower_to_bottom()
388 def get_text(self, node):
389 if node.nodeType == Node.TEXT_NODE:
390 return node.nodeValue.strip()
391 elif node.nodeType == Node.ELEMENT_NODE:
392 return node.nodeName
393 elif node.nodeType == Node.COMMENT_NODE:
394 return node.nodeValue.strip()
395 elif node.nodeName:
396 return node.nodeName
397 elif node.nodeValue:
398 return '<noname>' + node.nodeValue
399 else:
400 return '<unknown>'
402 def show_menu(self, bev):
403 pass
405 def bg_event(self, widget, event):
406 if event.type == g.gdk.BUTTON_PRESS and event.button == 3:
407 self.show_menu(event)
408 return 1
410 # 'group' and 'node' may be None (for the background)
411 def node_event(self, group, event, node):
412 if (event.type == g.gdk.BUTTON_PRESS or event.type == g.gdk._2BUTTON_PRESS) and event.button == 1:
413 self.do_update_now()
414 self.node_clicked(node, event)
415 return 1
416 return 0
418 def attrib_event(self, group, event, element, attrib):
419 if event.type != g.gdk.BUTTON_PRESS or event.button == 3:
420 return 0
421 self.do_update_now()
422 self.attrib_clicked(element, attrib, event)
423 return 1
425 def node_clicked(self, node, event):
426 return
428 def attrib_clicked(self, element, attrib, event):
429 return
431 def move_from(self, old = []):
432 self.set_current_attrib(self.view.current_attrib)
434 new = self.view.current_nodes
435 for n in old:
436 if self.view.current_attrib or n not in new:
437 try:
438 self.highlight(self.node_to_group[n], FALSE)
439 except KeyError:
440 pass
441 if self.update_timeout or not self.visible:
442 return # We'll highlight on the callback...
443 # We can update without structural problems...
444 if self.view.current_nodes:
445 self.scroll_to_show(self.view.current_nodes[0])
446 if self.view.current_attrib:
447 pass
448 else:
449 for n in new:
450 try:
451 self.highlight(self.node_to_group[n], TRUE)
452 except KeyError:
453 print "Warning: Node %s not in node_to_group" % n
455 def set_current_attrib(self, attrib):
456 "Select 'attrib' attribute node of the current node. None to unselect."
457 if self.current_attrib:
458 if self.current_attrib.get_property('parent'):
459 self.current_attrib.rect.set(fill_color = None)
460 self.current_attrib.text.set(fill_color = 'grey40')
461 if attrib:
462 try:
463 group = self.node_to_group[self.view.get_current()].attrib_to_group[attrib]
464 group.rect.set(fill_color = 'light blue')
465 group.text.set(fill_color = 'grey40')
466 self.current_attrib = group
467 except KeyError:
468 pass
469 else:
470 self.current_attrib = None
472 def marked_changed(self, nodes):
473 "nodes is a list of nodes to be rechecked."
474 marked = self.view.marked
475 for n in nodes:
476 try:
477 group = self.node_to_group[n]
478 group.rect.set(outline_color = (n in marked and 'orange') or None)
479 except KeyError:
480 pass # Will regenerate later
482 def highlight(self, group, state):
483 node = group.node
484 if state:
485 #group.text.set(fill_color = 'white')
486 rfill = 'light blue'
487 else:
488 rfill = None
490 nt = node.nodeType
491 if nt == 1: #Node.ELEMENT_NODE:
492 group.text.set(fill_color = 'black')
493 elif nt == 3: #Node.TEXT_NODE:
494 group.text.set(fill_color = 'blue')
495 elif nt == 8: #Node.COMMENT_NODE:
496 group.text.set(fill_color = 'darkgreen')
497 else:
498 group.text.set(fill_color = 'red')
500 if node in self.view.marked:
501 oc = 'orange'
502 else:
503 oc = None
504 group.rect.set(fill_color = rfill, outline_color = oc)
506 def world_to_canvas(self, (x, y)):
507 "Canvas routine seems to be broken..."
508 mx, my, maxx, maxy = self.get_scroll_region()
509 return (x - mx, y - my)
511 def scroll_to_show(self, node):
512 try:
513 group = self.node_to_group[node]
514 except KeyError:
515 return
516 #self.update_now() # GnomeCanvas bug?
517 (lx, ly, hx, hy) = group.rect.get_bounds()
518 x, y = self.world_to_canvas(group.i2w(0, 0))
519 lx += x
520 ly += y
521 hx += x
522 hy += y
523 lx -= 16
525 sx, sy = self.get_scroll_offsets()
526 if lx < sx:
527 sx = lx
528 if ly < sy:
529 sy = ly
531 (x, y, w, h) = self.get_allocation()
532 hx -= w
533 hy -= h
535 if hx > sx:
536 sx = hx
537 if hy > sy:
538 sy = hy
540 self.scroll_to(sx, sy)