Moved into a sub-dir, so that a svn checkout has the same structure as
[rox-lib/lack.git] / ROX-Lib2 / python / rox / OptionsBox.py
blobd259fda97c280668a21dc33940f9c5d6f4772599
1 """The OptionsBox widget is used to edit an OptionGroup.
2 For simple applications, rox.edit_options() provides an
3 easy way to edit the options.
5 You can add new types of option by appending to widget_registry (new
6 in ROX-Lib 1.9.13). Return a list of widgets (which are packed into either an
7 HBox or a VBox). For example, to add a button widget:
9 def build_button(box, node, label):
10 button = g.Button(label)
11 box.may_add_tip(button, node)
12 button.connect('clicked', my_button_handler)
13 return [button]
14 OptionsBox.widget_registry['button'] = build_button
16 You can then create such a button in Options.xml with:
18 <button label='...'>Tooltip</button>
20 Any element may have a 'size-group' attribute. Certain widgets (labels in
21 particular) in the same size group all have the same size.
23 For widgets that have options, your build function will be called with
24 the option as a third parameter. You should register get and set methods,
25 and arrange for box.check_widget to be called when the user changes the
26 value:
28 def build_toggle(box, node, label, option):
29 toggle = g.CheckButton(label)
30 box.may_add_tip(toggle, node)
32 box.handlers[option] = (
33 lambda: str(toggle.get_active()),
34 lambda: toggle.set_active(option.int_value))
36 toggle.connect('toggled', lambda w: box.check_widget(option))
38 return [toggle]
39 OptionsBox.widget_registry['mytoggle'] = build_toggle
40 """
42 from rox import g, options, _
43 import rox
44 from xml.dom import Node, minidom
45 import gobject
47 REVERT = 1
49 # Functions for extracting data from XML nodes
50 def data(node):
51 """Return all the text directly inside this DOM Node."""
52 return ''.join([text.nodeValue for text in node.childNodes
53 if text.nodeType == Node.TEXT_NODE])
55 def bool_attr(node, name, val=False):
56 """Interpret node attribute as a boolean value"""
57 try:
58 v=node.getAttribute(name)
59 if v=='yes':
60 return True
61 else:
62 return False
63 except:
64 pass
65 return val
67 def str_attr(node, name, val=''):
68 """Get string value of node attribute"""
69 try:
70 val=node.getAttribute(name)
71 except:
72 pass
73 return val
75 class OptionsBox(g.Dialog):
76 """A dialog box which lets the user edit the options. The file
77 Options.xml specifies the layout of this box."""
79 tips = None # GtkTooltips
80 options = None # The OptionGroup we are editing
81 revert = None # Option -> old value
82 handlers = None # Option -> (get, set)
83 trans = None # Translation function (application's, not ROX-Lib's)
85 def __init__(self, options_group, options_xml, translation = None):
86 """options_xml is an XML file, usually <app_dir>/Options.xml,
87 which defines the layout of the OptionsBox.
89 It contains an <options> root element containing (nested)
90 <section> elements. Each <section> contains a number of widgets,
91 some of which correspond to options. The build_* functions are
92 used to create them.
94 Example:
96 <?xml version='1.0'?>
97 <options>
98 <section title='First section'>
99 <label>Here are some options</label>
100 <entry name='default_name' label='Default file name'>
101 When saving an untitled file, use this name as the default.
102 </entry>
103 <section title='Nested section'>
105 </section>
106 </section>
107 </options>
109 assert isinstance(options_group, options.OptionGroup)
111 if translation is None:
112 import __main__
113 if hasattr(__main__.__builtins__, '_'):
114 translation = __main__.__builtins__._
115 else:
116 translation = lambda x: x
117 self.trans = translation
119 g.Dialog.__init__(self)
120 self.tips = g.Tooltips()
121 self.set_has_separator(False)
123 self.options = options_group
124 self.set_title((_('%s options')) % options_group.program)
125 self.set_position(g.WIN_POS_CENTER)
127 button = rox.ButtonMixed(g.STOCK_UNDO, _('_Revert'))
128 self.add_action_widget(button, REVERT)
129 self.tips.set_tip(button, _('Restore all options to how they were '
130 'when the window was opened'))
132 self.add_button(g.STOCK_OK, g.RESPONSE_OK)
134 self.set_default_response(g.RESPONSE_OK)
136 doc = minidom.parse(options_xml)
137 assert doc.documentElement.localName == 'options'
139 self.handlers = {} # Option -> (get, set)
140 self.revert = {} # Option -> old value
141 self.size_groups = {} # Name -> GtkSizeGroup
142 self.current_size_group = None
144 sections = []
145 for section in doc.documentElement.childNodes:
146 if section.nodeType != Node.ELEMENT_NODE:
147 continue
148 if section.localName != 'section':
149 print "Unknown section", section
150 continue
151 sections.append(section)
153 self.build_window_frame(add_frame = len(sections) > 1)
155 # Add each section
156 for section in sections:
157 self.build_section(section, None)
158 if len(sections) > 1:
159 self.tree_view.expand_all()
160 else:
161 self.sections_swin.hide()
163 self.updating = 0
165 def destroyed(widget):
166 rox.toplevel_unref()
167 if self.changed():
168 try:
169 self.options.save()
170 except:
171 rox.report_exception()
172 self.connect('destroy', destroyed)
174 def got_response(widget, response):
175 if response == int(g.RESPONSE_OK):
176 self.destroy()
177 elif response == REVERT:
178 for o in self.options:
179 o._set(self.revert[o])
180 self.update_widgets()
181 self.options.notify()
182 self.update_revert()
183 self.connect('response', got_response)
185 def open(self):
186 """Show the window, updating all the widgets at the same
187 time. Use this instead of show()."""
188 rox.toplevel_ref()
189 for option in self.options:
190 self.revert[option] = option.value
191 self.update_widgets()
192 self.update_revert()
193 self.show()
195 def update_revert(self):
196 "Shade/unshade the Revert button. Internal."
197 self.set_response_sensitive(REVERT, self.changed())
199 def changed(self):
200 """Check whether any options have different values (ie, whether Revert
201 will do anything)."""
202 for option in self.options:
203 if option.value != self.revert[option]:
204 return True
205 return False
207 def update_widgets(self):
208 "Make widgets show current values. Internal."
209 assert not self.updating
210 self.updating = 1
212 try:
213 for option in self.options:
214 try:
215 handler = self.handlers[option][1]
216 except KeyError:
217 print "No widget for option '%s'!" % option
218 else:
219 handler()
220 finally:
221 self.updating = 0
223 def build_window_frame(self, add_frame = True):
224 "Create the main structure of the window."
225 hbox = g.HBox(False, 4)
226 self.vbox.pack_start(hbox, True, True, 0)
228 # scrolled window for the tree view
229 sw = g.ScrolledWindow()
230 sw.set_shadow_type(g.SHADOW_IN)
231 sw.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
232 hbox.pack_start(sw, False, True, 0)
233 self.sections_swin = sw # Used to hide it...
235 # tree view
236 model = g.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT)
237 tv = g.TreeView(model)
238 sel = tv.get_selection()
239 sel.set_mode(g.SELECTION_BROWSE)
240 tv.set_headers_visible(False)
241 self.sections = model
242 self.tree_view = tv
243 tv.unset_flags(g.CAN_FOCUS) # Stop irritating highlight
245 # Add a column to display column 0 of the store...
246 cell = g.CellRendererText()
247 column = g.TreeViewColumn('Section', cell, text = 0)
248 tv.append_column(column)
250 sw.add(tv)
252 # main options area
253 notebook = g.Notebook()
254 notebook.set_show_tabs(False)
255 notebook.set_show_border(False)
256 self.notebook = notebook
258 if add_frame:
259 frame = g.Frame()
260 frame.set_shadow_type(g.SHADOW_IN)
261 hbox.pack_start(frame, True, True, 0)
262 frame.add(notebook)
263 else:
264 hbox.pack_start(notebook, True, True, 0)
266 # Flip pages
267 # (sel = sel; pygtk bug?)
268 def change_page(tv, sel = sel, notebook = notebook):
269 selected = sel.get_selected()
270 if not selected:
271 return
272 model, titer = selected
273 page = model.get_value(titer, 1)
275 notebook.set_current_page(page)
276 sel.connect('changed', change_page)
278 self.vbox.show_all()
280 def check_widget(self, option):
281 "A widgets call this when the user changes its value."
282 if self.updating:
283 return
285 assert isinstance(option, options.Option)
287 new = self.handlers[option][0]()
289 if new == option.value:
290 return
292 option._set(new)
293 self.options.notify()
294 self.update_revert()
296 def build_section(self, section, parent):
297 """Create a new page for the notebook and a new entry in the
298 sections tree, and build all the widgets inside the page."""
299 page = g.VBox(False, 4)
300 page.set_border_width(4)
301 self.notebook.append_page(page, g.Label('unused'))
303 titer = self.sections.append(parent)
304 self.sections.set(titer,
305 0, self.trans(section.getAttribute('title')),
306 1, self.notebook.page_num(page))
307 for node in section.childNodes:
308 if node.nodeType != Node.ELEMENT_NODE:
309 continue
310 name = node.localName
311 if name == 'section':
312 self.build_section(node, titer)
313 else:
314 self.build_widget(node, page)
315 page.show_all()
317 def build_widget(self, node, box):
318 """Dispatches the job of dealing with a DOM Node to the
319 appropriate build_* function."""
320 label = node.getAttribute('label')
321 name = node.getAttribute('name')
322 if label:
323 label = self.trans(label)
325 old_size_group = self.current_size_group
326 sg = node.getAttributeNode('size-group')
327 if sg is not None:
328 self.current_size_group = sg.value or None
330 option = None
331 if name:
332 try:
333 option = self.options.options[name]
334 except KeyError:
335 raise Exception("Unknown option '%s'" % name)
337 # Check for a new-style function in the registry...
338 new_fn = widget_registry.get(node.localName, None)
339 if new_fn:
340 # Wrap it up so it appears old-style
341 fn = lambda *args: new_fn(self, *args)
342 else:
343 # Not in the registry... look in the class instead
344 try:
345 name = node.localName.replace('-', '_')
346 fn = getattr(self, 'build_' + name)
347 except AttributeError:
348 fn = self.build_unknown
350 if option:
351 widgets = fn(node, label, option)
352 else:
353 widgets = fn(node, label)
354 for w in widgets:
355 box.pack_start(w, False, True, 0)
357 self.current_size_group = old_size_group
359 def may_add_tip(self, widget, node):
360 """If 'node' contains any text, use that as the tip for 'widget'."""
361 if node.childNodes:
362 data = ''.join([n.nodeValue for n in node.childNodes if n.nodeType == Node.TEXT_NODE]).strip()
363 else:
364 data = None
365 if data:
366 self.tips.set_tip(widget, self.trans(data))
368 def get_size_group(self, name):
369 """Return the GtkSizeGroup for this name, creating one
370 if it doesn't currently exist."""
371 try:
372 return self.size_groups[name]
373 except KeyError:
374 group = g.SizeGroup(g.SIZE_GROUP_HORIZONTAL)
375 self.size_groups[name] = group
376 return group
378 def make_sized_label(self, label, suffix = ""):
379 """Create a GtkLabel and add it to the current size-group, if any"""
380 widget = g.Label(label)
381 if self.current_size_group:
382 widget.set_alignment(1.0, 0.5)
383 group = self.get_size_group(self.current_size_group + suffix)
384 group.add_widget(widget)
385 return widget
387 # Each type of widget has a method called 'build_NAME' where name is
388 # the XML element name. This method is called as method(node, label,
389 # option) if it corresponds to an Option, or method(node, label)
390 # otherwise. It should return a list of widgets to add to the window
391 # and, if it's for an Option, set self.handlers[option] = (get, set).
393 def build_unknown(self, node, label, option = None):
394 return [g.Label("Unknown widget type <%s>" % node.localName)]
396 def build_label(self, node, label):
397 help_flag = int(node.getAttribute('help') or '0')
398 widget = self.make_sized_label(self.trans(data(node)))
399 if help_flag:
400 widget.set_alignment(0, 0.5)
401 else:
402 widget.set_alignment(0, 1)
403 widget.set_justify(g.JUSTIFY_LEFT)
404 widget.set_line_wrap(True)
406 if help_flag:
407 hbox = g.HBox(False, 4)
408 image = g.Image()
409 image.set_from_stock(g.STOCK_DIALOG_INFO,
410 g.ICON_SIZE_BUTTON)
411 align = g.Alignment(0, 0, 0, 0)
413 align.add(image)
414 hbox.pack_start(align, False, True, 0)
415 hbox.pack_start(widget, False, True, 0)
417 spacer = g.EventBox()
418 spacer.set_size_request(6, 6)
420 return [hbox, spacer]
421 return [widget]
423 def build_spacer(self, node, label):
424 """<spacer/>"""
425 eb = g.EventBox()
426 eb.set_size_request(8, 8)
427 return [eb]
429 def build_hbox(self, node, label):
430 """<hbox>...</hbox> to layout child widgets horizontally."""
431 return self.do_box(node, label, g.HBox(False, 4))
432 def build_vbox(self, node, label):
433 """<vbox>...</vbox> to layout child widgets vertically."""
434 return self.do_box(node, label, g.VBox(False, 0))
436 def do_box(self, node, label, widget):
437 "Helper function for building hbox, vbox and frame widgets."
438 if label:
439 widget.pack_start(self.make_sized_label(label),
440 False, True, 4)
442 for child in node.childNodes:
443 if child.nodeType == Node.ELEMENT_NODE:
444 self.build_widget(child, widget)
446 return [widget]
448 def build_frame(self, node, label):
449 """<frame label='Title'>...</frame> to group options under a heading."""
450 frame = g.Frame(label)
451 frame.set_shadow_type(g.SHADOW_NONE)
453 # Make the label bold...
454 # (bug in pygtk => use set_markup)
455 label_widget = frame.get_label_widget()
456 label_widget.set_markup('<b>' + label + '</b>')
457 #attr = pango.AttrWeight(pango.WEIGHT_BOLD)
458 #attr.start_index = 0
459 #attr.end_index = -1
460 #list = pango.AttrList()
461 #list.insert(attr)
462 #label_widget.set_attributes(list)
464 vbox = g.VBox(False, 4)
465 vbox.set_border_width(12)
466 frame.add(vbox)
468 self.do_box(node, None, vbox)
470 return [frame]
472 def do_entry(self, node, label, option):
473 "Helper function for entry and secretentry widgets"
474 box = g.HBox(False, 4)
475 entry = g.Entry()
477 if label:
478 label_wid = self.make_sized_label(label)
479 label_wid.set_alignment(1.0, 0.5)
480 box.pack_start(label_wid, False, True, 0)
481 box.pack_start(entry, True, True, 0)
482 else:
483 box = None
485 self.may_add_tip(entry, node)
487 entry.connect('changed', lambda e: self.check_widget(option))
489 def get():
490 return entry.get_chars(0, -1)
491 def set():
492 entry.set_text(option.value)
493 self.handlers[option] = (get, set)
495 return (entry, [box or entry])
497 def build_entry(self, node, label, option):
498 "<entry name='...' label='...'>Tooltip</entry>"
499 entry, result=self.do_entry(node, label, option)
500 return result
502 def build_secretentry(self, node, label, option):
503 "<secretentry name='...' label='...' char='*'>Tooltip</secretentry>"
504 entry, result=self.do_entry(node, label, option)
505 try:
506 ch=node.getAttribute('char')
507 if len(ch)>=1:
508 ch=ch[0]
509 else:
510 ch=u'\0'
511 except:
512 ch='*'
514 entry.set_visibility(False)
515 entry.set_invisible_char(ch)
517 return result
519 def build_font(self, node, label, option):
520 "<font name='...' label='...'>Tooltip</font>"
521 button = FontButton(self, option, label)
523 self.may_add_tip(button, node)
525 hbox = g.HBox(False, 4)
526 hbox.pack_start(self.make_sized_label(label), False, True, 0)
527 hbox.pack_start(button, False, True, 0)
529 self.handlers[option] = (button.get, button.set)
531 return [hbox]
533 def build_colour(self, node, label, option):
534 "<colour name='...' label='...'>Tooltip</colour>"
535 button = ColourButton(self, option, label)
537 self.may_add_tip(button, node)
539 hbox = g.HBox(False, 4)
540 hbox.pack_start(self.make_sized_label(label), False, True, 0)
541 hbox.pack_start(button, False, True, 0)
543 self.handlers[option] = (button.get, button.set)
545 return [hbox]
547 def build_numentry(self, node, label, option):
548 """<numentry name='...' label='...' min='0' max='100' step='1'>Tooltip</numentry>.
549 Lets the user choose a number from min to max."""
550 minv = int(node.getAttribute('min'))
551 maxv = int(node.getAttribute('max'))
552 step = node.getAttribute('step')
553 unit = node.getAttribute('unit')
554 if step:
555 step = int(step)
556 else:
557 step = 1
558 if unit:
559 unit = self.trans(unit)
561 hbox = g.HBox(False, 4)
562 if label:
563 widget = self.make_sized_label(label)
564 widget.set_alignment(1.0, 0.5)
565 hbox.pack_start(widget, False, True, 0)
567 spin = g.SpinButton(g.Adjustment(minv, minv, maxv, step))
568 spin.set_width_chars(max(len(str(minv)), len(str(maxv))))
569 hbox.pack_start(spin, False, True, 0)
570 self.may_add_tip(spin, node)
572 if unit:
573 hbox.pack_start(g.Label(unit), False, True, 0)
575 self.handlers[option] = (
576 lambda: str(spin.get_value()),
577 lambda: spin.set_value(option.int_value))
579 spin.connect('value-changed', lambda w: self.check_widget(option))
581 return [hbox]
583 def build_menu(self, node, label, option):
584 """Build an OptionMenu widget, only one item of which may be selected.
585 <menu name='...' label='...'>
586 <item value='...' label='...'/>
587 <item value='...' label='...'/>
588 </menu>"""
590 values = []
592 has_combo = hasattr(g, 'combo_box_new_text')
593 if has_combo:
594 option_menu = g.combo_box_new_text()
595 option_menu.get_history = option_menu.get_active
596 option_menu.set_history = option_menu.set_active
597 else:
598 option_menu = g.OptionMenu()
599 menu = g.Menu()
601 if label:
602 box = g.HBox(False, 4)
603 label_wid = self.make_sized_label(label)
604 label_wid.set_alignment(1.0, 0.5)
605 box.pack_start(label_wid, False, True, 0)
606 box.pack_start(option_menu, True, True, 0)
607 else:
608 box = None
610 #self.may_add_tip(option_menu, node)
612 for item in node.getElementsByTagName('item'):
613 assert item.hasAttribute('value')
614 value = item.getAttribute('value')
615 label_item = self.trans(item.getAttribute('label')) or value
617 if has_combo:
618 option_menu.append_text(label_item)
619 else:
620 menu.append(g.MenuItem(label_item))
622 values.append(value)
624 if not has_combo:
625 menu.show_all()
626 option_menu.set_menu(menu)
627 option_menu.connect('changed', lambda e: self.check_widget(option))
629 def get():
630 return values[option_menu.get_history()]
632 def set():
633 try:
634 option_menu.set_history(values.index(option.value))
635 except ValueError:
636 print "Value '%s' not in combo list" % option.value
638 self.handlers[option] = (get, set)
640 return [box or option_menu]
643 def build_radio_group(self, node, label, option):
644 """Build a list of radio buttons, only one of which may be selected.
645 <radio-group name='...'>
646 <radio value='...' label='...'>Tooltip</radio>
647 <radio value='...' label='...'>Tooltip</radio>
648 </radio-group>"""
649 radios = []
650 values = []
651 button = None
652 for radio in node.getElementsByTagName('radio'):
653 label = self.trans(radio.getAttribute('label'))
654 button = g.RadioButton(button, label)
655 self.may_add_tip(button, radio)
656 radios.append(button)
657 values.append(radio.getAttribute('value'))
658 button.connect('toggled', lambda b: self.check_widget(option))
660 def set():
661 try:
662 i = values.index(option.value)
663 except:
664 print "Value '%s' not in radio group!" % option.value
665 i = 0
666 radios[i].set_active(True)
667 def get():
668 for r, v in zip(radios, values):
669 if r.get_active():
670 return v
671 raise Exception('Nothing selected!')
673 self.handlers[option] = (get, set)
675 return radios
677 def build_toggle(self, node, label, option):
678 "<toggle name='...' label='...'>Tooltip</toggle>"
679 toggle = g.CheckButton(label)
680 self.may_add_tip(toggle, node)
682 self.handlers[option] = (
683 lambda: str(toggle.get_active()),
684 lambda: toggle.set_active(option.int_value))
686 toggle.connect('toggled', lambda w: self.check_widget(option))
688 return [toggle]
690 def build_slider(self, node, label, option):
691 minv = int(node.getAttribute('min'))
692 maxv = int(node.getAttribute('max'))
693 fixed = int(node.getAttribute('fixed') or "0")
694 showvalue = int(node.getAttribute('showvalue') or "0")
695 end = node.getAttribute('end')
697 hbox = g.HBox(False, 4)
698 if label:
699 widget = self.make_sized_label(label)
700 hbox.pack_start(widget, False, True, 0)
702 if end:
703 hbox.pack_end(self.make_sized_label(self.trans(end),
704 suffix = '-unit'),
705 False, True, 0)
707 adj = g.Adjustment(minv, minv, maxv, 1, 10, 0)
708 slide = g.HScale(adj)
710 if fixed:
711 slide.set_size_request(adj.upper, 24)
712 else:
713 slide.set_size_request(120, -1)
714 if showvalue:
715 slide.set_draw_value(True)
716 slide.set_value_pos(g.POS_LEFT)
717 slide.set_digits(0)
718 else:
719 slide.set_draw_value(False)
721 self.may_add_tip(slide, node)
722 hbox.pack_start(slide, not fixed, True, 0)
724 self.handlers[option] = (
725 lambda: str(adj.get_value()),
726 lambda: adj.set_value(option.int_value))
728 slide.connect('value-changed',
729 lambda w: self.check_widget(option))
731 return [hbox]
733 def build_fixedlist(self, node, label, option):
734 """<fixedlist name='...' label='...' selection='single|none|multiple'>Tooltip<listitem label='...'/><listitem label='...'/></fixedlist>"""
735 select=str_attr(node, 'selection', 'single')
737 cont=g.VBox(False, 4)
739 if label:
740 label_wid = g.Label(label)
741 cont.pack_start(label_wid, False, True, 0)
742 label_wid.show()
744 swin = g.ScrolledWindow()
745 swin.set_border_width(4)
746 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
747 swin.set_shadow_type(g.SHADOW_IN)
748 swin.set_size_request(-1, 128)
749 cont.pack_start(swin, True, True, 0)
751 model = g.ListStore(str)
752 view = g.TreeView(model)
753 swin.add(view)
755 selection=view.get_selection()
756 if select=='none':
757 selection.set_mode(g.SELECTION_NONE)
758 elif select=='multiple':
759 selection.set_mode(g.SELECTION_MULTIPLE)
760 else:
761 selection.set_mode(g.SELECTION_SINGLE)
762 select='single'
764 def sel_changed(sel, box):
765 box.check_widget(option)
767 selection.connect('changed', sel_changed, self)
769 cell = g.CellRendererText()
770 column = g.TreeViewColumn('', cell, text = 0)
771 view.append_column(column)
773 for item in node.getElementsByTagName('listitem'):
774 label=item.getAttribute('label')
775 iter=model.append()
776 model.set(iter, 0, label)
778 self.may_add_tip(swin, node)
780 def make_sel(model, path, iter, l):
781 l.append(str(model.get_value(iter, 0)))
783 def get():
784 mode=view.get_selection().get_mode()
785 if mode==int(g.SELECTION_NONE):
786 return []
787 elif mode==int(g.SELECTION_SINGLE):
788 model, iter=view.get_selection().get_selected()
789 return [str(model.get_value(iter, 0))]
791 v=[]
792 view.get_selection().selected_foreach(make_sel, v)
793 return v
795 def set():
796 sel=view.get_selection()
797 mode=sel.get_mode()
798 sel.unselect_all()
799 for v in option.list_value:
800 iter=model.get_iter_first()
801 while iter:
802 if v==model.get_value(iter, 0):
803 sel.select_iter(iter)
804 break
806 iter=model.iter_next(iter)
808 self.handlers[option]=(get, set)
810 return [cont]
812 def build_varlist(self, node, label, option):
813 """<varlist name='...' label='...' edit='yes|no' extend='yes|no' selection='single|none|multiple'>Tooltip</varlist>"""
814 edit=bool_attr(node, 'edit')
815 reorder=bool_attr(node, 'reorder')
816 extend=bool_attr(node, 'extend')
817 select=str_attr(node, 'selection', 'single')
819 cont=rox.g.VBox(False, 4)
821 if label:
822 label_wid = rox.g.Label(label)
823 cont.pack_start(label_wid, False, True, 0)
824 label_wid.show()
826 swin = g.ScrolledWindow()
827 swin.set_border_width(4)
828 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
829 swin.set_shadow_type(g.SHADOW_IN)
830 swin.set_size_request(-1, 128)
831 cont.pack_start(swin, True, True, 0)
833 model = g.ListStore(str, str)
834 view = g.TreeView(model)
835 swin.add(view)
837 selection=view.get_selection()
838 if select=='none':
839 selection.set_mode(g.SELECTION_NONE)
840 elif select=='multiple':
841 selection.set_mode(g.SELECTION_MULTIPLE)
842 else:
843 selection.set_mode(g.SELECTION_SINGLE)
844 select='single'
846 if reorder:
847 view.set_reorderable(True)
849 def cell_edited(ell, path, new_text, col):
850 if col==0 and new_text.find('=')>=0:
851 return
852 iter=model.get_iter_from_string(path)
853 model.set(iter, col, new_text)
854 self.check_widget(option)
856 cell = g.CellRendererText()
857 column = g.TreeViewColumn('Variable', cell, text = 0)
858 view.append_column(column)
859 if edit:
860 cell.set_property('editable', True)
861 cell.connect('edited', cell_edited, 0)
863 cell = g.CellRendererText()
864 column = g.TreeViewColumn('Value', cell, text = 1)
865 view.append_column(column)
866 if edit:
867 cell.set_property('editable', True)
868 cell.connect('edited', cell_edited, 1)
870 def add(widget, box):
871 iter=model.append()
872 model.set(iter, 0, 'newvar', 1, 'new value')
873 if select=='single':
874 view.get_selection().select_iter(iter)
875 box.check_widget(option)
876 if extend:
877 hbox=g.HBox(False, 2)
878 cont.pack_start(hbox, False)
880 but=g.Button(stock=g.STOCK_ADD)
881 but.connect('clicked', add, self)
882 hbox.pack_start(but, False)
884 self.may_add_tip(swin, node)
886 def get():
887 v=[]
888 iter=model.get_iter_first()
889 while iter:
890 var=model.get_value(iter, 0)
891 val=model.get_value(iter, 1)
892 v.append(var+'='+val)
894 iter=model.iter_next(iter)
895 return v
897 def set():
898 model.clear()
899 for v in option.list_value:
900 var, val=v.split('=', 1)
901 iter=model.append()
902 model.set(iter, 0, var, 1, val)
904 self.handlers[option]=(get, set)
906 return [cont]
909 class FontButton(g.Button):
910 """A button that opens a GtkFontSelectionDialog"""
911 def __init__(self, option_box, option, title):
912 g.Button.__init__(self)
913 self.option_box = option_box
914 self.option = option
915 self.title = title
916 self.label = g.Label('<font>')
917 self.add(self.label)
918 self.dialog = None
919 self.connect('clicked', self.clicked)
921 def set(self):
922 self.label.set_text(self.option.value)
923 if self.dialog:
924 self.dialog.destroy()
926 def get(self):
927 return self.label.get()
929 def clicked(self, button):
930 if self.dialog:
931 self.dialog.destroy()
933 def closed(dialog):
934 self.dialog = None
936 def response(dialog, resp):
937 if resp != int(g.RESPONSE_OK):
938 dialog.destroy()
939 return
940 self.label.set_text(dialog.get_font_name())
941 dialog.destroy()
942 self.option_box.check_widget(self.option)
944 self.dialog = g.FontSelectionDialog(self.title)
945 self.dialog.set_position(g.WIN_POS_MOUSE)
946 self.dialog.connect('destroy', closed)
947 self.dialog.connect('response', response)
949 self.dialog.set_font_name(self.get())
950 self.dialog.show()
952 class ColourButton(g.Button):
953 """A button that opens a GtkColorSelectionDialog"""
954 def __init__(self, option_box, option, title):
955 g.Button.__init__(self)
956 self.c_box = g.EventBox()
957 self.add(self.c_box)
958 self.option_box = option_box
959 self.option = option
960 self.title = title
961 self.set_size_request(64, 14)
962 self.dialog = None
963 self.connect('clicked', self.clicked)
964 self.connect('expose-event', self.expose)
966 def expose(self, widget, event):
967 # Some themes draw images and stuff here, so we have to
968 # override it manually.
969 self.c_box.window.draw_rectangle(
970 self.c_box.style.bg_gc[g.STATE_NORMAL], True,
971 0, 0,
972 self.c_box.allocation.width,
973 self.c_box.allocation.height)
975 def set(self, c = None):
976 if c is None:
977 c = g.gdk.color_parse(self.option.value)
978 self.c_box.modify_bg(g.STATE_NORMAL, c)
980 def get(self):
981 c = self.c_box.get_style().bg[g.STATE_NORMAL]
982 return '#%04x%04x%04x' % (c.red, c.green, c.blue)
984 def clicked(self, button):
985 if self.dialog:
986 self.dialog.destroy()
988 def closed(dialog):
989 self.dialog = None
991 def response(dialog, resp):
992 if resp != int(g.RESPONSE_OK):
993 dialog.destroy()
994 return
995 self.set(dialog.colorsel.get_current_color())
996 dialog.destroy()
997 self.option_box.check_widget(self.option)
999 self.dialog = g.ColorSelectionDialog(self.title)
1000 self.dialog.set_position(g.WIN_POS_MOUSE)
1001 self.dialog.connect('destroy', closed)
1002 self.dialog.connect('response', response)
1004 c = self.c_box.get_style().bg[g.STATE_NORMAL]
1005 self.dialog.colorsel.set_current_color(c)
1006 self.dialog.show()
1008 # Add your own options here... (maps element localName to build function)
1009 widget_registry = {