change how liststore columns are loaded.
[pythonicgtk.git] / pythonicgtk.py
blob853b3a14e7b9bd9d0b396c3a150a1c9161524ce4
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 if 'expand' not in kwargs and 'fill' not in kwargs:
49 # if we're creating a widget that has no parent, then we should get
50 # of this now or there'll be errors later
51 return
53 expand = kwargs.get("expand", True)
54 fill = kwargs.get("fill", True)
57 def set_packing(widget=None, old_parent=None):
58 parent = self.get_parent()
59 if parent is not None and 'query_child_packing' in dir(parent):
60 old_values = list(parent.query_child_packing(self))
61 # we just want to change the expand and fill
62 old_values[0] = expand
63 old_values[1] = fill
64 parent.set_child_packing(self, expand, fill, old_values[2], old_values[3])
66 set_packing()
67 self.connect("parent-set", set_packing)
69 def attach_signal_handlers(self, **cb_dict):
70 events_to_signals = { 'on_click': 'clicked' }
72 for event, func in cb_dict.items():
73 if event in events_to_signals:
74 self.connect(events_to_signals[event], func)
76 def set_name_param(self, **kwargs):
77 if 'name' in kwargs:
78 self.set_name(kwargs['name'])
80 @property
81 def parent_window(self):
82 if 'container' not in dir(self):
83 return None
85 parent = self.container
86 while parent:
87 if isinstance(parent, Window):
88 return parent
89 if 'container' not in dir(parent):
90 return None
91 parent = parent.container
93 assert False, "Widget %r has no parent window" % self
95 @property
96 def child_widgets(self):
97 if 'get_children' not in dir(self):
98 yield None
99 return
101 widgets = [self]
103 # When being called with self as a window, it is not returning widgets
105 while len(widgets) != 0:
106 widget = widgets.pop(0)
107 if not isinstance( widget, gtk.Container ):
108 yield widget
109 continue
110 else:
111 yield widget
112 for child in widget.get_children():
113 yield child
114 if isinstance( child, gtk.Container ):
115 widgets.extend(child.get_children())
118 class Button(gtk.Button, PythonicWidget):
119 def __init__(self, label=None, stock=None, *args, **kwargs):
120 gtk.Button.__init__(self)
121 PythonicWidget.__init__(self, *args, **kwargs)
122 assert len(args) == 0
123 if label == None:
124 label = ""
125 self.set_label(label)
126 if stock != None:
127 self.set_use_stock(True)
128 self.set_label(stock)
131 class VBox(gtk.VBox, PythonicWidget):
132 def __init__(self, *args, **kwargs):
133 gtk.VBox.__init__(self)
134 PythonicWidget.__init__(self, *args, **kwargs)
135 for widget in args:
136 widget.container = self
137 self.pack_start(widget)
139 class HBox(gtk.HBox, PythonicWidget):
140 def __init__(self, *args, **kwargs):
141 gtk.HBox.__init__(self)
142 PythonicWidget.__init__(self, *args, **kwargs)
143 for widget in args:
144 widget.container = self
145 self.pack_start(widget)
147 class Label(gtk.Label, PythonicWidget):
148 def __init__(self, text, *args, **kwargs):
149 gtk.Label.__init__(self)
150 PythonicWidget.__init__(self, *args, **kwargs)
151 self.set_text(text)
153 class TextEntry(gtk.Entry, PythonicWidget):
154 def __init__(self, *args, **kwargs):
155 gtk.Entry.__init__(self)
156 PythonicWidget.__init__(self, *args, **kwargs)
158 if 'text' in kwargs:
159 self.set_text(kwargs['text'])
161 @property
162 def value(self):
163 return self.get_text()
165 @property
166 def text(self):
167 return self.get_text()
169 class TextView(gtk.TextView, PythonicWidget):
170 def __init__(self, *args, **kwargs):
171 gtk.TextView.__init__(self)
172 PythonicWidget.__init__(self, *args, **kwargs)
174 @property
175 def value(self):
176 return self.get_text()
178 def Labeled( label, widget, **kwargs ):
179 return HBox( Label(label), widget, **kwargs )
181 class ScrolledWindow(gtk.ScrolledWindow, PythonicWidget):
182 def __init__(self, inside_widget, horizontal=None, vertical=None, **kwargs):
183 gtk.ScrolledWindow.__init__(self)
184 PythonicWidget.__init__(self, **kwargs)
185 # FIXME should this be add_with_viewport? Or should it check
186 # inside_widget to see if it should add_with_viewport
187 if isinstance(inside_widget, gtk.Viewport):
188 self.add(inside_widget)
189 else:
190 self.add_with_viewport(inside_widget)
191 exiting_horizontal, existing_vertical = self.get_policy()
192 if horizontal == 'always':
193 self.set_policy(gtk.POLICY_ALWAYS, existing_vertical)
194 elif horizontal == 'never':
195 self.set_policy(gtk.POLICY_NEVER, existing_vertical)
196 elif horizontal == 'auto' or horizontal == 'automatic':
197 self.set_policy(gtk.POLICY_AUTOMATIC, existing_vertical)
198 exiting_horizontal, existing_vertical = self.get_policy()
199 if vertical == 'always':
200 self.set_policy(exiting_horizontal, gtk.POLICY_ALWAYS)
201 elif vertical == 'never':
202 self.set_policy(exiting_horizontal, gtk.POLICY_NEVER)
203 elif vertical == 'auto' or vertical == 'automatic':
204 self.set_policy(exiting_horizontal, gtk.POLICY_AUTOMATIC)
208 class IconView(gtk.IconView, PythonicWidget):
209 def __init__(self, elements, *args, **kwargs):
210 gtk.IconView.__init__(self)
211 PythonicWidget.__init__(self, *args, **kwargs)
212 set_properties(self, kwargs, ignore_errors=True)
213 # FIXME check the format of args
214 self.__init_from_icon_set(elements)
216 def __init_from_icon_set(self, elements):
217 # elements should be a list. Each element should be the same.
218 # Possible values:
219 # string - each 'icon' is a text
220 # (string, pixbuf) - standard icon and text
221 list = None
222 if type(elements[0]) == type(""):
223 list = gtk.ListStore(str)
224 for string in elements:
225 list.append([string])
226 self.set_model(list)
227 self.set_text_column(0)
228 elif type(elements[0]) == type(()):
229 # FIXME finish this
230 pass
231 else:
232 raise TypeError
234 class Toolbar(gtk.Toolbar, PythonicWidget):
235 def __init__(self, *child_widgets, **kwargs):
236 gtk.Toolbar.__init__(self)
237 PythonicWidget.__init__(self, **kwargs)
238 for child_widget in child_widgets:
239 # insert it at the end
240 #toolitem = gtk.ToolItem()
241 #toolitem.pack_start(child_widget)
242 self.insert( child_widget, -1 )
244 class NewButton(Button, PythonicWidget):
245 def __init__(self, *args, **kwargs):
246 Button.__init__(self, stock="gtk-new")
247 PythonicWidget.__init__(self, *args, **kwargs)
249 class AddButton(Button, PythonicWidget):
250 def __init__(self, *args, **kwargs):
251 Button.__init__(self, stock="gtk-add")
252 PythonicWidget.__init__(self, *args, **kwargs)
254 class RemoveButton(Button, PythonicWidget):
255 def __init__(self, *args, **kwargs):
256 Button.__init__(self, stock="gtk-remove")
257 PythonicWidget.__init__(self, *args, **kwargs)
259 class SaveButton(Button, PythonicWidget):
260 def __init__(self, *args, **kwargs):
261 Button.__init__(self, stock="gtk-save")
262 PythonicWidget.__init__(self, *args, **kwargs)
264 class NewToolbarButton(gtk.ToolButton, PythonicWidget):
265 def __init__(self, *args, **kwargs):
266 gtk.ToolButton.__init__(self, 'gtk-new')
267 PythonicWidget.__init__(self, *args, **kwargs)
269 class PropertiesButton(Button, PythonicWidget):
270 def __init__(self, *args, **kwargs):
271 Button.__init__(self, stock="gtk-properties")
272 PythonicWidget.__init__(self, *args, **kwargs)
274 class PropertiesToolbarButton(gtk.ToolButton, PythonicWidget):
275 def __init__(self, *args, **kwargs):
276 gtk.ToolButton.__init__(self, "gtk-properties")
277 PythonicWidget.__init__(self, *args, **kwargs)
279 class ComboBox(gtk.ComboBox, PythonicWidget):
280 def __init__(self, *rows, **kwargs):
281 gtk.ComboBox.__init__(self)
282 PythonicWidget.__init__(self, **kwargs)
284 liststore = gtk.ListStore(str)
285 self.set_model( liststore )
287 cell = gtk.CellRendererText()
288 self.pack_start( cell, True )
289 self.add_attribute( cell, 'text', 0 )
291 active = None
293 for index, row in enumerate( rows ):
294 if isinstance( row, basestring ):
295 text = row
296 else:
297 text = row[0]
298 options = row[1]
299 if options['selected']:
300 active = index
301 self.append_text( text )
303 if active is not None:
304 self.set_active( active )
307 class Notebook(gtk.Notebook, PythonicWidget):
308 def __init__(self, *pages, **kwargs):
309 gtk.Notebook.__init__(self)
310 PythonicWidget.__init__(self, **kwargs)
312 for page_name, page_contents in pages:
313 if isinstance(page_name, basestring):
314 page_name = Label( page_name )
315 self.append_page( child=page_contents, tab_label=page_name )
317 class RadioButton(gtk.RadioButton, PythonicWidget):
318 def __init__(self, group=None, label=None, *args, **kwargs):
319 gtk.RadioButton.__init__(self, group=None, label=label)
320 PythonicWidget.__init__(self, *args, **kwargs)
321 self.group = group
322 self.label = label
323 if 'value' in kwargs:
324 self.value = kwargs['value']
327 def update_all_radiobutton_groups(widget, new_child):
328 if 'get_children' not in dir( widget ):
329 return
331 child_radiobuttons = [child for child in widget.child_widgets if isinstance( child, RadioButton ) ]
333 groups = {}
334 for radiobutton in child_radiobuttons:
335 group_name = radiobutton.group
336 if group_name not in groups:
337 groups[group_name] = []
338 groups[group_name].append(radiobutton)
340 for group_name in groups:
341 inital_group = groups[group_name][0]
342 if len(groups[group_name]) <= 1:
343 continue
344 for radiobutton in groups[group_name][1:]:
345 radiobutton.set_group(None)
346 radiobutton.set_group(inital_group)
349 def add_updater_callback(widget, old_parent):
350 #print "Adding the add_updater_callback to "+repr(widget)
351 # when the parent is set we need to tell the parent to update_all_radiobutton_groups
353 if "get_children" in dir( widget ):
354 #print repr(widget)+" is a container"
355 update_all_radiobutton_groups(widget, None)
356 widget.connect( "add", update_all_radiobutton_groups )
358 if 'container' in dir( widget ) and widget.container:
359 widget.container.connect( "parent-set", add_updater_callback )
361 self.connect("parent-set", add_updater_callback)
362 update_all_radiobutton_groups(self, None)
366 class ListBox(gtk.TreeView, PythonicWidget):
367 def __init__(self, *rows, **kwargs):
368 try:
369 columns = kwargs['columns']
370 except KeyError:
371 # Turn our key error into a type error
372 raise TypeError, "ListBox constructor requires the 'columns' argument"
373 gtk.TreeView.__init__(self)
374 PythonicWidget.__init__(self, **kwargs)
376 # We assume they are all text columns for now
377 # TODO allow the caller to change this.
378 for index, col in enumerate(columns):
379 if isinstance( col, basestring ):
380 type = str
381 else:
382 pass
383 column = gtk.TreeViewColumn(col, gtk.CellRendererText(), text=index)
384 column.set_resizable(True)
385 column.set_sort_column_id(index)
386 self.append_column(column)
388 #view_entries_list.connect("row-activated", show_single_entry)
390 #column_types = []
391 #column_types.extend([x['type'] for x in view_def['columns']])
392 #column_types.extend([gobject.TYPE_PYOBJECT])
393 # TODO more types
394 column_types = [str] * len(columns)
395 self.rows = ListStore(column_types=column_types, rows=rows)
397 self.set_model(self.rows)
399 class ListStore(gtk.ListStore, PythonicWidget):
400 def __init__(self, column_types, rows=None, *args, **kwargs):
401 gtk.ListStore.__init__(self, *column_types)
402 PythonicWidget.__init__(self, *args, **kwargs)
403 self.column_types = column_types
404 for row in rows:
405 if len(row) != len(column_types):
406 raise TypeError, "Row %r has %d entries but there are %d columns definied (%r)" % ( row, len(row), len(columns), columns)
407 gtk.ListStore.append(self, row)
410 class Window(gtk.Window, PythonicWidget):
411 def __init__(self, child_widget, *args, **kwargs):
412 gtk.Window.__init__(self)
413 PythonicWidget.__init__(self, **kwargs)
415 self.add(child_widget)
416 child_widget.container = self
418 if 'title' in kwargs:
419 self.set_title(kwargs['title'])
421 if 'quit_on_close' in kwargs:
422 if kwargs['quit_on_close'] == True:
423 self.connect("destroy", gtk.main_quit)
425 def get_value(self, value_name):
426 # Either the widget is a text box (eg) that we can read the value off,
427 # or it's some kind of multi-selection thing, like a set of radio
428 # buttons
429 widget_by_name = get_widget_by_name(self, value_name)
430 if widget_by_name:
431 return widget_by_name.value
432 else:
433 widget_by_group = [widget for widget in self.child_widgets if isinstance( widget, RadioButton ) and widget.group == value_name]
434 if len(widget_by_group) == 0:
435 # no radio buttons have value_name as a group name
436 return
437 for widget in widget_by_group:
438 if widget.get_active():
439 if 'value' in dir(widget):
440 return widget.value
441 else:
442 return widget.label
444 return None
446 class Dialog(gtk.Dialog, Window, PythonicWidget):
447 def __init__(self, central_area, button_area, *args, **kwargs):
448 title = None
449 if 'title' in kwargs:
450 title = kwargs['title']
452 gtk.Dialog.__init__(self, title=title, buttons=button_area)
453 PythonicWidget.__init__(self)
455 self.vbox.pack_start(central_area)
456 self.central_area = central_area
458 def wait_for_response(self):
459 self.show_all()
460 response = self.run()
461 self.destroy()
462 return response
464 def get_children(self):
465 # if we don't have this get_widget_by_name won't work. We'll just skip
466 # the vbox in the middle. We (probably) don't care about the buttons at
467 # the bottom
468 return [self.central_area]
470 def FileChooser(filetypes=None):
471 chooser = gtk.FileChooserDialog(buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
472 if filetypes is not None:
473 for filetype in filetypes:
474 # TODO allow the user to change the name of the filter
475 filter = gtk.FileFilter()
476 filter.add_pattern(filetype)
477 chooser.add_filter(filter)
478 chooser.show_all()
479 chooser.run()
480 response = chooser.run()
481 if response == gtk.RESPONSE_OK:
482 result = chooser.get_filename()
483 elif response == gtk.RESPONSE_CANCEL:
484 result = None
485 chooser.destroy()
486 return result
489 def main():
490 gtk.main()