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