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