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