Set background colour when hiding/showing.
[dom-editor.git] / Dome / GUIView.py
blob6a7e486179d4cc72512464f234005c39bccea174
1 from xml.dom import Node
2 from rox.loading import XDSLoader
4 import rox
5 from rox import g, TRUE, FALSE
6 keysyms = g.keysyms
8 from View import View
9 from Display import Display
10 from Beep import Beep
11 from GetArg import GetArg
12 from Path import make_relative_path
14 from rox.Menu import Menu
15 from gnome2 import canvas
17 menu = Menu('main', [
18 ('/File', None, '<Branch>', ''),
19 ('/File/Save', 'menu_save', '', 'F3'),
20 ('/File/Blank document', 'do_blank_all', '', '<Ctrl>N'),
21 ('/File/Clear undo buffer', 'menu_clear_undo', '', ''),
23 ('/Edit', None, '<Branch>', ''),
24 ('/Edit/Yank attributes', 'do_yank_attributes', '', ''),
25 ('/Edit/Paste attributes', 'do_paste_attribs', '', ''),
26 ('/Edit/Yank attrib value', 'do_yank_value', '', ''),
27 ('/Edit/', '', '', '<separator>'),
28 ('/Edit/Cut', 'do_delete_node', '', 'x'),
29 ('/Edit/Delete', 'do_delete_node_no_clipboard', '', '<Ctrl>X'),
30 ('/Edit/Shallow cut', 'do_delete_shallow', '', '<Shift>X'),
31 ('/Edit/', '', '', '<separator>'),
32 ('/Edit/Yank', 'do_yank', '', 'y'),
33 ('/Edit/Shallow yank', 'do_shallow_yank', '', '<Shift>Y'),
34 ('/Edit/', '', '', '<separator>'),
35 ('/Edit/Paste (replace)','do_put_replace', '', '<Shift>R'),
36 ('/Edit/Paste (inside)', 'do_put_as_child', '', 'bracketright'),
37 ('/Edit/Paste (before)', 'do_put_before', '', '<Shift>P'),
38 ('/Edit/Paste (after)', 'do_put_after', '', 'p'),
39 ('/Edit/', '', '', '<separator>'),
40 ('/Edit/Edit value', 'toggle_edit', '', 'Return'),
41 ('/Edit/', '', '', '<separator>'),
42 ('/Edit/Undo', 'do_undo', '', 'u'),
43 ('/Edit/Redo', 'do_redo', '', '<Ctrl>R'),
45 ('/Move', None, '<Branch>', ''),
46 ('/Move/XPath search', 'menu_show_search', '', 'slash'),
47 ('/Move/Text search', 'menu_show_text_search', '', 'T'),
48 ('/Move/Enter', 'do_enter', '', '<Shift>greater'),
49 ('/Move/Leave', 'do_leave', '', '<Shift>less'),
51 ('/Move/Root node', 'move_home', '', 'Home'),
52 ('/Move/Previous sibling', 'move_prev_sib', '', 'Up'),
53 ('/Move/Next sibling', 'move_next_sib', '', 'Down'),
54 ('/Move/Parent', 'move_left', '', 'Left'),
55 ('/Move/First child', 'move_right', '', 'Right'),
56 ('/Move/Last child', 'move_end', '', 'End'),
58 ('/Move/To attribute', 'menu_select_attrib', '', 'At'),
60 ('/Select', None, '<Branch>', ''),
61 ('/Select/By XPath', 'menu_show_global', '', 'numbersign'),
62 ('/Select/Duplicate Siblings', 'do_select_dups', '', ''),
63 ('/Select/To Mark', 'do_select_marked', '', 'minus'),
65 ('/Mark', None, '<Branch>', ''),
66 ('/Mark/Mark Selection', 'do_mark_selection', '', 'm'),
67 ('/Mark/Switch with Selection', 'do_mark_switch', '', 'comma'),
68 ('/Mark/Clear Mark', 'do_clear_mark', '', ''),
70 ('/Network', None, '<Branch>', ''),
71 ('/Network/HTTP GET', 'do_suck', '', '<Shift>asciicircum'),
72 ('/Network/HTTP POST', 'do_http_post', '', ''),
73 ('/Network/Send SOAP message', 'do_soap_send', '', ''),
75 ('/Create', None, '<Branch>', ''),
76 ('/Create/Insert element', 'menu_insert_element', '', '<Shift>I'),
77 ('/Create/Append element', 'menu_append_element', '', '<Shift>A'),
78 ('/Create/Open element', 'menu_open_element', '', '<Shift>O'),
79 ('/Create/Open element at end', 'menu_open_element_end', '', '<Shift>E'),
81 ('/Create/', '', '', '<separator>'),
83 ('/Create/Insert text node', 'menu_insert_text', '', 'I'),
84 ('/Create/Append text node', 'menu_append_text', '', 'A'),
85 ('/Create/Open text node', 'menu_open_text', '', 'O'),
86 ('/Create/Open text node at end', 'menu_open_text_end', '', 'E'),
88 ('/Create/', '', '', '<separator>'),
90 ('/Create/Attribute', 'menu_show_add_attrib', '', '<Shift>plus'),
92 ('/Process', None, '<Branch>', ''),
93 ('/Process/Substitute', 'menu_show_subst', '', 's'),
94 ('/Process/Python expression', 'menu_show_pipe', '', '<Shift>exclam'),
95 ('/Process/Normalise', 'do_normalise', '', ''),
96 ('/Process/Remove default namespaces', 'do_remove_ns', '', 'r'),
97 ('/Process/Comment to text', 'do_convert_to_text', '', ''),
99 ('/Program', None, '<Branch>', ''),
100 ('/Program/Input', 'menu_show_ask', '', 'question'),
101 ('/Program/Compare', 'do_compare', '', 'equal'),
102 ('/Program/Fail', 'do_fail', '', ''),
103 ('/Program/Pass', 'do_pass', '', ''),
104 ('/Program/Repeat last', 'do_again', '', 'dot'),
106 ('/View', None, '<Branch>', ''),
107 ('/View/Toggle hidden', 'do_toggle_hidden', '', '<Ctrl>H'),
108 ('/View/Show as HTML', 'do_show_html', '', ''),
109 ('/View/Show as canvas', 'do_show_canvas', '', ''),
110 ('/View/Close Window', 'menu_close_window', '', '<Ctrl>Q'),
112 #('/Options...', 'menu_options', '', '<Ctrl>O'),
115 def make_do(action):
116 return lambda(self): self.view.may_record([action])
118 class GUIView(Display, XDSLoader):
119 def __init__(self, window, view):
120 Display.__init__(self, window, view)
121 XDSLoader.__init__(self, ['application/x-dome', 'text/xml',
122 'application/xml'])
123 window.connect('key-press-event', self.key_press)
124 self.cursor_node = None
125 self.update_state()
127 menu.attach(window, self)
129 def update_state(self):
130 if self.view.rec_point:
131 state = "(recording)"
132 elif self.view.idle_cb or self.view.op_in_progress:
133 state = "(playing)"
134 else:
135 state = ""
136 self.parent_window.set_state(state)
137 self.do_update_now()
139 def xds_load_from_stream(self, path, type, stream):
140 if not path:
141 raise Exception('Can only load from files... sorry!')
142 if path.endswith('.html'):
143 self.view.load_html(path)
144 else:
145 self.view.load_xml(path)
146 if self.view.root == self.view.model.get_root():
147 self.parent_window.uri = path
148 self.parent_window.update_title()
150 def key_press(self, widget, kev):
151 focus = widget.focus_widget
152 if focus and focus is not widget and focus.get_toplevel() is widget:
153 if focus.event(kev):
154 return TRUE # Handled
156 if self.cursor_node:
157 return 0
158 if kev.keyval == keysyms.Up:
159 self.view.may_record(['move_prev_sib'])
160 elif kev.keyval == keysyms.Down:
161 self.view.may_record(['move_next_sib'])
162 elif kev.keyval == keysyms.Left:
163 self.view.may_record(['move_left'])
164 elif kev.keyval == keysyms.Right:
165 self.view.may_record(['move_right'])
166 elif kev.keyval == keysyms.KP_Add:
167 self.menu_show_add_attrib()
168 elif kev.keyval == keysyms.Tab:
169 self.toggle_edit()
170 else:
171 return 0
172 return 1
174 def node_clicked(self, node, bev):
175 print "Clicked", node.namespaceURI, node.localName
176 if node:
177 if bev.type == g.gdk.BUTTON_PRESS:
178 if len(self.view.current_nodes) == 0:
179 src = self.view.root
180 else:
181 src = self.view.current_nodes[-1]
182 shift = bev.state & g.gdk.SHIFT_MASK
183 add = bev.state & g.gdk.CONTROL_MASK
184 select_region = shift and node.nodeType == Node.ELEMENT_NODE
185 lit = shift and not select_region
187 ns = {}
188 path = make_relative_path(src, node, lit, ns)
189 if path == '.' and self.view.current_nodes and not self.view.current_attrib:
190 return
191 if select_region:
192 self.view.may_record(["select_region", path, ns])
193 else:
194 self.view.may_record(["do_search", path, ns, add])
195 else:
196 self.view.may_record(["toggle_hidden"])
198 def attrib_clicked(self, element, attrib, event):
199 if len(self.view.current_nodes) == 0:
200 src = self.view.root
201 else:
202 src = self.view.current_nodes[-1]
203 ns = {}
205 print "attrib_clicked", attrib, attrib.namespaceURI, attrib.localName
206 path = make_relative_path(src, element, FALSE, ns)
207 if path != '.':
208 self.view.may_record(["do_search", path, ns, FALSE])
209 self.view.may_record(["attribute", attrib.namespaceURI, attrib.localName])
211 def menu_save(self):
212 self.parent_window.save()
214 def show_menu(self, bev):
215 menu.popup(self, bev)
217 def playback(self, macro, map):
218 "Called when the user clicks on a macro button."
219 Exec.exec_state.clean()
220 if map:
221 self.view.may_record(['map', macro.uri])
222 else:
223 self.view.may_record(['play', macro.uri])
225 def menu_show_ask(self):
226 def do_ask(q, self = self):
227 action = ["ask", q]
228 self.view.may_record(action)
229 GetArg('Input:', do_ask, ('Prompt:',))
231 def menu_show_subst(self):
232 def do_subst(args, self = self):
233 action = ["subst", args[0], args[1]]
234 self.view.may_record(action)
235 GetArg('Substitute:', do_subst, ('Replace:', 'With:'))
237 def move_from(self, old = []):
238 self.hide_editbox()
239 Display.move_from(self, old)
241 def hide_editbox(self):
242 if self.cursor_node:
243 if self.cursor_attrib:
244 self.cursor_hidden_text.set(text = '%s=%s' %
245 (self.cursor_attrib.name, self.cursor_attrib.value))
246 self.cursor_hidden_text.show()
247 self.auto_highlight(self.cursor_node)
248 self.cursor_node = None
249 self.edit_box_item.destroy()
251 def show_editbox(self):
252 "Edit the current node/attribute"
253 self.do_update_now()
255 if self.cursor_node:
256 self.hide_editbox()
258 if not self.visible:
259 raise Exception("Can't edit while display is hidden!")
261 self.cursor_node = self.view.current_nodes[0]
262 group = self.node_to_group[self.cursor_node]
263 self.cursor_attrib = self.view.current_attrib
265 self.highlight(group, FALSE)
267 if self.cursor_attrib:
268 group = group.attrib_to_group[self.cursor_attrib]
270 self.cursor_hidden_text = group.text
271 if not self.cursor_attrib:
272 # Don't hide for attributes, so we can still see the name
273 group.text.hide()
274 else:
275 group.text.set(text = str(self.cursor_attrib.name) + '=')
277 self.update_now() # GnomeCanvas bug?
278 lx, ly, hx, hy = group.text.get_bounds()
279 x, y = group.i2w(lx, ly)
281 text = g.TextView()
282 text.show()
284 eb = g.Frame()
285 eb.add(text)
286 self.edit_box = eb
287 self.edit_box_text = text
288 m = 3
290 #s = eb.get_style().copy()
291 #s.font = load_font('fixed')
292 #eb.set_style(s)
293 #if self.cursor_attrib:
294 # name_width = s.font.measure(self.cursor_attrib.name + '=') + 1
295 #else:
296 # name_width = 0
297 name_width = 0
299 self.edit_box_item = self.root().add(canvas.CanvasWidget, widget = eb,
300 x = x - m + name_width, y = y - m,
301 anchor = g.ANCHOR_NW)
303 #text.set_editable(TRUE)
304 text.get_buffer().insert_at_cursor(self.get_edit_text(), -1)
305 text.set_wrap_mode(g.WRAP_WORD)
306 text.get_buffer().connect('changed', self.eb_changed)
307 text.connect('key-press-event', self.eb_key)
308 eb.show()
309 text.realize()
310 self.size_eb()
311 text.grab_focus()
312 #eb.select_region(0, -1)
313 eb.show()
315 def get_edit_text(self):
316 node = self.cursor_node
317 if node.nodeType == Node.ELEMENT_NODE:
318 if self.cursor_attrib:
319 return str(self.cursor_attrib.value)
320 return node.nodeName
321 else:
322 return node.nodeValue
324 def eb_key(self, eb, kev):
325 key = kev.keyval
326 if key == g.keysyms.KP_Enter:
327 key = g.keysyms.Return
328 if key == g.keysyms.Escape:
329 self.hide_editbox()
330 elif key == g.keysyms.Return and kev.state & g.gdk.CONTROL_MASK:
331 eb.insert_defaults('\n')
332 self.size_eb()
333 elif key == g.keysyms.Tab or key == g.keysyms.Return:
334 buffer = eb.get_buffer()
335 s = buffer.get_start_iter()
336 e = buffer.get_end_iter()
337 text = buffer.get_text(s, e, TRUE)
338 try:
339 if text != self.get_edit_text():
340 self.commit_edit(text)
341 finally:
342 self.hide_editbox()
343 return 0
345 def commit_edit(self, new):
346 if self.cursor_attrib:
347 self.view.may_record(['set_attrib', new])
348 else:
349 self.view.may_record(['change_node', new])
351 def eb_changed(self, eb):
352 self.size_eb()
354 def size_eb(self):
355 def cb():
356 req = self.edit_box_text.size_request()
357 print "Wants", req
358 width = max(req[0], 10)
359 height = max(req[1], 10)
360 self.edit_box_item.set(width = width + 12, height = height + 4)
361 g.idle_add(cb)
363 def toggle_edit(self):
364 if self.cursor_node:
365 self.hide_editbox()
366 else:
367 self.show_editbox()
369 def menu_select_attrib(self):
370 def do_attrib(name, self = self):
371 if ':' in name:
372 (prefix, localName) = name.split(':', 1)
373 else:
374 (prefix, localName) = (None, name)
375 namespaceURI = self.view.model.prefix_to_namespace(self.view.get_current(), prefix)
376 action = ["attribute", namespaceURI, localName]
377 self.view.may_record(action)
378 GetArg('Select attribute:', do_attrib, ['Name:'])
380 def menu_show_add_attrib(self):
381 def do_it(name, self = self):
382 action = ["add_attrib", "UNUSED", name]
383 self.view.may_record(action)
384 GetArg('Create attribute:', do_it, ['Name:'])
386 def menu_show_pipe(self):
387 def do_pipe(expr, self = self):
388 action = ["python", expr]
389 self.view.may_record(action)
390 GetArg('Python expression:', do_pipe, ['Eval:'], "'x' is the old text...")
392 def menu_show_global(self):
393 def do_global(pattern, self = self):
394 action = ["do_global", pattern]
395 self.view.may_record(action)
396 GetArg('Global:', do_global, ['Pattern:'],
397 '(@CURRENT@ is the current node\'s value)\n' +
398 'Perform next action on all nodes matching')
400 def menu_show_text_search(self):
401 def do_text_search(pattern, self = self):
402 action = ["do_text_search", pattern]
403 self.view.may_record(action)
404 GetArg('Search for:', do_text_search, ['Text pattern:'],
405 '(@CURRENT@ is the current node\'s value)\n')
407 def menu_show_search(self):
408 def do_search(pattern, self = self):
409 action = ["do_search", pattern]
410 self.view.may_record(action)
411 GetArg('Search for:',
412 do_search, ['XPath:'],
413 '(@CURRENT@ is the current node\'s value)')
415 def show_add_box(self, action):
416 if action[0] == 'i':
417 text = 'Insert'
418 elif action[0] == 'a':
419 text = 'Append'
420 elif action[0] == 'o':
421 text = 'Open'
422 elif action[0] == 'e':
423 text = 'Open at end'
424 else:
425 assert 0
426 if action[1] == 'e':
427 text += ' element'
428 prompt = 'Node name'
429 elif action[1] == 't':
430 text += ' text'
431 prompt = 'Text'
432 else:
433 assert 0
435 def cb(value):
436 self.view.may_record(['add_node', action, value])
437 GetArg('Add node', cb, [prompt], text)
439 def new_name(self):
440 cur = self.view.get_current()
441 if cur.nodeType == Node.ELEMENT_NODE:
442 return cur.nodeName
443 return cur.parentNode.nodeName
445 def menu_insert_element(self):
446 "Insert element"
447 self.show_add_box('ie')
449 def menu_append_element(self):
450 "Append element"
451 self.show_add_box('ae')
453 def menu_open_element(self):
454 "Open element"
455 self.show_add_box('oe')
457 def menu_open_element_end(self):
458 "Open element at end"
459 self.show_add_box('ee')
461 def menu_insert_text(self):
462 "Insert text"
463 self.show_add_box('it')
465 def menu_append_text(self):
466 "Append text"
467 self.show_add_box('at')
469 def menu_open_text(self):
470 "Open text"
471 self.show_add_box('ot')
473 def menu_open_text_end(self):
474 "Open text at end"
475 self.show_add_box('et')
477 def menu_close_window(self):
478 self.parent_window.destroy()
480 def menu_options(self):
481 rox.edit_options()
483 def menu_clear_undo(self):
484 if rox.confirm('Really clear the undo buffer?',
485 g.STOCK_CLEAR):
486 self.view.model.clear_undo()
488 do_blank_all = make_do('blank_all')
489 do_enter = make_do('enter')
490 do_leave = make_do('leave')
491 do_suck = make_do('suck')
492 do_http_post = make_do('http_post')
493 do_soap_send = make_do('soap_send')
494 do_select_dups = make_do('select_dups')
495 do_paste_attribs = make_do('paste_attribs')
496 do_yank_value = make_do('yank_value')
497 do_yank_attributes = make_do('yank_attribs')
498 do_delete_node = make_do('delete_node')
499 do_delete_node_no_clipboard = make_do('delete_node_no_clipboard')
500 do_delete_shallow = make_do('delete_shallow')
501 do_yank = make_do('yank')
502 do_shallow_yank = make_do('shallow_yank')
503 do_put_replace = make_do('put_replace')
504 do_put_as_child = make_do('put_as_child')
505 do_put_before = make_do('put_before')
506 do_put_after = make_do('put_after')
507 do_undo = make_do('undo')
508 do_redo = make_do('redo')
509 do_fail = make_do('fail')
510 do_pass = make_do('do_pass')
511 do_toggle_hidden = make_do('toggle_hidden')
512 do_show_html = make_do('show_html')
513 do_show_canvas = make_do('show_canvas')
514 do_compare = make_do('compare')
515 do_again = make_do('again')
516 do_normalise = make_do('normalise')
517 do_convert_to_text = make_do('convert_to_text')
518 do_remove_ns = make_do('remove_ns')
520 do_clear_mark = make_do('clear_mark')
521 do_mark_switch = make_do('mark_switch')
522 do_mark_selection = make_do('mark_selection')
523 do_select_marked = make_do('select_marked_region')
525 move_home = make_do('move_home')
526 move_end = make_do('move_end')
527 move_left = make_do('move_left')
528 move_right = make_do('move_right')
529 move_next_sib = make_do('move_next_sib')
530 move_prev_sib = make_do('move_prev_sib')