If a service property exists but the window it points to doesn't, then
[rox-lib.git] / python / rox / OptionsBox.py
blobb808b4cd29dde4cfca4716ad4b161bc3048e4a36
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 For widgets that have options, your build function will be called with
21 the option as a third parameter. You should register get and set methods,
22 and arrange for box.check_widget to be called when the user changes the
23 value:
25 def build_toggle(box, node, label, option):
26 toggle = g.CheckButton(label)
27 box.may_add_tip(toggle, node)
29 box.handlers[option] = (
30 lambda: str(toggle.get_active()),
31 lambda: toggle.set_active(option.int_value))
33 toggle.connect('toggled', lambda w: box.check_widget(option))
35 return [toggle]
36 OptionsBox.widget_registry['mytoggle'] = build_toggle
37 """
39 from rox import g, options, _
40 import rox
41 from xml.dom import Node, minidom
42 import gobject
44 REVERT = 1
46 # Functions for extracting data from XML nodes
47 def data(node):
48 """Return all the text directly inside this DOM Node."""
49 return ''.join([text.nodeValue for text in node.childNodes
50 if text.nodeType == Node.TEXT_NODE])
52 def bool_attr(node, name, val=False):
53 """Interpret node attribute as a boolean value"""
54 try:
55 v=node.getAttribute(name)
56 if v=='yes':
57 return True
58 else:
59 return False
60 except:
61 pass
62 return val
64 def str_attr(node, name, val=''):
65 """Get string value of node attribute"""
66 try:
67 val=node.getAttribute(name)
68 except:
69 pass
70 return val
72 class OptionsBox(g.Dialog):
73 """A dialog box which lets the user edit the options. The file
74 Options.xml specifies the layout of this box."""
76 tips = None # GtkTooltips
77 options = None # The OptionGroup we are editing
78 revert = None # Option -> old value
79 handlers = None # Option -> (get, set)
80 trans = None # Translation function (application's, not ROX-Lib's)
82 def __init__(self, options_group, options_xml, translation = None):
83 """options_xml is an XML file, usually <app_dir>/Options.xml,
84 which defines the layout of the OptionsBox.
86 It contains an <options> root element containing (nested)
87 <section> elements. Each <section> contains a number of widgets,
88 some of which correspond to options. The build_* functions are
89 used to create them.
91 Example:
93 <?xml version='1.0'?>
94 <options>
95 <section title='First section'>
96 <label>Here are some options</label>
97 <entry name='default_name' label='Default file name'>
98 When saving an untitled file, use this name as the default.
99 </entry>
100 <section title='Nested section'>
102 </section>
103 </section>
104 </options>
106 assert isinstance(options_group, options.OptionGroup)
108 if translation is None:
109 import __main__
110 if hasattr(__main__.__builtins__, '_'):
111 translation = __main__.__builtins__._
112 else:
113 translation = lambda x: x
114 self.trans = translation
116 g.Dialog.__init__(self)
117 self.tips = g.Tooltips()
118 self.set_has_separator(False)
120 self.options = options_group
121 self.set_title((_('%s options')) % options_group.program)
122 self.set_position(g.WIN_POS_CENTER)
124 button = rox.ButtonMixed(g.STOCK_UNDO, _('_Revert'))
125 self.add_action_widget(button, REVERT)
126 self.tips.set_tip(button, _('Restore all options to how they were '
127 'when the window was opened'))
129 self.add_button(g.STOCK_OK, g.RESPONSE_OK)
131 self.set_default_response(g.RESPONSE_OK)
133 doc = minidom.parse(options_xml)
134 assert doc.documentElement.localName == 'options'
136 self.handlers = {} # Option -> (get, set)
137 self.revert = {} # Option -> old value
138 self.size_groups = {} # Name -> GtkSizeGroup
139 self.current_size_group = None
141 self.build_window_frame()
143 # Add each section
144 n = 0
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 self.build_section(section, None)
152 n += 1
153 if n > 1:
154 self.tree_view.expand_all()
155 else:
156 self.sections_swin.hide()
158 self.updating = 0
160 def destroyed(widget):
161 rox.toplevel_unref()
162 if self.changed():
163 try:
164 self.options.save()
165 except:
166 rox.report_exception()
167 self.connect('destroy', destroyed)
169 def got_response(widget, response):
170 if response == g.RESPONSE_OK:
171 self.destroy()
172 elif response == REVERT:
173 for o in self.options:
174 o._set(self.revert[o])
175 self.update_widgets()
176 self.options.notify()
177 self.update_revert()
178 self.connect('response', got_response)
180 def open(self):
181 """Show the window, updating all the widgets at the same
182 time. Use this instead of show()."""
183 rox.toplevel_ref()
184 for option in self.options:
185 self.revert[option] = option.value
186 self.update_widgets()
187 self.update_revert()
188 self.show()
190 def update_revert(self):
191 "Shade/unshade the Revert button. Internal."
192 self.set_response_sensitive(REVERT, self.changed())
194 def changed(self):
195 """Check whether any options have different values (ie, whether Revert
196 will do anything)."""
197 for option in self.options:
198 if option.value != self.revert[option]:
199 return True
200 return False
202 def update_widgets(self):
203 "Make widgets show current values. Internal."
204 assert not self.updating
205 self.updating = 1
207 try:
208 for option in self.options:
209 try:
210 handler = self.handlers[option][1]
211 except KeyError:
212 print "No widget for option '%s'!" % option
213 else:
214 handler()
215 finally:
216 self.updating = 0
218 def build_window_frame(self):
219 "Create the main structure of the window."
220 hbox = g.HBox(False, 4)
221 self.vbox.pack_start(hbox, True, True, 0)
223 # scrolled window for the tree view
224 sw = g.ScrolledWindow()
225 sw.set_shadow_type(g.SHADOW_IN)
226 sw.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
227 hbox.pack_start(sw, False, True, 0)
228 self.sections_swin = sw # Used to hide it...
230 # tree view
231 model = g.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT)
232 tv = g.TreeView(model)
233 sel = tv.get_selection()
234 sel.set_mode(g.SELECTION_BROWSE)
235 tv.set_headers_visible(False)
236 self.sections = model
237 self.tree_view = tv
238 tv.unset_flags(g.CAN_FOCUS) # Stop irritating highlight
240 # Add a column to display column 0 of the store...
241 cell = g.CellRendererText()
242 column = g.TreeViewColumn('Section', cell, text = 0)
243 tv.append_column(column)
245 sw.add(tv)
247 # main options area
248 frame = g.Frame()
249 frame.set_shadow_type(g.SHADOW_IN)
250 hbox.pack_start(frame, True, True, 0)
252 notebook = g.Notebook()
253 notebook.set_show_tabs(False)
254 notebook.set_show_border(False)
255 frame.add(notebook)
256 self.notebook = notebook
258 # Flip pages
259 # (sel = sel; pygtk bug?)
260 def change_page(tv, sel = sel, notebook = notebook):
261 selected = sel.get_selected()
262 if not selected:
263 return
264 model, titer = selected
265 page = model.get_value(titer, 1)
267 notebook.set_current_page(page)
268 sel.connect('changed', change_page)
270 self.vbox.show_all()
272 def check_widget(self, option):
273 "A widgets call this when the user changes its value."
274 if self.updating:
275 return
277 assert isinstance(option, options.Option)
279 new = self.handlers[option][0]()
281 if new == option.value:
282 return
284 option._set(new)
285 self.options.notify()
286 self.update_revert()
288 def build_section(self, section, parent):
289 """Create a new page for the notebook and a new entry in the
290 sections tree, and build all the widgets inside the page."""
291 page = g.VBox(False, 4)
292 page.set_border_width(4)
293 self.notebook.append_page(page, g.Label('unused'))
295 titer = self.sections.append(parent)
296 self.sections.set(titer,
297 0, self.trans(section.getAttribute('title')),
298 1, self.notebook.page_num(page))
299 for node in section.childNodes:
300 if node.nodeType != Node.ELEMENT_NODE:
301 continue
302 name = node.localName
303 if name == 'section':
304 self.build_section(node, titer)
305 else:
306 self.build_widget(node, page)
307 page.show_all()
309 def build_widget(self, node, box):
310 """Dispatches the job of dealing with a DOM Node to the
311 appropriate build_* function."""
312 label = node.getAttribute('label')
313 name = node.getAttribute('name')
314 if label:
315 label = self.trans(label)
317 old_size_group = self.current_size_group
318 sg = node.getAttributeNode('size-group')
319 if sg is not None:
320 self.current_size_group = sg.value or None
322 option = None
323 if name:
324 try:
325 option = self.options.options[name]
326 except KeyError:
327 raise Exception("Unknown option '%s'" % name)
329 # Check for a new-style function in the registry...
330 new_fn = widget_registry.get(node.localName, None)
331 if new_fn:
332 # Wrap it up so it appears old-style
333 fn = lambda *args: new_fn(self, *args)
334 else:
335 # Not in the registry... look in the class instead
336 try:
337 name = node.localName.replace('-', '_')
338 fn = getattr(self, 'build_' + name)
339 except AttributeError:
340 fn = self.build_unknown
342 if option:
343 widgets = fn(node, label, option)
344 else:
345 widgets = fn(node, label)
346 for w in widgets:
347 box.pack_start(w, False, True, 0)
349 self.current_size_group = old_size_group
351 def may_add_tip(self, widget, node):
352 """If 'node' contains any text, use that as the tip for 'widget'."""
353 if node.childNodes:
354 data = ''.join([n.nodeValue for n in node.childNodes if n.nodeType == Node.TEXT_NODE]).strip()
355 else:
356 data = None
357 if data:
358 self.tips.set_tip(widget, self.trans(data))
360 def get_size_group(self, name):
361 """Return the GtkSizeGroup for this name, creating one
362 if it doesn't currently exist."""
363 try:
364 return self.size_groups[name]
365 except KeyError:
366 group = g.SizeGroup(g.SIZE_GROUP_HORIZONTAL)
367 self.size_groups[name] = group
368 return group
370 def make_sized_label(self, label, suffix = ""):
371 """Create a GtkLabel and add it to the current size-group, if any"""
372 widget = g.Label(label)
373 if self.current_size_group:
374 widget.set_alignment(1.0, 0.5)
375 group = self.get_size_group(self.current_size_group + suffix)
376 group.add_widget(widget)
377 return widget
379 # Each type of widget has a method called 'build_NAME' where name is
380 # the XML element name. This method is called as method(node, label,
381 # option) if it corresponds to an Option, or method(node, label)
382 # otherwise. It should return a list of widgets to add to the window
383 # and, if it's for an Option, set self.handlers[option] = (get, set).
385 def build_unknown(self, node, label, option = None):
386 return [g.Label("Unknown widget type <%s>" % node.localName)]
388 def build_label(self, node, label):
389 help_flag = int(node.getAttribute('help') or '0')
390 widget = self.make_sized_label(self.trans(data(node)))
391 if help_flag:
392 widget.set_alignment(0, 0.5)
393 else:
394 widget.set_alignment(0, 1)
395 widget.set_justify(g.JUSTIFY_LEFT)
396 widget.set_line_wrap(True)
398 if help_flag:
399 hbox = g.HBox(False, 4)
400 image = g.Image()
401 image.set_from_stock(g.STOCK_DIALOG_INFO,
402 g.ICON_SIZE_BUTTON)
403 align = g.Alignment(0, 0, 0, 0)
405 align.add(image)
406 hbox.pack_start(align, False, True, 0)
407 hbox.pack_start(widget, False, True, 0)
409 spacer = g.EventBox()
410 spacer.set_size_request(6, 6)
412 return [hbox, spacer]
413 return [widget]
415 def build_spacer(self, node, label):
416 """<spacer/>"""
417 eb = g.EventBox()
418 eb.set_size_request(8, 8)
419 return [eb]
421 def build_hbox(self, node, label):
422 """<hbox>...</hbox> to layout child widgets horizontally."""
423 return self.do_box(node, label, g.HBox(False, 4))
424 def build_vbox(self, node, label):
425 """<vbox>...</vbox> to layout child widgets vertically."""
426 return self.do_box(node, label, g.VBox(False, 0))
428 def do_box(self, node, label, widget):
429 "Helper function for building hbox, vbox and frame widgets."
430 if label:
431 widget.pack_start(self.make_sized_label(label),
432 False, True, 4)
434 for child in node.childNodes:
435 if child.nodeType == Node.ELEMENT_NODE:
436 self.build_widget(child, widget)
438 return [widget]
440 def build_frame(self, node, label):
441 """<frame label='Title'>...</frame> to group options under a heading."""
442 frame = g.Frame(label)
443 frame.set_shadow_type(g.SHADOW_NONE)
445 # Make the label bold...
446 # (bug in pygtk => use set_markup)
447 label_widget = frame.get_label_widget()
448 label_widget.set_markup('<b>' + label + '</b>')
449 #attr = pango.AttrWeight(pango.WEIGHT_BOLD)
450 #attr.start_index = 0
451 #attr.end_index = -1
452 #list = pango.AttrList()
453 #list.insert(attr)
454 #label_widget.set_attributes(list)
456 vbox = g.VBox(False, 4)
457 vbox.set_border_width(12)
458 frame.add(vbox)
460 self.do_box(node, None, vbox)
462 return [frame]
464 def do_entry(self, node, label, option):
465 "Helper function for entry and secretentry widgets"
466 box = g.HBox(False, 4)
467 entry = g.Entry()
469 if label:
470 label_wid = self.make_sized_label(label)
471 label_wid.set_alignment(1.0, 0.5)
472 box.pack_start(label_wid, False, True, 0)
473 box.pack_start(entry, True, True, 0)
474 else:
475 box = None
477 self.may_add_tip(entry, node)
479 entry.connect('changed', lambda e: self.check_widget(option))
481 def get():
482 return entry.get_chars(0, -1)
483 def set():
484 entry.set_text(option.value)
485 self.handlers[option] = (get, set)
487 return (entry, [box or entry])
489 def build_entry(self, node, label, option):
490 "<entry name='...' label='...'>Tooltip</entry>"
491 entry, result=self.do_entry(node, label, option)
492 return result
494 def build_secretentry(self, node, label, option):
495 "<secretentry name='...' label='...' char='*'>Tooltip</secretentry>"
496 entry, result=self.do_entry(node, label, option)
497 try:
498 ch=node.getAttribute('char')
499 if len(ch)>=1:
500 ch=ch[0]
501 else:
502 ch=u'\0'
503 except:
504 ch='*'
506 entry.set_visibility(False)
507 entry.set_invisible_char(ch)
509 return result
511 def build_font(self, node, label, option):
512 "<font name='...' label='...'>Tooltip</font>"
513 button = FontButton(self, option, label)
515 self.may_add_tip(button, node)
517 hbox = g.HBox(False, 4)
518 hbox.pack_start(self.make_sized_label(label), False, True, 0)
519 hbox.pack_start(button, False, True, 0)
521 self.handlers[option] = (button.get, button.set)
523 return [hbox]
525 def build_colour(self, node, label, option):
526 "<colour name='...' label='...'>Tooltip</colour>"
527 button = ColourButton(self, option, label)
529 self.may_add_tip(button, node)
531 hbox = g.HBox(False, 4)
532 hbox.pack_start(self.make_sized_label(label), False, True, 0)
533 hbox.pack_start(button, False, True, 0)
535 self.handlers[option] = (button.get, button.set)
537 return [hbox]
539 def build_numentry(self, node, label, option):
540 """<numentry name='...' label='...' min='0' max='100' step='1'>Tooltip</numentry>.
541 Lets the user choose a number from min to max."""
542 minv = int(node.getAttribute('min'))
543 maxv = int(node.getAttribute('max'))
544 step = node.getAttribute('step')
545 unit = node.getAttribute('unit')
546 if step:
547 step = int(step)
548 else:
549 step = 1
550 if unit:
551 unit = self.trans(unit)
553 hbox = g.HBox(False, 4)
554 if label:
555 widget = self.make_sized_label(label)
556 widget.set_alignment(1.0, 0.5)
557 hbox.pack_start(widget, False, True, 0)
559 spin = g.SpinButton(g.Adjustment(minv, minv, maxv, step))
560 spin.set_width_chars(max(len(str(minv)), len(str(maxv))))
561 hbox.pack_start(spin, False, True, 0)
562 self.may_add_tip(spin, node)
564 if unit:
565 hbox.pack_start(g.Label(unit), False, True, 0)
567 self.handlers[option] = (
568 lambda: str(spin.get_value()),
569 lambda: spin.set_value(option.int_value))
571 spin.connect('value-changed', lambda w: self.check_widget(option))
573 return [hbox]
575 def build_menu(self, node, label, option):
576 """Build an OptionMenu widget, only one item of which may be selected.
577 <menu name='...' label='...'>
578 <item value='...' label='...'/>
579 <item value='...' label='...'/>
580 </menu>"""
582 values = []
584 option_menu = g.OptionMenu()
585 menu = g.Menu()
586 option_menu.set_menu(menu)
588 if label:
589 box = g.HBox(False, 4)
590 label_wid = self.make_sized_label(label)
591 label_wid.set_alignment(1.0, 0.5)
592 box.pack_start(label_wid, False, True, 0)
593 box.pack_start(option_menu, True, True, 0)
594 else:
595 box = None
597 #self.may_add_tip(option_menu, node)
599 for item in node.getElementsByTagName('item'):
600 assert item.hasAttribute('value')
601 value = item.getAttribute('value')
602 label_item = self.trans(item.getAttribute('label')) or value
604 menu.append(g.MenuItem(label_item))
605 values.append(value)
607 option_menu.connect('changed', lambda e: self.check_widget(option))
609 def get():
610 return values[option_menu.get_history()]
612 def set():
613 try:
614 option_menu.set_history(values.index(option.value))
615 except ValueError:
616 print "Value '%s' not in combo list" % option.value
618 self.handlers[option] = (get, set)
620 return [box or option_menu]
623 def build_radio_group(self, node, label, option):
624 """Build a list of radio buttons, only one of which may be selected.
625 <radio-group name='...'>
626 <radio value='...' label='...'>Tooltip</radio>
627 <radio value='...' label='...'>Tooltip</radio>
628 </radio-group>"""
629 radios = []
630 values = []
631 button = None
632 for radio in node.getElementsByTagName('radio'):
633 label = self.trans(radio.getAttribute('label'))
634 button = g.RadioButton(button, label)
635 self.may_add_tip(button, radio)
636 radios.append(button)
637 values.append(radio.getAttribute('value'))
638 button.connect('toggled', lambda b: self.check_widget(option))
640 def set():
641 try:
642 i = values.index(option.value)
643 except:
644 print "Value '%s' not in radio group!" % option.value
645 i = 0
646 radios[i].set_active(True)
647 def get():
648 for r, v in zip(radios, values):
649 if r.get_active():
650 return v
651 raise Exception('Nothing selected!')
653 self.handlers[option] = (get, set)
655 return radios
657 def build_toggle(self, node, label, option):
658 "<toggle name='...' label='...'>Tooltip</toggle>"
659 toggle = g.CheckButton(label)
660 self.may_add_tip(toggle, node)
662 self.handlers[option] = (
663 lambda: str(toggle.get_active()),
664 lambda: toggle.set_active(option.int_value))
666 toggle.connect('toggled', lambda w: self.check_widget(option))
668 return [toggle]
670 def build_slider(self, node, label, option):
671 minv = int(node.getAttribute('min'))
672 maxv = int(node.getAttribute('max'))
673 fixed = int(node.getAttribute('fixed') or "0")
674 showvalue = int(node.getAttribute('showvalue') or "0")
675 end = node.getAttribute('end')
677 hbox = g.HBox(False, 4)
678 if label:
679 widget = self.make_sized_label(label)
680 hbox.pack_start(widget, False, True, 0)
682 if end:
683 hbox.pack_end(self.make_sized_label(self.trans(end),
684 suffix = '-unit'),
685 False, True, 0)
687 adj = g.Adjustment(minv, minv, maxv, 1, 10, 0)
688 slide = g.HScale(adj)
690 if fixed:
691 slide.set_size_request(adj.upper, 24)
692 else:
693 slide.set_size_request(120, -1)
694 if showvalue:
695 slide.set_draw_value(True)
696 slide.set_value_pos(g.POS_LEFT)
697 slide.set_digits(0)
698 else:
699 slide.set_draw_value(False)
701 self.may_add_tip(slide, node)
702 hbox.pack_start(slide, not fixed, True, 0)
704 self.handlers[option] = (
705 lambda: str(adj.get_value()),
706 lambda: adj.set_value(option.int_value))
708 slide.connect('value-changed',
709 lambda w: self.check_widget(option))
711 return [hbox]
713 def build_fixedlist(self, node, label, option):
714 """<fixedlist name='...' label='...' selection='single|none|multiple'>Tooltip<listitem label='...'/><listitem label='...'/></fixedlist>"""
715 select=str_attr(node, 'selection', 'single')
717 cont=g.VBox(False, 4)
719 if label:
720 label_wid = g.Label(label)
721 cont.pack_start(label_wid, False, True, 0)
722 label_wid.show()
724 swin = g.ScrolledWindow()
725 swin.set_border_width(4)
726 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
727 swin.set_shadow_type(g.SHADOW_IN)
728 swin.set_size_request(-1, 128)
729 cont.pack_start(swin, True, True, 0)
731 model = g.ListStore(str)
732 view = g.TreeView(model)
733 swin.add(view)
735 selection=view.get_selection()
736 if select=='none':
737 selection.set_mode(g.SELECTION_NONE)
738 elif select=='multiple':
739 selection.set_mode(g.SELECTION_MULTIPLE)
740 else:
741 selection.set_mode(g.SELECTION_SINGLE)
742 select='single'
744 def sel_changed(sel, box):
745 box.check_widget(option)
747 selection.connect('changed', sel_changed, self)
749 cell = g.CellRendererText()
750 column = g.TreeViewColumn('', cell, text = 0)
751 view.append_column(column)
753 for item in node.getElementsByTagName('listitem'):
754 label=item.getAttribute('label')
755 iter=model.append()
756 model.set(iter, 0, label)
758 self.may_add_tip(swin, node)
760 def make_sel(model, path, iter, l):
761 l.append(str(model.get_value(iter, 0)))
763 def get():
764 mode=view.get_selection().get_mode()
765 if mode==g.SELECTION_NONE:
766 return []
767 elif mode==g.SELECTION_SINGLE:
768 model, iter=view.get_selection().get_selected()
769 return [str(model.get_value(iter, 0))]
771 v=[]
772 view.get_selection().selected_foreach(make_sel, v)
773 return v
775 def set():
776 sel=view.get_selection()
777 mode=sel.get_mode()
778 sel.unselect_all()
779 for v in option.list_value:
780 iter=model.get_iter_first()
781 while iter:
782 if v==model.get_value(iter, 0):
783 sel.select_iter(iter)
784 break
786 iter=model.iter_next(iter)
788 self.handlers[option]=(get, set)
790 return [cont]
792 def build_varlist(self, node, label, option):
793 """<varlist name='...' label='...' edit='yes|no' extend='yes|no' selection='single|none|multiple'>Tooltip</varlist>"""
794 edit=bool_attr(node, 'edit')
795 reorder=bool_attr(node, 'reorder')
796 extend=bool_attr(node, 'extend')
797 select=str_attr(node, 'selection', 'single')
799 cont=rox.g.VBox(False, 4)
801 if label:
802 label_wid = rox.g.Label(label)
803 cont.pack_start(label_wid, False, True, 0)
804 label_wid.show()
806 swin = g.ScrolledWindow()
807 swin.set_border_width(4)
808 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
809 swin.set_shadow_type(g.SHADOW_IN)
810 swin.set_size_request(-1, 128)
811 cont.pack_start(swin, True, True, 0)
813 model = g.ListStore(str, str)
814 view = g.TreeView(model)
815 swin.add(view)
817 selection=view.get_selection()
818 if select=='none':
819 selection.set_mode(g.SELECTION_NONE)
820 elif select=='multiple':
821 selection.set_mode(g.SELECTION_MULTIPLE)
822 else:
823 selection.set_mode(g.SELECTION_SINGLE)
824 select='single'
826 if reorder:
827 view.set_reorderable(True)
829 def cell_edited(ell, path, new_text, col):
830 if col==0 and new_text.find('=')>=0:
831 return
832 iter=model.get_iter_from_string(path)
833 model.set(iter, col, new_text)
834 self.check_widget(option)
836 cell = g.CellRendererText()
837 column = g.TreeViewColumn('Variable', cell, text = 0)
838 view.append_column(column)
839 if edit:
840 cell.set_property('editable', True)
841 cell.connect('edited', cell_edited, 0)
843 cell = g.CellRendererText()
844 column = g.TreeViewColumn('Value', cell, text = 1)
845 view.append_column(column)
846 if edit:
847 cell.set_property('editable', True)
848 cell.connect('edited', cell_edited, 1)
850 def add(widget, box):
851 iter=model.append()
852 model.set(iter, 0, 'newvar', 1, 'new value')
853 if select=='single':
854 view.get_selection().select_iter(iter)
855 box.check_widget(option)
856 if extend:
857 hbox=g.HBox(False, 2)
858 cont.pack_start(hbox, False)
860 but=g.Button(stock=g.STOCK_ADD)
861 but.connect('clicked', add, self)
862 hbox.pack_start(but, False)
864 self.may_add_tip(swin, node)
866 def get():
867 v=[]
868 iter=model.get_iter_first()
869 while iter:
870 var=model.get_value(iter, 0)
871 val=model.get_value(iter, 1)
872 v.append(var+'='+val)
874 iter=model.iter_next(iter)
875 return v
877 def set():
878 model.clear()
879 for v in option.list_value:
880 var, val=v.split('=', 1)
881 iter=model.append()
882 model.set(iter, 0, var, 1, val)
884 self.handlers[option]=(get, set)
886 return [cont]
889 class FontButton(g.Button):
890 """A button that opens a GtkFontSelectionDialog"""
891 def __init__(self, option_box, option, title):
892 g.Button.__init__(self)
893 self.option_box = option_box
894 self.option = option
895 self.title = title
896 self.label = g.Label('<font>')
897 self.add(self.label)
898 self.dialog = None
899 self.connect('clicked', self.clicked)
901 def set(self):
902 self.label.set_text(self.option.value)
903 if self.dialog:
904 self.dialog.destroy()
906 def get(self):
907 return self.label.get()
909 def clicked(self, button):
910 if self.dialog:
911 self.dialog.destroy()
913 def closed(dialog):
914 self.dialog = None
916 def response(dialog, resp):
917 if resp != g.RESPONSE_OK:
918 dialog.destroy()
919 return
920 self.label.set_text(dialog.get_font_name())
921 dialog.destroy()
922 self.option_box.check_widget(self.option)
924 self.dialog = g.FontSelectionDialog(self.title)
925 self.dialog.set_position(g.WIN_POS_MOUSE)
926 self.dialog.connect('destroy', closed)
927 self.dialog.connect('response', response)
929 self.dialog.set_font_name(self.get())
930 self.dialog.show()
932 class ColourButton(g.Button):
933 """A button that opens a GtkColorSelectionDialog"""
934 def __init__(self, option_box, option, title):
935 g.Button.__init__(self)
936 self.c_box = g.EventBox()
937 self.add(self.c_box)
938 self.option_box = option_box
939 self.option = option
940 self.title = title
941 self.set_size_request(64, 14)
942 self.dialog = None
943 self.connect('clicked', self.clicked)
944 self.connect('expose-event', self.expose)
946 def expose(self, widget, event):
947 # Some themes draw images and stuff here, so we have to
948 # override it manually.
949 self.c_box.window.draw_rectangle(
950 self.c_box.style.bg_gc[g.STATE_NORMAL], True,
951 0, 0,
952 self.c_box.allocation.width,
953 self.c_box.allocation.height)
955 def set(self, c = None):
956 if c is None:
957 c = g.gdk.color_parse(self.option.value)
958 self.c_box.modify_bg(g.STATE_NORMAL, c)
960 def get(self):
961 c = self.c_box.get_style().bg[g.STATE_NORMAL]
962 return '#%04x%04x%04x' % (c.red, c.green, c.blue)
964 def clicked(self, button):
965 if self.dialog:
966 self.dialog.destroy()
968 def closed(dialog):
969 self.dialog = None
971 def response(dialog, resp):
972 if resp != g.RESPONSE_OK:
973 dialog.destroy()
974 return
975 self.set(dialog.colorsel.get_current_color())
976 dialog.destroy()
977 self.option_box.check_widget(self.option)
979 self.dialog = g.ColorSelectionDialog(self.title)
980 self.dialog.set_position(g.WIN_POS_MOUSE)
981 self.dialog.connect('destroy', closed)
982 self.dialog.connect('response', response)
984 c = self.c_box.get_style().bg[g.STATE_NORMAL]
985 self.dialog.colorsel.set_current_color(c)
986 self.dialog.show()
988 # Add your own options here... (maps element localName to build function)
989 widget_registry = {