Bigger text box.
[dom-editor.git] / Dome / Display2.py
blob7881ab560a2ad4d22fad45a93f42da54434e3d3a
1 from __future__ import generators
3 import rox
4 from rox import g
5 from xml.dom import Node
6 import pango
8 import __main__
9 default_font = __main__.default_font
11 def calc_node(display, node, pos):
12 attribs = []
13 if node.nodeType == Node.TEXT_NODE:
14 text = node.nodeValue.strip()
15 elif node.nodeType == Node.ELEMENT_NODE:
16 text = node.nodeName
17 elif node.nodeType == Node.ATTRIBUTE_NODE:
18 text = ' %s=%s' % (unicode(node.name), unicode(node.value))
19 elif node.nodeType == Node.COMMENT_NODE:
20 text = node.nodeValue.strip()
21 elif node.nodeName:
22 text = node.nodeName
23 elif node.nodeValue:
24 text = '<noname>' + node.nodeValue
25 else:
26 text = '<unknown>'
28 layout = display.create_pango_layout(text)
29 width, height = layout.get_pixel_size()
30 x, y = pos
32 text_x = x
33 if node.nodeType != Node.ATTRIBUTE_NODE:
34 text_x += 12
36 def draw_fn():
37 surface = display.pm
38 style = display.surface.style # Different surface ;-)
39 fg = style.fg_gc
40 bg = style.bg_gc
42 if node.nodeType != Node.ATTRIBUTE_NODE:
43 marker = True
44 surface.draw_rectangle(fg[g.STATE_NORMAL], True,
45 x, y, 8, height - 1)
46 if node.nodeType == Node.ELEMENT_NODE and node.hasAttributeNS(None, 'hidden'):
47 surface.draw_layout(fg[g.STATE_PRELIGHT], text_x + width + 2, y,
48 display.create_pango_layout('(hidden)'))
49 else:
50 marker = False
52 if node in display.selection:
53 if marker:
54 surface.draw_rectangle(style.bg_gc[g.STATE_SELECTED], True,
55 x + 1, y + 1, 6, height - 3)
56 surface.draw_rectangle(bg[g.STATE_SELECTED], True,
57 text_x, y, width - 1, height - 1)
58 surface.draw_layout(fg[g.STATE_SELECTED], text_x, y, layout)
59 else:
60 if marker:
61 surface.draw_rectangle(style.white_gc, True, x + 1, y + 1, 6, height - 3)
62 if node.nodeType == Node.TEXT_NODE:
63 gc = style.text_gc[g.STATE_NORMAL]
64 elif node.nodeType == Node.ATTRIBUTE_NODE:
65 gc = style.fg_gc[g.STATE_INSENSITIVE]
66 elif node.nodeType == Node.COMMENT_NODE:
67 gc = style.text_gc[g.STATE_INSENSITIVE]
68 else:
69 gc = style.fg_gc[g.STATE_NORMAL]
70 surface.draw_layout(gc, text_x, y, layout)
72 if node in display.view.marked:
73 surface.draw_rectangle(style.text_gc[g.STATE_PRELIGHT], False,
74 x - 1, y - 1, width + (text_x - x), height)
76 bbox = (x, y, text_x + width, y + height)
77 return bbox, draw_fn
79 class Display(g.HBox):
80 visible = 1 # Always visible
82 def __init__(self, window, view):
83 g.HBox.__init__(self, False, 0)
85 self.surface = g.EventBox()
86 self.pack_start(self.surface, True, True, 0)
87 self.surface.show()
88 self.surface.set_app_paintable(True)
89 self.surface.set_double_buffered(False)
90 self.update_timeout = 0
92 self.scroll_adj = g.Adjustment(lower = 0, upper = 100, step_incr = 1)
93 self.scroll_adj.connect('value-changed', self.scroll_to)
94 scale = g.VScale(self.scroll_adj)
95 scale.unset_flags(g.CAN_FOCUS)
96 scale.set_draw_value(False)
97 self.pack_start(scale, False, True, 0)
99 self.view = None
100 self.parent_window = window
101 self.pm = None
103 s = self.surface.get_style().copy()
104 s.bg[g.STATE_NORMAL] = g.gdk.color_parse('old lace')
105 s.text[g.STATE_NORMAL] = g.gdk.color_parse('blue')
106 s.text[g.STATE_PRELIGHT] = g.gdk.color_parse('orange') # Mark
107 s.text[g.STATE_INSENSITIVE] = g.gdk.color_parse('dark green')# Comment
108 s.fg[g.STATE_PRELIGHT] = g.gdk.color_parse('red') # Hidden
109 self.surface.set_style(s)
111 self.connect('destroy', self.destroyed)
112 self.surface.connect('button-press-event', self.bg_event)
113 self.surface.connect('button-release-event', self.bg_event)
115 # Display is relative to this node, which is the highest displayed node (possibly
116 # off the top of the screen)
117 self.ref_node = view.root
118 self.ref_pos = (0, 0)
120 self.last_alloc = None
121 self.surface.connect('size-allocate', lambda w, a: self.size_allocate(a))
122 self.surface.connect('size-request', lambda w, r: self.size_request(r))
123 self.surface.connect('expose-event', lambda w, e: 1)
125 self.pan_timeout = None
126 self.h_limits = (0, 0)
127 self.set_view(view)
129 def destroyed(self, widget):
130 self.view.remove_display(self)
132 def size_allocate(self, alloc):
133 new = (alloc.width, alloc.height)
134 if self.last_alloc == new:
135 return
136 self.last_alloc = new
137 assert self.window
138 #print "Alloc", alloc.width, alloc.height
139 pm = g.gdk.Pixmap(self.surface.window, alloc.width, alloc.height, -1)
140 self.surface.window.set_back_pixmap(pm, False)
141 self.pm = pm
142 self.update()
144 def update(self):
145 if not self.pm: return
146 #print "update"
148 self.update_timeout = 0
150 self.pm.draw_rectangle(self.surface.style.bg_gc[g.STATE_NORMAL], True,
151 0, 0, self.last_alloc[0], self.last_alloc[1])
153 self.drawn = {} # xmlNode -> ((x1, y1, y2, y2), attrib_parent)
155 n = self.ref_node
156 p = self.view.root.parentNode
157 while n is not p:
158 n = n.parentNode
159 if not n:
160 print "(lost root)"
161 self.ref_node = self.view.root
162 self.ref_pos = (0, 0)
163 break
165 if self.view.current_attrib:
166 self.selection = {self.view.current_attrib: None}
167 else:
168 self.selection = {}
169 for n in self.view.current_nodes:
170 self.selection[n] = None
172 pos = list(self.ref_pos)
173 self.h_limits = (self.ref_pos[0], self.ref_pos[0]) # Left, Right
174 node = self.ref_node
175 attr_parent = None
176 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
177 if bbox[1] > self.last_alloc[1]: break # Off-screen
179 draw_fn()
180 if node.nodeType == Node.ATTRIBUTE_NODE:
181 self.drawn[node] = (bbox, attr_parent)
182 else:
183 attr_parent = node
184 self.drawn[node] = (bbox, None)
186 if bbox[1] < 0 and node.nodeType != Node.ATTRIBUTE_NODE:
187 self.ref_node = node
188 self.ref_pos = bbox[:2]
189 self.h_limits = (min(self.h_limits[0], bbox[0]),
190 max(self.h_limits[1], bbox[2]))
192 self.surface.window.clear()
194 # Update adjustment
195 r = self.ref_node
196 n = 0
197 pos = 0
198 for x in self.quick_walk():
199 if x is r:
200 pos = n
201 n += 1
202 self.scroll_adj.value = float(pos)
203 self.scroll_adj.upper = float(n)
205 return 0
207 def quick_walk(self):
208 "Return all the nodes in the document, in document order. Not attributes."
209 yield self.view.root.parentNode
210 node = self.view.root
211 while node:
212 yield node
213 if node.childNodes:
214 node = node.childNodes[0]
215 else:
216 while not node.nextSibling:
217 node = node.parentNode
218 if not node: return
219 node = node.nextSibling
221 def walk_tree(self, node, pos):
222 """Yield this (node, bbox), and all following ones in document order."""
223 pos = list(pos)
224 while node:
225 bbox, draw_fn = calc_node(self, node, pos)
226 yield (node, bbox, draw_fn)
228 hidden = False
229 if node.nodeType == Node.ELEMENT_NODE:
230 hidden = node.hasAttributeNS(None, 'hidden')
231 if not hidden:
232 apos = [bbox[2] + 4, bbox[1]]
233 for key in node.attributes:
234 a = node.attributes[key]
235 abbox, draw_fn = calc_node(self, a, apos)
236 apos[0] = abbox[2] + 4
237 yield (a, abbox, draw_fn)
239 pos[1] = bbox[3] + 2
240 if node.childNodes and not hidden:
241 node = node.childNodes[0]
242 pos[0] += 16
243 else:
244 while not node.nextSibling:
245 node = node.parentNode
246 if not node: return
247 pos[0] -= 16
248 node = node.nextSibling
250 def size_request(self, req):
251 req.width = 4
252 req.height = 4
254 def do_update_now(self):
255 # Update now, if we need to
256 if self.update_timeout:
257 g.timeout_remove(self.update_timeout)
258 self.update()
260 def update_all(self, node = None):
261 if self.update_timeout:
262 return # Going to update anyway...
264 if self.view.running():
265 self.update_timeout = g.timeout_add(2000, self.update)
266 else:
267 self.update_timeout = g.timeout_add(10, self.update)
269 def move_from(self, old = []):
270 if not self.pm: return
271 if self.view.current_nodes:
272 selection = {}
273 for n in self.view.current_nodes:
274 selection[n] = None
275 shown = False
276 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
277 if bbox[1] > self.last_alloc[1]: break # Off-screen
278 if bbox[3] > 0 and node in selection:
279 shown = True
280 break # A selected node is shown
281 if not shown:
282 print "(selected nodes not shown)"
283 self.ref_node = self.view.current_nodes[0]
284 self.ref_pos = (40, self.last_alloc[1] / 2)
285 self.backup_ref_node()
286 self.update_all()
288 def set_view(self, view):
289 if self.view:
290 self.view.remove_display(self)
291 self.view = view
292 self.view.add_display(self)
293 self.update_all()
295 def show_menu(self, bev):
296 pass
298 def node_clicked(self, node, event):
299 pass
301 def xy_to_node(self, x, y):
302 "Return the node at this point and, if it's an attribute, its parent."
303 for (n, ((x1, y1, x2, y2), attrib_parent)) in self.drawn.iteritems():
304 if x >= x1 and x <= x2 and y >= y1 and y <= y2:
305 return n, attrib_parent
306 return None, None
308 def pan(self):
309 def scale(x):
310 val = (float(abs(x)) ** 1.4)
311 if x < 0:
312 return -val
313 else:
314 return val
315 def chop(x):
316 if x > 10: return x - 10
317 if x < -10: return x + 10
318 return 0
319 x, y, mask = self.surface.window.get_pointer()
320 sx, sy = self.pan_start
321 dx, dy = scale(chop(x - sx)) / 20, scale(chop(y - sy))
322 dx = max(dx, 10 - self.h_limits[1])
323 dx = min(dx, self.last_alloc[0] - 10 - self.h_limits[0])
324 new = [self.ref_pos[0] + dx, self.ref_pos[1] + dy]
326 if new == self.ref_pos:
327 return 1
329 self.ref_pos = new
331 self.backup_ref_node()
333 self.update()
335 return 1
337 def backup_ref_node(self):
338 self.ref_pos = list(self.ref_pos)
339 # Walk up the parents until we get a ref node above the start of the screen
340 # (redraw will come back down)
341 while self.ref_pos[1] > 0:
342 src = self.ref_node
344 if self.ref_node.previousSibling:
345 self.ref_node = self.ref_node.previousSibling
346 elif self.ref_node.parentNode:
347 self.ref_node = self.ref_node.parentNode
348 else:
349 break
351 # Walk from the parent node to find how far it is to this node...
352 for node, bbox, draw_fn in self.walk_tree(self.ref_node, (0, 0)):
353 if node is src: break
354 else:
355 assert 0
357 self.ref_pos[0] -= bbox[0]
358 self.ref_pos[1] -= bbox[1]
360 #print "(start from %s at (%d,%d))" % (self.ref_node, self.ref_pos[0], self.ref_pos[1])
362 if self.ref_pos[1] > 10:
363 self.ref_pos[1] = 10
364 elif self.ref_pos[1] < -100:
365 for node, bbox, draw_fn in self.walk_tree(self.ref_node, self.ref_pos):
366 if bbox[3] > 10: break # Something is visible
367 else:
368 self.ref_pos[1] = -100
370 def bg_event(self, widget, event):
371 if event.type == g.gdk.BUTTON_PRESS and event.button == 3:
372 self.show_menu(event)
373 elif event.type == g.gdk.BUTTON_PRESS or event.type == g.gdk._2BUTTON_PRESS:
374 self.do_update_now()
375 node, attr_parent = self.xy_to_node(event.x, event.y)
376 if event.button == 1:
377 if node:
378 if attr_parent:
379 self.attrib_clicked(attr_parent, node, event)
380 else:
381 self.node_clicked(node, event)
382 elif event.button == 2:
383 assert self.pan_timeout is None
384 self.pan_start = (event.x, event.y)
385 self.pan_timeout = g.timeout_add(100, self.pan)
386 elif event.type == g.gdk.BUTTON_RELEASE and event.button == 2:
387 assert self.pan_timeout is not None
388 g.timeout_remove(self.pan_timeout)
389 self.pan_timeout = None
390 else:
391 return 0
392 return 1
394 def marked_changed(self, nodes):
395 "nodes is a list of nodes to be rechecked."
396 self.update_all()
398 def options_changed(self):
399 if default_font.has_changed:
400 #self.modify_font(pango.FontDescription(default_font.value))
401 self.update_all()
403 def scroll_to(self, adj):
404 n = int(adj.value)
405 for node in self.quick_walk():
406 n -= 1
407 if n < 0:
408 break
409 if self.ref_node == node:
410 return
411 self.ref_node = node
412 x = 0
413 while node.parentNode:
414 x += 16
415 node = node.parentNode
416 self.ref_pos = (x, 0)
417 self.update_all()