1 from __future__
import generators
5 from xml
.dom
import Node
9 default_font
= __main__
.default_font
11 def calc_node(display
, node
, pos
):
13 if node
.nodeType
== Node
.TEXT_NODE
:
14 text
= node
.nodeValue
.strip()
15 elif node
.nodeType
== Node
.ELEMENT_NODE
:
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()
24 text
= '<noname>' + node
.nodeValue
28 # PyGtk leaks PangoLayouts, so just reuse a single one
29 layout
= display
.surface_layout
30 layout
.set_text(text
, -1)
31 width
, height
= layout
.get_pixel_size()
35 if node
.nodeType
!= Node
.ATTRIBUTE_NODE
:
40 style
= display
.surface
.style
# Different surface ;-)
44 if node
.nodeType
!= Node
.ATTRIBUTE_NODE
:
46 surface
.draw_rectangle(fg
[g
.STATE_NORMAL
], True,
48 if node
.nodeType
== Node
.ELEMENT_NODE
and node
.hasAttributeNS(None, 'hidden'):
49 surface
.draw_layout(fg
[g
.STATE_PRELIGHT
], text_x
+ width
+ 2, y
,
50 display
.create_pango_layout('(hidden)'))
54 if node
in display
.selection
:
56 surface
.draw_rectangle(style
.bg_gc
[g
.STATE_SELECTED
], True,
57 x
+ 1, y
+ 1, 6, height
- 3)
58 surface
.draw_rectangle(bg
[g
.STATE_SELECTED
], True,
59 text_x
, y
, width
- 1, height
- 1)
60 surface
.draw_layout(fg
[g
.STATE_SELECTED
], text_x
, y
, layout
)
63 surface
.draw_rectangle(style
.white_gc
, True, x
+ 1, y
+ 1, 6, height
- 3)
64 if node
.nodeType
== Node
.TEXT_NODE
:
65 gc
= style
.text_gc
[g
.STATE_NORMAL
]
66 elif node
.nodeType
== Node
.ATTRIBUTE_NODE
:
67 gc
= style
.fg_gc
[g
.STATE_INSENSITIVE
]
68 elif node
.nodeType
== Node
.COMMENT_NODE
:
69 gc
= style
.text_gc
[g
.STATE_INSENSITIVE
]
71 gc
= style
.fg_gc
[g
.STATE_NORMAL
]
72 surface
.draw_layout(gc
, text_x
, y
, layout
)
74 if node
in display
.view
.marked
:
75 surface
.draw_rectangle(style
.text_gc
[g
.STATE_PRELIGHT
], False,
76 x
- 1, y
- 1, width
+ (text_x
- x
), height
)
78 bbox
= (x
, y
, text_x
+ width
, y
+ height
)
81 class Display(g
.HBox
):
82 visible
= 1 # Always visible
84 def __init__(self
, window
, view
):
85 g
.HBox
.__init
__(self
, False, 0)
87 self
.surface
= g
.EventBox()
88 self
.surface_layout
= self
.surface
.create_pango_layout('')
89 self
.pack_start(self
.surface
, True, True, 0)
91 self
.surface
.set_app_paintable(True)
92 self
.surface
.set_double_buffered(False)
93 self
.update_timeout
= 0
94 self
.cached_nodes
= None
96 self
.scroll_adj
= g
.Adjustment(lower
= 0, upper
= 100, step_incr
= 1)
97 self
.scroll_adj
.connect('value-changed', self
.scroll_to
)
98 scale
= g
.VScale(self
.scroll_adj
)
99 scale
.unset_flags(g
.CAN_FOCUS
)
100 scale
.set_draw_value(False)
101 self
.pack_start(scale
, False, True, 0)
104 self
.parent_window
= window
107 s
= self
.surface
.get_style().copy()
108 s
.bg
[g
.STATE_NORMAL
] = g
.gdk
.color_parse('old lace')
109 s
.text
[g
.STATE_NORMAL
] = g
.gdk
.color_parse('blue')
110 s
.text
[g
.STATE_PRELIGHT
] = g
.gdk
.color_parse('orange') # Mark
111 s
.text
[g
.STATE_INSENSITIVE
] = g
.gdk
.color_parse('dark green')# Comment
112 s
.fg
[g
.STATE_PRELIGHT
] = g
.gdk
.color_parse('red') # Hidden
113 self
.surface
.set_style(s
)
115 self
.signals
= [self
.connect('destroy', self
.destroyed
)]
116 self
.surface
.connect('button-press-event', self
.bg_event
)
117 self
.surface
.connect('button-release-event', self
.bg_event
)
119 # Display is relative to this node, which is the highest
120 # displayed node (possibly off the top of the screen)
121 self
.ref_node
= view
.root
122 self
.ref_pos
= (0, 0)
124 self
.last_alloc
= None
125 self
.surface
.connect('size-allocate', lambda w
, a
: self
.size_allocate(a
))
126 self
.surface
.connect('size-request', lambda w
, r
: self
.size_request(r
))
127 self
.surface
.connect('expose-event', lambda w
, e
: 1)
129 self
.pan_timeout
= None
130 self
.h_limits
= (0, 0)
133 def destroyed(self
, widget
):
134 self
.view
.remove_display(self
)
135 for s
in self
.signals
:
137 if self
.update_timeout
:
138 g
.timeout_remove(self
.update_timeout
)
139 self
.update_timeout
= 0
143 del self
.parent_window
145 del self
.surface_layout
151 def size_allocate(self
, alloc
):
152 new
= (alloc
.width
, alloc
.height
)
153 if self
.last_alloc
== new
:
155 self
.last_alloc
= new
157 #print "Alloc", alloc.width, alloc.height
158 pm
= g
.gdk
.Pixmap(self
.surface
.window
, alloc
.width
, alloc
.height
, -1)
159 self
.surface
.window
.set_back_pixmap(pm
, False)
162 if self
.update_timeout
:
163 g
.timeout_remove(self
.update_timeout
)
164 self
.update_timeout
= 0
168 # Must be called either:
169 # - With no update_timeout running, or
170 # - From the timeout callback
172 self
.update_timeout
= 0
174 if not self
.pm
: return 0
177 self
.pm
.draw_rectangle(self
.surface
.style
.bg_gc
[g
.STATE_NORMAL
], True,
178 0, 0, self
.last_alloc
[0], self
.last_alloc
[1])
180 self
.drawn
= {} # xmlNode -> ((x1, y1, y2, y2), attrib_parent)
183 p
= self
.view
.root
.parentNode
188 self
.ref_node
= self
.view
.root
189 self
.ref_pos
= (0, 0)
192 if self
.view
.current_attrib
:
193 self
.selection
= {self
.view
.current_attrib
: None}
196 for n
in self
.view
.current_nodes
:
197 self
.selection
[n
] = None
199 pos
= list(self
.ref_pos
)
200 self
.h_limits
= (self
.ref_pos
[0], self
.ref_pos
[0]) # Left, Right
203 for node
, bbox
, draw_fn
in self
.walk_tree(self
.ref_node
, self
.ref_pos
):
204 if bbox
[1] > self
.last_alloc
[1]: break # Off-screen
205 if bbox
[1] > -self
.last_alloc
[1]:
208 print 'Warning: Ref node way off:', bbox
[1]
209 if node
.nodeType
== Node
.ATTRIBUTE_NODE
:
210 self
.drawn
[node
] = (bbox
, attr_parent
)
213 self
.drawn
[node
] = (bbox
, None)
215 if bbox
[1] < 0 and node
.nodeType
!= Node
.ATTRIBUTE_NODE
:
217 self
.ref_pos
= bbox
[:2]
218 self
.h_limits
= (min(self
.h_limits
[0], bbox
[0]),
219 max(self
.h_limits
[1], bbox
[2]))
221 self
.surface
.window
.clear()
226 pos
= self
.cached_nodes
.index(self
.ref_node
)
229 print "Missing ref node!!"
230 self
.scroll_adj
.value
= float(pos
)
231 self
.scroll_adj
.upper
= float(len(self
.cached_nodes
))
235 def ensure_cache(self
):
236 "Find all the nodes in the document, in document order. Not attributes."
237 if self
.cached_nodes
is not None:
239 nodes
= [self
.view
.root
.parentNode
]
240 node
= self
.view
.root
244 node
= node
.childNodes
[0]
246 while not node
.nextSibling
:
247 node
= node
.parentNode
249 self
.cached_nodes
= nodes
251 node
= node
.nextSibling
252 self
.cached_nodes
= nodes
254 def walk_tree(self
, node
, pos
):
255 """Yield this (node, bbox), and all following ones in document order."""
258 bbox
, draw_fn
= calc_node(self
, node
, pos
)
259 yield (node
, bbox
, draw_fn
)
262 if node
.nodeType
== Node
.ELEMENT_NODE
:
263 hidden
= node
.hasAttributeNS(None, 'hidden')
265 apos
= [bbox
[2] + 4, bbox
[1]]
266 for key
in node
.attributes
:
267 a
= node
.attributes
[key
]
268 abbox
, draw_fn
= calc_node(self
, a
, apos
)
269 apos
[0] = abbox
[2] + 4
270 yield (a
, abbox
, draw_fn
)
273 if node
.childNodes
and not hidden
:
274 node
= node
.childNodes
[0]
277 while not node
.nextSibling
:
278 node
= node
.parentNode
281 node
= node
.nextSibling
283 def size_request(self
, req
):
287 def do_update_now(self
):
288 # Update now, if we need to
289 if self
.update_timeout
:
290 g
.timeout_remove(self
.update_timeout
)
291 self
.update_timeout
= 0
294 def update_all(self
, node
= None):
295 self
.cached_nodes
= None
297 if self
.update_timeout
:
298 return # Going to update anyway...
300 if self
.view
.running():
301 self
.update_timeout
= g
.timeout_add(2000, self
.update
)
303 self
.update_timeout
= g
.timeout_add(10, self
.update
)
305 def move_from(self
, old
= []):
306 if not self
.pm
: return
307 if self
.view
.current_nodes
:
309 for n
in self
.view
.current_nodes
:
312 for node
, bbox
, draw_fn
in self
.walk_tree(self
.ref_node
, self
.ref_pos
):
313 if bbox
[1] > self
.last_alloc
[1]: break # Off-screen
314 if bbox
[3] > 0 and node
in selection
:
316 break # A selected node is shown
318 print "(selected nodes not shown)"
319 self
.ref_node
= self
.view
.current_nodes
[0]
320 self
.ref_pos
= (40, self
.last_alloc
[1] / 2)
321 self
.backup_ref_node()
324 def set_view(self
, view
):
326 self
.view
.remove_display(self
)
328 self
.view
.add_display(self
)
331 def show_menu(self
, bev
):
334 def node_clicked(self
, node
, event
):
337 def xy_to_node(self
, x
, y
):
338 "Return the node at this point and, if it's an attribute, its parent."
339 for (n
, ((x1
, y1
, x2
, y2
), attrib_parent
)) in self
.drawn
.iteritems():
340 if x
>= x1
and x
<= x2
and y
>= y1
and y
<= y2
:
341 return n
, attrib_parent
346 val
= (float(abs(x
)) ** 1.4)
352 if x
> 10: return x
- 10
353 if x
< -10: return x
+ 10
355 x
, y
, mask
= self
.surface
.window
.get_pointer()
356 sx
, sy
= self
.pan_start
357 dx
, dy
= scale(chop(x
- sx
)) / 20, scale(chop(y
- sy
))
358 dx
= max(dx
, 10 - self
.h_limits
[1])
359 dx
= min(dx
, self
.last_alloc
[0] - 10 - self
.h_limits
[0])
360 new
= [self
.ref_pos
[0] + dx
, self
.ref_pos
[1] + dy
]
362 if new
== self
.ref_pos
:
367 self
.backup_ref_node()
369 if self
.update_timeout
:
370 g
.timeout_remove(self
.update_timeout
)
371 self
.update_timeout
= 0
376 def backup_ref_node(self
):
377 self
.ref_pos
= list(self
.ref_pos
)
378 # Walk up the parents until we get a ref node above the start of the screen
379 # (redraw will come back down)
380 while self
.ref_pos
[1] > 0:
383 if self
.ref_node
.previousSibling
:
384 self
.ref_node
= self
.ref_node
.previousSibling
385 elif self
.ref_node
.parentNode
:
386 self
.ref_node
= self
.ref_node
.parentNode
390 # Walk from the parent node to find how far it is to this node...
391 for node
, bbox
, draw_fn
in self
.walk_tree(self
.ref_node
, (0, 0)):
392 if node
is src
: break
396 self
.ref_pos
[0] -= bbox
[0]
397 self
.ref_pos
[1] -= bbox
[1]
399 #print "(start from %s at (%d,%d))" % (self.ref_node, self.ref_pos[0], self.ref_pos[1])
401 if self
.ref_pos
[1] > 10:
403 elif self
.ref_pos
[1] < -100:
404 for node
, bbox
, draw_fn
in self
.walk_tree(self
.ref_node
, self
.ref_pos
):
405 if bbox
[3] > 10: break # Something is visible
407 self
.ref_pos
[1] = -100
409 def bg_event(self
, widget
, event
):
410 if event
.type == g
.gdk
.BUTTON_PRESS
and event
.button
== 3:
411 self
.show_menu(event
)
412 elif event
.type == g
.gdk
.BUTTON_PRESS
or event
.type == g
.gdk
._2BUTTON
_PRESS
:
414 node
, attr_parent
= self
.xy_to_node(event
.x
, event
.y
)
415 if event
.button
== 1:
418 self
.attrib_clicked(attr_parent
, node
, event
)
420 self
.node_clicked(node
, event
)
421 elif event
.button
== 2:
422 assert self
.pan_timeout
is None
423 self
.pan_start
= (event
.x
, event
.y
)
424 self
.pan_timeout
= g
.timeout_add(100, self
.pan
)
425 elif event
.type == g
.gdk
.BUTTON_RELEASE
and event
.button
== 2:
426 assert self
.pan_timeout
is not None
427 g
.timeout_remove(self
.pan_timeout
)
428 self
.pan_timeout
= None
433 def marked_changed(self
, nodes
):
434 "nodes is a list of nodes to be rechecked."
437 def options_changed(self
):
438 if default_font
.has_changed
:
439 #self.modify_font(pango.FontDescription(default_font.value))
442 def scroll_to(self
, adj
):
446 node
= self
.cached_nodes
[n
]
448 node
= self
.cached_nodes
[-1]
449 if self
.ref_node
== node
:
453 while node
.parentNode
:
455 node
= node
.parentNode
456 self
.ref_pos
= (x
, 0)