s/basestr/basestring/g
[pythonicgtk.git] / pythonicgtk.py
blob120bc95d2e4816f929d6dcdf8f97c5258766e45c
1 import new
3 import pygtk, gtk
5 from pprint import pprint
6 from debug_print_statements import *
9 # These are functions that work on widgets in order to do 'magic'
12 def get_widget_by_name(container, widget_name):
13 if 'get_children' not in dir(container):
14 # no get_children() method => this is not a container
15 raise TypeError
17 containers = [container]
19 while len(containers) != 0:
20 container = containers.pop(0)
21 for child in container.get_children():
22 if child.get_name() == widget_name:
23 return child
24 if 'get_children' in dir(child):
25 containers.extend(child.get_children())
27 # haven't returned by now => not found
28 return None
30 def set_properties(widget, properties_dict, ignore_errors=False):
31 for property_name, property_value in properties_dict.iteritems():
32 if ignore_errors:
33 try:
34 widget.set_property(property_name, property_value)
35 except TypeError:
36 pass
37 else:
38 widget.set_property(property_name, property_value)
40 class PythonicWidget():
42 def __init__(self, *args, **kwargs):
43 self.set_packing_vars(*args, **kwargs)
44 self.attach_signal_handlers(**kwargs)
45 self.set_name_param(**kwargs)
47 def set_packing_vars(self, *args, **kwargs):
48 expand = kwargs.get("expand", True)
49 fill = kwargs.get("fill", True)
52 def set_packing(widget=None, old_parent=None):
53 parent = self.get_parent()
54 if parent is not None and 'query_child_packing' in dir(parent):
55 old_values = list(parent.query_child_packing(self))
56 # we just want to change the expand and fill
57 old_values[0] = expand
58 old_values[1] = fill
59 parent.set_child_packing(self, expand, fill, old_values[2], old_values[3])
61 set_packing()
62 self.connect("parent-set", set_packing)
64 def attach_signal_handlers(self, **cb_dict):
65 events_to_signals = { 'on_click': 'clicked' }
67 for event, func in cb_dict.items():
68 if event in events_to_signals:
69 self.connect(events_to_signals[event], func)
71 def set_name_param(self, **kwargs):
72 if 'name' in kwargs:
73 self.set_name(kwargs['name'])
75 @property
76 def parent_window(self):
77 if 'container' not in dir(self):
78 return None
80 parent = self.container
81 while parent:
82 if isinstance(parent, Window):
83 return parent
84 if 'container' not in dir(parent):
85 return None
86 parent = parent.container
88 assert False, "Widget %r has no parent window" % self
90 @property
91 def child_widgets(self):
92 if 'get_children' not in dir(self):
93 yield None
94 return
96 widgets = [self]
98 # When being called with self as a window, it is not returning widgets
100 while len(widgets) != 0:
101 widget = widgets.pop(0)
102 if not isinstance( widget, gtk.Container ):
103 yield widget
104 continue
105 else:
106 yield widget
107 for child in widget.get_children():
108 yield child
109 if isinstance( child, gtk.Container ):
110 widgets.extend(child.get_children())
113 class Button(gtk.Button, PythonicWidget):
114 def __init__(self, label=None, stock=None, *args, **kwargs):
115 gtk.Button.__init__(self)
116 PythonicWidget.__init__(self, *args, **kwargs)
117 assert len(args) == 0
118 if label == None:
119 label = ""
120 self.set_label(label)
121 if stock != None:
122 self.set_use_stock(True)
123 self.set_label(stock)
126 class VBox(gtk.VBox, PythonicWidget):
127 def __init__(self, *args, **kwargs):
128 gtk.VBox.__init__(self)
129 PythonicWidget.__init__(self, *args, **kwargs)
130 for widget in args:
131 widget.container = self
132 self.pack_start(widget)
134 class HBox(gtk.HBox, PythonicWidget):
135 def __init__(self, *args, **kwargs):
136 gtk.HBox.__init__(self)
137 PythonicWidget.__init__(self, *args, **kwargs)
138 for widget in args:
139 widget.container = self
140 self.pack_start(widget)
142 class Label(gtk.Label, PythonicWidget):
143 def __init__(self, text, *args, **kwargs):
144 gtk.Label.__init__(self)
145 PythonicWidget.__init__(self, *args, **kwargs)
146 self.set_text(text)
148 class TextEntry(gtk.Entry, PythonicWidget):
149 def __init__(self, *args, **kwargs):
150 gtk.Entry.__init__(self)
151 PythonicWidget.__init__(self, *args, **kwargs)
153 if 'text' in kwargs:
154 self.set_text(kwargs['text'])
156 @property
157 def value(self):
158 return self.get_text()
160 @property
161 def text(self):
162 return self.get_text()
164 class TextView(gtk.TextView, PythonicWidget):
165 def __init__(self, *args, **kwargs):
166 gtk.TextView.__init__(self)
167 PythonicWidget.__init__(self, *args, **kwargs)
169 @property
170 def value(self):
171 return self.get_text()
173 def Labeled( label, widget, **kwargs ):
174 return HBox( Label(label), widget, **kwargs )
176 class ScrolledWindow(gtk.ScrolledWindow, PythonicWidget):
177 def __init__(self, inside_widget, horizontal=None, vertical=None, **kwargs):
178 gtk.ScrolledWindow.__init__(self)
179 PythonicWidget.__init__(self, **kwargs)
180 # FIXME should this be add_with_viewport? Or should it check
181 # inside_widget to see if it should add_with_viewport
182 if isinstance(inside_widget, gtk.Viewport):
183 self.add(inside_widget)
184 else:
185 self.add_with_viewport(inside_widget)
186 exiting_horizontal, existing_vertical = self.get_policy()
187 if horizontal == 'always':
188 self.set_policy(gtk.POLICY_ALWAYS, existing_vertical)
189 elif horizontal == 'never':
190 self.set_policy(gtk.POLICY_NEVER, existing_vertical)
191 elif horizontal == 'auto' or horizontal == 'automatic':
192 self.set_policy(gtk.POLICY_AUTOMATIC, existing_vertical)
193 exiting_horizontal, existing_vertical = self.get_policy()
194 if vertical == 'always':
195 self.set_policy(exiting_horizontal, gtk.POLICY_ALWAYS)
196 elif vertical == 'never':
197 self.set_policy(exiting_horizontal, gtk.POLICY_NEVER)
198 elif vertical == 'auto' or vertical == 'automatic':
199 self.set_policy(exiting_horizontal, gtk.POLICY_AUTOMATIC)
203 class IconView(gtk.IconView, PythonicWidget):
204 def __init__(self, elements, *args, **kwargs):
205 gtk.IconView.__init__(self)
206 PythonicWidget.__init__(self, *args, **kwargs)
207 set_properties(self, kwargs, ignore_errors=True)
208 # FIXME check the format of args
209 self.__init_from_icon_set(elements)
211 def __init_from_icon_set(self, elements):
212 # elements should be a list. Each element should be the same.
213 # Possible values:
214 # string - each 'icon' is a text
215 # (string, pixbuf) - standard icon and text
216 list = None
217 if type(elements[0]) == type(""):
218 list = gtk.ListStore(str)
219 for string in elements:
220 list.append([string])
221 self.set_model(list)
222 self.set_text_column(0)
223 elif type(elements[0]) == type(()):
224 # FIXME finish this
225 pass
226 else:
227 raise TypeError
229 class Toolbar(gtk.Toolbar, PythonicWidget):
230 def __init__(self, *child_widgets, **kwargs):
231 gtk.Toolbar.__init__(self)
232 PythonicWidget.__init__(self, **kwargs)
233 for child_widget in child_widgets:
234 # insert it at the end
235 #toolitem = gtk.ToolItem()
236 #toolitem.pack_start(child_widget)
237 self.insert( child_widget, -1 )
239 class NewButton(Button, PythonicWidget):
240 def __init__(self, *args, **kwargs):
241 Button.__init__(self, stock="gtk-new")
242 PythonicWidget.__init__(self, *args, **kwargs)
244 class AddButton(Button, PythonicWidget):
245 def __init__(self, *args, **kwargs):
246 Button.__init__(self, stock="gtk-add")
247 PythonicWidget.__init__(self, *args, **kwargs)
249 class RemoveButton(Button, PythonicWidget):
250 def __init__(self, *args, **kwargs):
251 Button.__init__(self, stock="gtk-remove")
252 PythonicWidget.__init__(self, *args, **kwargs)
254 class SaveButton(Button, PythonicWidget):
255 def __init__(self, *args, **kwargs):
256 Button.__init__(self, stock="gtk-save")
257 PythonicWidget.__init__(self, *args, **kwargs)
259 class NewToolbarButton(gtk.ToolButton, PythonicWidget):
260 def __init__(self, *args, **kwargs):
261 gtk.ToolButton.__init__(self, 'gtk-new')
262 PythonicWidget.__init__(self, *args, **kwargs)
264 class PropertiesButton(Button, PythonicWidget):
265 def __init__(self, *args, **kwargs):
266 Button.__init__(self, stock="gtk-properties")
267 PythonicWidget.__init__(self, *args, **kwargs)
269 class PropertiesToolbarButton(gtk.ToolButton, PythonicWidget):
270 def __init__(self, *args, **kwargs):
271 gtk.ToolButton.__init__(self, "gtk-properties")
272 PythonicWidget.__init__(self, *args, **kwargs)
274 class ComboBox(gtk.ComboBox, PythonicWidget):
275 def __init__(self, *rows, **kwargs):
276 gtk.ComboBox.__init__(self)
277 PythonicWidget.__init__(self, **kwargs)
279 liststore = gtk.ListStore(str)
280 self.set_model( liststore )
282 cell = gtk.CellRendererText()
283 self.pack_start( cell, True )
284 self.add_attribute( cell, 'text', 0 )
286 active = None
288 for index, row in enumerate( rows ):
289 if isinstance( row, basestring ):
290 text = row
291 else:
292 text = row[0]
293 options = row[1]
294 if options['selected']:
295 active = index
296 self.append_text( text )
298 if active is not None:
299 self.set_active( active )
302 class Notebook(gtk.Notebook, PythonicWidget):
303 def __init__(self, *pages, **kwargs):
304 gtk.Notebook.__init__(self)
305 PythonicWidget.__init__(self, **kwargs)
307 for page_name, page_contents in pages:
308 if isinstance(page_name, basestring):
309 page_name = Label( page_name )
310 self.append_page( child=page_contents, tab_label=page_name )
312 class RadioButton(gtk.RadioButton, PythonicWidget):
313 def __init__(self, group=None, label=None, *args, **kwargs):
314 gtk.RadioButton.__init__(self, group=None, label=label)
315 PythonicWidget.__init__(self, *args, **kwargs)
316 self.group = group
317 self.label = label
318 if 'value' in kwargs:
319 self.value = kwargs['value']
322 def update_all_radiobutton_groups(widget, new_child):
323 if 'get_children' not in dir( widget ):
324 return
326 child_radiobuttons = [child for child in widget.child_widgets if isinstance( child, RadioButton ) ]
328 groups = {}
329 for radiobutton in child_radiobuttons:
330 group_name = radiobutton.group
331 if group_name not in groups:
332 groups[group_name] = []
333 groups[group_name].append(radiobutton)
335 for group_name in groups:
336 inital_group = groups[group_name][0]
337 if len(groups[group_name]) <= 1:
338 continue
339 for radiobutton in groups[group_name][1:]:
340 radiobutton.set_group(None)
341 radiobutton.set_group(inital_group)
344 def add_updater_callback(widget, old_parent):
345 #print "Adding the add_updater_callback to "+repr(widget)
346 # when the parent is set we need to tell the parent to update_all_radiobutton_groups
348 if "get_children" in dir( widget ):
349 #print repr(widget)+" is a container"
350 update_all_radiobutton_groups(widget, None)
351 widget.connect( "add", update_all_radiobutton_groups )
353 if 'container' in dir( widget ) and widget.container:
354 widget.container.connect( "parent-set", add_updater_callback )
356 self.connect("parent-set", add_updater_callback)
357 update_all_radiobutton_groups(self, None)
361 class ListBox(gtk.TreeView, PythonicWidget):
362 def __init__(self, *rows, **kwargs):
363 try:
364 columns = kwargs['columns']
365 except KeyError:
366 # Turn our key error into a type error
367 raise TypeError, "ListBox constructor requires the 'columns' argument"
368 gtk.TreeView.__init__(self)
369 PythonicWidget.__init__(self, **kwargs)
371 # We assume they are all text columns for now
372 # TODO allow the caller to change this.
373 for index, col in enumerate(columns):
374 if isinstance( col, basestring ):
375 type = str
376 else:
377 pass
378 column = gtk.TreeViewColumn(col, gtk.CellRendererText(), text=index)
379 column.set_resizable(True)
380 column.set_sort_column_id(index)
381 self.append_column(column)
383 #view_entries_list.connect("row-activated", show_single_entry)
385 #column_types = []
386 #column_types.extend([x['type'] for x in view_def['columns']])
387 #column_types.extend([gobject.TYPE_PYOBJECT])
388 # TODO more types
389 column_types = [str] * len(columns)
390 column_data = gtk.ListStore(*column_types)
392 self.set_model(column_data)
394 for row in rows:
395 if len(row) != len(columns):
396 raise TypeError, "Row %r has %d entries but there are %d columns definied (%r)" % ( row, len(row), len(columns), columns)
397 column_data.append(row)
403 class Window(gtk.Window, PythonicWidget):
404 def __init__(self, child_widget, *args, **kwargs):
405 gtk.Window.__init__(self)
406 PythonicWidget.__init__(self, **kwargs)
408 self.add(child_widget)
409 child_widget.container = self
411 if 'title' in kwargs:
412 self.set_title(kwargs['title'])
414 if 'quit_on_close' in kwargs:
415 if kwargs['quit_on_close'] == True:
416 self.connect("destroy", gtk.main_quit)
418 def get_value(self, value_name):
419 # Either the widget is a text box (eg) that we can read the value off,
420 # or it's some kind of multi-selection thing, like a set of radio
421 # buttons
422 widget_by_name = get_widget_by_name(self, value_name)
423 if widget_by_name:
424 return widget_by_name.value
425 else:
426 widget_by_group = [widget for widget in self.child_widgets if isinstance( widget, RadioButton ) and widget.group == value_name]
427 if len(widget_by_group) == 0:
428 # no radio buttons have value_name as a group name
429 return
430 for widget in widget_by_group:
431 if widget.get_active():
432 if 'value' in dir(widget):
433 return widget.value
434 else:
435 return widget.label
437 return None
439 class Dialog(gtk.Dialog, Window, PythonicWidget):
440 def __init__(self, central_area, button_area, *args, **kwargs):
441 title = None
442 if 'title' in kwargs:
443 title = kwargs['title']
445 gtk.Dialog.__init__(self, title=title, buttons=button_area)
446 PythonicWidget.__init__(self)
448 self.vbox.pack_start(central_area)
449 self.central_area = central_area
451 def wait_for_response(self):
452 self.show_all()
453 response = self.run()
454 self.destroy()
455 return response
457 def get_children(self):
458 # if we don't have this get_widget_by_name won't work. We'll just skip
459 # the vbox in the middle. We (probably) don't care about the buttons at
460 # the bottom
461 return [self.central_area]
463 def FileChooser(filetypes=None):
464 chooser = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
465 if filetypes is not None:
466 for filetype in filetypes:
467 # TODO allow the user to change the name of the filter
468 filter = gtk.FileFilter()
469 filter.add_pattern(filetype)
470 chooser.add_filter(filter)
471 chooser.show_all()
472 chooser.run()
473 response = chooser.run()
474 if response == gtk.RESPONSE_OK:
475 result = chooser.get_filename()
476 elif response == gtk.RESPONSE_CANCEL:
477 result = None
478 chooser.destroy()
479 return result
482 def main():
483 gtk.main()