Deprecation warning: gtk.Tooltips deprecated in pygtk 2.12
[rox-lib/lack.git] / ROX-Lib2 / python / rox / OptionsBox.py
blob1c4a10af79b7c96cccc96c263796b8bca38305ff
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 if (g.pygtk_version < (2, 12, 0)):
121 # gtk.Tooltips deprecated as of pygtk-2.12.0
122 self.tips = g.Tooltips()
123 self.set_has_separator(False)
125 self.options = options_group
126 self.set_title((_('%s options')) % options_group.program)
127 self.set_position(g.WIN_POS_CENTER)
129 button = rox.ButtonMixed(g.STOCK_UNDO, _('_Revert'))
130 self.add_action_widget(button, REVERT)
131 revert_tooltip = _('Restore all options to how they were '
132 'when the window was opened')
133 if (g.pygtk_version >= (2, 12, 0)):
134 button.set_tooltip_text(revert_tooltip)
135 else:
136 self.tips.set_tip(button, revert_tooltip)
138 self.add_button(g.STOCK_OK, g.RESPONSE_OK)
140 self.set_default_response(g.RESPONSE_OK)
142 doc = minidom.parse(options_xml)
143 assert doc.documentElement.localName == 'options'
145 self.handlers = {} # Option -> (get, set)
146 self.revert = {} # Option -> old value
147 self.size_groups = {} # Name -> GtkSizeGroup
148 self.current_size_group = None
150 sections = []
151 for section in doc.documentElement.childNodes:
152 if section.nodeType != Node.ELEMENT_NODE:
153 continue
154 if section.localName != 'section':
155 print "Unknown section", section
156 continue
157 sections.append(section)
159 self.build_window_frame(add_frame = len(sections) > 1)
161 # Add each section
162 for section in sections:
163 self.build_section(section, None)
164 if len(sections) > 1:
165 self.tree_view.expand_all()
166 else:
167 self.sections_swin.hide()
169 self.updating = 0
171 def destroyed(widget):
172 rox.toplevel_unref()
173 if self.changed():
174 try:
175 self.options.save()
176 except:
177 rox.report_exception()
178 self.connect('destroy', destroyed)
180 def got_response(widget, response):
181 if response == int(g.RESPONSE_OK):
182 self.destroy()
183 elif response == REVERT:
184 for o in self.options:
185 o._set(self.revert[o])
186 self.update_widgets()
187 self.options.notify()
188 self.update_revert()
189 self.connect('response', got_response)
191 def open(self):
192 """Show the window, updating all the widgets at the same
193 time. Use this instead of show()."""
194 rox.toplevel_ref()
195 for option in self.options:
196 self.revert[option] = option.value
197 self.update_widgets()
198 self.update_revert()
199 self.show()
201 def update_revert(self):
202 "Shade/unshade the Revert button. Internal."
203 self.set_response_sensitive(REVERT, self.changed())
205 def changed(self):
206 """Check whether any options have different values (ie, whether Revert
207 will do anything)."""
208 for option in self.options:
209 if option.value != self.revert[option]:
210 return True
211 return False
213 def update_widgets(self):
214 "Make widgets show current values. Internal."
215 assert not self.updating
216 self.updating = 1
218 try:
219 for option in self.options:
220 try:
221 handler = self.handlers[option][1]
222 except KeyError:
223 print "No widget for option '%s'!" % option
224 else:
225 handler()
226 finally:
227 self.updating = 0
229 def build_window_frame(self, add_frame = True):
230 "Create the main structure of the window."
231 hbox = g.HBox(False, 4)
232 self.vbox.pack_start(hbox, True, True, 0)
234 # scrolled window for the tree view
235 sw = g.ScrolledWindow()
236 sw.set_shadow_type(g.SHADOW_IN)
237 sw.set_policy(g.POLICY_NEVER, g.POLICY_AUTOMATIC)
238 hbox.pack_start(sw, False, True, 0)
239 self.sections_swin = sw # Used to hide it...
241 # tree view
242 model = g.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT)
243 tv = g.TreeView(model)
244 sel = tv.get_selection()
245 sel.set_mode(g.SELECTION_BROWSE)
246 tv.set_headers_visible(False)
247 self.sections = model
248 self.tree_view = tv
249 tv.unset_flags(g.CAN_FOCUS) # Stop irritating highlight
251 # Add a column to display column 0 of the store...
252 cell = g.CellRendererText()
253 column = g.TreeViewColumn('Section', cell, text = 0)
254 tv.append_column(column)
256 sw.add(tv)
258 # main options area
259 notebook = g.Notebook()
260 notebook.set_show_tabs(False)
261 notebook.set_show_border(False)
262 self.notebook = notebook
264 if add_frame:
265 frame = g.Frame()
266 frame.set_shadow_type(g.SHADOW_IN)
267 hbox.pack_start(frame, True, True, 0)
268 frame.add(notebook)
269 else:
270 hbox.pack_start(notebook, True, True, 0)
272 # Flip pages
273 def change_page(sel, notebook):
274 selected = sel.get_selected()
275 if not selected:
276 return
277 model, titer = selected
278 page = model.get_value(titer, 1)
279 notebook.set_current_page(page)
281 sel.connect('changed', change_page, notebook)
283 self.vbox.show_all()
285 def check_widget(self, option):
286 "A widgets call this when the user changes its value."
287 if self.updating:
288 return
290 assert isinstance(option, options.Option)
292 new = self.handlers[option][0]()
294 if new == option.value:
295 return
297 option._set(new)
298 self.options.notify()
299 self.update_revert()
301 def build_section(self, section, parent):
302 """Create a new page for the notebook and a new entry in the
303 sections tree, and build all the widgets inside the page."""
304 page = g.VBox(False, 4)
305 page.set_border_width(4)
306 self.notebook.append_page(page, g.Label('unused'))
308 titer = self.sections.append(parent)
309 self.sections.set(titer,
310 0, self.trans(section.getAttribute('title')),
311 1, self.notebook.page_num(page))
312 for node in section.childNodes:
313 if node.nodeType != Node.ELEMENT_NODE:
314 continue
315 name = node.localName
316 if name == 'section':
317 self.build_section(node, titer)
318 else:
319 self.build_widget(node, page)
320 page.show_all()
322 def build_widget(self, node, box):
323 """Dispatches the job of dealing with a DOM Node to the
324 appropriate build_* function."""
325 label = node.getAttribute('label')
326 name = node.getAttribute('name')
327 if label:
328 label = self.trans(label)
330 old_size_group = self.current_size_group
331 sg = node.getAttributeNode('size-group')
332 if sg is not None:
333 self.current_size_group = sg.value or None
335 option = None
336 if name:
337 try:
338 option = self.options.options[name]
339 except KeyError:
340 raise Exception("Unknown option '%s'" % name)
342 # Check for a new-style function in the registry...
343 new_fn = widget_registry.get(node.localName, None)
344 if new_fn:
345 # Wrap it up so it appears old-style
346 fn = lambda *args: new_fn(self, *args)
347 else:
348 # Not in the registry... look in the class instead
349 try:
350 name = node.localName.replace('-', '_')
351 fn = getattr(self, 'build_' + name)
352 except AttributeError:
353 fn = self.build_unknown
355 if option:
356 widgets = fn(node, label, option)
357 else:
358 widgets = fn(node, label)
359 for w in widgets:
360 if hasattr(w, '_rox_lib_expand'):
361 expand=w._rox_lib_expand
362 else:
363 expand=False
364 box.pack_start(w, expand, True, 0)
366 self.current_size_group = old_size_group
368 def may_add_tip(self, widget, node):
369 """If 'node' contains any text, use that as the tip for 'widget'."""
370 if node.childNodes:
371 data = ''.join([n.nodeValue for n in node.childNodes if n.nodeType == Node.TEXT_NODE]).strip()
372 else:
373 data = None
374 if data:
375 if (g.pygtk_version >= (2, 12, 0)):
376 widget.set_tooltip_text(self.trans(data))
377 else:
378 self.tips.set_tip(widget, self.trans(data))
380 def get_size_group(self, name):
381 """Return the GtkSizeGroup for this name, creating one
382 if it doesn't currently exist."""
383 try:
384 return self.size_groups[name]
385 except KeyError:
386 group = g.SizeGroup(g.SIZE_GROUP_HORIZONTAL)
387 self.size_groups[name] = group
388 return group
390 def make_sized_label(self, label, suffix = ""):
391 """Create a GtkLabel and add it to the current size-group, if any"""
392 widget = g.Label(label)
393 if self.current_size_group:
394 widget.set_alignment(1.0, 0.5)
395 group = self.get_size_group(self.current_size_group + suffix)
396 group.add_widget(widget)
397 return widget
399 # Each type of widget has a method called 'build_NAME' where name is
400 # the XML element name. This method is called as method(node, label,
401 # option) if it corresponds to an Option, or method(node, label)
402 # otherwise. It should return a list of widgets to add to the window
403 # and, if it's for an Option, set self.handlers[option] = (get, set).
405 def build_unknown(self, node, label, option = None):
406 return [g.Label("Unknown widget type <%s>" % node.localName)]
408 def build_label(self, node, label):
409 help_flag = int(node.getAttribute('help') or '0')
410 widget = self.make_sized_label(self.trans(data(node)))
411 if help_flag:
412 widget.set_alignment(0, 0.5)
413 else:
414 widget.set_alignment(0, 1)
415 widget.set_justify(g.JUSTIFY_LEFT)
416 widget.set_line_wrap(True)
418 if help_flag:
419 hbox = g.HBox(False, 4)
420 image = g.Image()
421 image.set_from_stock(g.STOCK_DIALOG_INFO,
422 g.ICON_SIZE_BUTTON)
423 align = g.Alignment(0, 0, 0, 0)
425 align.add(image)
426 hbox.pack_start(align, False, True, 0)
427 hbox.pack_start(widget, False, True, 0)
429 spacer = g.EventBox()
430 spacer.set_size_request(6, 6)
432 return [hbox, spacer]
433 return [widget]
435 def build_spacer(self, node, label):
436 """<spacer/>"""
437 eb = g.EventBox()
438 eb.set_size_request(8, 8)
439 return [eb]
441 def build_hbox(self, node, label):
442 """<hbox>...</hbox> to layout child widgets horizontally."""
443 return self.do_box(node, label, g.HBox(False, 4))
444 def build_vbox(self, node, label):
445 """<vbox>...</vbox> to layout child widgets vertically."""
446 return self.do_box(node, label, g.VBox(False, 0))
448 def do_box(self, node, label, widget):
449 "Helper function for building hbox, vbox and frame widgets."
450 if label:
451 widget.pack_start(self.make_sized_label(label),
452 False, True, 4)
454 for child in node.childNodes:
455 if child.nodeType == Node.ELEMENT_NODE:
456 self.build_widget(child, widget)
458 return [widget]
460 def build_frame(self, node, label):
461 """<frame label='Title'>...</frame> to group options under a heading."""
462 frame = g.Frame(label)
463 frame.set_shadow_type(g.SHADOW_NONE)
465 # Make the label bold...
466 # (bug in pygtk => use set_markup)
467 label_widget = frame.get_label_widget()
468 label_widget.set_markup('<b>' + label + '</b>')
469 #attr = pango.AttrWeight(pango.WEIGHT_BOLD)
470 #attr.start_index = 0
471 #attr.end_index = -1
472 #list = pango.AttrList()
473 #list.insert(attr)
474 #label_widget.set_attributes(list)
476 vbox = g.VBox(False, 4)
477 vbox.set_border_width(12)
478 frame.add(vbox)
480 self.do_box(node, None, vbox)
482 return [frame]
484 def do_entry(self, node, label, option):
485 "Helper function for entry and secretentry widgets"
486 box = g.HBox(False, 4)
487 entry = g.Entry()
489 if label:
490 label_wid = self.make_sized_label(label)
491 label_wid.set_alignment(1.0, 0.5)
492 box.pack_start(label_wid, False, True, 0)
493 box.pack_start(entry, True, True, 0)
494 else:
495 box = None
497 self.may_add_tip(entry, node)
499 entry.connect('changed', lambda e: self.check_widget(option))
501 def get():
502 return entry.get_chars(0, -1)
503 def set():
504 entry.set_text(option.value)
505 self.handlers[option] = (get, set)
507 return (entry, [box or entry])
509 def build_entry(self, node, label, option):
510 "<entry name='...' label='...'>Tooltip</entry>"
511 entry, result=self.do_entry(node, label, option)
512 return result
514 def build_secretentry(self, node, label, option):
515 "<secretentry name='...' label='...' char='*'>Tooltip</secretentry>"
516 entry, result=self.do_entry(node, label, option)
517 try:
518 ch=node.getAttribute('char')
519 if len(ch)>=1:
520 ch=ch[0]
521 else:
522 ch=u'\0'
523 except:
524 ch='*'
526 entry.set_visibility(False)
527 entry.set_invisible_char(ch)
529 return result
531 def build_font(self, node, label, option):
532 "<font name='...' label='...'>Tooltip</font>"
533 button = FontButton(self, option, label)
535 self.may_add_tip(button, node)
537 hbox = g.HBox(False, 4)
538 hbox.pack_start(self.make_sized_label(label), False, True, 0)
539 hbox.pack_start(button, False, True, 0)
541 self.handlers[option] = (button.get, button.set)
543 return [hbox]
545 def build_colour(self, node, label, option):
546 "<colour name='...' label='...'>Tooltip</colour>"
547 button = ColourButton(self, option, label)
549 self.may_add_tip(button, node)
551 hbox = g.HBox(False, 4)
552 hbox.pack_start(self.make_sized_label(label), False, True, 0)
553 hbox.pack_start(button, False, True, 0)
555 self.handlers[option] = (button.get, button.set)
557 return [hbox]
559 def build_numentry(self, node, label, option):
560 """<numentry name='...' label='...' min='0' max='100' step='1'>Tooltip</numentry>.
561 Lets the user choose a number from min to max."""
562 minv = int(node.getAttribute('min'))
563 maxv = int(node.getAttribute('max'))
564 step = node.getAttribute('step')
565 unit = node.getAttribute('unit')
566 if step:
567 step = int(step)
568 else:
569 step = 1
570 if unit:
571 unit = self.trans(unit)
573 hbox = g.HBox(False, 4)
574 if label:
575 widget = self.make_sized_label(label)
576 widget.set_alignment(1.0, 0.5)
577 hbox.pack_start(widget, False, True, 0)
579 spin = g.SpinButton(g.Adjustment(minv, minv, maxv, step))
580 spin.set_width_chars(max(len(str(minv)), len(str(maxv))))
581 hbox.pack_start(spin, False, True, 0)
582 self.may_add_tip(spin, node)
584 if unit:
585 hbox.pack_start(g.Label(unit), False, True, 0)
587 self.handlers[option] = (
588 lambda: str(spin.get_value()),
589 lambda: spin.set_value(option.int_value))
591 spin.connect('value-changed', lambda w: self.check_widget(option))
593 return [hbox]
595 def build_filechooser(self, node, label, option):
596 """<filechooser name='...' label='...'/>Tooltip</filechooser>.
597 Lets the user choose a file (using a GtkFileChooser or by drag-and-drop).
598 Note: requires GTK >= 2.6
600 filebutton = g.FileChooserButton(label)
601 eb = g.EventBox()
602 eb.add(filebutton)
603 self.may_add_tip(eb, node)
605 clearbutton = g.Button(stock = g.STOCK_CLEAR)
606 hbox = g.HBox(False, 4)
607 if label:
608 hbox.pack_start(g.Label(label + ":"), False, True, 0)
609 hbox.pack_start(eb, True, True, 0)
610 hbox.pack_start(clearbutton, False, True, 0)
612 self.handlers[option] = (
613 lambda: filebutton.get_filename(),
614 lambda: filebutton.set_filename(option.value))
615 filebutton.connect('selection-changed', lambda w: self.check_widget(option))
617 def clear(w):
618 filebutton.set_filename("")
619 self.check_widget(option)
620 clearbutton.connect('clicked', clear)
622 return [hbox or eb]
624 def build_menu(self, node, label, option):
625 """Build an OptionMenu widget, only one item of which may be selected.
626 <menu name='...' label='...'>
627 <item value='...' label='...'/>
628 <item value='...' label='...'/>
629 </menu>"""
631 values = []
633 has_combo = hasattr(g, 'combo_box_new_text')
634 if has_combo:
635 option_menu = g.combo_box_new_text()
636 option_menu.get_history = option_menu.get_active
637 option_menu.set_history = option_menu.set_active
638 else:
639 option_menu = g.OptionMenu()
640 menu = g.Menu()
642 if label:
643 box = g.HBox(False, 4)
644 label_wid = self.make_sized_label(label)
645 label_wid.set_alignment(1.0, 0.5)
646 box.pack_start(label_wid, False, True, 0)
647 box.pack_start(option_menu, True, True, 0)
648 else:
649 box = None
651 #self.may_add_tip(option_menu, node)
653 for item in node.getElementsByTagName('item'):
654 assert item.hasAttribute('value')
655 value = item.getAttribute('value')
656 label_item = self.trans(item.getAttribute('label')) or value
658 if has_combo:
659 option_menu.append_text(label_item)
660 else:
661 menu.append(g.MenuItem(label_item))
663 values.append(value)
665 if not has_combo:
666 menu.show_all()
667 option_menu.set_menu(menu)
668 option_menu.connect('changed', lambda e: self.check_widget(option))
670 def get():
671 return values[option_menu.get_history()]
673 def set():
674 try:
675 option_menu.set_history(values.index(option.value))
676 except ValueError:
677 print "Value '%s' not in combo list" % option.value
679 self.handlers[option] = (get, set)
681 return [box or option_menu]
684 def build_radio_group(self, node, label, option):
685 """Build a list of radio buttons, only one of which may be selected.
686 <radio-group name='...'>
687 <radio value='...' label='...'>Tooltip</radio>
688 <radio value='...' label='...'>Tooltip</radio>
689 </radio-group>"""
690 radios = []
691 values = []
692 button = None
693 for radio in node.getElementsByTagName('radio'):
694 label = self.trans(radio.getAttribute('label'))
695 button = g.RadioButton(button, label)
696 self.may_add_tip(button, radio)
697 radios.append(button)
698 values.append(radio.getAttribute('value'))
699 button.connect('toggled', lambda b: self.check_widget(option))
701 def set():
702 try:
703 i = values.index(option.value)
704 except:
705 print "Value '%s' not in radio group!" % option.value
706 i = 0
707 radios[i].set_active(True)
708 def get():
709 for r, v in zip(radios, values):
710 if r.get_active():
711 return v
712 raise Exception('Nothing selected!')
714 self.handlers[option] = (get, set)
716 return radios
718 def build_toggle(self, node, label, option):
719 "<toggle name='...' label='...'>Tooltip</toggle>"
720 toggle = g.CheckButton(label)
721 self.may_add_tip(toggle, node)
723 self.handlers[option] = (
724 lambda: str(toggle.get_active()),
725 lambda: toggle.set_active(option.int_value))
727 toggle.connect('toggled', lambda w: self.check_widget(option))
729 return [toggle]
731 def build_slider(self, node, label, option):
732 minv = int(node.getAttribute('min'))
733 maxv = int(node.getAttribute('max'))
734 fixed = int(node.getAttribute('fixed') or "0")
735 showvalue = int(node.getAttribute('showvalue') or "0")
736 end = node.getAttribute('end')
738 hbox = g.HBox(False, 4)
739 if label:
740 widget = self.make_sized_label(label)
741 hbox.pack_start(widget, False, True, 0)
743 if end:
744 hbox.pack_end(self.make_sized_label(self.trans(end),
745 suffix = '-unit'),
746 False, True, 0)
748 adj = g.Adjustment(minv, minv, maxv, 1, 10, 0)
749 slide = g.HScale(adj)
751 if fixed:
752 slide.set_size_request(adj.upper, 24)
753 else:
754 slide.set_size_request(120, -1)
755 if showvalue:
756 slide.set_draw_value(True)
757 slide.set_value_pos(g.POS_LEFT)
758 slide.set_digits(0)
759 else:
760 slide.set_draw_value(False)
762 self.may_add_tip(slide, node)
763 hbox.pack_start(slide, not fixed, True, 0)
765 self.handlers[option] = (
766 lambda: str(adj.get_value()),
767 lambda: adj.set_value(option.int_value))
769 slide.connect('value-changed',
770 lambda w: self.check_widget(option))
772 return [hbox]
774 def build_fixedlist(self, node, label, option):
775 """<fixedlist name='...' label='...' selection='single|none|multiple'>Tooltip<listitem label='...'/><listitem label='...'/></fixedlist>"""
776 select=str_attr(node, 'selection', 'single')
778 cont=g.VBox(False, 4)
779 cont._rox_lib_expand=True
781 if label:
782 label_wid = g.Label(label)
783 cont.pack_start(label_wid, False, True, 0)
784 label_wid.show()
786 swin = g.ScrolledWindow()
787 swin.set_border_width(4)
788 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
789 swin.set_shadow_type(g.SHADOW_IN)
790 #swin.set_size_request(-1, 128)
791 cont.pack_start(swin, True, True, 0)
793 model = g.ListStore(str)
794 view = g.TreeView(model)
795 swin.add(view)
797 selection=view.get_selection()
798 if select=='none':
799 selection.set_mode(g.SELECTION_NONE)
800 elif select=='multiple':
801 selection.set_mode(g.SELECTION_MULTIPLE)
802 else:
803 selection.set_mode(g.SELECTION_SINGLE)
804 select='single'
806 def sel_changed(sel, box):
807 box.check_widget(option)
809 selection.connect('changed', sel_changed, self)
811 cell = g.CellRendererText()
812 column = g.TreeViewColumn('', cell, text = 0)
813 view.append_column(column)
815 for item in node.getElementsByTagName('listitem'):
816 label=item.getAttribute('label')
817 iter=model.append()
818 model.set(iter, 0, label)
820 self.may_add_tip(swin, node)
822 def make_sel(model, path, iter, l):
823 l.append(str(model.get_value(iter, 0)))
825 def get():
826 mode=view.get_selection().get_mode()
827 if mode==int(g.SELECTION_NONE):
828 return []
829 elif mode==int(g.SELECTION_SINGLE):
830 model, iter=view.get_selection().get_selected()
831 return [str(model.get_value(iter, 0))]
833 v=[]
834 view.get_selection().selected_foreach(make_sel, v)
835 return v
837 def set():
838 sel=view.get_selection()
839 mode=sel.get_mode()
840 sel.unselect_all()
841 for v in option.list_value:
842 iter=model.get_iter_first()
843 while iter:
844 if v==model.get_value(iter, 0):
845 sel.select_iter(iter)
846 break
848 iter=model.iter_next(iter)
850 self.handlers[option]=(get, set)
852 return [cont]
854 def build_varlist(self, node, label, option):
855 """<varlist name='...' label='...' edit='yes|no' extend='yes|no' selection='single|none|multiple'>Tooltip</varlist>"""
856 edit=bool_attr(node, 'edit')
857 reorder=bool_attr(node, 'reorder')
858 extend=bool_attr(node, 'extend')
859 select=str_attr(node, 'selection', 'single')
861 cont=rox.g.VBox(False, 4)
862 cont._rox_lib_expand=True
864 if label:
865 label_wid = rox.g.Label(label)
866 cont.pack_start(label_wid, False, True, 0)
867 label_wid.show()
869 swin = g.ScrolledWindow()
870 swin.set_border_width(4)
871 swin.set_policy(g.POLICY_NEVER, g.POLICY_ALWAYS)
872 swin.set_shadow_type(g.SHADOW_IN)
873 #swin.set_size_request(-1, 128)
874 cont.pack_start(swin, True, True, 0)
876 model = g.ListStore(str, str)
877 view = g.TreeView(model)
878 swin.add(view)
880 selection=view.get_selection()
881 if select=='none':
882 selection.set_mode(g.SELECTION_NONE)
883 elif select=='multiple':
884 selection.set_mode(g.SELECTION_MULTIPLE)
885 else:
886 selection.set_mode(g.SELECTION_SINGLE)
887 select='single'
889 if reorder:
890 view.set_reorderable(True)
892 def cell_edited(ell, path, new_text, col):
893 if col==0 and new_text.find('=')>=0:
894 return
895 iter=model.get_iter_from_string(path)
896 model.set(iter, col, new_text)
897 self.check_widget(option)
899 cell = g.CellRendererText()
900 column = g.TreeViewColumn('Variable', cell, text = 0)
901 view.append_column(column)
902 if edit:
903 cell.set_property('editable', True)
904 cell.connect('edited', cell_edited, 0)
906 cell = g.CellRendererText()
907 column = g.TreeViewColumn('Value', cell, text = 1)
908 view.append_column(column)
909 if edit:
910 cell.set_property('editable', True)
911 cell.connect('edited', cell_edited, 1)
913 def add(widget, box):
914 iter=model.append()
915 model.set(iter, 0, 'newvar', 1, 'new value')
916 if select=='single':
917 view.get_selection().select_iter(iter)
918 box.check_widget(option)
919 if extend:
920 hbox=g.HBox(False, 2)
921 cont.pack_start(hbox, False)
923 but=g.Button(stock=g.STOCK_ADD)
924 but.connect('clicked', add, self)
925 hbox.pack_start(but, False)
927 self.may_add_tip(swin, node)
929 def get():
930 v=[]
931 iter=model.get_iter_first()
932 while iter:
933 var=model.get_value(iter, 0)
934 val=model.get_value(iter, 1)
935 v.append(var+'='+val)
937 iter=model.iter_next(iter)
938 return v
940 def set():
941 model.clear()
942 for v in option.list_value:
943 var, val=v.split('=', 1)
944 iter=model.append()
945 model.set(iter, 0, var, 1, val)
947 self.handlers[option]=(get, set)
949 return [cont]
952 class FontButton(g.Button):
953 """A button that opens a GtkFontSelectionDialog"""
954 def __init__(self, option_box, option, title):
955 g.Button.__init__(self)
956 self.option_box = option_box
957 self.option = option
958 self.title = title
959 self.label = g.Label('<font>')
960 self.add(self.label)
961 self.dialog = None
962 self.connect('clicked', self.clicked)
964 def set(self):
965 self.label.set_text(self.option.value)
966 if self.dialog:
967 self.dialog.destroy()
969 def get(self):
970 return self.label.get()
972 def clicked(self, button):
973 if self.dialog:
974 self.dialog.destroy()
976 def closed(dialog):
977 self.dialog = None
979 def response(dialog, resp):
980 if resp != int(g.RESPONSE_OK):
981 dialog.destroy()
982 return
983 self.label.set_text(dialog.get_font_name())
984 dialog.destroy()
985 self.option_box.check_widget(self.option)
987 self.dialog = g.FontSelectionDialog(self.title)
988 self.dialog.set_position(g.WIN_POS_MOUSE)
989 self.dialog.connect('destroy', closed)
990 self.dialog.connect('response', response)
992 self.dialog.set_font_name(self.get())
993 self.dialog.show()
995 class ColourButton(g.Button):
996 """A button that opens a GtkColorSelectionDialog"""
997 def __init__(self, option_box, option, title):
998 g.Button.__init__(self)
999 self.c_box = g.EventBox()
1000 self.add(self.c_box)
1001 self.option_box = option_box
1002 self.option = option
1003 self.title = title
1004 self.set_size_request(64, 14)
1005 self.dialog = None
1006 self.connect('clicked', self.clicked)
1007 self.connect('expose-event', self.expose)
1009 def expose(self, widget, event):
1010 # Some themes draw images and stuff here, so we have to
1011 # override it manually.
1012 self.c_box.window.draw_rectangle(
1013 self.c_box.style.bg_gc[g.STATE_NORMAL], True,
1014 0, 0,
1015 self.c_box.allocation.width,
1016 self.c_box.allocation.height)
1018 def set(self, c = None):
1019 if c is None:
1020 c = g.gdk.color_parse(self.option.value)
1021 self.c_box.modify_bg(g.STATE_NORMAL, c)
1023 def get(self):
1024 c = self.c_box.get_style().bg[g.STATE_NORMAL]
1025 return '#%04x%04x%04x' % (c.red, c.green, c.blue)
1027 def clicked(self, button):
1028 if self.dialog:
1029 self.dialog.destroy()
1031 def closed(dialog):
1032 self.dialog = None
1034 def response(dialog, resp):
1035 if resp != int(g.RESPONSE_OK):
1036 dialog.destroy()
1037 return
1038 self.set(dialog.colorsel.get_current_color())
1039 dialog.destroy()
1040 self.option_box.check_widget(self.option)
1042 self.dialog = g.ColorSelectionDialog(self.title)
1043 self.dialog.set_position(g.WIN_POS_MOUSE)
1044 self.dialog.connect('destroy', closed)
1045 self.dialog.connect('response', response)
1047 c = self.c_box.get_style().bg[g.STATE_NORMAL]
1048 self.dialog.colorsel.set_current_color(c)
1049 self.dialog.show()
1051 # Add your own options here... (maps element localName to build function)
1052 widget_registry = {