1 from __future__
import generators
5 from xml
.dom
import Node
7 from constants
import XMLNS_NAMESPACE
11 default_font
= __main__
.default_font
13 drag_cursor
= g
.gdk
.Cursor(g
.gdk
.HAND1
)
15 def calc_node(display
, node
, pos
):
17 if node
.nodeType
== Node
.TEXT_NODE
:
18 text
= node
.nodeValue
.strip()
19 elif node
.nodeType
== Node
.ELEMENT_NODE
:
21 text
= display
.view
.model
.namespaces
.prefix
.get(node
.namespaceURI
, 'ERROR') + \
25 elif node
.nodeType
== Node
.ATTRIBUTE_NODE
:
27 text
= display
.view
.model
.namespaces
.prefix
.get(node
.namespaceURI
, 'ERROR') + \
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
)
37 text
= "subtree (%d levels)" % chroots
39 text
= 'subtree (one level)'
41 text
= display
.view
.model
.uri
45 text
= '<noname>' + node
.nodeValue
49 # PyGtk leaks PangoLayouts, so just reuse a single one
50 layout
= display
.surface_layout
52 width
, height
= layout
.get_pixel_size()
56 if node
.nodeType
!= Node
.ATTRIBUTE_NODE
:
61 style
= display
.surface
.style
# Different surface ;-)
65 if node
in display
.selection
:
66 state
= g
.STATE_SELECTED
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)
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
)
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
]
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
)
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)
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)
130 self
.parent_window
= window
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
))
158 w
.window
.clear_area(area
.x
, area
.y
, area
.width
, area
.height
)
160 self
.surface
.connect('expose-event', expose
)
162 self
.pan_timeout
= None
163 self
.h_limits
= (0, 0)
166 def destroyed(self
, widget
):
167 self
.view
.remove_display(self
)
168 for s
in self
.signals
:
170 if self
.update_timeout
:
171 g
.timeout_remove(self
.update_timeout
)
172 self
.update_timeout
= 0
176 del self
.parent_window
178 del self
.surface_layout
184 def size_allocate(self
, alloc
):
185 new
= (alloc
.width
, alloc
.height
)
186 if self
.last_alloc
== new
:
188 self
.last_alloc
= new
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)
195 if self
.update_timeout
:
196 g
.timeout_remove(self
.update_timeout
)
197 self
.update_timeout
= 0
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
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)
216 p
= self
.view
.root
.parentNode
221 self
.ref_node
= self
.view
.root
222 self
.ref_pos
= (0, 0)
225 if self
.view
.current_attrib
:
226 self
.selection
= {self
.view
.current_attrib
: None}
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
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]:
243 pass#print 'Warning: Ref node way off:', bbox[1]
244 if node
.nodeType
== Node
.ATTRIBUTE_NODE
:
245 self
.drawn
[node
] = (bbox
, attr_parent
)
248 self
.drawn
[node
] = (bbox
, None)
250 if bbox
[1] < 0 and node
.nodeType
!= Node
.ATTRIBUTE_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]))
256 # Didn't have enough nodes to fill the screen
257 frac_filled
= float(bbox
[3]) / self
.last_alloc
[1]
261 self
.surface
.window
.clear()
266 pos
= self
.cached_nodes
.index(self
.ref_node
)
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
)
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:
282 nodes
= [self
.view
.root
.parentNode
]
283 node
= self
.view
.root
284 hidden
= self
.view
.model
.hidden
287 if node
.childNodes
and node
not in hidden
:
288 node
= node
.childNodes
[0]
290 while not node
.nextSibling
:
291 node
= node
.parentNode
293 self
.cached_nodes
= nodes
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."""
301 hidden
= self
.view
.model
.hidden
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
:
313 abbox
, draw_fn
= calc_node(self
, a
, apos
)
314 apos
[0] = abbox
[2] + 4
315 yield (a
, abbox
, draw_fn
)
318 if node
.childNodes
and node
not in hidden
:
319 node
= node
.childNodes
[0]
322 while not node
.nextSibling
:
323 node
= node
.parentNode
326 node
= node
.nextSibling
328 def size_request(self
, req
):
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
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
)
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
:
354 for n
in self
.view
.current_nodes
:
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
:
361 break # A selected node is 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
369 node
= node
.parentNode
370 if node
in all_hidden
:
373 self
.backup_ref_node()
376 def set_view(self
, view
):
378 self
.view
.remove_display(self
)
380 self
.view
.add_display(self
)
383 def show_menu(self
, bev
):
386 def node_clicked(self
, node
, event
):
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
398 val
= (float(abs(x
)) ** 1.4)
404 if x
> 10: return x
- 10
405 if x
< -10: return x
+ 10
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
:
419 self
.backup_ref_node()
421 if self
.update_timeout
:
422 g
.timeout_remove(self
.update_timeout
)
423 self
.update_timeout
= 0
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:
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
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
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:
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
459 self
.ref_pos
[1] = -100
461 def bg_motion(self
, widget
, event
):
462 if not self
.drag_info
:
464 node
, attr_parent
, x
, y
, in_progress
= self
.drag_info
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
)
473 def do_drag(self
, src
, dst
):
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
:
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)
486 self
.attrib_clicked(attr_parent
, node
, event
)
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
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
):
506 self
.do_drag((src_node
, src_attr_parent
), (dst_node
, dst_attr_parent
))
511 def marked_changed(self
, nodes
):
512 "nodes is a list of nodes to be rechecked."
515 def options_changed(self
):
516 if default_font
.has_changed
:
517 #self.modify_font(pango.FontDescription(default_font.value))
520 def scroll_to(self
, adj
):
524 node
= self
.cached_nodes
[n
]
526 node
= self
.cached_nodes
[-1]
527 if self
.ref_node
== node
:
531 while node
.parentNode
:
533 node
= node
.parentNode
534 self
.ref_pos
= (x
, 0)
537 def set_status(self
, message
):
538 self
.parent_window
.set_status(message
)