Show local variables in report_exception, and allow expressions to be
[rox-lib.git] / python / rox / __init__.py
blobef99a305bf62fcb9c5b87f88537141bf949dd688
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 import findrox
12 import rox
14 window = rox.Window()
15 window.set_title('My window')
16 window.show()
18 rox.mainloop()
20 This program creates and displays a window. The rox.Window widget keeps
21 track of how many toplevel windows are open. rox.mainloop() will return
22 when the last one is closed.
24 Other useful values from this module are:
26 TRUE and FALSE (copied from g.TRUE and g.FALSE as a convenience), and
27 'app_dir', which is the absolute pathname of your application (extracted from
28 sys.argv)."""
30 import sys, os
31 import i18n
33 _roxlib_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
34 _ = i18n.translation(os.path.join(_roxlib_dir, 'Messages'))
36 try:
37 try:
38 import gtk2 as g
39 except:
40 import gtk as g
41 assert g.Window
42 except:
43 sys.stderr.write(_('The pygtk2 package must be '
44 'installed to use this program:\n'
45 'http://rox.sourceforge.net/rox_lib.php3'))
46 raise
48 TRUE = g.TRUE
49 FALSE = g.FALSE
51 app_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
53 def alert(message):
54 "Display message in an error box. Return when the user closes the box."
55 toplevel_ref()
56 box = g.MessageDialog(None, 0, g.MESSAGE_ERROR, g.BUTTONS_OK, message)
57 box.set_position(g.WIN_POS_CENTER)
58 box.set_title(_('Error'))
59 box.run()
60 box.destroy()
61 toplevel_unref()
63 def croak(message):
64 """Display message in an error box, then quit the program, returning
65 with a non-zero exit status."""
66 alert(message)
67 sys.exit(1)
69 def info(message):
70 "Display informational message. Returns when the user closes the box."
71 toplevel_ref()
72 box = g.MessageDialog(None, 0, g.MESSAGE_INFO, g.BUTTONS_OK, message)
73 box.set_position(g.WIN_POS_CENTER)
74 box.set_title(_('Information'))
75 box.run()
76 box.destroy()
77 toplevel_unref()
79 def confirm(message, stock_icon, action = None):
80 """Display a <Cancel>/<Action> dialog. Result is true if the user
81 chooses the action, false otherwise. If action is given then that
82 is used as the text instead of the default for the stock item. Eg:
83 if rox.confirm('Really delete everything?', g.STOCK_DELETE): delete()
84 """
85 toplevel_ref()
86 box = g.MessageDialog(None, 0, g.MESSAGE_QUESTION,
87 g.BUTTONS_CANCEL, message)
88 if action:
89 button = ButtonMixed(stock_icon, action)
90 else:
91 button = g.Button(stock = stock_icon)
92 button.set_flags(g.CAN_DEFAULT)
93 button.show()
94 box.add_action_widget(button, g.RESPONSE_OK)
95 box.set_position(g.WIN_POS_CENTER)
96 box.set_title(_('Confirm:'))
97 box.set_default_response(g.RESPONSE_OK)
98 resp = box.run()
99 box.destroy()
100 toplevel_unref()
101 return resp == g.RESPONSE_OK
103 def report_exception():
104 """Display the current python exception in an error box, returning
105 when the user closes the box. This is useful in the 'except' clause
106 of a 'try' block. Uses rox.debug.show_exception()."""
107 type, value, tb = sys.exc_info()
108 import debug
109 debug.show_exception(type, value, tb)
111 class Window(g.Window):
112 """This works in exactly the same way as a GtkWindow, except that
113 it calls the toplevel_(un)ref functions for you automatically."""
114 def __init__(*args, **kwargs):
115 apply(g.Window.__init__, args, kwargs)
116 toplevel_ref()
117 args[0].connect('destroy', toplevel_unref)
119 class ButtonMixed(g.Button):
120 """A button with a standard stock icon, but any label. This is useful
121 when you want to express a concept similar to one of the stock ones."""
122 def __init__(self, stock, message):
123 """Specify the icon and text for the new button. The text
124 may specify the mnemonic for the widget by putting a _ before
125 the letter, eg:
126 button = ButtonMixed(g.STOCK_DELETE, '_Delete message')."""
127 g.Button.__init__(self)
129 label = g.Label('')
130 label.set_text_with_mnemonic(message)
131 label.set_mnemonic_widget(self)
133 image = g.image_new_from_stock(stock, g.ICON_SIZE_BUTTON)
134 box = g.HBox(FALSE, 2)
135 align = g.Alignment(0.5, 0.5, 0.0, 0.0)
137 box.pack_start(image, FALSE, FALSE, 0)
138 box.pack_end(label, FALSE, FALSE, 0)
140 self.add(align)
141 align.add(box)
142 align.show_all()
144 _toplevel_windows = 0
145 _in_mainloops = 0
146 def mainloop():
147 """This is a wrapper around the gtk2.mainloop function. It only runs
148 the loop if there are top level references, and exits when
149 rox.toplevel_unref() reduces the count to zero."""
150 global _toplevel_windows, _in_mainloops
152 _in_mainloops += 1
153 try:
154 while _toplevel_windows:
155 g.mainloop()
156 finally:
157 _in_mainloops -= 1
159 def toplevel_ref():
160 """Increment the toplevel ref count. rox.mainloop() won't exit until
161 toplevel_unref() is called the same number of times."""
162 global _toplevel_windows
163 _toplevel_windows += 1
165 def toplevel_unref(*unused):
166 """Decrement the toplevel ref count. If this is called while in
167 rox.mainloop() and the count has reached zero, then rox.mainloop()
168 will exit. Ignores any arguments passed in, so you can use it
169 easily as a callback function."""
170 global _toplevel_windows
171 assert _toplevel_windows > 0
172 _toplevel_windows -= 1
173 if _toplevel_windows == 0 and _in_mainloops:
174 g.mainquit()
176 _host_name = None
177 def our_host_name():
178 """Try to return the canonical name for this computer. This is used
179 in the drag-and-drop protocol to work out whether a drop is coming from
180 a remote machine (and therefore has to be fetched differently)."""
181 from socket import gethostbyaddr, gethostname
182 global _host_name
183 if _host_name:
184 return _host_name
185 try:
186 (host, alias, ips) = gethostbyaddr(gethostname())
187 for name in [host] + alias:
188 if name.find('.') != -1:
189 _host_name = name
190 return name
191 return name
192 except:
193 sys.stderr.write(
194 "*** ROX-Lib gethostbyaddr(gethostname()) failed!\n")
195 return "localhost"
197 def get_local_path(uri):
198 """Convert 'uri' to a local path and return, if possible. If 'uri'
199 is a resource on a remote machine, return None."""
200 if not uri:
201 return None
203 if uri[0] == '/':
204 if uri[1:2] != '/':
205 return uri # A normal Unix pathname
206 i = uri.find('/', 2)
207 if i == -1:
208 return None # //something
209 if i == 2:
210 return uri[2:] # ///path
211 remote_host = uri[2:i]
212 if remote_host == our_host_name():
213 return uri[i:] # //localhost/path
214 # //otherhost/path
215 elif uri[:5].lower() == 'file:':
216 if uri[5:6] == '/':
217 return get_local_path(uri[5:])
218 elif uri[:2] == './' or uri[:3] == '../':
219 return uri
220 return None
222 app_options = None
223 def setup_app_options(program, leaf = 'Options.xml'):
224 """Most applications only have one set of options. This function can be
225 used to set up the default group. 'program' is the name of the
226 directory to use in <Choices> and 'leaf' is the name of the file used
227 to store the group. You can refer to the group using rox.app_options.
228 See rox.options.OptionGroup."""
229 global app_options
230 assert not app_options
231 from options import OptionGroup
232 app_options = OptionGroup(program, leaf)
234 _options_box = None
235 def edit_options(options_file = None):
236 """Edit the app_options (set using setup_app_options()) using the GUI
237 specified in 'options_file' (default <app_dir>/Options.xml).
238 If this function is called again while the box is still open, the
239 old box will be redisplayed to the user."""
240 assert app_options
242 global _options_box
243 if _options_box:
244 _options_box.present()
245 return
247 if not options_file:
248 options_file = os.path.join(app_dir, 'Options.xml')
250 import OptionsBox
251 _options_box = OptionsBox.OptionsBox(app_options, options_file)
253 def closed(widget):
254 global _options_box
255 assert _options_box == widget
256 _options_box = None
257 _options_box.connect('destroy', closed)
258 _options_box.open()
260 try:
261 import xml
262 except:
263 alert(_("You do not have the Python 'xml' module installed, which "
264 "ROX-Lib2 requires. You need to install python-xmlbase "
265 "(this is a small package; the full PyXML package is not "
266 "required)."))