Fixed setting icon for old pygtk's
[rox-lib.git] / python / rox / __init__.py
blob7fa52fdbaebb5584dc6c9787bcb415bb511e4d9b
1 """To use ROX-Lib2 you need to copy the findrox.py script into your application
2 directory and import that before anything else. This module will locate
3 ROX-Lib2 and add ROX-Lib2/python to sys.path. If ROX-Lib2 is not found, it
4 will display a suitable error and quit.
6 Since the name of the gtk2 module can vary, it is best to import it from rox,
7 where it is named 'g'.
9 The AppRun script of a simple application might look like this:
11 #!/usr/bin/env python
12 import findrox
13 import rox
15 window = rox.Window()
16 window.set_title('My window')
17 window.show()
19 rox.mainloop()
21 This program creates and displays a window. The rox.Window widget keeps
22 track of how many toplevel windows are open. rox.mainloop() will return
23 when the last one is closed.
25 'rox.app_dir' is set to the absolute pathname of your application (extracted
26 from sys.argv).
28 The builtin names True and False are defined to 1 and 0, if your version of
29 python is old enough not to include them already.
30 """
32 import sys, os
34 _path = os.path.realpath(sys.argv[0])
35 app_dir = os.path.dirname(_path)
36 if _path.endswith('/AppRun') or _path.endswith('/AppletRun'):
37 sys.argv[0] = os.path.dirname(_path)
39 # In python2.3 there is a bool type. Later versions of 2.2 use ints, but
40 # early versions don't support them at all, so create them here.
41 try:
42 True
43 except:
44 import __builtin__
45 __builtin__.False = 0
46 __builtin__.True = 1
48 try:
49 iter
50 except:
51 sys.stderr.write('Sorry, you need to have python 2.2, and it \n'
52 'must be the default version. You may be able to \n'
53 'change the first line of your program\'s AppRun \n'
54 'file to end \'python2.2\' as a workaround.\n')
55 raise SystemExit(1)
57 import i18n
59 _roxlib_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
60 _ = i18n.translation(os.path.join(_roxlib_dir, 'Messages'))
62 try:
63 zhost = 'zero-install.sourceforge.net'
64 zpath = '/uri/0install/' + zhost
65 if os.path.exists(zpath):
66 zpath = os.path.join(zpath, 'libs/pygtk-2/platform/latest')
67 if not os.path.exists(zpath):
68 os.system('0refresh ' + zhost)
69 if os.path.exists(zpath):
70 sys.path.insert(0, zpath +
71 '/lib/python%d.%d/site-packages' % sys.version_info[:2])
72 try:
73 # Try to support 1.99.12, at lest to show an error
74 import pygtk; pygtk.require('2.0')
75 except:
76 pass
77 import gtk; g = gtk # Don't syntax error for python1.5
78 assert g.Window # Ensure not 1.2 bindings
79 except:
80 sys.stderr.write(_('The pygtk2 package (1.99.13 or later) must be '
81 'installed to use this program:\n'
82 'http://rox.sourceforge.net/rox_lib.php3\n'))
83 raise
85 # Put argv back the way it was, now that Gtk has initialised
86 sys.argv[0] = _path
88 TRUE = True
89 FALSE = False
91 def alert(message):
92 "Display message in an error box. Return when the user closes the box."
93 toplevel_ref()
94 box = g.MessageDialog(None, 0, g.MESSAGE_ERROR, g.BUTTONS_OK, message)
95 box.set_position(g.WIN_POS_CENTER)
96 box.set_title(_('Error'))
97 box.run()
98 box.destroy()
99 toplevel_unref()
101 def croak(message):
102 """Display message in an error box, then quit the program, returning
103 with a non-zero exit status."""
104 alert(message)
105 sys.exit(1)
107 def info(message):
108 "Display informational message. Returns when the user closes the box."
109 toplevel_ref()
110 box = g.MessageDialog(None, 0, g.MESSAGE_INFO, g.BUTTONS_OK, message)
111 box.set_position(g.WIN_POS_CENTER)
112 box.set_title(_('Information'))
113 box.run()
114 box.destroy()
115 toplevel_unref()
117 def confirm(message, stock_icon, action = None):
118 """Display a <Cancel>/<Action> dialog. Result is true if the user
119 chooses the action, false otherwise. If action is given then that
120 is used as the text instead of the default for the stock item. Eg:
121 if rox.confirm('Really delete everything?', g.STOCK_DELETE): delete()
123 toplevel_ref()
124 box = g.MessageDialog(None, 0, g.MESSAGE_QUESTION,
125 g.BUTTONS_CANCEL, message)
126 if action:
127 button = ButtonMixed(stock_icon, action)
128 else:
129 button = g.Button(stock = stock_icon)
130 button.set_flags(g.CAN_DEFAULT)
131 button.show()
132 box.add_action_widget(button, g.RESPONSE_OK)
133 box.set_position(g.WIN_POS_CENTER)
134 box.set_title(_('Confirm:'))
135 box.set_default_response(g.RESPONSE_OK)
136 resp = box.run()
137 box.destroy()
138 toplevel_unref()
139 return resp == g.RESPONSE_OK
141 def report_exception():
142 """Display the current python exception in an error box, returning
143 when the user closes the box. This is useful in the 'except' clause
144 of a 'try' block. Uses rox.debug.show_exception()."""
145 type, value, tb = sys.exc_info()
146 import debug
147 debug.show_exception(type, value, tb)
149 _icon_path = os.path.join(app_dir, '.DirIcon')
150 _window_icon=None
151 if os.path.exists(_icon_path):
152 try:
153 g.window_set_default_icon_list(g.gdk.pixbuf_new_from_file(_icon_path))
154 except:
155 # Older pygtk
156 _window_icon = g.gdk.pixbuf_new_from_file(_icon_path)
157 del _icon_path
159 class Window(g.Window):
160 """This works in exactly the same way as a GtkWindow, except that
161 it calls the toplevel_(un)ref functions for you automatically,
162 and sets the window icon to <app_dir>/.DirIcon if it exists."""
163 def __init__(*args, **kwargs):
164 apply(g.Window.__init__, args, kwargs)
165 toplevel_ref()
166 args[0].connect('destroy', toplevel_unref)
168 if _window_icon:
169 args[0].set_icon(_window_icon)
171 class Dialog(g.Dialog):
172 """This works in exactly the same way as a GtkDialog, except that
173 it calls the toplevel_(un)ref functions for you automatically."""
174 def __init__(*args, **kwargs):
175 apply(g.Dialog.__init__, args, kwargs)
176 toplevel_ref()
177 args[0].connect('destroy', toplevel_unref)
179 class ButtonMixed(g.Button):
180 """A button with a standard stock icon, but any label. This is useful
181 when you want to express a concept similar to one of the stock ones."""
182 def __init__(self, stock, message):
183 """Specify the icon and text for the new button. The text
184 may specify the mnemonic for the widget by putting a _ before
185 the letter, eg:
186 button = ButtonMixed(g.STOCK_DELETE, '_Delete message')."""
187 g.Button.__init__(self)
189 label = g.Label('')
190 label.set_text_with_mnemonic(message)
191 label.set_mnemonic_widget(self)
193 image = g.image_new_from_stock(stock, g.ICON_SIZE_BUTTON)
194 box = g.HBox(FALSE, 2)
195 align = g.Alignment(0.5, 0.5, 0.0, 0.0)
197 box.pack_start(image, FALSE, FALSE, 0)
198 box.pack_end(label, FALSE, FALSE, 0)
200 self.add(align)
201 align.add(box)
202 align.show_all()
204 _toplevel_windows = 0
205 _in_mainloops = 0
206 def mainloop():
207 """This is a wrapper around the gtk2.mainloop function. It only runs
208 the loop if there are top level references, and exits when
209 rox.toplevel_unref() reduces the count to zero."""
210 global _toplevel_windows, _in_mainloops
212 _in_mainloops = _in_mainloops + 1 # Python1.5 syntax
213 try:
214 while _toplevel_windows:
215 g.mainloop()
216 finally:
217 _in_mainloops = _in_mainloops - 1
219 def toplevel_ref():
220 """Increment the toplevel ref count. rox.mainloop() won't exit until
221 toplevel_unref() is called the same number of times."""
222 global _toplevel_windows
223 _toplevel_windows = _toplevel_windows + 1
225 def toplevel_unref(*unused):
226 """Decrement the toplevel ref count. If this is called while in
227 rox.mainloop() and the count has reached zero, then rox.mainloop()
228 will exit. Ignores any arguments passed in, so you can use it
229 easily as a callback function."""
230 global _toplevel_windows
231 assert _toplevel_windows > 0
232 _toplevel_windows = _toplevel_windows - 1
233 if _toplevel_windows == 0 and _in_mainloops:
234 g.mainquit()
236 _host_name = None
237 def our_host_name():
238 """Try to return the canonical name for this computer. This is used
239 in the drag-and-drop protocol to work out whether a drop is coming from
240 a remote machine (and therefore has to be fetched differently)."""
241 from socket import gethostbyaddr, gethostname
242 global _host_name
243 if _host_name:
244 return _host_name
245 try:
246 (host, alias, ips) = gethostbyaddr(gethostname())
247 for name in [host] + alias:
248 if name.find('.') != -1:
249 _host_name = name
250 return name
251 return name
252 except:
253 sys.stderr.write(
254 "*** ROX-Lib gethostbyaddr(gethostname()) failed!\n")
255 return "localhost"
257 def get_local_path(uri):
258 """Convert 'uri' to a local path and return, if possible. If 'uri'
259 is a resource on a remote machine, return None."""
260 if not uri:
261 return None
263 if uri[0] == '/':
264 if uri[1:2] != '/':
265 return uri # A normal Unix pathname
266 i = uri.find('/', 2)
267 if i == -1:
268 return None # //something
269 if i == 2:
270 return uri[2:] # ///path
271 remote_host = uri[2:i]
272 if remote_host == our_host_name():
273 return uri[i:] # //localhost/path
274 # //otherhost/path
275 elif uri[:5].lower() == 'file:':
276 if uri[5:6] == '/':
277 return get_local_path(uri[5:])
278 elif uri[:2] == './' or uri[:3] == '../':
279 return uri
280 return None
282 app_options = None
283 def setup_app_options(program, leaf = 'Options.xml'):
284 """Most applications only have one set of options. This function can be
285 used to set up the default group. 'program' is the name of the
286 directory to use in <Choices> and 'leaf' is the name of the file used
287 to store the group. You can refer to the group using rox.app_options.
288 See rox.options.OptionGroup."""
289 global app_options
290 assert not app_options
291 from options import OptionGroup
292 app_options = OptionGroup(program, leaf)
294 _options_box = None
295 def edit_options(options_file = None):
296 """Edit the app_options (set using setup_app_options()) using the GUI
297 specified in 'options_file' (default <app_dir>/Options.xml).
298 If this function is called again while the box is still open, the
299 old box will be redisplayed to the user."""
300 assert app_options
302 global _options_box
303 if _options_box:
304 _options_box.present()
305 return
307 if not options_file:
308 options_file = os.path.join(app_dir, 'Options.xml')
310 import OptionsBox
311 _options_box = OptionsBox.OptionsBox(app_options, options_file)
313 def closed(widget):
314 global _options_box
315 assert _options_box == widget
316 _options_box = None
317 _options_box.connect('destroy', closed)
318 _options_box.open()
320 try:
321 import xml
322 except:
323 alert(_("You do not have the Python 'xml' module installed, which "
324 "ROX-Lib2 requires. You need to install python-xmlbase "
325 "(this is a small package; the full PyXML package is not "
326 "required)."))
328 if g.pygtk_version[:2] == (1, 99) and g.pygtk_version[2] < 12:
329 # 1.99.12 is really too old too, but RH8.0 uses it so we'll have
330 # to work around any problems...
331 sys.stderr.write('Your version of pygtk (%d.%d.%d) is too old. '
332 'Things might not work correctly.' % g.pygtk_version)