Removed extra arg that shouldn't have been added.
[rox-lib.git] / python / rox / __init__.py
blob5021f7910feb7f74bbad9e1b5d7d15e8844e3c1f
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 bug(message = "A bug has been detected in this program. Please report "
102 "the problem to the authors."):
103 try:
104 raise Exception(message)
105 except:
106 type, value, tb = sys.exc_info()
107 import debug
108 debug.show_exception(type, value, tb, auto_details = True)
110 def croak(message):
111 """Display message in an error box, then quit the program, returning
112 with a non-zero exit status."""
113 alert(message)
114 sys.exit(1)
116 def info(message):
117 "Display informational message. Returns when the user closes the box."
118 toplevel_ref()
119 box = g.MessageDialog(None, 0, g.MESSAGE_INFO, g.BUTTONS_OK, message)
120 box.set_position(g.WIN_POS_CENTER)
121 box.set_title(_('Information'))
122 box.run()
123 box.destroy()
124 toplevel_unref()
126 def confirm(message, stock_icon, action = None):
127 """Display a <Cancel>/<Action> dialog. Result is true if the user
128 chooses the action, false otherwise. If action is given then that
129 is used as the text instead of the default for the stock item. Eg:
130 if rox.confirm('Really delete everything?', g.STOCK_DELETE): delete()
132 toplevel_ref()
133 box = g.MessageDialog(None, 0, g.MESSAGE_QUESTION,
134 g.BUTTONS_CANCEL, message)
135 if action:
136 button = ButtonMixed(stock_icon, action)
137 else:
138 button = g.Button(stock = stock_icon)
139 button.set_flags(g.CAN_DEFAULT)
140 button.show()
141 box.add_action_widget(button, g.RESPONSE_OK)
142 box.set_position(g.WIN_POS_CENTER)
143 box.set_title(_('Confirm:'))
144 box.set_default_response(g.RESPONSE_OK)
145 resp = box.run()
146 box.destroy()
147 toplevel_unref()
148 return resp == g.RESPONSE_OK
150 def report_exception():
151 """Display the current python exception in an error box, returning
152 when the user closes the box. This is useful in the 'except' clause
153 of a 'try' block. Uses rox.debug.show_exception()."""
154 type, value, tb = sys.exc_info()
155 import debug
156 debug.show_exception(type, value, tb)
158 _icon_path = os.path.join(app_dir, '.DirIcon')
159 _window_icon=None
160 if os.path.exists(_icon_path):
161 try:
162 g.window_set_default_icon_list(g.gdk.pixbuf_new_from_file(_icon_path))
163 except:
164 # Older pygtk
165 _window_icon = g.gdk.pixbuf_new_from_file(_icon_path)
166 del _icon_path
168 class Window(g.Window):
169 """This works in exactly the same way as a GtkWindow, except that
170 it calls the toplevel_(un)ref functions for you automatically,
171 and sets the window icon to <app_dir>/.DirIcon if it exists."""
172 def __init__(*args, **kwargs):
173 apply(g.Window.__init__, args, kwargs)
174 toplevel_ref()
175 args[0].connect('destroy', toplevel_unref)
177 if _window_icon:
178 args[0].set_icon(_window_icon)
180 class Dialog(g.Dialog):
181 """This works in exactly the same way as a GtkDialog, except that
182 it calls the toplevel_(un)ref functions for you automatically."""
183 def __init__(*args, **kwargs):
184 apply(g.Dialog.__init__, args, kwargs)
185 toplevel_ref()
186 args[0].connect('destroy', toplevel_unref)
188 class ButtonMixed(g.Button):
189 """A button with a standard stock icon, but any label. This is useful
190 when you want to express a concept similar to one of the stock ones."""
191 def __init__(self, stock, message):
192 """Specify the icon and text for the new button. The text
193 may specify the mnemonic for the widget by putting a _ before
194 the letter, eg:
195 button = ButtonMixed(g.STOCK_DELETE, '_Delete message')."""
196 g.Button.__init__(self)
198 label = g.Label('')
199 label.set_text_with_mnemonic(message)
200 label.set_mnemonic_widget(self)
202 image = g.image_new_from_stock(stock, g.ICON_SIZE_BUTTON)
203 box = g.HBox(FALSE, 2)
204 align = g.Alignment(0.5, 0.5, 0.0, 0.0)
206 box.pack_start(image, FALSE, FALSE, 0)
207 box.pack_end(label, FALSE, FALSE, 0)
209 self.add(align)
210 align.add(box)
211 align.show_all()
213 _toplevel_windows = 0
214 _in_mainloops = 0
215 def mainloop():
216 """This is a wrapper around the gtk2.mainloop function. It only runs
217 the loop if there are top level references, and exits when
218 rox.toplevel_unref() reduces the count to zero."""
219 global _toplevel_windows, _in_mainloops
221 _in_mainloops = _in_mainloops + 1 # Python1.5 syntax
222 try:
223 while _toplevel_windows:
224 g.mainloop()
225 finally:
226 _in_mainloops = _in_mainloops - 1
228 def toplevel_ref():
229 """Increment the toplevel ref count. rox.mainloop() won't exit until
230 toplevel_unref() is called the same number of times."""
231 global _toplevel_windows
232 _toplevel_windows = _toplevel_windows + 1
234 def toplevel_unref(*unused):
235 """Decrement the toplevel ref count. If this is called while in
236 rox.mainloop() and the count has reached zero, then rox.mainloop()
237 will exit. Ignores any arguments passed in, so you can use it
238 easily as a callback function."""
239 global _toplevel_windows
240 assert _toplevel_windows > 0
241 _toplevel_windows = _toplevel_windows - 1
242 if _toplevel_windows == 0 and _in_mainloops:
243 g.mainquit()
245 _host_name = None
246 def our_host_name():
247 """Try to return the canonical name for this computer. This is used
248 in the drag-and-drop protocol to work out whether a drop is coming from
249 a remote machine (and therefore has to be fetched differently)."""
250 from socket import getfqdn
251 global _host_name
252 if _host_name:
253 return _host_name
254 try:
255 _host_name = getfqdn()
256 except:
257 _host_name = 'localhost'
258 alert("ROX-Lib socket.getfqdn() failed!")
259 return _host_name
261 def get_local_path(uri):
262 """Convert 'uri' to a local path and return, if possible. If 'uri'
263 is a resource on a remote machine, return None."""
264 if not uri:
265 return None
267 if uri[0] == '/':
268 if uri[1:2] != '/':
269 return uri # A normal Unix pathname
270 i = uri.find('/', 2)
271 if i == -1:
272 return None # //something
273 if i == 2:
274 return uri[2:] # ///path
275 remote_host = uri[2:i]
276 if remote_host == our_host_name():
277 return uri[i:] # //localhost/path
278 # //otherhost/path
279 elif uri[:5].lower() == 'file:':
280 if uri[5:6] == '/':
281 return get_local_path(uri[5:])
282 elif uri[:2] == './' or uri[:3] == '../':
283 return uri
284 return None
286 app_options = None
287 def setup_app_options(program, leaf = 'Options.xml'):
288 """Most applications only have one set of options. This function can be
289 used to set up the default group. 'program' is the name of the
290 directory to use in <Choices> and 'leaf' is the name of the file used
291 to store the group. You can refer to the group using rox.app_options.
292 See rox.options.OptionGroup."""
293 global app_options
294 assert not app_options
295 from options import OptionGroup
296 app_options = OptionGroup(program, leaf)
298 _options_box = None
299 def edit_options(options_file = None):
300 """Edit the app_options (set using setup_app_options()) using the GUI
301 specified in 'options_file' (default <app_dir>/Options.xml).
302 If this function is called again while the box is still open, the
303 old box will be redisplayed to the user."""
304 assert app_options
306 global _options_box
307 if _options_box:
308 _options_box.present()
309 return
311 if not options_file:
312 options_file = os.path.join(app_dir, 'Options.xml')
314 import OptionsBox
315 _options_box = OptionsBox.OptionsBox(app_options, options_file)
317 def closed(widget):
318 global _options_box
319 assert _options_box == widget
320 _options_box = None
321 _options_box.connect('destroy', closed)
322 _options_box.open()
324 try:
325 import xml
326 except:
327 alert(_("You do not have the Python 'xml' module installed, which "
328 "ROX-Lib2 requires. You need to install python-xmlbase "
329 "(this is a small package; the full PyXML package is not "
330 "required)."))
332 if g.pygtk_version[:2] == (1, 99) and g.pygtk_version[2] < 12:
333 # 1.99.12 is really too old too, but RH8.0 uses it so we'll have
334 # to work around any problems...
335 sys.stderr.write('Your version of pygtk (%d.%d.%d) is too old. '
336 'Things might not work correctly.' % g.pygtk_version)