]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ErrorDialogs.py
Fixed some wx prefix issues, changed the self test to use a sizer.
[wxWidgets.git] / wxPython / wx / lib / ErrorDialogs.py
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}).
11
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)
879