1 from xml
.dom
import Node
2 from rox
.loading
import XDSLoader
5 from rox
import g
, TRUE
, FALSE
9 from Display2
import Display
11 from GetArg
import GetArg
12 from Path
import make_relative_path
14 from rox
.Menu
import Menu
17 ('/File', None, '<Branch>', ''),
18 ('/File/Save', 'menu_save', '<StockItem>', '<Ctrl>S', g
.STOCK_SAVE
),
19 ('/File/Blank document', 'do_blank_all', '<StockItem>', '<Ctrl>N', g
.STOCK_NEW
),
20 ('/File/Clear undo buffer', 'menu_clear_undo', '<StockItem>', '', g
.STOCK_CLEAR
),
22 ('/Edit', None, '<Branch>', ''),
23 ('/Edit/Copy attributes', 'do_yank_attributes', '<StockItem>', '', g
.STOCK_COPY
),
24 ('/Edit/Paste attributes', 'do_paste_attribs', '<StockItem>', '', g
.STOCK_PASTE
),
25 ('/Edit/Copy attrib value', 'do_yank_value', '<StockItem>', '', g
.STOCK_COPY
),
26 ('/Edit/Rename attribute', 'menu_rename_attr', '<StockItem>', '', g
.STOCK_PROPERTIES
),
27 ('/Edit/', '', '', '<separator>'),
28 ('/Edit/Cut', 'do_delete_node', '<StockItem>', '<Ctrl>X', g
.STOCK_DELETE
),
29 ('/Edit/Delete', 'do_delete_node_no_clipboard', '<StockItem>', '', g
.STOCK_DELETE
),
30 ('/Edit/Shallow cut', 'do_delete_shallow', '<StockItem>', '<Shift>X', g
.STOCK_DELETE
),
31 ('/Edit/', '', '', '<separator>'),
32 ('/Edit/Copy', 'do_yank', '<StockItem>', '<Ctrl>C', g
.STOCK_COPY
),
33 ('/Edit/Shallow copy', 'do_shallow_yank', '<StockItem>', '<Shift>Y', g
.STOCK_COPY
),
34 ('/Edit/', '', '', '<separator>'),
35 ('/Edit/Paste (replace)','do_put_replace', '<StockItem>', '<Ctrl>V', g
.STOCK_PASTE
),
36 ('/Edit/Paste (inside)', 'do_put_as_child', '<StockItem>', 'bracketright', g
.STOCK_GO_FORWARD
),
37 ('/Edit/Paste (before)', 'do_put_before', '<StockItem>', '<Shift>P', g
.STOCK_GO_UP
),
38 ('/Edit/Paste (after)', 'do_put_after', '<StockItem>', 'p', g
.STOCK_GO_DOWN
),
39 ('/Edit/', '', '', '<separator>'),
40 ('/Edit/Edit value', 'toggle_edit', '<StockItem>', 'Return', g
.STOCK_PROPERTIES
),
41 ('/Edit/', '', '', '<separator>'),
42 ('/Edit/Undo', 'do_undo', '<StockItem>', '<Ctrl>Z', g
.STOCK_UNDO
),
43 ('/Edit/Redo', 'do_redo', '<StockItem>', '<Ctrl>Y', g
.STOCK_REDO
),
45 ('/Move', None, '<Branch>', ''),
46 ('/Move/XPath search', 'menu_show_search', '<StockItem>', 'slash', g
.STOCK_FIND
),
47 ('/Move/Text search', 'menu_show_text_search', '<StockItem>', 'T', g
.STOCK_FIND
),
48 ('/Move/Enter', 'do_enter', '', '<Shift>greater'),
49 ('/Move/Leave', 'do_leave', '', '<Shift>less'),
51 ('/Move/Root node', 'move_home', '<StockItem>', 'Home', g
.STOCK_HOME
),
52 ('/Move/Previous sibling', 'move_prev_sib', '<StockItem>', 'Up', g
.STOCK_GO_UP
),
53 ('/Move/Next sibling', 'move_next_sib', '<StockItem>', 'Down', g
.STOCK_GO_DOWN
),
54 ('/Move/Parent', 'move_left', '<StockItem>', 'Left', g
.STOCK_GO_BACK
),
55 ('/Move/First child', 'move_right', '<StockItem>', 'Right', g
.STOCK_GO_FORWARD
),
56 ('/Move/Last child', 'move_end', '<StockItem>', 'End', g
.STOCK_GOTO_LAST
),
58 ('/Move/To attribute', 'menu_select_attrib', '<StockItem>', 'At', g
.STOCK_JUMP_TO
),
60 ('/Select', None, '<Branch>', ''),
61 ('/Select/Matching Nodes', 'menu_show_global', '', 'numbersign'),
62 ('/Select/Duplicate Siblings', 'do_select_dups', '', ''),
63 ('/Select/To Mark', 'do_select_marked', '', 'minus'),
64 ('/Select/Child Nodes', 'do_select_children', '', 'asterisk'),
66 ('/Mark', None, '<Branch>', ''),
67 ('/Mark/Mark Selection', 'do_mark_selection', '', 'm'),
68 ('/Mark/Switch with Selection', 'do_mark_switch', '', 'comma'),
69 ('/Mark/Clear Mark', 'do_clear_mark', '<StockItem>', '', g
.STOCK_CLEAR
),
70 ('/Mark/Move marked here', 'do_move_marked', '<StockItem>', '', g
.STOCK_GO_FORWARD
),
72 ('/Network', None, '<Branch>', ''),
73 ('/Network/HTTP GET', 'do_suck', '', '<Shift>asciicircum'),
74 ('/Network/HTTP POST', 'do_http_post', '', ''),
75 ('/Network/Send SOAP message', 'do_soap_send', '', ''),
77 ('/Create', None, '<Branch>', ''),
78 ('/Create/Insert node', 'menu_insert_element', '<StockItem>', 'I', g
.STOCK_ADD
),
79 ('/Create/Append node', 'menu_append_element', '<StockItem>', 'A', g
.STOCK_ADD
),
80 ('/Create/Open node inside', 'menu_open_element', '<StockItem>', 'O', g
.STOCK_ADD
),
81 ('/Create/Open node at end', 'menu_open_element_end', '<StockItem>', 'E', g
.STOCK_ADD
),
83 ('/Process', None, '<Branch>', ''),
84 ('/Process/Substitute...', 'menu_show_subst', '', 's'),
85 ('/Process/Split at...', 'menu_show_split', '', ''),
86 ('/Process/Python expression...', 'menu_show_pipe', '', '<Shift>exclam'),
87 ('/Process/XPath expression...', 'menu_show_xpath', '', ''),
88 ('/Process/Normalise', 'do_normalise', '', ''),
89 ('/Process/Remove default namespaces', 'do_remove_ns', '', 'r'),
90 ('/Process/Convert to text', 'do_convert_to_text', '', ''),
91 ('/Process/Convert to comment', 'do_convert_to_comment', '', ''),
92 ('/Process/Convert to element', 'do_convert_to_element', '', ''),
94 ('/Program', None, '<Branch>', ''),
95 ('/Program/Input', 'menu_show_ask', '', 'question'),
96 ('/Program/Compare', 'do_compare', '', 'equal'),
97 ('/Program/Pass', 'do_pass', '', ''),
98 ('/Program/Fail', 'do_fail', '', ''),
99 ('/Program/Assert', 'menu_assert', '', ''),
100 ('/Program/Repeat last', 'do_again', '', 'dot'),
102 ('/View', None, '<Branch>', ''),
103 ('/View/Toggle hidden', 'do_toggle_hidden', '', '<Ctrl>H'),
104 ('/View/Hide with label', 'menu_hide_with_expr', '', ''),
105 ('/View/Show as HTML', 'do_show_html', '', ''),
106 ('/View/Show as canvas', 'do_show_canvas', '', ''),
107 ('/View/Show namespaces', 'show_namespaces', '', '<Ctrl>semicolon'),
108 ('/View/Close Window', 'menu_close_window', '<StockItem>', '<Ctrl>Q', g
.STOCK_CLOSE
),
110 #('/Options...', 'menu_options', '', '<Ctrl>O'),
114 return lambda(self
): self
.view
.may_record([action
])
116 class GUIView(Display
, XDSLoader
):
117 def __init__(self
, window
, view
):
118 Display
.__init
__(self
, window
, view
)
119 XDSLoader
.__init
__(self
, ['application/x-dome', 'text/xml',
121 window
.connect('key-press-event', self
.key_press
)
122 self
.cursor_node
= None
123 self
.edit_dialog
= None
126 menu
.attach(window
, self
)
128 def destroyed(self
, widget
):
129 print "GUIView destroyed!"
130 Display
.destroyed(self
, widget
)
133 def update_state(self
):
134 if self
.view
.rec_point
:
135 state
= "(recording)"
136 elif self
.view
.idle_cb
or self
.view
.op_in_progress
:
140 self
.parent_window
.set_state(state
)
143 def xds_load_from_stream(self
, path
, type, stream
):
145 raise Exception('Can only load from files... sorry!')
146 if path
.endswith('.html'):
147 self
.view
.load_html(path
)
149 self
.view
.load_xml(path
)
150 if self
.view
.root
== self
.view
.model
.get_root():
151 self
.parent_window
.uri
= path
152 self
.parent_window
.update_title()
154 def key_press(self
, widget
, kev
):
155 focus
= widget
.focus_widget
156 if focus
and focus
is not widget
and focus
.get_toplevel() is widget
:
158 return TRUE
# Handled
162 if kev
.keyval
== keysyms
.Up
:
163 self
.view
.may_record(['move_prev_sib'])
164 elif kev
.keyval
== keysyms
.Down
:
165 self
.view
.may_record(['move_next_sib'])
166 elif kev
.keyval
== keysyms
.Left
:
167 self
.view
.may_record(['move_left'])
168 elif kev
.keyval
== keysyms
.Right
:
169 self
.view
.may_record(['move_right'])
170 elif kev
.keyval
== keysyms
.KP_Add
:
171 self
.menu_show_add_attrib()
172 elif kev
.keyval
== keysyms
.Tab
:
178 def do_drag(self
, src
, dst
):
180 return # Don't do attribute drags yet
181 src
, dst
= src
[0], dst
[0]
182 path
= make_relative_path(src
, dst
, None, self
.view
.model
.namespaces
)
183 self
.view
.may_record(['move_selection', path
])
185 def node_clicked(self
, node
, bev
):
186 #print "Clicked", node.namespaceURI, node.localName
188 if bev
.type == g
.gdk
.BUTTON_PRESS
:
189 if len(self
.view
.current_nodes
) == 0:
192 src
= self
.view
.current_nodes
[-1]
193 shift
= bev
.state
& g
.gdk
.SHIFT_MASK
194 add
= bev
.state
& g
.gdk
.CONTROL_MASK
195 select_region
= shift
and node
.nodeType
== Node
.ELEMENT_NODE
196 lit
= shift
and not select_region
198 path
= make_relative_path(src
, node
, lit
, self
.view
.model
.namespaces
)
199 if path
== '.' and self
.view
.current_nodes
and not self
.view
.current_attrib
:
202 self
.view
.may_record(["select_region", path
, "unused"])
204 self
.view
.may_record(["do_search", path
, "unused", add
])
206 self
.view
.may_record(["toggle_hidden"])
208 def attrib_clicked(self
, element
, attrib
, event
):
209 if len(self
.view
.current_nodes
) == 0:
212 src
= self
.view
.current_nodes
[-1]
214 print "attrib_clicked", attrib
, attrib
.namespaceURI
, attrib
.localName
215 path
= make_relative_path(src
, element
, FALSE
, self
.view
.model
.namespaces
)
217 self
.view
.may_record(["do_search", path
, "unused", FALSE
])
218 self
.view
.may_record(["attribute", attrib
.namespaceURI
, attrib
.localName
])
221 self
.parent_window
.save()
223 def show_menu(self
, bev
):
224 menu
.popup(self
, bev
)
226 def playback(self
, macro
, map):
227 "Called when the user clicks on a macro button."
228 Exec
.exec_state
.clean()
230 self
.view
.may_record(['map', macro
.uri
])
232 self
.view
.may_record(['play', macro
.uri
])
234 def menu_show_ask(self
):
235 def do_ask(q
, self
= self
):
237 self
.view
.may_record(action
)
238 GetArg('Input:', do_ask
, ('Prompt:',))
240 def menu_show_subst(self
):
241 def do_subst(args
, self
= self
):
242 action
= ["subst", args
[0], args
[1]]
243 self
.view
.may_record(action
)
244 GetArg('Substitute:', do_subst
, ('Replace:', 'With:'))
246 def show_namespaces(self
):
248 Namespaces
.GUI(self
.view
.model
).show()
250 def move_from(self
, old
= []):
252 Display
.move_from(self
, old
)
254 def hide_editbox(self
):
256 if self
.cursor_attrib
:
257 self
.cursor_hidden_text
.set(text
= '%s=%s' %
258 (self
.cursor_attrib
.name
, self
.cursor_attrib
.value
))
259 self
.cursor_hidden_text
.show()
260 self
.auto_highlight(self
.cursor_node
)
261 self
.cursor_node
= None
262 self
.edit_box_item
.destroy()
264 def toggle_edit(self
):
265 node
= self
.view
.current_nodes
[0]
266 attrib
= self
.view
.current_attrib
269 self
.edit_dialog
.destroy()
270 self
.edit_dialog
= rox
.Dialog()
271 eb
= self
.edit_dialog
273 if node
.nodeType
== Node
.ELEMENT_NODE
:
275 text
= unicode(attrib
.value
)
280 entry
.set_activates_default(True)
281 def get_text(): return entry
.get_text()
283 text
= node
.nodeValue
285 buffer = entry
.get_buffer()
286 buffer.insert_at_cursor(text
)
287 entry
.set_size_request(400, 200)
290 start
= buffer.get_start_iter()
291 end
= buffer.get_end_iter()
292 return buffer.get_text(start
, end
, True)
293 eb
.vbox
.pack_start(entry
, TRUE
, FALSE
, 0)
295 eb
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
296 eb
.add_button(g
.STOCK_APPLY
, g
.RESPONSE_OK
)
297 eb
.set_default_response(g
.RESPONSE_OK
)
300 self
.edit_dialog
= None
301 eb
.connect('destroy', destroy
)
302 def response(eb
, resp
):
303 if resp
== g
.RESPONSE_CANCEL
:
305 elif resp
== g
.RESPONSE_OK
:
309 self
.view
.may_record(['set_attrib', new
])
311 self
.view
.may_record(['change_node', new
])
313 eb
.connect('response', response
)
317 def menu_select_attrib(self
):
320 (prefix
, localName
) = name
.split(':', 1)
321 namespaceURI
= self
.view
.model
.prefix_to_namespace(self
.view
.get_current(), prefix
)
323 (prefix
, localName
) = (None, name
)
325 action
= ["attribute", namespaceURI
, localName
]
326 self
.view
.may_record(action
)
327 GetArg('Select attribute:', do_attrib
, ['Name:'])
329 def menu_show_add_attrib(self
):
331 action
= ["add_attrib", "UNUSED", name
]
332 self
.view
.may_record(action
)
333 GetArg('Create attribute:', do_it
, ['Name:'])
335 def menu_show_split(self
):
337 action
= ["split", expr
]
338 self
.view
.may_record(action
)
339 GetArg('Split at:', do_split
, ['Separator:'], "Split text node at:",
340 hints
= ((':', 'Split colon-separated items'),
341 ('\\n', 'Split at line breaks'),
342 ('', 'Split into words (empty pattern)')
345 def menu_show_pipe(self
):
347 action
= ["python", expr
]
348 self
.view
.may_record(action
)
349 GetArg('Python expression:', do_pipe
, ['Eval:'], "'x' is the old text...")
351 def menu_show_xpath(self
):
353 action
= ["xpath", expr
]
354 self
.view
.may_record(action
)
355 GetArg('XPath expression:', go
, ['Eval:'], "Result goes on the clipboard")
357 def menu_assert(self
):
359 action
= ["assert_xpath", expr
]
360 self
.view
.may_record(action
)
361 GetArg('Assert:', go
, ['Test:'], "Check this is true:",
362 hints
= (('. > 0', 'greater than zero'),
363 ('. = "Green"', "value is 'Green'"),
364 ('@href', "href attribute present"),
365 ('.//error', 'there are any <error>s in the subtree'),
368 def menu_hide_with_expr(self
):
370 action
= ["toggle_hidden", expr
]
371 self
.view
.may_record(action
)
372 GetArg('Hide with label:', go
, ['Pattern:'], "Generate each label from:",
373 hints
= (('title', 'the <title> child element'),
374 ('@title', "the 'title' attribute"),
375 ('count(.//*)', 'the number of elements in the subtree'),
376 ('substring(para[1], 1, 40)', 'the start of the first paragraph'),
379 def menu_show_global(self
):
380 def do_global(pattern
):
381 action
= ["do_global", pattern
]
382 self
.view
.may_record(action
)
383 GetArg('Select matching nodes', do_global
, ['Pattern:'],
384 'Select nodes which match:',
385 hints
= (('img', 'all <img> child elements'),
386 ('//img', 'all <img> elements anywhere'),
387 ('.//img', 'all <img> descendant elements'),
388 ('//img[@src]', 'all <img> elements with "src" attributes'),
389 ('//chapter[title="Intro"]',
390 'Any <chapter> with a <title>Intro</title> child'),
391 ('img[1]', 'the first child <img> element'),
392 ('text()', 'all child text nodes'),
393 ('//img[@src="@CURRENT@"]',
394 "all <img>s whose src is the current node's value"),
395 ('*[not(self::img)]',
396 "all child elements except <img> elements"),
399 def menu_show_text_search(self
):
400 def do_text_search(pattern
):
401 action
= ["do_text_search", pattern
]
402 self
.view
.may_record(action
)
403 GetArg('Search for:', do_text_search
, ['Text pattern:'],
404 '(@CURRENT@ is the current node\'s value)\n')
406 def menu_show_search(self
):
407 def do_search(pattern
):
408 action
= ["do_search", pattern
]
409 self
.view
.may_record(action
)
410 GetArg('Search for:',
411 do_search
, ['XPath:'],
412 '(@CURRENT@ is the current node\'s value)')
414 def menu_rename_attr(self
):
416 action
= ["rename_attrib", name
]
417 self
.view
.may_record(action
)
418 GetArg('Rename to:', do
, ['New name:'])
421 def do_create(self
, action
, nodeType
, data
):
424 if nodeType
== Node
.ELEMENT_NODE
:
426 elif nodeType
== Node
.TEXT_NODE
:
429 elif nodeType
== Node
.ATTRIBUTE_NODE
:
432 raise Exception("Unknown nodeType %d" % nodeType
)
435 # Check name is valid
436 # XXX: Should be more strict
438 assert '\n' not in data
439 assert ' ' not in data
441 self
.view
.may_record(['add_node', action
, data
])
443 def show_add_box(self
, action
):
446 elif action
[0] == 'a':
448 elif action
[0] == 'o':
450 elif action
[0] == 'e':
457 elif action
[1] == 't':
465 box
.vbox
.pack_start(text
, TRUE
, FALSE
, 0)
466 text
.set_size_request(400, 200)
467 box
.set_has_separator(False)
469 box
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
470 box
.add_button('Add _Attribute', Node
.ATTRIBUTE_NODE
)
471 box
.add_button('Add _Text', Node
.TEXT_NODE
)
472 box
.add_button('Add _Element', Node
.ELEMENT_NODE
)
473 box
.set_default_response(g
.RESPONSE_OK
)
475 def response(box
, resp
):
476 if resp
in (g
.RESPONSE_CANCEL
, g
.RESPONSE_DELETE_EVENT
):
479 buffer = text
.get_buffer()
480 start
= buffer.get_start_iter()
481 end
= buffer.get_end_iter()
482 new
= buffer.get_text(start
, end
, True)
484 self
.do_create(action
, resp
, new
)
486 box
.connect('response', response
)
491 cur
= self
.view
.get_current()
492 if cur
.nodeType
== Node
.ELEMENT_NODE
:
494 return cur
.parentNode
.nodeName
496 def menu_insert_element(self
):
498 self
.show_add_box('ie')
500 def menu_append_element(self
):
502 self
.show_add_box('ae')
504 def menu_open_element(self
):
506 self
.show_add_box('oe')
508 def menu_open_element_end(self
):
509 "Open element at end"
510 self
.show_add_box('ee')
512 def menu_insert_text(self
):
514 self
.show_add_box('it')
516 def menu_append_text(self
):
518 self
.show_add_box('at')
520 def menu_open_text(self
):
522 self
.show_add_box('ot')
524 def menu_open_text_end(self
):
526 self
.show_add_box('et')
528 def menu_close_window(self
):
529 self
.parent_window
.destroy()
531 def menu_options(self
):
534 def menu_clear_undo(self
):
535 if rox
.confirm('Really clear the undo buffer?',
537 self
.view
.model
.clear_undo()
539 do_blank_all
= make_do('blank_all')
540 do_enter
= make_do('enter')
541 do_leave
= make_do('leave')
542 do_suck
= make_do('suck')
543 do_http_post
= make_do('http_post')
544 do_soap_send
= make_do('soap_send')
545 do_select_dups
= make_do('select_dups')
546 do_paste_attribs
= make_do('paste_attribs')
547 do_yank_value
= make_do('yank_value')
548 do_yank_attributes
= make_do('yank_attribs')
549 do_delete_node
= make_do('delete_node')
550 do_delete_node_no_clipboard
= make_do('delete_node_no_clipboard')
551 do_delete_shallow
= make_do('delete_shallow')
552 do_yank
= make_do('yank')
553 do_shallow_yank
= make_do('shallow_yank')
554 do_put_replace
= make_do('put_replace')
555 do_put_as_child
= make_do('put_as_child')
556 do_put_before
= make_do('put_before')
557 do_put_after
= make_do('put_after')
558 do_undo
= make_do('undo')
559 do_redo
= make_do('redo')
560 do_fail
= make_do('fail')
561 do_pass
= make_do('do_pass')
562 do_toggle_hidden
= make_do('toggle_hidden')
563 do_show_html
= make_do('show_html')
564 do_show_canvas
= make_do('show_canvas')
565 do_compare
= make_do('compare')
566 do_again
= make_do('again')
567 do_normalise
= make_do('normalise')
568 do_convert_to_text
= make_do('convert_to_text')
569 do_convert_to_comment
= make_do('convert_to_comment')
570 do_convert_to_element
= make_do('convert_to_element')
571 do_convert_to_pi
= make_do('convert_to_pi')
572 do_remove_ns
= make_do('remove_ns')
574 do_clear_mark
= make_do('clear_mark')
575 do_move_marked
= make_do('move_marked')
576 do_mark_switch
= make_do('mark_switch')
577 do_mark_selection
= make_do('mark_selection')
578 do_select_marked
= make_do('select_marked_region')
579 do_select_children
= make_do('select_children')
581 move_home
= make_do('move_home')
582 move_end
= make_do('move_end')
583 move_left
= make_do('move_left')
584 move_right
= make_do('move_right')
585 move_next_sib
= make_do('move_next_sib')
586 move_prev_sib
= make_do('move_prev_sib')