]>
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 | ||
8b7fa2d9 RD |
145 | |
146 | #---------------------------------------------------------------------- | |
147 | import warnings | |
148 | ||
149 | warningmsg = r"""\ | |
150 | ||
151 | ##############################################################\ | |
c7fa61e5 | 152 | # THIS MODULE IS DEPRECATED and NOT MAINTAINED | |
8b7fa2d9 RD |
153 | ##############################################################/ |
154 | ||
155 | """ | |
156 | ||
157 | warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) | |
158 | ||
159 | #---------------------------------------------------------------------- | |
160 | ||
161 | ||
d14a1e28 RD |
162 | _debug = 0 |
163 | #_debug = 1 # uncomment to display some information (to stdout) | |
164 | Version = 1.3 | |
165 | ||
166 | from wxPython.wx import * | |
167 | import string, sys, traceback, time, rexec, operator, types, cStringIO, os | |
168 | #from wxPython.lib.createandsendHTMLmail import *# now inline | |
169 | #import MimeWriter, mimetools, tempfile, smtplib | |
170 | import urllib, webbrowser | |
171 | ||
172 | from ErrorDialogs_wdr import * | |
173 | ||
174 | # You may see from the above line that I used the excellent RAD tool | |
175 | # wxDesigner, by Robert Roebling, to accelerate development of this | |
176 | # module... The above is left for the convenience of making future | |
177 | # changes with wxDesigner; also so the wxDesigner-generated codedoes | |
178 | # not need to precede the "hand-generated" code in this file; finally, | |
179 | # as a personal endorsement: it is truly a brilliant time-saver! | |
180 | # Please note that, at the time of writing, the wxDesigner-generated | |
181 | # output requires manual removal of the PythonBitmaps function--an | |
182 | # appropriate version of this function will be imported from a | |
183 | # similarly- named module. Another manual change will have to be made | |
184 | # to the automatically-generated source: "parent.sizerAroundText = " | |
185 | # should be added [immediately] before the text similar to "item13 = | |
186 | # wxStaticBoxSizer( item14, wxVERTICAL )", this sizer being the one | |
187 | # containing the wxTextCtrl... [IMPORTANT NOTE: THIS LINE SHOULD BE | |
188 | # THE ONE INSTANTIATING A wxStat2icBoxSizer, *NOT* THE wxStaticBox | |
189 | # ITSELF...] As of version 1.2 [November 2001], this also needs to be | |
190 | # done for the {sizers around the} wxPyClickableHtmlWindow's generated in | |
191 | # populate_wxPyNonFatalError and populate_wxPyFatalError--note that | |
192 | # for ease this sizer is still called "sizerAroundText"... | |
193 | ||
194 | def wxPyDestroyErrorDialogIfPresent(): | |
195 | if isinstance(sys.stderr,wxPyNonFatalErrorDialog): | |
196 | sys.stderr.Destroy() | |
197 | sys.stderr = None | |
198 | ||
199 | def wxPyNewErrorDialog(dlg): | |
200 | wxPyDestroyErrorDialogIfPresent() | |
201 | sys.stderr = dlg | |
202 | ||
203 | class wxPyNonWindowingErrorHandler: | |
204 | this_exception = 0 | |
205 | softspace = 0 | |
206 | def __init__(self,fatal=0,file=sys.__stderr__): | |
207 | self.fatal = fatal | |
208 | self.file = file | |
209 | def write(self,s): | |
210 | import sys | |
211 | if s.find("Warning") <> 0\ | |
212 | and self.this_exception is not sys.last_traceback: | |
213 | wxPyNonWindowingError("The Python interpreter encountered an error " | |
214 | "not handled by any\nexception handler--this " | |
215 | "may represent some programming error.", | |
216 | fatal=self.fatal, | |
217 | stderr=self.file, | |
218 | last=1) | |
219 | self.this_exception = sys.last_traceback | |
220 | ||
221 | def wxPyNonWindowingError(msg,#output=None,errors=None, | |
222 | stderr=sys.__stderr__, | |
223 | fatal=1, | |
224 | last=None): | |
225 | if os.path.exists("wxPyNonWindowingErrors.txt"): | |
226 | mode = 'a+' | |
227 | else: | |
228 | mode = 'w' | |
229 | fl = open("wxPyNonWindowingErrors.txt",mode) | |
230 | if stderr is not None: | |
231 | l = [fl,stderr] # so that the error will be written to the file | |
232 | # before any potential error in stderr.write()... (this is largely | |
233 | # for my own sake in developing...) | |
234 | else: | |
235 | l = [fl] | |
236 | for f in l: | |
237 | f.write(time.ctime (time.time ()) + ": ") | |
238 | f.write(msg) | |
239 | #f.flush() | |
240 | if sys.exc_info () [0] is not None: | |
241 | if last: | |
242 | f.write('Currently handled exception:\n') | |
243 | f.flush() | |
244 | traceback.print_exc(file=f) | |
245 | if last: | |
246 | f.write('\nPrevious (?) error:\n') | |
247 | elif last or sys.last_traceback: | |
248 | f.write("\n\n(For wizards only) ") | |
249 | if last: | |
250 | if type(last) <> types.ListType or len(last) < 3: | |
251 | if (hasattr(sys,"last_traceback") and sys.last_traceback): | |
252 | last = [sys.last_type ,sys.last_value,sys.last_traceback] | |
253 | if type(last) == types.ListType: | |
254 | traceback.print_exception(last[0],last[1],last[2], | |
255 | None,f) | |
256 | #f.flush() | |
257 | if f is sys.__stderr__: | |
258 | s = ' (see the file "wxPyNonWindowingErrors.txt")' | |
259 | else: | |
260 | s = "" | |
261 | f.write("Please contact the author with a copy of this\n" | |
262 | "message%s.\n" % s) | |
263 | #f.flush() | |
264 | fl.close() | |
265 | if fatal and stderr is sys.__stderr__: | |
266 | if sys.__stderr__ and sys.platform in ["windows",'nt',"win32"]: | |
267 | sys.__stderr__.write( | |
268 | "\nYou may have to manually close this window to exit.") | |
269 | sys.exit() | |
270 | ||
271 | class wxPythonRExec (rexec.RExec): | |
272 | def __init__(self,securityhole=0,*args,**kwargs): | |
273 | apply(rexec.RExec.__init__, (self,) + args, kwargs) | |
274 | if securityhole: | |
275 | self.ok_builtin_modules = self.ok_builtin_modules + \ | |
276 | ('wxPython', 'wxPython.wxc','wxPython.wx','wxPython.misc', | |
277 | 'wxPython.misc2', 'wxPython.windows', 'wxPython.gdi', | |
278 | 'wxPython.clip_dnd', 'wxPython.events', 'wxPython.mdi', | |
279 | 'wxPython.frames', 'wxPython.stattool', 'wxPython.controls', | |
280 | 'wxPython.controls2', 'wxPython.windows2', 'wxPython.cmndlgs', | |
281 | 'wxPython.windows3', 'wxPython.image', 'wxPython.printfw', | |
282 | 'wxc','misc', 'misc2', 'windows', 'gdi', 'clip_dnd', 'events', | |
283 | 'mdi', 'frames', 'stattool', 'controls', 'controls2', 'windows2', | |
284 | 'cmndlgs', 'windows3', 'image', 'printfw', 'wx') | |
285 | # possible security hole! | |
286 | ||
287 | ##def wxPyFatalError(msg,frame=None,**kwargs): | |
288 | ## kwargs.update({'fatal' : 1}) | |
289 | ## apply(wxPyNonFatalError, | |
290 | ## (frame,msg), | |
291 | ## kwargs) | |
292 | ||
293 | class wxPyNonFatalErrorDialogWithTraceback(wxDialog): | |
294 | this_exception = 0 | |
295 | populate_function = populate_wxPyNonFatalErrorDialogWithTraceback | |
296 | no_continue_button = False | |
297 | fatal = False | |
298 | modal = True | |
299 | exitjustreturns = False # really only for testing! | |
300 | ||
301 | def __init__(self, parent, id, | |
302 | pos=wxDefaultPosition, | |
303 | size=wxDefaultSize, | |
304 | style=wxDEFAULT_DIALOG_STYLE, | |
305 | programname="Python program", | |
306 | version="?", | |
307 | mailto=None, | |
308 | whendismissed="", | |
309 | extraversioninformation="", | |
310 | caption="Python error!", | |
311 | versionname=None, | |
312 | errorname=None, | |
313 | disable_exit_button=False): | |
314 | ||
315 | if self.fatal: | |
316 | whetherNF = "" | |
317 | else: | |
318 | whetherNF = "non-" | |
319 | title = "A (%sfatal) error has occurred in %s!"\ | |
320 | % (whetherNF,programname) | |
321 | self.programname = programname # save for later use | |
322 | self.mailto = mailto # save for later use | |
323 | self.parent = parent # save for later use | |
324 | self.whendismissed = whendismissed # save for later use | |
325 | self.dialogtitle = title # save for later use | |
326 | ||
327 | wxDialog.__init__(self, parent, id, title, pos, size, style) | |
328 | ||
329 | self.topsizer = self.populate_function( False,True ) | |
330 | ||
331 | self.SetProgramName(programname) | |
332 | self.SetVersion(version) | |
333 | if errorname: | |
334 | self.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) | |
335 | if versionname: | |
336 | self.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) | |
337 | self.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) | |
338 | self.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( | |
339 | extraversioninformation)) | |
340 | if caption: | |
341 | self.SetTitle(caption) | |
342 | ||
343 | if not self.no_continue_button: | |
344 | EVT_BUTTON(self, wxPyError_ID_CONTINUE, self.OnContinue) | |
345 | if mailto: | |
346 | disable_mail_button = 0 | |
347 | else: | |
348 | disable_mail_button = 1 | |
349 | if not disable_mail_button: | |
350 | EVT_BUTTON(self, wxPyError_ID_MAIL, self.OnMail) | |
351 | else: | |
352 | self.GetMailButton().Enable(False) | |
353 | # disable the entry box for an e-mail address by default (NOT PROPERLY DOCUMENTED) | |
354 | if not hasattr(self,"enable_mail_address_box"): | |
355 | self.FindWindowById(wxPyError_ID_ADDRESS).Enable(False) | |
356 | if not disable_exit_button: | |
357 | EVT_BUTTON(self, wxPyError_ID_EXIT, self.OnExit) | |
358 | ||
359 | def GetExtraInformation(self): | |
360 | return self.extraexceptioninformation | |
361 | ||
362 | def SetExtraInformation(self,value): | |
363 | self.extraexceptioninformation = value | |
364 | c = self.GetExtraInformationCtrl() | |
365 | if c is not None: | |
366 | c.SetLabel(str(value)) | |
367 | self.topsizer.Layout() | |
368 | ||
369 | def GetExtraInformationCtrl(self): | |
370 | return self.FindWindowById(wxPyError_ID_EXTRAINFORMATION) | |
371 | ||
372 | def GetExceptionName(self): | |
373 | return str(self.exceptiontype) | |
374 | ||
375 | def SetExceptionName(self,value): | |
376 | self.exceptiontype = str(value) | |
377 | c = self.GetExceptionNameCtrl() | |
378 | if c is not None: | |
379 | c.SetLabel(str(value)) | |
380 | self.topsizer.Layout() | |
381 | ||
382 | def GetExceptionNameCtrl(self): | |
383 | return self.FindWindowById(wxPyError_ID_EXCEPTIONNAME) | |
384 | ||
385 | def GetTraceback(self): | |
386 | try: | |
387 | return self.traceback | |
388 | except AttributeError: | |
389 | return None | |
390 | ||
391 | def SetTraceback(self,value): | |
392 | self.traceback = value | |
393 | c = self.GetTracebackCtrl() | |
394 | if c is not None: | |
395 | s,cs = c.GetSize(), c.GetClientSize() | |
396 | if value[-1] == '\n': | |
397 | value = value[:-1] | |
398 | if _debug: | |
399 | print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\ | |
400 | % (self,value.replace('\n',"^J")) | |
401 | c.SetValue(value) | |
402 | ||
403 | # Despite using the wxADJUST_MINSIZE flag in the | |
404 | # appropriate AddWindow method of the sizer, this doesn't | |
405 | # size the control appropriately... evidently the control's | |
406 | # GetBestSize method is not returning the "correct" | |
407 | # value... So we perform a rather ugly "fix"... note that | |
408 | # this also requires that we remove the wxADJUST_MINSIZE | |
409 | # flag from the AddWindow method of the sizer containing | |
410 | # the wxTextCtrl, which adds the wxTextCtrl... (this | |
411 | # amounts, as of wxDesigner 2.6, to only a few mouse | |
412 | # clicks...) | |
413 | ||
414 | if _debug: | |
415 | size = c.GetBestSize() | |
416 | print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\ | |
417 | % (self,c,size.width,size.height) | |
418 | w,h = 0,0 | |
419 | for v in value.split("\n"): | |
420 | pw,ph,d,e = t = c.GetFullTextExtent(v) | |
421 | if _debug: | |
422 | print v, t | |
423 | h = h + ph + e# + d | |
424 | pw = pw + wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X) | |
425 | if pw > w: | |
426 | w = pw | |
427 | w = w + s.width - cs.width | |
428 | h = h + s.height - cs.height | |
429 | if _debug: | |
430 | print "%s.SetTraceback(): calculated w,h =" % c,\ | |
431 | w,h,"and sys.platform = '%s'" % sys.platform | |
432 | self.sizerAroundText.SetItemMinSize (c,w,h) | |
433 | c.SetSize ((w,h)) | |
434 | c.SetSizeHints (w,h,w,h) | |
435 | c.Refresh()#.SetAutoLayout(False) | |
436 | ||
437 | #^ the reason we need the above seems to be to replace the | |
438 | #faulty GetBestSize of wxTextCtrl... | |
439 | #self.sizerAroundText.Layout() | |
440 | self.topsizer.Layout() | |
441 | ||
442 | def GetTracebackCtrl(self): | |
443 | return self.FindWindowById(wxPyError_ID_TEXTCTRL) | |
444 | ||
445 | def GetVersion(self): | |
446 | return self.version | |
447 | ||
448 | def SetVersion(self,value): | |
449 | self.version = value | |
450 | c = self.GetVersionNumberCtrl() | |
451 | if c is not None: | |
452 | c.SetLabel(value) | |
453 | self.topsizer.Layout() | |
454 | ||
455 | def GetVersionNumberCtrl(self): | |
456 | return self.FindWindowById(wxPyError_ID_VERSIONNUMBER) | |
457 | ||
458 | def GetProgramName(self): | |
459 | return self.programname | |
460 | ||
461 | def SetProgramName(self,value): | |
462 | self.programname = value | |
463 | c = self.GetProgramNameCtrl() | |
464 | if c is not None: | |
465 | c.SetLabel(value) | |
466 | self.topsizer.Layout() | |
467 | ||
468 | def GetProgramNameCtrl(self): | |
469 | return self.FindWindowById(wxPyError_ID_PROGRAMNAME) | |
470 | ||
471 | def GetContinueButton(self): | |
472 | return self.FindWindowById(wxPyError_ID_CONTINUE) | |
473 | ||
474 | def GetMailButton(self): | |
475 | return self.FindWindowById(wxPyError_ID_MAIL) | |
476 | ||
477 | def GetExitButton(self): | |
478 | return self.FindWindowById(wxPyError_ID_EXIT) | |
479 | ||
480 | # write handler (which is really the guts of the thing... | |
481 | # [Note that this doesn't use sys.excepthook because I already had a | |
482 | # working body of code... | |
483 | ||
484 | def write(self,s): | |
485 | if self.this_exception is not sys.last_traceback: | |
486 | if not wxThread_IsMain(): | |
487 | # Aquire the GUI mutex before making GUI calls. Mutex is released | |
488 | # when locker is deleted at the end of this function. | |
489 | locker = wxMutexGuiLocker() | |
490 | ||
491 | self.this_exception = sys.last_traceback | |
492 | # this is meant to be done once per traceback's sys.stderr.write's | |
493 | # - on the first in fact..... | |
494 | try: | |
495 | #from wxPython.wx import wxBell | |
496 | wxBell() | |
497 | ||
498 | if _debug: | |
499 | if sys.stdout: sys.stdout.write( | |
500 | 'in %s.write(): ' % self) | |
501 | ||
502 | self.exceptiontype = sys.last_type | |
503 | self.extraexceptioninformation = sys.last_value | |
504 | c = cStringIO.StringIO() | |
505 | traceback.print_last(None,c) | |
506 | self.traceback = c.getvalue() | |
507 | ||
508 | if _debug: | |
509 | #import traceback | |
510 | traceback.print_last(None,sys.stdout) | |
511 | ||
512 | self.SetExceptionName(str(self.exceptiontype)) | |
513 | self.SetExtraInformation(str(self.extraexceptioninformation)) | |
514 | self.SetTraceback(str(self.traceback)) | |
515 | ||
516 | self.topsizer.Fit(self) | |
517 | self.topsizer.SetSizeHints(self) | |
518 | self.CentreOnScreen() | |
519 | ||
520 | if self.modal: | |
521 | self.ShowModal() | |
522 | else: | |
523 | self.Show(True) | |
524 | ||
525 | except: | |
526 | if not locals().has_key("c"): | |
527 | c = cStringIO.StringIO() | |
528 | c.write("[Exception occurred before data from " | |
529 | "sys.last_traceback available]") | |
530 | wxPyNonWindowingError("Warning: " | |
531 | "a %s error was encountered trying to " | |
532 | "handle the exception\n%s\nThis was:"#%s\n" | |
533 | % (sys.exc_type, c.getvalue()),#, c2.getvalue()), | |
534 | stderr=sys.stdout, | |
535 | last=0) | |
536 | ||
537 | ||
538 | # button handlers: | |
539 | ||
540 | def OnContinue(self, event): | |
541 | try: | |
542 | if self.whendismissed: | |
543 | parent = self.parent # so whendismissed can refer to "parent" | |
544 | if 1: | |
545 | if _debug: | |
546 | if sys.stdout: sys.stdout.write("exec '''%s''': " | |
547 | % (self.whendismissed)) | |
548 | exec self.whendismissed | |
549 | if _debug: print "\n", | |
550 | else: | |
551 | if _debug: | |
552 | if sys.stdout: sys.stdout.write("wxPythonRExec(%s).r_exec('''%s'''): " | |
553 | % (self.securityhole, | |
554 | self.whendismissed)) | |
555 | wxPythonRExec(self.securityhole).r_exec(self.whendismissed) | |
556 | if _debug: print "\n", | |
557 | if self.modal: | |
558 | self.EndModal(wxID_OK) | |
559 | else: | |
560 | self.Close () | |
561 | if _debug: print "reimporting ", | |
562 | for m in sys.modules.values(): | |
563 | if m and m.__dict__["__name__"][0] in string.uppercase:#hack! | |
564 | if _debug: | |
565 | print m.__dict__["__name__"], | |
566 | reload (m) | |
567 | if _debug: | |
568 | print ' ', | |
569 | if _debug: | |
570 | print '\nENDING %s.OnContinue()..\n\n' % (self,), | |
571 | except: | |
572 | wxPyNonWindowingError("Warning: the following exception information" | |
573 | " may not be the full story.. (because " | |
574 | "a %s(%s) error was encountered trying to " | |
575 | "handle the exception)\n\n" | |
576 | % tuple(sys.exc_info()[:2]), | |
577 | stderr=sys.stdout, | |
578 | last=0) | |
579 | ||
580 | PlainMessageTemplate = \ | |
581 | "Hello,\n\n"\ | |
582 | '%(programname)s'\ | |
583 | " error.\n\n"\ | |
584 | "I encountered the following error when running your "\ | |
585 | 'program %(programname)s,'\ | |
586 | "at %(date)s.\n\n"\ | |
587 | "(The following has been automatically generated...)\n"\ | |
588 | '%(traceback)s\n\n'\ | |
589 | "More information follows:\n\n"\ | |
590 | '[Insert more '\ | |
591 | "information about the error here, such as what you were "\ | |
592 | "trying to do at the time of the error. Please "\ | |
593 | "understand that failure to fill in this field will be "\ | |
594 | "interpreted as an invitation to consign this e-mail "\ | |
595 | "straight to the trash can!]\n\n"\ | |
596 | 'Yours sincerely,\n'\ | |
597 | "[insert your name here]\n" | |
598 | ||
599 | HTMLMessageTemplate = \ | |
600 | '<html>\n'\ | |
601 | '<body>'\ | |
602 | "<p>\n"\ | |
603 | "<i><b>Hello,</b></i>\n<p>\n"\ | |
604 | '<p><h2><font color="#CC6C00">%(programname)s</font>'\ | |
605 | " error.</h2>\n"\ | |
606 | "I encountered the following error when running your "\ | |
607 | 'program <font color="#CC6C00">%(programname)s</font>,'\ | |
608 | "at %(date)s.\n<p>\n"\ | |
609 | "<p>"\ | |
610 | "<h2>Traceback (automatically generated):</h2>\n"\ | |
611 | '<p><font size="-1">\n<pre>%(traceback)s</pre>\n<p></font><p>'\ | |
612 | "\n<p>\n<h2>More information follows:</h2>\n<p>\n"\ | |
613 | '<font color="#CC6C00">'\ | |
614 | '<i>[Insert more '\ | |
615 | "information about the error here, such as what you were "\ | |
616 | "trying to do at the time of the error. Please "\ | |
617 | "understand that failure to fill in this field will be "\ | |
618 | "interpreted as an invitation to consign this e-mail "\ | |
619 | "straight to the trash can!]\n</i><p>\n"\ | |
620 | "</font><p>\n"\ | |
621 | '<i><b>Yours sincerely,</b></i>\n<p>'\ | |
622 | '<font color="#CC6C00">'\ | |
623 | "[insert your name here]\n"\ | |
624 | "</font>\n"\ | |
625 | "</body>\n"\ | |
626 | "</html>\n" | |
627 | # text="#000000" bgcolor="#FFFFFF">\n'\ | |
628 | ||
629 | def OnMail(self,event): | |
630 | try: | |
631 | if _debug: | |
632 | print 'Attempting to write mail message.\n', | |
633 | gmtdate = time.asctime(time.gmtime(time.time())) + ' GMT' | |
634 | tm = time.localtime(time.time()) | |
635 | date = time.asctime(tm) + ' ' +\ | |
636 | time.strftime("%Z",tm) | |
637 | programname = self.programname | |
638 | traceback = self.traceback | |
639 | mailto = self.mailto | |
640 | ||
641 | subject = "Un-caught exception when running %s." % programname | |
642 | mailfrom = None#self.FindWindowById (wxPyError_ID_ADDRESS) | |
643 | if mailfrom: | |
644 | mailfrom = mailfrom.GetValue() | |
645 | if _startmailerwithhtml(mailto,subject, | |
646 | self.HTMLMessageTemplate % vars(), | |
647 | text=self.PlainMessageTemplate % vars(), | |
648 | mailfrom=mailfrom): | |
649 | if not (hasattr(self,"fatal") and self.fatal): | |
650 | self.OnContinue(event) # if ok, then act as if "Continue" selected | |
651 | except: | |
652 | wxPyNonWindowingError("Warning: the following exception information" | |
653 | " may not be the full story... (because " | |
654 | "a %s error was encountered trying to " | |
655 | "handle the original exception)\n\n"#%s" | |
656 | % (sys.exc_type,),#self.msg), | |
657 | stderr=sys.stdout, | |
658 | last=0) | |
659 | ||
660 | def OnExit(self, event): | |
661 | if self.IsModal(): | |
662 | self.EndModal(wxID_CANCEL) | |
663 | if self.exitjustreturns: | |
664 | return | |
665 | wxGetApp().ExitMainLoop() | |
666 | ||
667 | def SetText(self,number,string): | |
668 | self.FindWindowById(eval("wxPyError_ID_TEXT%d" | |
669 | % number)).SetLabel(string) | |
670 | self.topsizer.Layout() | |
671 | ||
672 | class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback): | |
673 | populate_function = populate_wxPyFatalErrorDialogWithTraceback | |
674 | no_continue_button = True | |
675 | fatal = True | |
676 | ||
677 | class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback): | |
678 | populate_function = populate_wxPyNonFatalErrorDialog | |
679 | ||
680 | class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback): | |
681 | populate_function = populate_wxPyFatalErrorDialog | |
682 | ||
683 | def _startmailerwithhtml(mailto,subject,html,text=None,mailfrom=None): | |
684 | if sys.hexversion >= 0x02000000:#\ | |
685 | # and sys.platform in ["windows",'nt',"w is in32"]: | |
686 | s = 'mailto:%s?subject=%s&body=%s' % (mailto, | |
687 | urllib.quote(subject), | |
688 | urllib.quote( | |
689 | text.replace('\n','\r\n'), | |
690 | "")) | |
691 | ||
692 | # Note that RFC 2368 requires that line breaks in the body of | |
693 | # a message contained in a mailto URL MUST be encoded with | |
694 | # "%0D%0A"--even on Unix/Linux. Also note that there appears | |
695 | # to be no way to specify that the body of a mailto tag be | |
696 | # interpreted as HTML (mailto tags shouldn't stuff around with | |
697 | # the MIME-Version/Content-Type headers, the RFC says, so I | |
698 | # can't even be bothered trying, as the Python | |
699 | # webbrowser/urllib modules quite likely won't allow | |
700 | # this... anyway, a plain text message is [at least!] fine...). | |
701 | ||
702 | if _debug: | |
703 | t = urllib.quote(text) | |
704 | if len(t) > 20 * 80: | |
705 | t = t[0:20 * 80] + "..." | |
706 | print "\nSummarizing (only shortened version of argument "\ | |
707 | "printed here), ", | |
708 | print 'webbrowser.open("' \ | |
709 | 'mailto:%s?subject=%s&body=%s"' % (mailto, | |
710 | urllib.quote(subject), | |
711 | t) | |
712 | webbrowser.open(s) | |
713 | return 1 | |
714 | else: | |
715 | return _writehtmlmessage(mailto,subject,html,text,mailfrom=mailfrom) | |
716 | ||
717 | def _writehtmlmessage(mailto,subject,html,text=None,parent=None,mailfrom=None): | |
718 | dlg = wxFileDialog (parent, | |
719 | "Please choose a a file to save the message to...", | |
720 | ".", | |
721 | "bug-report", | |
722 | "HTML files (*.htm,*.html)|*.htm,*.html|" | |
723 | "All files (*)|*", | |
724 | wxSAVE | wxHIDE_READONLY) | |
725 | if dlg.ShowModal() <> wxID_CANCEL: | |
726 | f = open(dlg.GetPath(),"w") | |
727 | dlg.Destroy() | |
728 | f.write(_createhtmlmail(html,text,subject,to=mailto,mailfrom=mailfrom)) | |
729 | f.close() | |
730 | return 1 | |
731 | else: | |
732 | return 0 | |
733 | ||
734 | # PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN | |
735 | #(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly]. | |
736 | ||
737 | def _createhtmlmail (html, text, subject, to=None, mailfrom=None): | |
738 | """Create a mime-message that will render HTML in popular | |
739 | MUAs, text in better ones (if indeed text is not unTrue (e.g. None) | |
740 | """ | |
741 | import MimeWriter, mimetools, cStringIO | |
742 | ||
743 | out = cStringIO.StringIO() # output buffer for our message | |
744 | htmlin = cStringIO.StringIO(html) | |
745 | if text: | |
746 | txtin = cStringIO.StringIO(text) | |
747 | ||
748 | writer = MimeWriter.MimeWriter(out) | |
749 | # | |
750 | # set up some basic headers... we put subject here | |
751 | # because smtplib.sendmail expects it to be in the | |
752 | # message body | |
753 | # | |
754 | if mailfrom: | |
755 | writer.addheader("From", mailfrom) | |
756 | #writer.addheader("Reply-to", mailfrom) | |
757 | writer.addheader("Subject", subject) | |
758 | if to: | |
759 | writer.addheader("To", to) | |
760 | writer.addheader("MIME-Version", "1.0") | |
761 | # | |
762 | # start the multipart section of the message | |
763 | # multipart/alternative seems to work better | |
764 | # on some MUAs than multipart/mixed | |
765 | # | |
766 | writer.startmultipartbody("alternative") | |
767 | writer.flushheaders() | |
768 | # | |
769 | # the plain text section | |
770 | # | |
771 | if text: | |
772 | subpart = writer.nextpart() | |
773 | subpart.addheader("Content-Transfer-Encoding", "quoted-printable") | |
774 | pout = subpart.startbody("text/plain", [("charset", 'us-ascii')]) | |
775 | mimetools.encode(txtin, pout, 'quoted-printable') | |
776 | txtin.close() | |
777 | # | |
778 | # start the html subpart of the message | |
779 | # | |
780 | subpart = writer.nextpart() | |
781 | subpart.addheader("Content-Transfer-Encoding", "quoted-printable") | |
782 | pout = subpart.startbody("text/html", [("charset", 'us-ascii')]) | |
783 | mimetools.encode(htmlin, pout, 'quoted-printable') | |
784 | htmlin.close() | |
785 | # | |
786 | # Now that we're done, close our writer and | |
787 | # return the message body | |
788 | # | |
789 | writer.lastpart() | |
790 | msg = out.getvalue() | |
791 | out.close() | |
792 | return msg | |
793 | ||
794 | def _sendmail(mailto,subject,html,text):# currently unused | |
795 | """For illustration only--this function is not actually used.""" | |
796 | import smtplib | |
797 | message = _createhtmlmail(html, text, subject) | |
798 | server = smtplib.SMTP("localhost") | |
799 | server.sendmail(mailto, subject, message) | |
800 | server.quit() | |
801 | ||
802 | def wxPyFatalError(parent,msg,**kw): | |
803 | return wxPyFatalOrNonFatalError(parent,msg,fatal=1,**kw) | |
804 | ||
805 | def wxPyNonFatalError(parent,msg,**kw): | |
806 | return wxPyFatalOrNonFatalError(parent,msg,fatal=0,**kw) | |
807 | ||
808 | def wxPyResizeHTMLWindowToDispelScrollbar(window, | |
809 | fraction, | |
810 | sizer=None, | |
811 | defaultfraction=0.7): | |
812 | # Try to `grow' parent window (typically a dialog), only so far as | |
813 | # no scrollbar is necessary, mantaining aspect ratio of display. | |
814 | # Will go no further than specified fraction of display size. | |
815 | w = 200 | |
816 | if type(fraction) == type(''): | |
817 | fraction = int(fraction[:-1]) / 100. | |
818 | ds = wxDisplaySize () | |
819 | c = window.GetInternalRepresentation () | |
820 | while w < ds[0] * fraction: | |
821 | c.Layout(w) | |
822 | if _debug: | |
823 | print '(c.GetHeight() + 20, w * ds[1]/ds[0]):',\ | |
824 | (c.GetHeight() + 20, w * ds[1]/ds[0]) | |
825 | if c.GetHeight() + 20 < w * ds[1]/ds[0]: | |
826 | size = (w,min(int ((w) * ds[1]/ds[0]), | |
827 | c.GetHeight())) | |
828 | break | |
829 | w = w + 20 | |
830 | else: | |
831 | if type(defaultfraction) == type(''): | |
832 | defaultfraction = int(defaultfraction[:-1]) / 100. | |
833 | defaultsize = (defaultfraction * ds[0], defaultfraction * ds[1]) | |
834 | if _debug: | |
835 | print 'defaultsize =',defaultsize | |
836 | size = defaultsize | |
837 | window.SetSize(size) | |
838 | if sizer is not None: | |
839 | sizer.SetMinSize(size) | |
840 | sizer.Fit(window) | |
841 | #sizer.SetSizeHints(window) | |
842 | ||
843 | def wxPyFatalOrNonFatalError(parent, | |
844 | msg, | |
845 | fatal=0, | |
846 | extraversioninformation="", | |
847 | caption=None, | |
848 | versionname=None, | |
849 | errorname=None, | |
850 | version="?", | |
851 | programname="Python program", | |
852 | tback=None,# backwards compatibility, and for | |
853 | #possible future inclusion of ability to display | |
854 | #a traceback along with the given message | |
855 | #"msg"... currently ignored though... | |
856 | modal=0): | |
857 | if not wxThread_IsMain(): | |
858 | # Aquire the GUI mutex before making GUI calls. Mutex is released | |
859 | # when locker is deleted at the end of this function. | |
860 | locker = wxMutexGuiLocker() | |
861 | ||
862 | dlg = wxDialog(parent,-1,"Error!") | |
863 | ||
864 | if fatal: | |
865 | populate_function = populate_wxPyFatalError | |
866 | else: | |
867 | populate_function = populate_wxPyNonFatalError | |
868 | ||
869 | sizer = populate_function(dlg,False,True) | |
870 | ||
871 | window = dlg.FindWindowById(wxPyError_ID_HTML) | |
872 | window.SetPage(msg) | |
873 | wxPyResizeHTMLWindowToDispelScrollbar(window, | |
874 | "85%", | |
875 | sizer=dlg.sizerAroundText) | |
876 | dlg.FindWindowById(wxPyError_ID_PROGRAMNAME).SetLabel(str(programname)) | |
877 | if errorname: | |
878 | dlg.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) | |
879 | if versionname: | |
880 | dlg.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) | |
881 | dlg.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) | |
882 | dlg.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( | |
883 | extraversioninformation)) | |
884 | if caption: | |
885 | dlg.SetTitle(caption) | |
886 | sizer.Fit(dlg) | |
887 | sizer.SetSizeHints(dlg) | |
888 | dlg.CentreOnScreen() | |
889 | ||
890 | if modal: | |
891 | v = dlg.ShowModal() | |
892 | dlg.Destroy() | |
893 | return v | |
894 | else: | |
895 | dlg.Show(True) | |
1fded56b | 896 |