1 #----------------------------------------------------------------------------
2 # Name: ErrorDialogs.py
4 # Created: September--October 2001
5 # Author: Chris Fama of Wholly Snakes Software,
6 # Chris.Fama@whollysnakes.com
7 #----------------------------------------------------------------------------
9 ErrorDialogs.py: by Christopher J. Fama (trading under the name
10 Wholly Snakes Software {Australian Business Number: 28379514278}).
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...
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.
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.]
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...):
51 wxPyNewErrorDialog (<win> (['frame='] <frame (can be None)>,
54 wxPyDestroyErrorDialogIfPresent () # e.g. when top frame destroyed)
56 for unhandled errors, or
58 returnval = wxpyNonFatalError (<frame (can be None)>,
62 returnval = wxPyFatalError (<HTML message>,
63 [,<OPTIONS, an extra one of which may be
64 'frame=' <frame (defaults to None)>>])
67 wxPybNonWindowingError (message)
72 wxPyNonFatalErrorDialog
74 wxPyFatalErrorDialogWithTraceback
75 wxPyNonFatalErrorDialogWithTraceback
76 wxPyNonWindowingErrorHandler
78 and the OPTIONS (with defaults) are: (please note that the options for
79 wxPyNonWindowingErrorHandler / wxPyNonWindowingError are 'almost' a (small))
82 'modal' [default 1]: block until dismissed.
84 'programname' [default "Python program"]: appears inThe
85 caption of the dialog, amidst it's text and (by default) in mailings.
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
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].
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.)
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
124 'version': str(this) will appear after 'Version' below "Error in
125 <programname>" at the top of the dialog.
129 sys.stderr = wxPyNonFatalErrorWindowWithTraceback (
131 programname='sumthing',
133 whendismissed="from wxPython.wx import * ; wxBell()")
135 FOR INTERNATIONAL [NON-ENGLISH-SPEAKING] USE:
136 wxPyNonFatalErrorDialog and relatives have the method
137 SetText(Number NUMBER, STRING)
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...
146 #_debug = 1 # uncomment to display some information (to stdout)
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
155 from ErrorDialogs_wdr
import *
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"...
177 def wxPyDestroyErrorDialogIfPresent():
178 if isinstance(sys
.stderr
,wxPyNonFatalErrorDialog
):
182 def wxPyNewErrorDialog(dlg
):
183 wxPyDestroyErrorDialogIfPresent()
186 class wxPyNonWindowingErrorHandler
:
189 def __init__(self
,fatal
=0,file=sys
.__stderr
__):
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.",
202 self
.this_exception
= sys
.last_traceback
204 def wxPyNonWindowingError(msg
,#output=None,errors=None,
205 stderr
=sys
.__stderr
__,
208 if os
.path
.exists("wxPyNonWindowingErrors.txt"):
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...)
220 f
.write(time
.ctime (time
.time ()) + ": ")
223 if sys
.exc_info () [0] is not None:
225 f
.write('Currently handled exception:\n')
227 traceback
.print_exc(file=f
)
229 f
.write('\nPrevious (?) error:\n')
230 elif last
or sys
.last_traceback
:
231 f
.write("\n\n(For wizards only) ")
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],
240 if f
is sys
.__stderr
__:
241 s
= ' (see the file "wxPyNonWindowingErrors.txt")'
244 f
.write("Please contact the author with a copy of this\n"
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.")
254 class wxPythonRExec (rexec
.RExec
):
255 def __init__(self
,securityhole
=0,*args
,**kwargs
):
256 apply(rexec
.RExec
.__init
__, (self
,) + args
, kwargs
)
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!
270 ##def wxPyFatalError(msg,frame=None,**kwargs):
271 ## kwargs.update({'fatal' : 1})
272 ## apply(wxPyNonFatalError,
276 class wxPyNonFatalErrorDialogWithTraceback(wxDialog
):
278 populate_function
= populate_wxPyNonFatalErrorDialogWithTraceback
279 no_continue_button
= False
282 exitjustreturns
= False # really only for testing!
284 def __init__(self
, parent
, id,
285 pos
=wxDefaultPosition
,
287 style
=wxDEFAULT_DIALOG_STYLE
,
288 programname
="Python program",
292 extraversioninformation
="",
293 caption
="Python error!",
296 disable_exit_button
=False):
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
310 wxDialog
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
312 self
.topsizer
= self
.populate_function( False,True )
314 self
.SetProgramName(programname
)
315 self
.SetVersion(version
)
317 self
.FindWindowById(wxPyError_ID_TEXT1
).SetLabel(str(errorname
))
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
))
324 self
.SetTitle(caption
)
326 if not self
.no_continue_button
:
327 EVT_BUTTON(self
, wxPyError_ID_CONTINUE
, self
.OnContinue
)
329 disable_mail_button
= 0
331 disable_mail_button
= 1
332 if not disable_mail_button
:
333 EVT_BUTTON(self
, wxPyError_ID_MAIL
, self
.OnMail
)
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
)
342 def GetExtraInformation(self
):
343 return self
.extraexceptioninformation
345 def SetExtraInformation(self
,value
):
346 self
.extraexceptioninformation
= value
347 c
= self
.GetExtraInformationCtrl()
349 c
.SetLabel(str(value
))
350 self
.topsizer
.Layout()
352 def GetExtraInformationCtrl(self
):
353 return self
.FindWindowById(wxPyError_ID_EXTRAINFORMATION
)
355 def GetExceptionName(self
):
356 return str(self
.exceptiontype
)
358 def SetExceptionName(self
,value
):
359 self
.exceptiontype
= str(value
)
360 c
= self
.GetExceptionNameCtrl()
362 c
.SetLabel(str(value
))
363 self
.topsizer
.Layout()
365 def GetExceptionNameCtrl(self
):
366 return self
.FindWindowById(wxPyError_ID_EXCEPTIONNAME
)
368 def GetTraceback(self
):
370 return self
.traceback
371 except AttributeError:
374 def SetTraceback(self
,value
):
375 self
.traceback
= value
376 c
= self
.GetTracebackCtrl()
378 s
,cs
= c
.GetSize(), c
.GetClientSize()
379 if value
[-1] == '\n':
382 print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\
383 % (self
,value
.replace('\n',"^J"))
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
398 size
= c
.GetBestSize()
399 print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\
400 % (self
,c
,size
.width
,size
.height
)
402 for v
in value
.split("\n"):
403 pw
,ph
,d
,e
= t
= c
.GetFullTextExtent(v
)
407 pw
= pw
+ wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X
)
410 w
= w
+ s
.width
- cs
.width
411 h
= h
+ s
.height
- cs
.height
413 print "%s.SetTraceback(): calculated w,h =" % c
,\
414 w
,h
,"and sys.platform = '%s'" % sys
.platform
415 self
.sizerAroundText
.SetItemMinSize (c
,w
,h
)
417 c
.SetSizeHints (w
,h
,w
,h
)
418 c
.Refresh()#.SetAutoLayout(False)
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()
425 def GetTracebackCtrl(self
):
426 return self
.FindWindowById(wxPyError_ID_TEXTCTRL
)
428 def GetVersion(self
):
431 def SetVersion(self
,value
):
433 c
= self
.GetVersionNumberCtrl()
436 self
.topsizer
.Layout()
438 def GetVersionNumberCtrl(self
):
439 return self
.FindWindowById(wxPyError_ID_VERSIONNUMBER
)
441 def GetProgramName(self
):
442 return self
.programname
444 def SetProgramName(self
,value
):
445 self
.programname
= value
446 c
= self
.GetProgramNameCtrl()
449 self
.topsizer
.Layout()
451 def GetProgramNameCtrl(self
):
452 return self
.FindWindowById(wxPyError_ID_PROGRAMNAME
)
454 def GetContinueButton(self
):
455 return self
.FindWindowById(wxPyError_ID_CONTINUE
)
457 def GetMailButton(self
):
458 return self
.FindWindowById(wxPyError_ID_MAIL
)
460 def GetExitButton(self
):
461 return self
.FindWindowById(wxPyError_ID_EXIT
)
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...
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()
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.....
478 #from wxPython.wx import wxBell
482 if sys
.stdout
: sys
.stdout
.write(
483 'in %s.write(): ' % self
)
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()
493 traceback
.print_last(None,sys
.stdout
)
495 self
.SetExceptionName(str(self
.exceptiontype
))
496 self
.SetExtraInformation(str(self
.extraexceptioninformation
))
497 self
.SetTraceback(str(self
.traceback
))
499 self
.topsizer
.Fit(self
)
500 self
.topsizer
.SetSizeHints(self
)
501 self
.CentreOnScreen()
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()),
523 def OnContinue(self
, event
):
525 if self
.whendismissed
:
526 parent
= self
.parent
# so whendismissed can refer to "parent"
529 if sys
.stdout
: sys
.stdout
.write("exec '''%s''': "
530 % (self
.whendismissed
))
531 exec self
.whendismissed
532 if _debug
: print "\n",
535 if sys
.stdout
: sys
.stdout
.write("wxPythonRExec(%s).r_exec('''%s'''): "
536 % (self
.securityhole
,
538 wxPythonRExec(self
.securityhole
).r_exec(self
.whendismissed
)
539 if _debug
: print "\n",
541 self
.EndModal(wxID_OK
)
544 if _debug
: print "reimporting ",
545 for m
in sys
.modules
.values():
546 if m
and m
.__dict
__["__name__"][0] in string
.uppercase
:#hack!
548 print m
.__dict
__["__name__"],
553 print '\nENDING %s.OnContinue()..\n\n' % (self
,),
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]),
563 PlainMessageTemplate
= \
567 "I encountered the following error when running your "\
568 'program %(programname)s,'\
570 "(The following has been automatically generated...)\n"\
572 "More information follows:\n\n"\
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"
582 HTMLMessageTemplate
= \
586 "<i><b>Hello,</b></i>\n<p>\n"\
587 '<p><h2><font color="#CC6C00">%(programname)s</font>'\
589 "I encountered the following error when running your "\
590 'program <font color="#CC6C00">%(programname)s</font>,'\
591 "at %(date)s.\n<p>\n"\
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">'\
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"\
604 '<i><b>Yours sincerely,</b></i>\n<p>'\
605 '<font color="#CC6C00">'\
606 "[insert your name here]\n"\
610 # text="#000000" bgcolor="#FFFFFF">\n'\
612 def OnMail(self
,event
):
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
624 subject
= "Un-caught exception when running %s." % programname
625 mailfrom
= None#self.FindWindowById (wxPyError_ID_ADDRESS)
627 mailfrom
= mailfrom
.GetValue()
628 if _startmailerwithhtml(mailto
,subject
,
629 self
.HTMLMessageTemplate
% vars(),
630 text
=self
.PlainMessageTemplate
% vars(),
632 if not (hasattr(self
,"fatal") and self
.fatal
):
633 self
.OnContinue(event
) # if ok, then act as if "Continue" selected
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),
643 def OnExit(self
, event
):
645 self
.EndModal(wxID_CANCEL
)
646 if self
.exitjustreturns
:
648 wxGetApp().ExitMainLoop()
650 def SetText(self
,number
,string
):
651 self
.FindWindowById(eval("wxPyError_ID_TEXT%d"
652 % number
)).SetLabel(string
)
653 self
.topsizer
.Layout()
655 class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback
):
656 populate_function
= populate_wxPyFatalErrorDialogWithTraceback
657 no_continue_button
= True
660 class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback
):
661 populate_function
= populate_wxPyNonFatalErrorDialog
663 class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback
):
664 populate_function
= populate_wxPyFatalErrorDialog
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
),
672 text
.replace('\n','\r\n'),
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...).
686 t
= urllib
.quote(text
)
688 t
= t
[0:20 * 80] + "..."
689 print "\nSummarizing (only shortened version of argument "\
691 print 'webbrowser.open("' \
692 'mailto:%s?subject=%s&body=%s"' % (mailto
,
693 urllib
.quote(subject
),
698 return _writehtmlmessage(mailto
,subject
,html
,text
,mailfrom
=mailfrom
)
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...",
705 "HTML files (*.htm,*.html)|*.htm,*.html|"
707 wxSAVE | wxHIDE_READONLY
)
708 if dlg
.ShowModal() <> wxID_CANCEL
:
709 f
= open(dlg
.GetPath(),"w")
711 f
.write(_createhtmlmail(html
,text
,subject
,to
=mailto
,mailfrom
=mailfrom
))
717 # PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN
718 #(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly].
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)
724 import MimeWriter
, mimetools
, cStringIO
726 out
= cStringIO
.StringIO() # output buffer for our message
727 htmlin
= cStringIO
.StringIO(html
)
729 txtin
= cStringIO
.StringIO(text
)
731 writer
= MimeWriter
.MimeWriter(out
)
733 # set up some basic headers... we put subject here
734 # because smtplib.sendmail expects it to be in the
738 writer
.addheader("From", mailfrom
)
739 #writer.addheader("Reply-to", mailfrom)
740 writer
.addheader("Subject", subject
)
742 writer
.addheader("To", to
)
743 writer
.addheader("MIME-Version", "1.0")
745 # start the multipart section of the message
746 # multipart/alternative seems to work better
747 # on some MUAs than multipart/mixed
749 writer
.startmultipartbody("alternative")
750 writer
.flushheaders()
752 # the plain text section
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')
761 # start the html subpart of the message
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')
769 # Now that we're done, close our writer and
770 # return the message body
777 def _sendmail(mailto
,subject
,html
,text
):# currently unused
778 """For illustration only--this function is not actually used."""
780 message
= _createhtmlmail(html
, text
, subject
)
781 server
= smtplib
.SMTP("localhost")
782 server
.sendmail(mailto
, subject
, message
)
785 def wxPyFatalError(parent
,msg
,**kw
):
786 return wxPyFatalOrNonFatalError(parent
,msg
,fatal
=1,**kw
)
788 def wxPyNonFatalError(parent
,msg
,**kw
):
789 return wxPyFatalOrNonFatalError(parent
,msg
,fatal
=0,**kw
)
791 def wxPyResizeHTMLWindowToDispelScrollbar(window
,
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.
799 if type(fraction
) == type(''):
800 fraction
= int(fraction
[:-1]) / 100.
801 ds
= wxDisplaySize ()
802 c
= window
.GetInternalRepresentation ()
803 while w
< ds
[0] * fraction
:
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]),
814 if type(defaultfraction
) == type(''):
815 defaultfraction
= int(defaultfraction
[:-1]) / 100.
816 defaultsize
= (defaultfraction
* ds
[0], defaultfraction
* ds
[1])
818 print 'defaultsize =',defaultsize
821 if sizer
is not None:
822 sizer
.SetMinSize(size
)
824 #sizer.SetSizeHints(window)
826 def wxPyFatalOrNonFatalError(parent
,
829 extraversioninformation
="",
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...
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()
845 dlg
= wxDialog(parent
,-1,"Error!")
848 populate_function
= populate_wxPyFatalError
850 populate_function
= populate_wxPyNonFatalError
852 sizer
= populate_function(dlg
,False,True)
854 window
= dlg
.FindWindowById(wxPyError_ID_HTML
)
856 wxPyResizeHTMLWindowToDispelScrollbar(window
,
858 sizer
=dlg
.sizerAroundText
)
859 dlg
.FindWindowById(wxPyError_ID_PROGRAMNAME
).SetLabel(str(programname
))
861 dlg
.FindWindowById(wxPyError_ID_TEXT1
).SetLabel(str(errorname
))
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
))
868 dlg
.SetTitle(caption
)
870 sizer
.SetSizeHints(dlg
)