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 #----------------------------------------------------------------------
151 ##############################################################\
152 # THIS MODULE IS DEPRECATED and NOT MAINTAINED |
153 ##############################################################/
157 warnings
.warn(warningmsg
, DeprecationWarning, stacklevel
=2)
159 #----------------------------------------------------------------------
163 #_debug = 1 # uncomment to display some information (to stdout)
166 from wxPython
.wx
import *
167 import string
, sys
, traceback
, time
, rexec
, operator
, types
, cStringIO
, os
168 #from wxPython.lib.createandsendHTMLmail import *# now inline
169 #import MimeWriter, mimetools, tempfile, smtplib
170 import urllib
, webbrowser
172 from ErrorDialogs_wdr
import *
174 # You may see from the above line that I used the excellent RAD tool
175 # wxDesigner, by Robert Roebling, to accelerate development of this
176 # module... The above is left for the convenience of making future
177 # changes with wxDesigner; also so the wxDesigner-generated codedoes
178 # not need to precede the "hand-generated" code in this file; finally,
179 # as a personal endorsement: it is truly a brilliant time-saver!
180 # Please note that, at the time of writing, the wxDesigner-generated
181 # output requires manual removal of the PythonBitmaps function--an
182 # appropriate version of this function will be imported from a
183 # similarly- named module. Another manual change will have to be made
184 # to the automatically-generated source: "parent.sizerAroundText = "
185 # should be added [immediately] before the text similar to "item13 =
186 # wxStaticBoxSizer( item14, wxVERTICAL )", this sizer being the one
187 # containing the wxTextCtrl... [IMPORTANT NOTE: THIS LINE SHOULD BE
188 # THE ONE INSTANTIATING A wxStat2icBoxSizer, *NOT* THE wxStaticBox
189 # ITSELF...] As of version 1.2 [November 2001], this also needs to be
190 # done for the {sizers around the} wxPyClickableHtmlWindow's generated in
191 # populate_wxPyNonFatalError and populate_wxPyFatalError--note that
192 # for ease this sizer is still called "sizerAroundText"...
194 def wxPyDestroyErrorDialogIfPresent():
195 if isinstance(sys
.stderr
,wxPyNonFatalErrorDialog
):
199 def wxPyNewErrorDialog(dlg
):
200 wxPyDestroyErrorDialogIfPresent()
203 class wxPyNonWindowingErrorHandler
:
206 def __init__(self
,fatal
=0,file=sys
.__stderr
__):
211 if s
.find("Warning") <> 0\
212 and self
.this_exception
is not sys
.last_traceback
:
213 wxPyNonWindowingError("The Python interpreter encountered an error "
214 "not handled by any\nexception handler--this "
215 "may represent some programming error.",
219 self
.this_exception
= sys
.last_traceback
221 def wxPyNonWindowingError(msg
,#output=None,errors=None,
222 stderr
=sys
.__stderr
__,
225 if os
.path
.exists("wxPyNonWindowingErrors.txt"):
229 fl
= open("wxPyNonWindowingErrors.txt",mode
)
230 if stderr
is not None:
231 l
= [fl
,stderr
] # so that the error will be written to the file
232 # before any potential error in stderr.write()... (this is largely
233 # for my own sake in developing...)
237 f
.write(time
.ctime (time
.time ()) + ": ")
240 if sys
.exc_info () [0] is not None:
242 f
.write('Currently handled exception:\n')
244 traceback
.print_exc(file=f
)
246 f
.write('\nPrevious (?) error:\n')
247 elif last
or sys
.last_traceback
:
248 f
.write("\n\n(For wizards only) ")
250 if type(last
) <> types
.ListType
or len(last
) < 3:
251 if (hasattr(sys
,"last_traceback") and sys
.last_traceback
):
252 last
= [sys
.last_type
,sys
.last_value
,sys
.last_traceback
]
253 if type(last
) == types
.ListType
:
254 traceback
.print_exception(last
[0],last
[1],last
[2],
257 if f
is sys
.__stderr
__:
258 s
= ' (see the file "wxPyNonWindowingErrors.txt")'
261 f
.write("Please contact the author with a copy of this\n"
265 if fatal
and stderr
is sys
.__stderr
__:
266 if sys
.__stderr
__ and sys
.platform
in ["windows",'nt',"win32"]:
267 sys
.__stderr
__.write(
268 "\nYou may have to manually close this window to exit.")
271 class wxPythonRExec (rexec
.RExec
):
272 def __init__(self
,securityhole
=0,*args
,**kwargs
):
273 apply(rexec
.RExec
.__init
__, (self
,) + args
, kwargs
)
275 self
.ok_builtin_modules
= self
.ok_builtin_modules
+ \
276 ('wxPython', 'wxPython.wxc','wxPython.wx','wxPython.misc',
277 'wxPython.misc2', 'wxPython.windows', 'wxPython.gdi',
278 'wxPython.clip_dnd', 'wxPython.events', 'wxPython.mdi',
279 'wxPython.frames', 'wxPython.stattool', 'wxPython.controls',
280 'wxPython.controls2', 'wxPython.windows2', 'wxPython.cmndlgs',
281 'wxPython.windows3', 'wxPython.image', 'wxPython.printfw',
282 'wxc','misc', 'misc2', 'windows', 'gdi', 'clip_dnd', 'events',
283 'mdi', 'frames', 'stattool', 'controls', 'controls2', 'windows2',
284 'cmndlgs', 'windows3', 'image', 'printfw', 'wx')
285 # possible security hole!
287 ##def wxPyFatalError(msg,frame=None,**kwargs):
288 ## kwargs.update({'fatal' : 1})
289 ## apply(wxPyNonFatalError,
293 class wxPyNonFatalErrorDialogWithTraceback(wxDialog
):
295 populate_function
= populate_wxPyNonFatalErrorDialogWithTraceback
296 no_continue_button
= False
299 exitjustreturns
= False # really only for testing!
301 def __init__(self
, parent
, id,
302 pos
=wxDefaultPosition
,
304 style
=wxDEFAULT_DIALOG_STYLE
,
305 programname
="Python program",
309 extraversioninformation
="",
310 caption
="Python error!",
313 disable_exit_button
=False):
319 title
= "A (%sfatal) error has occurred in %s!"\
320 % (whetherNF
,programname
)
321 self
.programname
= programname
# save for later use
322 self
.mailto
= mailto
# save for later use
323 self
.parent
= parent
# save for later use
324 self
.whendismissed
= whendismissed
# save for later use
325 self
.dialogtitle
= title
# save for later use
327 wxDialog
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
329 self
.topsizer
= self
.populate_function( False,True )
331 self
.SetProgramName(programname
)
332 self
.SetVersion(version
)
334 self
.FindWindowById(wxPyError_ID_TEXT1
).SetLabel(str(errorname
))
336 self
.FindWindowById(wxPyError_ID_TEXT2
).SetLabel(str(versionname
))
337 self
.FindWindowById(wxPyError_ID_VERSIONNUMBER
).SetLabel(str(version
))
338 self
.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION
).SetLabel(str(
339 extraversioninformation
))
341 self
.SetTitle(caption
)
343 if not self
.no_continue_button
:
344 EVT_BUTTON(self
, wxPyError_ID_CONTINUE
, self
.OnContinue
)
346 disable_mail_button
= 0
348 disable_mail_button
= 1
349 if not disable_mail_button
:
350 EVT_BUTTON(self
, wxPyError_ID_MAIL
, self
.OnMail
)
352 self
.GetMailButton().Enable(False)
353 # disable the entry box for an e-mail address by default (NOT PROPERLY DOCUMENTED)
354 if not hasattr(self
,"enable_mail_address_box"):
355 self
.FindWindowById(wxPyError_ID_ADDRESS
).Enable(False)
356 if not disable_exit_button
:
357 EVT_BUTTON(self
, wxPyError_ID_EXIT
, self
.OnExit
)
359 def GetExtraInformation(self
):
360 return self
.extraexceptioninformation
362 def SetExtraInformation(self
,value
):
363 self
.extraexceptioninformation
= value
364 c
= self
.GetExtraInformationCtrl()
366 c
.SetLabel(str(value
))
367 self
.topsizer
.Layout()
369 def GetExtraInformationCtrl(self
):
370 return self
.FindWindowById(wxPyError_ID_EXTRAINFORMATION
)
372 def GetExceptionName(self
):
373 return str(self
.exceptiontype
)
375 def SetExceptionName(self
,value
):
376 self
.exceptiontype
= str(value
)
377 c
= self
.GetExceptionNameCtrl()
379 c
.SetLabel(str(value
))
380 self
.topsizer
.Layout()
382 def GetExceptionNameCtrl(self
):
383 return self
.FindWindowById(wxPyError_ID_EXCEPTIONNAME
)
385 def GetTraceback(self
):
387 return self
.traceback
388 except AttributeError:
391 def SetTraceback(self
,value
):
392 self
.traceback
= value
393 c
= self
.GetTracebackCtrl()
395 s
,cs
= c
.GetSize(), c
.GetClientSize()
396 if value
[-1] == '\n':
399 print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\
400 % (self
,value
.replace('\n',"^J"))
403 # Despite using the wxADJUST_MINSIZE flag in the
404 # appropriate AddWindow method of the sizer, this doesn't
405 # size the control appropriately... evidently the control's
406 # GetBestSize method is not returning the "correct"
407 # value... So we perform a rather ugly "fix"... note that
408 # this also requires that we remove the wxADJUST_MINSIZE
409 # flag from the AddWindow method of the sizer containing
410 # the wxTextCtrl, which adds the wxTextCtrl... (this
411 # amounts, as of wxDesigner 2.6, to only a few mouse
415 size
= c
.GetBestSize()
416 print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\
417 % (self
,c
,size
.width
,size
.height
)
419 for v
in value
.split("\n"):
420 pw
,ph
,d
,e
= t
= c
.GetFullTextExtent(v
)
424 pw
= pw
+ wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X
)
427 w
= w
+ s
.width
- cs
.width
428 h
= h
+ s
.height
- cs
.height
430 print "%s.SetTraceback(): calculated w,h =" % c
,\
431 w
,h
,"and sys.platform = '%s'" % sys
.platform
432 self
.sizerAroundText
.SetItemMinSize (c
,w
,h
)
434 c
.SetSizeHints (w
,h
,w
,h
)
435 c
.Refresh()#.SetAutoLayout(False)
437 #^ the reason we need the above seems to be to replace the
438 #faulty GetBestSize of wxTextCtrl...
439 #self.sizerAroundText.Layout()
440 self
.topsizer
.Layout()
442 def GetTracebackCtrl(self
):
443 return self
.FindWindowById(wxPyError_ID_TEXTCTRL
)
445 def GetVersion(self
):
448 def SetVersion(self
,value
):
450 c
= self
.GetVersionNumberCtrl()
453 self
.topsizer
.Layout()
455 def GetVersionNumberCtrl(self
):
456 return self
.FindWindowById(wxPyError_ID_VERSIONNUMBER
)
458 def GetProgramName(self
):
459 return self
.programname
461 def SetProgramName(self
,value
):
462 self
.programname
= value
463 c
= self
.GetProgramNameCtrl()
466 self
.topsizer
.Layout()
468 def GetProgramNameCtrl(self
):
469 return self
.FindWindowById(wxPyError_ID_PROGRAMNAME
)
471 def GetContinueButton(self
):
472 return self
.FindWindowById(wxPyError_ID_CONTINUE
)
474 def GetMailButton(self
):
475 return self
.FindWindowById(wxPyError_ID_MAIL
)
477 def GetExitButton(self
):
478 return self
.FindWindowById(wxPyError_ID_EXIT
)
480 # write handler (which is really the guts of the thing...
481 # [Note that this doesn't use sys.excepthook because I already had a
482 # working body of code...
485 if self
.this_exception
is not sys
.last_traceback
:
486 if not wxThread_IsMain():
487 # Aquire the GUI mutex before making GUI calls. Mutex is released
488 # when locker is deleted at the end of this function.
489 locker
= wxMutexGuiLocker()
491 self
.this_exception
= sys
.last_traceback
492 # this is meant to be done once per traceback's sys.stderr.write's
493 # - on the first in fact.....
495 #from wxPython.wx import wxBell
499 if sys
.stdout
: sys
.stdout
.write(
500 'in %s.write(): ' % self
)
502 self
.exceptiontype
= sys
.last_type
503 self
.extraexceptioninformation
= sys
.last_value
504 c
= cStringIO
.StringIO()
505 traceback
.print_last(None,c
)
506 self
.traceback
= c
.getvalue()
510 traceback
.print_last(None,sys
.stdout
)
512 self
.SetExceptionName(str(self
.exceptiontype
))
513 self
.SetExtraInformation(str(self
.extraexceptioninformation
))
514 self
.SetTraceback(str(self
.traceback
))
516 self
.topsizer
.Fit(self
)
517 self
.topsizer
.SetSizeHints(self
)
518 self
.CentreOnScreen()
526 if not locals().has_key("c"):
527 c
= cStringIO
.StringIO()
528 c
.write("[Exception occurred before data from "
529 "sys.last_traceback available]")
530 wxPyNonWindowingError("Warning: "
531 "a %s error was encountered trying to "
532 "handle the exception\n%s\nThis was:"#%s\n"
533 % (sys
.exc_type
, c
.getvalue()),#, c2.getvalue()),
540 def OnContinue(self
, event
):
542 if self
.whendismissed
:
543 parent
= self
.parent
# so whendismissed can refer to "parent"
546 if sys
.stdout
: sys
.stdout
.write("exec '''%s''': "
547 % (self
.whendismissed
))
548 exec self
.whendismissed
549 if _debug
: print "\n",
552 if sys
.stdout
: sys
.stdout
.write("wxPythonRExec(%s).r_exec('''%s'''): "
553 % (self
.securityhole
,
555 wxPythonRExec(self
.securityhole
).r_exec(self
.whendismissed
)
556 if _debug
: print "\n",
558 self
.EndModal(wxID_OK
)
561 if _debug
: print "reimporting ",
562 for m
in sys
.modules
.values():
563 if m
and m
.__dict
__["__name__"][0] in string
.uppercase
:#hack!
565 print m
.__dict
__["__name__"],
570 print '\nENDING %s.OnContinue()..\n\n' % (self
,),
572 wxPyNonWindowingError("Warning: the following exception information"
573 " may not be the full story.. (because "
574 "a %s(%s) error was encountered trying to "
575 "handle the exception)\n\n"
576 % tuple(sys
.exc_info()[:2]),
580 PlainMessageTemplate
= \
584 "I encountered the following error when running your "\
585 'program %(programname)s,'\
587 "(The following has been automatically generated...)\n"\
589 "More information follows:\n\n"\
591 "information about the error here, such as what you were "\
592 "trying to do at the time of the error. Please "\
593 "understand that failure to fill in this field will be "\
594 "interpreted as an invitation to consign this e-mail "\
595 "straight to the trash can!]\n\n"\
596 'Yours sincerely,\n'\
597 "[insert your name here]\n"
599 HTMLMessageTemplate
= \
603 "<i><b>Hello,</b></i>\n<p>\n"\
604 '<p><h2><font color="#CC6C00">%(programname)s</font>'\
606 "I encountered the following error when running your "\
607 'program <font color="#CC6C00">%(programname)s</font>,'\
608 "at %(date)s.\n<p>\n"\
610 "<h2>Traceback (automatically generated):</h2>\n"\
611 '<p><font size="-1">\n<pre>%(traceback)s</pre>\n<p></font><p>'\
612 "\n<p>\n<h2>More information follows:</h2>\n<p>\n"\
613 '<font color="#CC6C00">'\
615 "information about the error here, such as what you were "\
616 "trying to do at the time of the error. Please "\
617 "understand that failure to fill in this field will be "\
618 "interpreted as an invitation to consign this e-mail "\
619 "straight to the trash can!]\n</i><p>\n"\
621 '<i><b>Yours sincerely,</b></i>\n<p>'\
622 '<font color="#CC6C00">'\
623 "[insert your name here]\n"\
627 # text="#000000" bgcolor="#FFFFFF">\n'\
629 def OnMail(self
,event
):
632 print 'Attempting to write mail message.\n',
633 gmtdate
= time
.asctime(time
.gmtime(time
.time())) + ' GMT'
634 tm
= time
.localtime(time
.time())
635 date
= time
.asctime(tm
) + ' ' +\
636 time
.strftime("%Z",tm
)
637 programname
= self
.programname
638 traceback
= self
.traceback
641 subject
= "Un-caught exception when running %s." % programname
642 mailfrom
= None#self.FindWindowById (wxPyError_ID_ADDRESS)
644 mailfrom
= mailfrom
.GetValue()
645 if _startmailerwithhtml(mailto
,subject
,
646 self
.HTMLMessageTemplate
% vars(),
647 text
=self
.PlainMessageTemplate
% vars(),
649 if not (hasattr(self
,"fatal") and self
.fatal
):
650 self
.OnContinue(event
) # if ok, then act as if "Continue" selected
652 wxPyNonWindowingError("Warning: the following exception information"
653 " may not be the full story... (because "
654 "a %s error was encountered trying to "
655 "handle the original exception)\n\n"#%s"
656 % (sys
.exc_type
,),#self.msg),
660 def OnExit(self
, event
):
662 self
.EndModal(wxID_CANCEL
)
663 if self
.exitjustreturns
:
665 wxGetApp().ExitMainLoop()
667 def SetText(self
,number
,string
):
668 self
.FindWindowById(eval("wxPyError_ID_TEXT%d"
669 % number
)).SetLabel(string
)
670 self
.topsizer
.Layout()
672 class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback
):
673 populate_function
= populate_wxPyFatalErrorDialogWithTraceback
674 no_continue_button
= True
677 class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback
):
678 populate_function
= populate_wxPyNonFatalErrorDialog
680 class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback
):
681 populate_function
= populate_wxPyFatalErrorDialog
683 def _startmailerwithhtml(mailto
,subject
,html
,text
=None,mailfrom
=None):
684 if sys
.hexversion
>= 0x02000000:#\
685 # and sys.platform in ["windows",'nt',"w is in32"]:
686 s
= 'mailto:%s?subject=%s&body=%s' % (mailto
,
687 urllib
.quote(subject
),
689 text
.replace('\n','\r\n'),
692 # Note that RFC 2368 requires that line breaks in the body of
693 # a message contained in a mailto URL MUST be encoded with
694 # "%0D%0A"--even on Unix/Linux. Also note that there appears
695 # to be no way to specify that the body of a mailto tag be
696 # interpreted as HTML (mailto tags shouldn't stuff around with
697 # the MIME-Version/Content-Type headers, the RFC says, so I
698 # can't even be bothered trying, as the Python
699 # webbrowser/urllib modules quite likely won't allow
700 # this... anyway, a plain text message is [at least!] fine...).
703 t
= urllib
.quote(text
)
705 t
= t
[0:20 * 80] + "..."
706 print "\nSummarizing (only shortened version of argument "\
708 print 'webbrowser.open("' \
709 'mailto:%s?subject=%s&body=%s"' % (mailto
,
710 urllib
.quote(subject
),
715 return _writehtmlmessage(mailto
,subject
,html
,text
,mailfrom
=mailfrom
)
717 def _writehtmlmessage(mailto
,subject
,html
,text
=None,parent
=None,mailfrom
=None):
718 dlg
= wxFileDialog (parent
,
719 "Please choose a a file to save the message to...",
722 "HTML files (*.htm,*.html)|*.htm,*.html|"
724 wxSAVE | wxHIDE_READONLY
)
725 if dlg
.ShowModal() <> wxID_CANCEL
:
726 f
= open(dlg
.GetPath(),"w")
728 f
.write(_createhtmlmail(html
,text
,subject
,to
=mailto
,mailfrom
=mailfrom
))
734 # PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN
735 #(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly].
737 def _createhtmlmail (html
, text
, subject
, to
=None, mailfrom
=None):
738 """Create a mime-message that will render HTML in popular
739 MUAs, text in better ones (if indeed text is not unTrue (e.g. None)
741 import MimeWriter
, mimetools
, cStringIO
743 out
= cStringIO
.StringIO() # output buffer for our message
744 htmlin
= cStringIO
.StringIO(html
)
746 txtin
= cStringIO
.StringIO(text
)
748 writer
= MimeWriter
.MimeWriter(out
)
750 # set up some basic headers... we put subject here
751 # because smtplib.sendmail expects it to be in the
755 writer
.addheader("From", mailfrom
)
756 #writer.addheader("Reply-to", mailfrom)
757 writer
.addheader("Subject", subject
)
759 writer
.addheader("To", to
)
760 writer
.addheader("MIME-Version", "1.0")
762 # start the multipart section of the message
763 # multipart/alternative seems to work better
764 # on some MUAs than multipart/mixed
766 writer
.startmultipartbody("alternative")
767 writer
.flushheaders()
769 # the plain text section
772 subpart
= writer
.nextpart()
773 subpart
.addheader("Content-Transfer-Encoding", "quoted-printable")
774 pout
= subpart
.startbody("text/plain", [("charset", 'us-ascii')])
775 mimetools
.encode(txtin
, pout
, 'quoted-printable')
778 # start the html subpart of the message
780 subpart
= writer
.nextpart()
781 subpart
.addheader("Content-Transfer-Encoding", "quoted-printable")
782 pout
= subpart
.startbody("text/html", [("charset", 'us-ascii')])
783 mimetools
.encode(htmlin
, pout
, 'quoted-printable')
786 # Now that we're done, close our writer and
787 # return the message body
794 def _sendmail(mailto
,subject
,html
,text
):# currently unused
795 """For illustration only--this function is not actually used."""
797 message
= _createhtmlmail(html
, text
, subject
)
798 server
= smtplib
.SMTP("localhost")
799 server
.sendmail(mailto
, subject
, message
)
802 def wxPyFatalError(parent
,msg
,**kw
):
803 return wxPyFatalOrNonFatalError(parent
,msg
,fatal
=1,**kw
)
805 def wxPyNonFatalError(parent
,msg
,**kw
):
806 return wxPyFatalOrNonFatalError(parent
,msg
,fatal
=0,**kw
)
808 def wxPyResizeHTMLWindowToDispelScrollbar(window
,
811 defaultfraction
=0.7):
812 # Try to `grow' parent window (typically a dialog), only so far as
813 # no scrollbar is necessary, mantaining aspect ratio of display.
814 # Will go no further than specified fraction of display size.
816 if type(fraction
) == type(''):
817 fraction
= int(fraction
[:-1]) / 100.
818 ds
= wxDisplaySize ()
819 c
= window
.GetInternalRepresentation ()
820 while w
< ds
[0] * fraction
:
823 print '(c.GetHeight() + 20, w * ds[1]/ds[0]):',\
824 (c
.GetHeight() + 20, w
* ds
[1]/ds
[0])
825 if c
.GetHeight() + 20 < w
* ds
[1]/ds
[0]:
826 size
= (w
,min(int ((w
) * ds
[1]/ds
[0]),
831 if type(defaultfraction
) == type(''):
832 defaultfraction
= int(defaultfraction
[:-1]) / 100.
833 defaultsize
= (defaultfraction
* ds
[0], defaultfraction
* ds
[1])
835 print 'defaultsize =',defaultsize
838 if sizer
is not None:
839 sizer
.SetMinSize(size
)
841 #sizer.SetSizeHints(window)
843 def wxPyFatalOrNonFatalError(parent
,
846 extraversioninformation
="",
851 programname
="Python program",
852 tback
=None,# backwards compatibility, and for
853 #possible future inclusion of ability to display
854 #a traceback along with the given message
855 #"msg"... currently ignored though...
857 if not wxThread_IsMain():
858 # Aquire the GUI mutex before making GUI calls. Mutex is released
859 # when locker is deleted at the end of this function.
860 locker
= wxMutexGuiLocker()
862 dlg
= wxDialog(parent
,-1,"Error!")
865 populate_function
= populate_wxPyFatalError
867 populate_function
= populate_wxPyNonFatalError
869 sizer
= populate_function(dlg
,False,True)
871 window
= dlg
.FindWindowById(wxPyError_ID_HTML
)
873 wxPyResizeHTMLWindowToDispelScrollbar(window
,
875 sizer
=dlg
.sizerAroundText
)
876 dlg
.FindWindowById(wxPyError_ID_PROGRAMNAME
).SetLabel(str(programname
))
878 dlg
.FindWindowById(wxPyError_ID_TEXT1
).SetLabel(str(errorname
))
880 dlg
.FindWindowById(wxPyError_ID_TEXT2
).SetLabel(str(versionname
))
881 dlg
.FindWindowById(wxPyError_ID_VERSIONNUMBER
).SetLabel(str(version
))
882 dlg
.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION
).SetLabel(str(
883 extraversioninformation
))
885 dlg
.SetTitle(caption
)
887 sizer
.SetSizeHints(dlg
)