]>
Commit | Line | Data |
---|---|---|
d14a1e28 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: ErrorDialogs.py | |
3 | # Version: 1.0 | |
4 | # Created: September--October 2001 | |
5 | # Author: Chris Fama of Wholly Snakes Software, | |
6 | # Chris.Fama@whollysnakes.com | |
7 | #---------------------------------------------------------------------------- | |
8 | """ | |
9 | ErrorDialogs.py: by Christopher J. Fama (trading under the name | |
10 | Wholly Snakes Software {Australian Business Number: 28379514278}). | |
1fded56b | 11 | |
d14a1e28 RD |
12 | This code is released under the auspices of the modified version of |
13 | the GNU Library Public License (LGPL) that constitutes the license | |
14 | under which the GUI package wxPython is released [see the file | |
15 | LICENSE.TXT accompanying that distribution). I must also thank Graham | |
16 | Boyd, of Boyd Mining, and CEO of the Minserve group of companies | |
17 | (www.minserve.com.au), for kindly allowing the release of this | |
18 | module under a "free" license, despite a certain part of it's | |
19 | development having taken place while working for him... | |
20 | ||
21 | Please note that this code, written for Python 2.1, is derives from | |
22 | code written when Python 1.5.2 was current. Although since Python 2.0 | |
23 | the sys.excepthook variable has been available, for ease and potential | |
24 | backwards compatibility (or to be more realistic | |
25 | backwards-PARTIAL-compatibility!), the code catches errors by | |
26 | assigning a custom object to sys.stderr and monitoring calls to it's | |
27 | writ) method. Such calls which take place with a new value of | |
28 | sys.last_traceback stand as evidence that at interpreter error has | |
29 | just occurred; please note that this means that NO OTHER OUTPUT TO | |
30 | sys.stderr WILL BE DISPLAYED. THIS INCLUDES "warnings" generated by | |
31 | the interpreter. As far as I am aware, unless your code itself writes | |
32 | to sys.stderr itself, these will be the only things you miss out | |
33 | on--and many, if not most or all, of these will occur before your code | |
34 | even gets the opportunity to set one of these file-like objects in | |
35 | place. If this is a problem for you and you can't work around it, | |
36 | please contact means or the wxPython-users mailing list. | |
37 | ||
38 | ||
39 | DOCUMENTATION: | |
40 | ||
41 | This is a module to display errors--either explicitly requested by a | |
42 | script or arbitrary interpreter errors--these needs arose in the | |
43 | course of a commercial (contract) project, but were not developed in | |
44 | the course of work on such [well, very little, albeit concurrently]... | |
45 | [NBNB.Does not currently support display of other than interpreter errors.] | |
46 | ||
47 | Usage, after 'from wxPython.lib.ErrorDialogs import *' (all | |
48 | identifiers defined in this module begin with "wxPy", and many of them | |
49 | with "wxPyError_", so there should be no namespace conflicts...): | |
50 | ||
51 | wxPyNewErrorDialog (<win> (['frame='] <frame (can be None)>, | |
52 | <OPTIONS>)) | |
53 | ... | |
54 | wxPyDestroyErrorDialogIfPresent () # e.g. when top frame destroyed) | |
55 | ||
56 | for unhandled errors, or | |
57 | ||
58 | returnval = wxpyNonFatalError (<frame (can be None)>, | |
59 | <HTML message> | |
60 | [,<OPTIONS>]) | |
61 | or | |
62 | returnval = wxPyFatalError (<HTML message>, | |
63 | [,<OPTIONS, an extra one of which may be | |
64 | 'frame=' <frame (defaults to None)>>]) | |
65 | or | |
66 | ||
67 | wxPybNonWindowingError (message) | |
68 | ||
69 | for explicit errors. | |
70 | ||
71 | <win> is one of | |
72 | wxPyNonFatalErrorDialog | |
73 | wxPyFatalErrorDialog | |
74 | wxPyFatalErrorDialogWithTraceback | |
75 | wxPyNonFatalErrorDialogWithTraceback | |
76 | wxPyNonWindowingErrorHandler | |
77 | ||
78 | and the OPTIONS (with defaults) are: (please note that the options for | |
79 | wxPyNonWindowingErrorHandler / wxPyNonWindowingError are 'almost' a (small)) | |
80 | subset of these): | |
81 | ||
82 | 'modal' [default 1]: block until dismissed. | |
83 | ||
84 | 'programname' [default "Python program"]: appears inThe | |
85 | caption of the dialog, amidst it's text and (by default) in mailings. | |
86 | ||
87 | 'whendismissed' option, if given, this should be a string, to be | |
88 | executed in a restricted environment (see rexec module) after | |
89 | dialog dismissal. Typically, this should be Python code to "clean | |
90 | up" after what was presumably the partial execution of some | |
91 | operation started by the user, after the unexpected interruption | |
92 | by some error [which--at least the way I work, means an unexpected | |
93 | error in the code, since exceptions that may be raised by some | |
94 | foreseen (in a programmatic sense) should normally be handled by | |
95 | try...except clauses]. | |
96 | NOTE THAT CURRENTLY THE rexec CODE IS NOT WORKING, SO THIS IS JUST DONE | |
97 | BY exec... | |
98 | ||
99 | 'mailto': if None, give no "e-mail support" option, otherwise this | |
100 | is meant to be an address for 'bug reports'; a command button will | |
101 | be created for this, which pops up another dialog [Linux], or a window of | |
102 | another program [Windows]. | |
103 | ||
104 | On Windows, this will launch your mailer (assuming your mailer | |
105 | would start when a file with the '.eml'extension is double-clicked | |
106 | in Windows Explorer--this will be the case for Microsoft Outlook | |
107 | Express [tm and all those legal necessities] if installed, and the | |
108 | association has not been taken over by some other program. On | |
109 | Linux, it will open a file dialog to save the message, by default | |
110 | as a '.html' file...{Please note that, on Windows and with current | |
111 | (when I got the machine I'm writing this on--early 2000) versions | |
112 | of Outlook Express, you may need to enter your e-mail address in | |
113 | the box beside the "mail support" button.) | |
114 | ||
115 | The template for the mail message is in the 'MessageTemplate' | |
116 | attribute of the dialog-derived class in question (e.g., | |
117 | wxPyNonFatalErrorDialog). Search for this in the code below to | |
118 | see the default (note that this template is in HTML format). This | |
119 | attributes uses the '%{name}s' string conversion feature of Python | |
120 | [see sec.2 of the Python Library Reference]; allowed Lance are: | |
121 | programname version exceptionname exceptionvalue | |
122 | extraexceptioninformation traceback | |
123 | ||
124 | 'version': str(this) will appear after 'Version' below "Error in | |
125 | <programname>" at the top of the dialog. | |
126 | ||
127 | EXAMPLES: | |
128 | ||
129 | sys.stderr = wxPyNonFatalErrorWindowWithTraceback ( | |
130 | parentframe, | |
131 | programname='sumthing', | |
132 | mailto='me@sumwear', | |
133 | whendismissed="from wxPython.wx import * ; wxBell()") | |
134 | ||
135 | FOR INTERNATIONAL [NON-ENGLISH-SPEAKING] USE: | |
136 | wxPyNonFatalErrorDialog and relatives have the method | |
137 | SetText(Number NUMBER, STRING) | |
138 | ||
139 | where STRING is to displayed in the wxStaticText control with ID | |
140 | wxPyError_ID_TEXT<NUMBER>--see the automatically-generated code | |
141 | for information about the meaning of these... | |
142 | ||
143 | """ | |
144 | ||
145 | _debug = 0 | |
146 | #_debug = 1 # uncomment to display some information (to stdout) | |
147 | Version = 1.3 | |
148 | ||
149 | from wxPython.wx import * | |
150 | import string, sys, traceback, time, rexec, operator, types, cStringIO, os | |
151 | #from wxPython.lib.createandsendHTMLmail import *# now inline | |
152 | #import MimeWriter, mimetools, tempfile, smtplib | |
153 | import urllib, webbrowser | |
154 | ||
155 | from ErrorDialogs_wdr import * | |
156 | ||
157 | # You may see from the above line that I used the excellent RAD tool | |
158 | # wxDesigner, by Robert Roebling, to accelerate development of this | |
159 | # module... The above is left for the convenience of making future | |
160 | # changes with wxDesigner; also so the wxDesigner-generated codedoes | |
161 | # not need to precede the "hand-generated" code in this file; finally, | |
162 | # as a personal endorsement: it is truly a brilliant time-saver! | |
163 | # Please note that, at the time of writing, the wxDesigner-generated | |
164 | # output requires manual removal of the PythonBitmaps function--an | |
165 | # appropriate version of this function will be imported from a | |
166 | # similarly- named module. Another manual change will have to be made | |
167 | # to the automatically-generated source: "parent.sizerAroundText = " | |
168 | # should be added [immediately] before the text similar to "item13 = | |
169 | # wxStaticBoxSizer( item14, wxVERTICAL )", this sizer being the one | |
170 | # containing the wxTextCtrl... [IMPORTANT NOTE: THIS LINE SHOULD BE | |
171 | # THE ONE INSTANTIATING A wxStat2icBoxSizer, *NOT* THE wxStaticBox | |
172 | # ITSELF...] As of version 1.2 [November 2001], this also needs to be | |
173 | # done for the {sizers around the} wxPyClickableHtmlWindow's generated in | |
174 | # populate_wxPyNonFatalError and populate_wxPyFatalError--note that | |
175 | # for ease this sizer is still called "sizerAroundText"... | |
176 | ||
177 | def wxPyDestroyErrorDialogIfPresent(): | |
178 | if isinstance(sys.stderr,wxPyNonFatalErrorDialog): | |
179 | sys.stderr.Destroy() | |
180 | sys.stderr = None | |
181 | ||
182 | def wxPyNewErrorDialog(dlg): | |
183 | wxPyDestroyErrorDialogIfPresent() | |
184 | sys.stderr = dlg | |
185 | ||
186 | class wxPyNonWindowingErrorHandler: | |
187 | this_exception = 0 | |
188 | softspace = 0 | |
189 | def __init__(self,fatal=0,file=sys.__stderr__): | |
190 | self.fatal = fatal | |
191 | self.file = file | |
192 | def write(self,s): | |
193 | import sys | |
194 | if s.find("Warning") <> 0\ | |
195 | and self.this_exception is not sys.last_traceback: | |
196 | wxPyNonWindowingError("The Python interpreter encountered an error " | |
197 | "not handled by any\nexception handler--this " | |
198 | "may represent some programming error.", | |
199 | fatal=self.fatal, | |
200 | stderr=self.file, | |
201 | last=1) | |
202 | self.this_exception = sys.last_traceback | |
203 | ||
204 | def wxPyNonWindowingError(msg,#output=None,errors=None, | |
205 | stderr=sys.__stderr__, | |
206 | fatal=1, | |
207 | last=None): | |
208 | if os.path.exists("wxPyNonWindowingErrors.txt"): | |
209 | mode = 'a+' | |
210 | else: | |
211 | mode = 'w' | |
212 | fl = open("wxPyNonWindowingErrors.txt",mode) | |
213 | if stderr is not None: | |
214 | l = [fl,stderr] # so that the error will be written to the file | |
215 | # before any potential error in stderr.write()... (this is largely | |
216 | # for my own sake in developing...) | |
217 | else: | |
218 | l = [fl] | |
219 | for f in l: | |
220 | f.write(time.ctime (time.time ()) + ": ") | |
221 | f.write(msg) | |
222 | #f.flush() | |
223 | if sys.exc_info () [0] is not None: | |
224 | if last: | |
225 | f.write('Currently handled exception:\n') | |
226 | f.flush() | |
227 | traceback.print_exc(file=f) | |
228 | if last: | |
229 | f.write('\nPrevious (?) error:\n') | |
230 | elif last or sys.last_traceback: | |
231 | f.write("\n\n(For wizards only) ") | |
232 | if last: | |
233 | if type(last) <> types.ListType or len(last) < 3: | |
234 | if (hasattr(sys,"last_traceback") and sys.last_traceback): | |
235 | last = [sys.last_type ,sys.last_value,sys.last_traceback] | |
236 | if type(last) == types.ListType: | |
237 | traceback.print_exception(last[0],last[1],last[2], | |
238 | None,f) | |
239 | #f.flush() | |
240 | if f is sys.__stderr__: | |
241 | s = ' (see the file "wxPyNonWindowingErrors.txt")' | |
242 | else: | |
243 | s = "" | |
244 | f.write("Please contact the author with a copy of this\n" | |
245 | "message%s.\n" % s) | |
246 | #f.flush() | |
247 | fl.close() | |
248 | if fatal and stderr is sys.__stderr__: | |
249 | if sys.__stderr__ and sys.platform in ["windows",'nt',"win32"]: | |
250 | sys.__stderr__.write( | |
251 | "\nYou may have to manually close this window to exit.") | |
252 | sys.exit() | |
253 | ||
254 | class wxPythonRExec (rexec.RExec): | |
255 | def __init__(self,securityhole=0,*args,**kwargs): | |
256 | apply(rexec.RExec.__init__, (self,) + args, kwargs) | |
257 | if securityhole: | |
258 | self.ok_builtin_modules = self.ok_builtin_modules + \ | |
259 | ('wxPython', 'wxPython.wxc','wxPython.wx','wxPython.misc', | |
260 | 'wxPython.misc2', 'wxPython.windows', 'wxPython.gdi', | |
261 | 'wxPython.clip_dnd', 'wxPython.events', 'wxPython.mdi', | |
262 | 'wxPython.frames', 'wxPython.stattool', 'wxPython.controls', | |
263 | 'wxPython.controls2', 'wxPython.windows2', 'wxPython.cmndlgs', | |
264 | 'wxPython.windows3', 'wxPython.image', 'wxPython.printfw', | |
265 | 'wxc','misc', 'misc2', 'windows', 'gdi', 'clip_dnd', 'events', | |
266 | 'mdi', 'frames', 'stattool', 'controls', 'controls2', 'windows2', | |
267 | 'cmndlgs', 'windows3', 'image', 'printfw', 'wx') | |
268 | # possible security hole! | |
269 | ||
270 | ##def wxPyFatalError(msg,frame=None,**kwargs): | |
271 | ## kwargs.update({'fatal' : 1}) | |
272 | ## apply(wxPyNonFatalError, | |
273 | ## (frame,msg), | |
274 | ## kwargs) | |
275 | ||
276 | class wxPyNonFatalErrorDialogWithTraceback(wxDialog): | |
277 | this_exception = 0 | |
278 | populate_function = populate_wxPyNonFatalErrorDialogWithTraceback | |
279 | no_continue_button = False | |
280 | fatal = False | |
281 | modal = True | |
282 | exitjustreturns = False # really only for testing! | |
283 | ||
284 | def __init__(self, parent, id, | |
285 | pos=wxDefaultPosition, | |
286 | size=wxDefaultSize, | |
287 | style=wxDEFAULT_DIALOG_STYLE, | |
288 | programname="Python program", | |
289 | version="?", | |
290 | mailto=None, | |
291 | whendismissed="", | |
292 | extraversioninformation="", | |
293 | caption="Python error!", | |
294 | versionname=None, | |
295 | errorname=None, | |
296 | disable_exit_button=False): | |
297 | ||
298 | if self.fatal: | |
299 | whetherNF = "" | |
300 | else: | |
301 | whetherNF = "non-" | |
302 | title = "A (%sfatal) error has occurred in %s!"\ | |
303 | % (whetherNF,programname) | |
304 | self.programname = programname # save for later use | |
305 | self.mailto = mailto # save for later use | |
306 | self.parent = parent # save for later use | |
307 | self.whendismissed = whendismissed # save for later use | |
308 | self.dialogtitle = title # save for later use | |
309 | ||
310 | wxDialog.__init__(self, parent, id, title, pos, size, style) | |
311 | ||
312 | self.topsizer = self.populate_function( False,True ) | |
313 | ||
314 | self.SetProgramName(programname) | |
315 | self.SetVersion(version) | |
316 | if errorname: | |
317 | self.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) | |
318 | if versionname: | |
319 | self.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) | |
320 | self.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) | |
321 | self.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( | |
322 | extraversioninformation)) | |
323 | if caption: | |
324 | self.SetTitle(caption) | |
325 | ||
326 | if not self.no_continue_button: | |
327 | EVT_BUTTON(self, wxPyError_ID_CONTINUE, self.OnContinue) | |
328 | if mailto: | |
329 | disable_mail_button = 0 | |
330 | else: | |
331 | disable_mail_button = 1 | |
332 | if not disable_mail_button: | |
333 | EVT_BUTTON(self, wxPyError_ID_MAIL, self.OnMail) | |
334 | else: | |
335 | self.GetMailButton().Enable(False) | |
336 | # disable the entry box for an e-mail address by default (NOT PROPERLY DOCUMENTED) | |
337 | if not hasattr(self,"enable_mail_address_box"): | |
338 | self.FindWindowById(wxPyError_ID_ADDRESS).Enable(False) | |
339 | if not disable_exit_button: | |
340 | EVT_BUTTON(self, wxPyError_ID_EXIT, self.OnExit) | |
341 | ||
342 | def GetExtraInformation(self): | |
343 | return self.extraexceptioninformation | |
344 | ||
345 | def SetExtraInformation(self,value): | |
346 | self.extraexceptioninformation = value | |
347 | c = self.GetExtraInformationCtrl() | |
348 | if c is not None: | |
349 | c.SetLabel(str(value)) | |
350 | self.topsizer.Layout() | |
351 | ||
352 | def GetExtraInformationCtrl(self): | |
353 | return self.FindWindowById(wxPyError_ID_EXTRAINFORMATION) | |
354 | ||
355 | def GetExceptionName(self): | |
356 | return str(self.exceptiontype) | |
357 | ||
358 | def SetExceptionName(self,value): | |
359 | self.exceptiontype = str(value) | |
360 | c = self.GetExceptionNameCtrl() | |
361 | if c is not None: | |
362 | c.SetLabel(str(value)) | |
363 | self.topsizer.Layout() | |
364 | ||
365 | def GetExceptionNameCtrl(self): | |
366 | return self.FindWindowById(wxPyError_ID_EXCEPTIONNAME) | |
367 | ||
368 | def GetTraceback(self): | |
369 | try: | |
370 | return self.traceback | |
371 | except AttributeError: | |
372 | return None | |
373 | ||
374 | def SetTraceback(self,value): | |
375 | self.traceback = value | |
376 | c = self.GetTracebackCtrl() | |
377 | if c is not None: | |
378 | s,cs = c.GetSize(), c.GetClientSize() | |
379 | if value[-1] == '\n': | |
380 | value = value[:-1] | |
381 | if _debug: | |
382 | print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\ | |
383 | % (self,value.replace('\n',"^J")) | |
384 | c.SetValue(value) | |
385 | ||
386 | # Despite using the wxADJUST_MINSIZE flag in the | |
387 | # appropriate AddWindow method of the sizer, this doesn't | |
388 | # size the control appropriately... evidently the control's | |
389 | # GetBestSize method is not returning the "correct" | |
390 | # value... So we perform a rather ugly "fix"... note that | |
391 | # this also requires that we remove the wxADJUST_MINSIZE | |
392 | # flag from the AddWindow method of the sizer containing | |
393 | # the wxTextCtrl, which adds the wxTextCtrl... (this | |
394 | # amounts, as of wxDesigner 2.6, to only a few mouse | |
395 | # clicks...) | |
396 | ||
397 | if _debug: | |
398 | size = c.GetBestSize() | |
399 | print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\ | |
400 | % (self,c,size.width,size.height) | |
401 | w,h = 0,0 | |
402 | for v in value.split("\n"): | |
403 | pw,ph,d,e = t = c.GetFullTextExtent(v) | |
404 | if _debug: | |
405 | print v, t | |
406 | h = h + ph + e# + d | |
407 | pw = pw + wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X) | |
408 | if pw > w: | |
409 | w = pw | |
410 | w = w + s.width - cs.width | |
411 | h = h + s.height - cs.height | |
412 | if _debug: | |
413 | print "%s.SetTraceback(): calculated w,h =" % c,\ | |
414 | w,h,"and sys.platform = '%s'" % sys.platform | |
415 | self.sizerAroundText.SetItemMinSize (c,w,h) | |
416 | c.SetSize ((w,h)) | |
417 | c.SetSizeHints (w,h,w,h) | |
418 | c.Refresh()#.SetAutoLayout(False) | |
419 | ||
420 | #^ the reason we need the above seems to be to replace the | |
421 | #faulty GetBestSize of wxTextCtrl... | |
422 | #self.sizerAroundText.Layout() | |
423 | self.topsizer.Layout() | |
424 | ||
425 | def GetTracebackCtrl(self): | |
426 | return self.FindWindowById(wxPyError_ID_TEXTCTRL) | |
427 | ||
428 | def GetVersion(self): | |
429 | return self.version | |
430 | ||
431 | def SetVersion(self,value): | |
432 | self.version = value | |
433 | c = self.GetVersionNumberCtrl() | |
434 | if c is not None: | |
435 | c.SetLabel(value) | |
436 | self.topsizer.Layout() | |
437 | ||
438 | def GetVersionNumberCtrl(self): | |
439 | return self.FindWindowById(wxPyError_ID_VERSIONNUMBER) | |
440 | ||
441 | def GetProgramName(self): | |
442 | return self.programname | |
443 | ||
444 | def SetProgramName(self,value): | |
445 | self.programname = value | |
446 | c = self.GetProgramNameCtrl() | |
447 | if c is not None: | |
448 | c.SetLabel(value) | |
449 | self.topsizer.Layout() | |
450 | ||
451 | def GetProgramNameCtrl(self): | |
452 | return self.FindWindowById(wxPyError_ID_PROGRAMNAME) | |
453 | ||
454 | def GetContinueButton(self): | |
455 | return self.FindWindowById(wxPyError_ID_CONTINUE) | |
456 | ||
457 | def GetMailButton(self): | |
458 | return self.FindWindowById(wxPyError_ID_MAIL) | |
459 | ||
460 | def GetExitButton(self): | |
461 | return self.FindWindowById(wxPyError_ID_EXIT) | |
462 | ||
463 | # write handler (which is really the guts of the thing... | |
464 | # [Note that this doesn't use sys.excepthook because I already had a | |
465 | # working body of code... | |
466 | ||
467 | def write(self,s): | |
468 | if self.this_exception is not sys.last_traceback: | |
469 | if not wxThread_IsMain(): | |
470 | # Aquire the GUI mutex before making GUI calls. Mutex is released | |
471 | # when locker is deleted at the end of this function. | |
472 | locker = wxMutexGuiLocker() | |
473 | ||
474 | self.this_exception = sys.last_traceback | |
475 | # this is meant to be done once per traceback's sys.stderr.write's | |
476 | # - on the first in fact..... | |
477 | try: | |
478 | #from wxPython.wx import wxBell | |
479 | wxBell() | |
480 | ||
481 | if _debug: | |
482 | if sys.stdout: sys.stdout.write( | |
483 | 'in %s.write(): ' % self) | |
484 | ||
485 | self.exceptiontype = sys.last_type | |
486 | self.extraexceptioninformation = sys.last_value | |
487 | c = cStringIO.StringIO() | |
488 | traceback.print_last(None,c) | |
489 | self.traceback = c.getvalue() | |
490 | ||
491 | if _debug: | |
492 | #import traceback | |
493 | traceback.print_last(None,sys.stdout) | |
494 | ||
495 | self.SetExceptionName(str(self.exceptiontype)) | |
496 | self.SetExtraInformation(str(self.extraexceptioninformation)) | |
497 | self.SetTraceback(str(self.traceback)) | |
498 | ||
499 | self.topsizer.Fit(self) | |
500 | self.topsizer.SetSizeHints(self) | |
501 | self.CentreOnScreen() | |
502 | ||
503 | if self.modal: | |
504 | self.ShowModal() | |
505 | else: | |
506 | self.Show(True) | |
507 | ||
508 | except: | |
509 | if not locals().has_key("c"): | |
510 | c = cStringIO.StringIO() | |
511 | c.write("[Exception occurred before data from " | |
512 | "sys.last_traceback available]") | |
513 | wxPyNonWindowingError("Warning: " | |
514 | "a %s error was encountered trying to " | |
515 | "handle the exception\n%s\nThis was:"#%s\n" | |
516 | % (sys.exc_type, c.getvalue()),#, c2.getvalue()), | |
517 | stderr=sys.stdout, | |
518 | last=0) | |
519 | ||
520 | ||
521 | # button handlers: | |
522 | ||
523 | def OnContinue(self, event): | |
524 | try: | |
525 | if self.whendismissed: | |
526 | parent = self.parent # so whendismissed can refer to "parent" | |
527 | if 1: | |
528 | if _debug: | |
529 | if sys.stdout: sys.stdout.write("exec '''%s''': " | |
530 | % (self.whendismissed)) | |
531 | exec self.whendismissed | |
532 | if _debug: print "\n", | |
533 | else: | |
534 | if _debug: | |
535 | if sys.stdout: sys.stdout.write("wxPythonRExec(%s).r_exec('''%s'''): " | |
536 | % (self.securityhole, | |
537 | self.whendismissed)) | |
538 | wxPythonRExec(self.securityhole).r_exec(self.whendismissed) | |
539 | if _debug: print "\n", | |
540 | if self.modal: | |
541 | self.EndModal(wxID_OK) | |
542 | else: | |
543 | self.Close () | |
544 | if _debug: print "reimporting ", | |
545 | for m in sys.modules.values(): | |
546 | if m and m.__dict__["__name__"][0] in string.uppercase:#hack! | |
547 | if _debug: | |
548 | print m.__dict__["__name__"], | |
549 | reload (m) | |
550 | if _debug: | |
551 | print ' ', | |
552 | if _debug: | |
553 | print '\nENDING %s.OnContinue()..\n\n' % (self,), | |
554 | except: | |
555 | wxPyNonWindowingError("Warning: the following exception information" | |
556 | " may not be the full story.. (because " | |
557 | "a %s(%s) error was encountered trying to " | |
558 | "handle the exception)\n\n" | |
559 | % tuple(sys.exc_info()[:2]), | |
560 | stderr=sys.stdout, | |
561 | last=0) | |
562 | ||
563 | PlainMessageTemplate = \ | |
564 | "Hello,\n\n"\ | |
565 | '%(programname)s'\ | |
566 | " error.\n\n"\ | |
567 | "I encountered the following error when running your "\ | |
568 | 'program %(programname)s,'\ | |
569 | "at %(date)s.\n\n"\ | |
570 | "(The following has been automatically generated...)\n"\ | |
571 | '%(traceback)s\n\n'\ | |
572 | "More information follows:\n\n"\ | |
573 | '[Insert more '\ | |
574 | "information about the error here, such as what you were "\ | |
575 | "trying to do at the time of the error. Please "\ | |
576 | "understand that failure to fill in this field will be "\ | |
577 | "interpreted as an invitation to consign this e-mail "\ | |
578 | "straight to the trash can!]\n\n"\ | |
579 | 'Yours sincerely,\n'\ | |
580 | "[insert your name here]\n" | |
581 | ||
582 | HTMLMessageTemplate = \ | |
583 | '<html>\n'\ | |
584 | '<body>'\ | |
585 | "<p>\n"\ | |
586 | "<i><b>Hello,</b></i>\n<p>\n"\ | |
587 | '<p><h2><font color="#CC6C00">%(programname)s</font>'\ | |
588 | " error.</h2>\n"\ | |
589 | "I encountered the following error when running your "\ | |
590 | 'program <font color="#CC6C00">%(programname)s</font>,'\ | |
591 | "at %(date)s.\n<p>\n"\ | |
592 | "<p>"\ | |
593 | "<h2>Traceback (automatically generated):</h2>\n"\ | |
594 | '<p><font size="-1">\n<pre>%(traceback)s</pre>\n<p></font><p>'\ | |
595 | "\n<p>\n<h2>More information follows:</h2>\n<p>\n"\ | |
596 | '<font color="#CC6C00">'\ | |
597 | '<i>[Insert more '\ | |
598 | "information about the error here, such as what you were "\ | |
599 | "trying to do at the time of the error. Please "\ | |
600 | "understand that failure to fill in this field will be "\ | |
601 | "interpreted as an invitation to consign this e-mail "\ | |
602 | "straight to the trash can!]\n</i><p>\n"\ | |
603 | "</font><p>\n"\ | |
604 | '<i><b>Yours sincerely,</b></i>\n<p>'\ | |
605 | '<font color="#CC6C00">'\ | |
606 | "[insert your name here]\n"\ | |
607 | "</font>\n"\ | |
608 | "</body>\n"\ | |
609 | "</html>\n" | |
610 | # text="#000000" bgcolor="#FFFFFF">\n'\ | |
611 | ||
612 | def OnMail(self,event): | |
613 | try: | |
614 | if _debug: | |
615 | print 'Attempting to write mail message.\n', | |
616 | gmtdate = time.asctime(time.gmtime(time.time())) + ' GMT' | |
617 | tm = time.localtime(time.time()) | |
618 | date = time.asctime(tm) + ' ' +\ | |
619 | time.strftime("%Z",tm) | |
620 | programname = self.programname | |
621 | traceback = self.traceback | |
622 | mailto = self.mailto | |
623 | ||
624 | subject = "Un-caught exception when running %s." % programname | |
625 | mailfrom = None#self.FindWindowById (wxPyError_ID_ADDRESS) | |
626 | if mailfrom: | |
627 | mailfrom = mailfrom.GetValue() | |
628 | if _startmailerwithhtml(mailto,subject, | |
629 | self.HTMLMessageTemplate % vars(), | |
630 | text=self.PlainMessageTemplate % vars(), | |
631 | mailfrom=mailfrom): | |
632 | if not (hasattr(self,"fatal") and self.fatal): | |
633 | self.OnContinue(event) # if ok, then act as if "Continue" selected | |
634 | except: | |
635 | wxPyNonWindowingError("Warning: the following exception information" | |
636 | " may not be the full story... (because " | |
637 | "a %s error was encountered trying to " | |
638 | "handle the original exception)\n\n"#%s" | |
639 | % (sys.exc_type,),#self.msg), | |
640 | stderr=sys.stdout, | |
641 | last=0) | |
642 | ||
643 | def OnExit(self, event): | |
644 | if self.IsModal(): | |
645 | self.EndModal(wxID_CANCEL) | |
646 | if self.exitjustreturns: | |
647 | return | |
648 | wxGetApp().ExitMainLoop() | |
649 | ||
650 | def SetText(self,number,string): | |
651 | self.FindWindowById(eval("wxPyError_ID_TEXT%d" | |
652 | % number)).SetLabel(string) | |
653 | self.topsizer.Layout() | |
654 | ||
655 | class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback): | |
656 | populate_function = populate_wxPyFatalErrorDialogWithTraceback | |
657 | no_continue_button = True | |
658 | fatal = True | |
659 | ||
660 | class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback): | |
661 | populate_function = populate_wxPyNonFatalErrorDialog | |
662 | ||
663 | class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback): | |
664 | populate_function = populate_wxPyFatalErrorDialog | |
665 | ||
666 | def _startmailerwithhtml(mailto,subject,html,text=None,mailfrom=None): | |
667 | if sys.hexversion >= 0x02000000:#\ | |
668 | # and sys.platform in ["windows",'nt',"w is in32"]: | |
669 | s = 'mailto:%s?subject=%s&body=%s' % (mailto, | |
670 | urllib.quote(subject), | |
671 | urllib.quote( | |
672 | text.replace('\n','\r\n'), | |
673 | "")) | |
674 | ||
675 | # Note that RFC 2368 requires that line breaks in the body of | |
676 | # a message contained in a mailto URL MUST be encoded with | |
677 | # "%0D%0A"--even on Unix/Linux. Also note that there appears | |
678 | # to be no way to specify that the body of a mailto tag be | |
679 | # interpreted as HTML (mailto tags shouldn't stuff around with | |
680 | # the MIME-Version/Content-Type headers, the RFC says, so I | |
681 | # can't even be bothered trying, as the Python | |
682 | # webbrowser/urllib modules quite likely won't allow | |
683 | # this... anyway, a plain text message is [at least!] fine...). | |
684 | ||
685 | if _debug: | |
686 | t = urllib.quote(text) | |
687 | if len(t) > 20 * 80: | |
688 | t = t[0:20 * 80] + "..." | |
689 | print "\nSummarizing (only shortened version of argument "\ | |
690 | "printed here), ", | |
691 | print 'webbrowser.open("' \ | |
692 | 'mailto:%s?subject=%s&body=%s"' % (mailto, | |
693 | urllib.quote(subject), | |
694 | t) | |
695 | webbrowser.open(s) | |
696 | return 1 | |
697 | else: | |
698 | return _writehtmlmessage(mailto,subject,html,text,mailfrom=mailfrom) | |
699 | ||
700 | def _writehtmlmessage(mailto,subject,html,text=None,parent=None,mailfrom=None): | |
701 | dlg = wxFileDialog (parent, | |
702 | "Please choose a a file to save the message to...", | |
703 | ".", | |
704 | "bug-report", | |
705 | "HTML files (*.htm,*.html)|*.htm,*.html|" | |
706 | "All files (*)|*", | |
707 | wxSAVE | wxHIDE_READONLY) | |
708 | if dlg.ShowModal() <> wxID_CANCEL: | |
709 | f = open(dlg.GetPath(),"w") | |
710 | dlg.Destroy() | |
711 | f.write(_createhtmlmail(html,text,subject,to=mailto,mailfrom=mailfrom)) | |
712 | f.close() | |
713 | return 1 | |
714 | else: | |
715 | return 0 | |
716 | ||
717 | # PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN | |
718 | #(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly]. | |
719 | ||
720 | def _createhtmlmail (html, text, subject, to=None, mailfrom=None): | |
721 | """Create a mime-message that will render HTML in popular | |
722 | MUAs, text in better ones (if indeed text is not unTrue (e.g. None) | |
723 | """ | |
724 | import MimeWriter, mimetools, cStringIO | |
725 | ||
726 | out = cStringIO.StringIO() # output buffer for our message | |
727 | htmlin = cStringIO.StringIO(html) | |
728 | if text: | |
729 | txtin = cStringIO.StringIO(text) | |
730 | ||
731 | writer = MimeWriter.MimeWriter(out) | |
732 | # | |
733 | # set up some basic headers... we put subject here | |
734 | # because smtplib.sendmail expects it to be in the | |
735 | # message body | |
736 | # | |
737 | if mailfrom: | |
738 | writer.addheader("From", mailfrom) | |
739 | #writer.addheader("Reply-to", mailfrom) | |
740 | writer.addheader("Subject", subject) | |
741 | if to: | |
742 | writer.addheader("To", to) | |
743 | writer.addheader("MIME-Version", "1.0") | |
744 | # | |
745 | # start the multipart section of the message | |
746 | # multipart/alternative seems to work better | |
747 | # on some MUAs than multipart/mixed | |
748 | # | |
749 | writer.startmultipartbody("alternative") | |
750 | writer.flushheaders() | |
751 | # | |
752 | # the plain text section | |
753 | # | |
754 | if text: | |
755 | subpart = writer.nextpart() | |
756 | subpart.addheader("Content-Transfer-Encoding", "quoted-printable") | |
757 | pout = subpart.startbody("text/plain", [("charset", 'us-ascii')]) | |
758 | mimetools.encode(txtin, pout, 'quoted-printable') | |
759 | txtin.close() | |
760 | # | |
761 | # start the html subpart of the message | |
762 | # | |
763 | subpart = writer.nextpart() | |
764 | subpart.addheader("Content-Transfer-Encoding", "quoted-printable") | |
765 | pout = subpart.startbody("text/html", [("charset", 'us-ascii')]) | |
766 | mimetools.encode(htmlin, pout, 'quoted-printable') | |
767 | htmlin.close() | |
768 | # | |
769 | # Now that we're done, close our writer and | |
770 | # return the message body | |
771 | # | |
772 | writer.lastpart() | |
773 | msg = out.getvalue() | |
774 | out.close() | |
775 | return msg | |
776 | ||
777 | def _sendmail(mailto,subject,html,text):# currently unused | |
778 | """For illustration only--this function is not actually used.""" | |
779 | import smtplib | |
780 | message = _createhtmlmail(html, text, subject) | |
781 | server = smtplib.SMTP("localhost") | |
782 | server.sendmail(mailto, subject, message) | |
783 | server.quit() | |
784 | ||
785 | def wxPyFatalError(parent,msg,**kw): | |
786 | return wxPyFatalOrNonFatalError(parent,msg,fatal=1,**kw) | |
787 | ||
788 | def wxPyNonFatalError(parent,msg,**kw): | |
789 | return wxPyFatalOrNonFatalError(parent,msg,fatal=0,**kw) | |
790 | ||
791 | def wxPyResizeHTMLWindowToDispelScrollbar(window, | |
792 | fraction, | |
793 | sizer=None, | |
794 | defaultfraction=0.7): | |
795 | # Try to `grow' parent window (typically a dialog), only so far as | |
796 | # no scrollbar is necessary, mantaining aspect ratio of display. | |
797 | # Will go no further than specified fraction of display size. | |
798 | w = 200 | |
799 | if type(fraction) == type(''): | |
800 | fraction = int(fraction[:-1]) / 100. | |
801 | ds = wxDisplaySize () | |
802 | c = window.GetInternalRepresentation () | |
803 | while w < ds[0] * fraction: | |
804 | c.Layout(w) | |
805 | if _debug: | |
806 | print '(c.GetHeight() + 20, w * ds[1]/ds[0]):',\ | |
807 | (c.GetHeight() + 20, w * ds[1]/ds[0]) | |
808 | if c.GetHeight() + 20 < w * ds[1]/ds[0]: | |
809 | size = (w,min(int ((w) * ds[1]/ds[0]), | |
810 | c.GetHeight())) | |
811 | break | |
812 | w = w + 20 | |
813 | else: | |
814 | if type(defaultfraction) == type(''): | |
815 | defaultfraction = int(defaultfraction[:-1]) / 100. | |
816 | defaultsize = (defaultfraction * ds[0], defaultfraction * ds[1]) | |
817 | if _debug: | |
818 | print 'defaultsize =',defaultsize | |
819 | size = defaultsize | |
820 | window.SetSize(size) | |
821 | if sizer is not None: | |
822 | sizer.SetMinSize(size) | |
823 | sizer.Fit(window) | |
824 | #sizer.SetSizeHints(window) | |
825 | ||
826 | def wxPyFatalOrNonFatalError(parent, | |
827 | msg, | |
828 | fatal=0, | |
829 | extraversioninformation="", | |
830 | caption=None, | |
831 | versionname=None, | |
832 | errorname=None, | |
833 | version="?", | |
834 | programname="Python program", | |
835 | tback=None,# backwards compatibility, and for | |
836 | #possible future inclusion of ability to display | |
837 | #a traceback along with the given message | |
838 | #"msg"... currently ignored though... | |
839 | modal=0): | |
840 | if not wxThread_IsMain(): | |
841 | # Aquire the GUI mutex before making GUI calls. Mutex is released | |
842 | # when locker is deleted at the end of this function. | |
843 | locker = wxMutexGuiLocker() | |
844 | ||
845 | dlg = wxDialog(parent,-1,"Error!") | |
846 | ||
847 | if fatal: | |
848 | populate_function = populate_wxPyFatalError | |
849 | else: | |
850 | populate_function = populate_wxPyNonFatalError | |
851 | ||
852 | sizer = populate_function(dlg,False,True) | |
853 | ||
854 | window = dlg.FindWindowById(wxPyError_ID_HTML) | |
855 | window.SetPage(msg) | |
856 | wxPyResizeHTMLWindowToDispelScrollbar(window, | |
857 | "85%", | |
858 | sizer=dlg.sizerAroundText) | |
859 | dlg.FindWindowById(wxPyError_ID_PROGRAMNAME).SetLabel(str(programname)) | |
860 | if errorname: | |
861 | dlg.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) | |
862 | if versionname: | |
863 | dlg.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) | |
864 | dlg.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) | |
865 | dlg.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( | |
866 | extraversioninformation)) | |
867 | if caption: | |
868 | dlg.SetTitle(caption) | |
869 | sizer.Fit(dlg) | |
870 | sizer.SetSizeHints(dlg) | |
871 | dlg.CentreOnScreen() | |
872 | ||
873 | if modal: | |
874 | v = dlg.ShowModal() | |
875 | dlg.Destroy() | |
876 | return v | |
877 | else: | |
878 | dlg.Show(True) | |
1fded56b | 879 |