|
| 1 | +""" |
| 2 | + SimpleGladeApp.py |
| 3 | + Module that provides an object oriented abstraction to pygtk and libglade. |
| 4 | + Copyright (C) 2004 Sandino Flores Moreno |
| 5 | +""" |
| 6 | + |
| 7 | +# This library is free software; you can redistribute it and/or |
| 8 | +# modify it under the terms of the GNU Lesser General Public |
| 9 | +# License as published by the Free Software Foundation; either |
| 10 | +# version 2.1 of the License, or (at your option) any later version. |
| 11 | +# |
| 12 | +# This library is distributed in the hope that it will be useful, |
| 13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | +# Lesser General Public License for more details. |
| 16 | +# |
| 17 | +# You should have received a copy of the GNU Lesser General Public |
| 18 | +# License along with this library; if not, write to the Free Software |
| 19 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| 20 | +# USA |
| 21 | + |
| 22 | +import os |
| 23 | +import sys |
| 24 | +import re |
| 25 | + |
| 26 | +import tokenize |
| 27 | +import gtk |
| 28 | +import gtk.glade |
| 29 | +import weakref |
| 30 | +import inspect |
| 31 | + |
| 32 | + |
| 33 | +__version__ = "1.0" |
| 34 | +__author__ = 'Sandino "tigrux" Flores-Moreno' |
| 35 | + |
| 36 | +def bindtextdomain(app_name, locale_dir=None): |
| 37 | + """ |
| 38 | + Bind the domain represented by app_name to the locale directory locale_dir. |
| 39 | + It has the effect of loading translations, enabling applications for different |
| 40 | + languages. |
| 41 | +
|
| 42 | + app_name: |
| 43 | + a domain to look for translations, tipically the name of an application. |
| 44 | +
|
| 45 | + locale_dir: |
| 46 | + a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo |
| 47 | + If omitted or None, then the current binding for app_name is used. |
| 48 | + """ |
| 49 | + try: |
| 50 | + import locale |
| 51 | + import gettext |
| 52 | + locale.setlocale(locale.LC_ALL, "") |
| 53 | + gtk.glade.bindtextdomain(app_name, locale_dir) |
| 54 | + gettext.install(app_name, locale_dir, unicode=1) |
| 55 | + except (IOError,locale.Error), e: |
| 56 | + #force english as default locale |
| 57 | + try: |
| 58 | + os.environ["LANGUAGE"] = "en_US.UTF-8" |
| 59 | + locale.setlocale(locale.LC_ALL, "en_US.UTF-8") |
| 60 | + gtk.glade.bindtextdomain(app_name, locale_dir) |
| 61 | + gettext.install(app_name, locale_dir, unicode=1) |
| 62 | + return |
| 63 | + except: |
| 64 | + #english didnt work, just use spanish |
| 65 | + try: |
| 66 | + __builtins__.__dict__["_"] = lambda x : x |
| 67 | + except: |
| 68 | + __builtins__["_"] = lambda x : x |
| 69 | + |
| 70 | +class SimpleGladeApp: |
| 71 | + |
| 72 | + def __init__(self, path, root=None, domain=None, **kwargs): |
| 73 | + """ |
| 74 | + Load a glade file specified by glade_filename, using root as |
| 75 | + root widget and domain as the domain for translations. |
| 76 | +
|
| 77 | + If it receives extra named arguments (argname=value), then they are used |
| 78 | + as attributes of the instance. |
| 79 | +
|
| 80 | + path: |
| 81 | + path to a glade filename. |
| 82 | + If glade_filename cannot be found, then it will be searched in the |
| 83 | + same directory of the program (sys.argv[0]) |
| 84 | +
|
| 85 | + root: |
| 86 | + the name of the widget that is the root of the user interface, |
| 87 | + usually a window or dialog (a top level widget). |
| 88 | + If None or ommited, the full user interface is loaded. |
| 89 | +
|
| 90 | + domain: |
| 91 | + A domain to use for loading translations. |
| 92 | + If None or ommited, no translation is loaded. |
| 93 | +
|
| 94 | + **kwargs: |
| 95 | + a dictionary representing the named extra arguments. |
| 96 | + It is useful to set attributes of new instances, for example: |
| 97 | + glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value") |
| 98 | + sets two attributes (foo and bar) to glade_app. |
| 99 | + """ |
| 100 | + if os.path.isfile(path): |
| 101 | + self.glade_path = path |
| 102 | + else: |
| 103 | + glade_dir = os.path.dirname( sys.argv[0] ) |
| 104 | + self.glade_path = os.path.join(glade_dir, path) |
| 105 | + for key, value in kwargs.items(): |
| 106 | + try: |
| 107 | + setattr(self, key, weakref.proxy(value) ) |
| 108 | + except TypeError: |
| 109 | + setattr(self, key, value) |
| 110 | + self.glade = None |
| 111 | + self.install_custom_handler(self.custom_handler) |
| 112 | + self.glade = self.create_glade(self.glade_path, root, domain) |
| 113 | + if root: |
| 114 | + self.main_widget = self.get_widget(root) |
| 115 | + else: |
| 116 | + self.main_widget = None |
| 117 | + self.normalize_names() |
| 118 | + self.add_callbacks(self) |
| 119 | + self.new() |
| 120 | + |
| 121 | + def __repr__(self): |
| 122 | + class_name = self.__class__.__name__ |
| 123 | + if self.main_widget: |
| 124 | + root = gtk.Widget.get_name(self.main_widget) |
| 125 | + repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root) |
| 126 | + else: |
| 127 | + repr = '%s(path="%s")' % (class_name, self.glade_path) |
| 128 | + return repr |
| 129 | + |
| 130 | + def new(self): |
| 131 | + """ |
| 132 | + Method called when the user interface is loaded and ready to be used. |
| 133 | + At this moment, the widgets are loaded and can be refered as self.widget_name |
| 134 | + """ |
| 135 | + pass |
| 136 | + |
| 137 | + def add_callbacks(self, callbacks_proxy): |
| 138 | + """ |
| 139 | + It uses the methods of callbacks_proxy as callbacks. |
| 140 | + The callbacks are specified by using: |
| 141 | + Properties window -> Signals tab |
| 142 | + in glade-2 (or any other gui designer like gazpacho). |
| 143 | +
|
| 144 | + Methods of classes inheriting from SimpleGladeApp are used as |
| 145 | + callbacks automatically. |
| 146 | +
|
| 147 | + callbacks_proxy: |
| 148 | + an instance with methods as code of callbacks. |
| 149 | + It means it has methods like on_button1_clicked, on_entry1_activate, etc. |
| 150 | + """ |
| 151 | + self.glade.signal_autoconnect(callbacks_proxy) |
| 152 | + |
| 153 | + def normalize_names(self): |
| 154 | + """ |
| 155 | + It is internally used to normalize the name of the widgets. |
| 156 | + It means a widget named foo:vbox-dialog in glade |
| 157 | + is refered self.vbox_dialog in the code. |
| 158 | +
|
| 159 | + It also sets a data "prefixes" with the list of |
| 160 | + prefixes a widget has for each widget. |
| 161 | + """ |
| 162 | + for widget in self.get_widgets(): |
| 163 | + widget_name = gtk.Widget.get_name(widget) |
| 164 | + prefixes_name_l = widget_name.split(":") |
| 165 | + prefixes = prefixes_name_l[ : -1] |
| 166 | + widget_api_name = prefixes_name_l[-1] |
| 167 | + widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) ) |
| 168 | + gtk.Widget.set_name(widget, widget_api_name) |
| 169 | + if hasattr(self, widget_api_name): |
| 170 | + raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name)) |
| 171 | + else: |
| 172 | + setattr(self, widget_api_name, widget) |
| 173 | + if prefixes: |
| 174 | + gtk.Widget.set_data(widget, "prefixes", prefixes) |
| 175 | + |
| 176 | + def add_prefix_actions(self, prefix_actions_proxy): |
| 177 | + """ |
| 178 | + By using a gui designer (glade-2, gazpacho, etc) |
| 179 | + widgets can have a prefix in theirs names |
| 180 | + like foo:entry1 or foo:label3 |
| 181 | + It means entry1 and label3 has a prefix action named foo. |
| 182 | +
|
| 183 | + Then, prefix_actions_proxy must have a method named prefix_foo which |
| 184 | + is called everytime a widget with prefix foo is found, using the found widget |
| 185 | + as argument. |
| 186 | +
|
| 187 | + prefix_actions_proxy: |
| 188 | + An instance with methods as prefix actions. |
| 189 | + It means it has methods like prefix_foo, prefix_bar, etc. |
| 190 | + """ |
| 191 | + prefix_s = "prefix_" |
| 192 | + prefix_pos = len(prefix_s) |
| 193 | + |
| 194 | + is_method = lambda t : callable( t[1] ) |
| 195 | + is_prefix_action = lambda t : t[0].startswith(prefix_s) |
| 196 | + drop_prefix = lambda (k,w): (k[prefix_pos:],w) |
| 197 | + |
| 198 | + members_t = inspect.getmembers(prefix_actions_proxy) |
| 199 | + methods_t = filter(is_method, members_t) |
| 200 | + prefix_actions_t = filter(is_prefix_action, methods_t) |
| 201 | + prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) ) |
| 202 | + |
| 203 | + for widget in self.get_widgets(): |
| 204 | + prefixes = gtk.Widget.get_data(widget, "prefixes") |
| 205 | + if prefixes: |
| 206 | + for prefix in prefixes: |
| 207 | + if prefix in prefix_actions_d: |
| 208 | + prefix_action = prefix_actions_d[prefix] |
| 209 | + prefix_action(widget) |
| 210 | + |
| 211 | + def custom_handler(self, |
| 212 | + glade, function_name, widget_name, |
| 213 | + str1, str2, int1, int2): |
| 214 | + """ |
| 215 | + Generic handler for creating custom widgets, internally used to |
| 216 | + enable custom widgets (custom widgets of glade). |
| 217 | +
|
| 218 | + The custom widgets have a creation function specified in design time. |
| 219 | + Those creation functions are always called with str1,str2,int1,int2 as |
| 220 | + arguments, that are values specified in design time. |
| 221 | +
|
| 222 | + Methods of classes inheriting from SimpleGladeApp are used as |
| 223 | + creation functions automatically. |
| 224 | +
|
| 225 | + If a custom widget has create_foo as creation function, then the |
| 226 | + method named create_foo is called with str1,str2,int1,int2 as arguments. |
| 227 | + """ |
| 228 | + try: |
| 229 | + handler = getattr(self, function_name) |
| 230 | + return handler(str1, str2, int1, int2) |
| 231 | + except AttributeError: |
| 232 | + return None |
| 233 | + |
| 234 | + def gtk_widget_show(self, widget, *args): |
| 235 | + """ |
| 236 | + Predefined callback. |
| 237 | + The widget is showed. |
| 238 | + Equivalent to widget.show() |
| 239 | + """ |
| 240 | + widget.show() |
| 241 | + |
| 242 | + def gtk_widget_hide(self, widget, *args): |
| 243 | + """ |
| 244 | + Predefined callback. |
| 245 | + The widget is hidden. |
| 246 | + Equivalent to widget.hide() |
| 247 | + """ |
| 248 | + widget.hide() |
| 249 | + |
| 250 | + def gtk_widget_grab_focus(self, widget, *args): |
| 251 | + """ |
| 252 | + Predefined callback. |
| 253 | + The widget grabs the focus. |
| 254 | + Equivalent to widget.grab_focus() |
| 255 | + """ |
| 256 | + widget.grab_focus() |
| 257 | + |
| 258 | + def gtk_widget_destroy(self, widget, *args): |
| 259 | + """ |
| 260 | + Predefined callback. |
| 261 | + The widget is destroyed. |
| 262 | + Equivalent to widget.destroy() |
| 263 | + """ |
| 264 | + widget.destroy() |
| 265 | + |
| 266 | + def gtk_window_activate_default(self, window, *args): |
| 267 | + """ |
| 268 | + Predefined callback. |
| 269 | + The default widget of the window is activated. |
| 270 | + Equivalent to window.activate_default() |
| 271 | + """ |
| 272 | + widget.activate_default() |
| 273 | + |
| 274 | + def gtk_true(self, *args): |
| 275 | + """ |
| 276 | + Predefined callback. |
| 277 | + Equivalent to return True in a callback. |
| 278 | + Useful for stopping propagation of signals. |
| 279 | + """ |
| 280 | + return True |
| 281 | + |
| 282 | + def gtk_false(self, *args): |
| 283 | + """ |
| 284 | + Predefined callback. |
| 285 | + Equivalent to return False in a callback. |
| 286 | + """ |
| 287 | + return False |
| 288 | + |
| 289 | + def gtk_main_quit(self, *args): |
| 290 | + """ |
| 291 | + Predefined callback. |
| 292 | + Equivalent to self.quit() |
| 293 | + """ |
| 294 | + self.quit() |
| 295 | + |
| 296 | + def main(self): |
| 297 | + """ |
| 298 | + Starts the main loop of processing events. |
| 299 | + The default implementation calls gtk.main() |
| 300 | +
|
| 301 | + Useful for applications that needs a non gtk main loop. |
| 302 | + For example, applications based on gstreamer needs to override |
| 303 | + this method with gst.main() |
| 304 | +
|
| 305 | + Do not directly call this method in your programs. |
| 306 | + Use the method run() instead. |
| 307 | + """ |
| 308 | + gtk.main() |
| 309 | + |
| 310 | + def quit(self): |
| 311 | + """ |
| 312 | + Quit processing events. |
| 313 | + The default implementation calls gtk.main_quit() |
| 314 | + |
| 315 | + Useful for applications that needs a non gtk main loop. |
| 316 | + For example, applications based on gstreamer needs to override |
| 317 | + this method with gst.main_quit() |
| 318 | + """ |
| 319 | + gtk.main_quit() |
| 320 | + |
| 321 | + def run(self): |
| 322 | + """ |
| 323 | + Starts the main loop of processing events checking for Control-C. |
| 324 | +
|
| 325 | + The default implementation checks wheter a Control-C is pressed, |
| 326 | + then calls on_keyboard_interrupt(). |
| 327 | +
|
| 328 | + Use this method for starting programs. |
| 329 | + """ |
| 330 | + try: |
| 331 | + self.main() |
| 332 | + except KeyboardInterrupt: |
| 333 | + self.on_keyboard_interrupt() |
| 334 | + |
| 335 | + def on_keyboard_interrupt(self): |
| 336 | + """ |
| 337 | + This method is called by the default implementation of run() |
| 338 | + after a program is finished by pressing Control-C. |
| 339 | + """ |
| 340 | + pass |
| 341 | + |
| 342 | + def install_custom_handler(self, custom_handler): |
| 343 | + gtk.glade.set_custom_handler(custom_handler) |
| 344 | + |
| 345 | + def create_glade(self, glade_path, root, domain): |
| 346 | + return gtk.glade.XML(self.glade_path, root, domain) |
| 347 | + |
| 348 | + def get_widget(self, widget_name): |
| 349 | + return self.glade.get_widget(widget_name) |
| 350 | + |
| 351 | + def get_widgets(self): |
| 352 | + return self.glade.get_widget_prefix("") |
0 commit comments