X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1fded56b375bf7a4687af1cdb182899614c1b2a8..2620b36e036a1d93bf57c97ee2f7cb9a339fd457:/wxPython/wx/lib/ErrorDialogs.py diff --git a/wxPython/wx/lib/ErrorDialogs.py b/wxPython/wx/lib/ErrorDialogs.py index eb20380b30..0fb94350a2 100644 --- a/wxPython/wx/lib/ErrorDialogs.py +++ b/wxPython/wx/lib/ErrorDialogs.py @@ -1,11 +1,896 @@ +#---------------------------------------------------------------------------- +# Name: ErrorDialogs.py +# Version: 1.0 +# Created: September--October 2001 +# Author: Chris Fama of Wholly Snakes Software, +# Chris.Fama@whollysnakes.com +#---------------------------------------------------------------------------- +""" +ErrorDialogs.py: by Christopher J. Fama (trading under the name +Wholly Snakes Software {Australian Business Number: 28379514278}). -"""Renamer stub: provides a way to drop the wx prefix from wxPython objects.""" +This code is released under the auspices of the modified version of +the GNU Library Public License (LGPL) that constitutes the license +under which the GUI package wxPython is released [see the file +LICENSE.TXT accompanying that distribution). I must also thank Graham +Boyd, of Boyd Mining, and CEO of the Minserve group of companies +(www.minserve.com.au), for kindly allowing the release of this +module under a "free" license, despite a certain part of it's +development having taken place while working for him... -__cvsid__ = "$Id$" -__revision__ = "$Revision$"[11:-2] +Please note that this code, written for Python 2.1, is derives from +code written when Python 1.5.2 was current. Although since Python 2.0 +the sys.excepthook variable has been available, for ease and potential +backwards compatibility (or to be more realistic +backwards-PARTIAL-compatibility!), the code catches errors by +assigning a custom object to sys.stderr and monitoring calls to it's +writ) method. Such calls which take place with a new value of +sys.last_traceback stand as evidence that at interpreter error has +just occurred; please note that this means that NO OTHER OUTPUT TO +sys.stderr WILL BE DISPLAYED. THIS INCLUDES "warnings" generated by +the interpreter. As far as I am aware, unless your code itself writes +to sys.stderr itself, these will be the only things you miss out +on--and many, if not most or all, of these will occur before your code +even gets the opportunity to set one of these file-like objects in +place. If this is a problem for you and you can't work around it, +please contact means or the wxPython-users mailing list. + + +DOCUMENTATION: + +This is a module to display errors--either explicitly requested by a +script or arbitrary interpreter errors--these needs arose in the +course of a commercial (contract) project, but were not developed in +the course of work on such [well, very little, albeit concurrently]... +[NBNB.Does not currently support display of other than interpreter errors.] + +Usage, after 'from wxPython.lib.ErrorDialogs import *' (all +identifiers defined in this module begin with "wxPy", and many of them +with "wxPyError_", so there should be no namespace conflicts...): + + wxPyNewErrorDialog ( (['frame='] , + )) + ... + wxPyDestroyErrorDialogIfPresent () # e.g. when top frame destroyed) + +for unhandled errors, or + + returnval = wxpyNonFatalError (, + + [,]) +or + returnval = wxPyFatalError (, + [," at the top of the dialog. + +EXAMPLES: + + sys.stderr = wxPyNonFatalErrorWindowWithTraceback ( + parentframe, + programname='sumthing', + mailto='me@sumwear', + whendismissed="from wxPython.wx import * ; wxBell()") + +FOR INTERNATIONAL [NON-ENGLISH-SPEAKING] USE: + wxPyNonFatalErrorDialog and relatives have the method + SetText(Number NUMBER, STRING) + + where STRING is to displayed in the wxStaticText control with ID + wxPyError_ID_TEXT--see the automatically-generated code + for information about the meaning of these... + +""" + + +#---------------------------------------------------------------------- +import warnings + +warningmsg = r"""\ + +##############################################################\ +# THIS MODULE IS DEPRECATED and NOT MAINTAINED | +##############################################################/ + +""" + +warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) + +#---------------------------------------------------------------------- + + +_debug = 0 +#_debug = 1 # uncomment to display some information (to stdout) +Version = 1.3 + +from wxPython.wx import * +import string, sys, traceback, time, rexec, operator, types, cStringIO, os +#from wxPython.lib.createandsendHTMLmail import *# now inline +#import MimeWriter, mimetools, tempfile, smtplib +import urllib, webbrowser + +from ErrorDialogs_wdr import * + +# You may see from the above line that I used the excellent RAD tool +# wxDesigner, by Robert Roebling, to accelerate development of this +# module... The above is left for the convenience of making future +# changes with wxDesigner; also so the wxDesigner-generated codedoes +# not need to precede the "hand-generated" code in this file; finally, +# as a personal endorsement: it is truly a brilliant time-saver! +# Please note that, at the time of writing, the wxDesigner-generated +# output requires manual removal of the PythonBitmaps function--an +# appropriate version of this function will be imported from a +# similarly- named module. Another manual change will have to be made +# to the automatically-generated source: "parent.sizerAroundText = " +# should be added [immediately] before the text similar to "item13 = +# wxStaticBoxSizer( item14, wxVERTICAL )", this sizer being the one +# containing the wxTextCtrl... [IMPORTANT NOTE: THIS LINE SHOULD BE +# THE ONE INSTANTIATING A wxStat2icBoxSizer, *NOT* THE wxStaticBox +# ITSELF...] As of version 1.2 [November 2001], this also needs to be +# done for the {sizers around the} wxPyClickableHtmlWindow's generated in +# populate_wxPyNonFatalError and populate_wxPyFatalError--note that +# for ease this sizer is still called "sizerAroundText"... + +def wxPyDestroyErrorDialogIfPresent(): + if isinstance(sys.stderr,wxPyNonFatalErrorDialog): + sys.stderr.Destroy() + sys.stderr = None + +def wxPyNewErrorDialog(dlg): + wxPyDestroyErrorDialogIfPresent() + sys.stderr = dlg + +class wxPyNonWindowingErrorHandler: + this_exception = 0 + softspace = 0 + def __init__(self,fatal=0,file=sys.__stderr__): + self.fatal = fatal + self.file = file + def write(self,s): + import sys + if s.find("Warning") <> 0\ + and self.this_exception is not sys.last_traceback: + wxPyNonWindowingError("The Python interpreter encountered an error " + "not handled by any\nexception handler--this " + "may represent some programming error.", + fatal=self.fatal, + stderr=self.file, + last=1) + self.this_exception = sys.last_traceback + +def wxPyNonWindowingError(msg,#output=None,errors=None, + stderr=sys.__stderr__, + fatal=1, + last=None): + if os.path.exists("wxPyNonWindowingErrors.txt"): + mode = 'a+' + else: + mode = 'w' + fl = open("wxPyNonWindowingErrors.txt",mode) + if stderr is not None: + l = [fl,stderr] # so that the error will be written to the file + # before any potential error in stderr.write()... (this is largely + # for my own sake in developing...) + else: + l = [fl] + for f in l: + f.write(time.ctime (time.time ()) + ": ") + f.write(msg) + #f.flush() + if sys.exc_info () [0] is not None: + if last: + f.write('Currently handled exception:\n') + f.flush() + traceback.print_exc(file=f) + if last: + f.write('\nPrevious (?) error:\n') + elif last or sys.last_traceback: + f.write("\n\n(For wizards only) ") + if last: + if type(last) <> types.ListType or len(last) < 3: + if (hasattr(sys,"last_traceback") and sys.last_traceback): + last = [sys.last_type ,sys.last_value,sys.last_traceback] + if type(last) == types.ListType: + traceback.print_exception(last[0],last[1],last[2], + None,f) + #f.flush() + if f is sys.__stderr__: + s = ' (see the file "wxPyNonWindowingErrors.txt")' + else: + s = "" + f.write("Please contact the author with a copy of this\n" + "message%s.\n" % s) + #f.flush() + fl.close() + if fatal and stderr is sys.__stderr__: + if sys.__stderr__ and sys.platform in ["windows",'nt',"win32"]: + sys.__stderr__.write( + "\nYou may have to manually close this window to exit.") + sys.exit() + +class wxPythonRExec (rexec.RExec): + def __init__(self,securityhole=0,*args,**kwargs): + apply(rexec.RExec.__init__, (self,) + args, kwargs) + if securityhole: + self.ok_builtin_modules = self.ok_builtin_modules + \ + ('wxPython', 'wxPython.wxc','wxPython.wx','wxPython.misc', + 'wxPython.misc2', 'wxPython.windows', 'wxPython.gdi', + 'wxPython.clip_dnd', 'wxPython.events', 'wxPython.mdi', + 'wxPython.frames', 'wxPython.stattool', 'wxPython.controls', + 'wxPython.controls2', 'wxPython.windows2', 'wxPython.cmndlgs', + 'wxPython.windows3', 'wxPython.image', 'wxPython.printfw', + 'wxc','misc', 'misc2', 'windows', 'gdi', 'clip_dnd', 'events', + 'mdi', 'frames', 'stattool', 'controls', 'controls2', 'windows2', + 'cmndlgs', 'windows3', 'image', 'printfw', 'wx') + # possible security hole! + +##def wxPyFatalError(msg,frame=None,**kwargs): +## kwargs.update({'fatal' : 1}) +## apply(wxPyNonFatalError, +## (frame,msg), +## kwargs) + +class wxPyNonFatalErrorDialogWithTraceback(wxDialog): + this_exception = 0 + populate_function = populate_wxPyNonFatalErrorDialogWithTraceback + no_continue_button = False + fatal = False + modal = True + exitjustreturns = False # really only for testing! + + def __init__(self, parent, id, + pos=wxDefaultPosition, + size=wxDefaultSize, + style=wxDEFAULT_DIALOG_STYLE, + programname="Python program", + version="?", + mailto=None, + whendismissed="", + extraversioninformation="", + caption="Python error!", + versionname=None, + errorname=None, + disable_exit_button=False): + + if self.fatal: + whetherNF = "" + else: + whetherNF = "non-" + title = "A (%sfatal) error has occurred in %s!"\ + % (whetherNF,programname) + self.programname = programname # save for later use + self.mailto = mailto # save for later use + self.parent = parent # save for later use + self.whendismissed = whendismissed # save for later use + self.dialogtitle = title # save for later use + + wxDialog.__init__(self, parent, id, title, pos, size, style) + + self.topsizer = self.populate_function( False,True ) + + self.SetProgramName(programname) + self.SetVersion(version) + if errorname: + self.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) + if versionname: + self.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) + self.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) + self.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( + extraversioninformation)) + if caption: + self.SetTitle(caption) + + if not self.no_continue_button: + EVT_BUTTON(self, wxPyError_ID_CONTINUE, self.OnContinue) + if mailto: + disable_mail_button = 0 + else: + disable_mail_button = 1 + if not disable_mail_button: + EVT_BUTTON(self, wxPyError_ID_MAIL, self.OnMail) + else: + self.GetMailButton().Enable(False) + # disable the entry box for an e-mail address by default (NOT PROPERLY DOCUMENTED) + if not hasattr(self,"enable_mail_address_box"): + self.FindWindowById(wxPyError_ID_ADDRESS).Enable(False) + if not disable_exit_button: + EVT_BUTTON(self, wxPyError_ID_EXIT, self.OnExit) + + def GetExtraInformation(self): + return self.extraexceptioninformation + + def SetExtraInformation(self,value): + self.extraexceptioninformation = value + c = self.GetExtraInformationCtrl() + if c is not None: + c.SetLabel(str(value)) + self.topsizer.Layout() + + def GetExtraInformationCtrl(self): + return self.FindWindowById(wxPyError_ID_EXTRAINFORMATION) + + def GetExceptionName(self): + return str(self.exceptiontype) + + def SetExceptionName(self,value): + self.exceptiontype = str(value) + c = self.GetExceptionNameCtrl() + if c is not None: + c.SetLabel(str(value)) + self.topsizer.Layout() + + def GetExceptionNameCtrl(self): + return self.FindWindowById(wxPyError_ID_EXCEPTIONNAME) + + def GetTraceback(self): + try: + return self.traceback + except AttributeError: + return None + + def SetTraceback(self,value): + self.traceback = value + c = self.GetTracebackCtrl() + if c is not None: + s,cs = c.GetSize(), c.GetClientSize() + if value[-1] == '\n': + value = value[:-1] + if _debug: + print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\ + % (self,value.replace('\n',"^J")) + c.SetValue(value) + + # Despite using the wxADJUST_MINSIZE flag in the + # appropriate AddWindow method of the sizer, this doesn't + # size the control appropriately... evidently the control's + # GetBestSize method is not returning the "correct" + # value... So we perform a rather ugly "fix"... note that + # this also requires that we remove the wxADJUST_MINSIZE + # flag from the AddWindow method of the sizer containing + # the wxTextCtrl, which adds the wxTextCtrl... (this + # amounts, as of wxDesigner 2.6, to only a few mouse + # clicks...) + + if _debug: + size = c.GetBestSize() + print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\ + % (self,c,size.width,size.height) + w,h = 0,0 + for v in value.split("\n"): + pw,ph,d,e = t = c.GetFullTextExtent(v) + if _debug: + print v, t + h = h + ph + e# + d + pw = pw + wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X) + if pw > w: + w = pw + w = w + s.width - cs.width + h = h + s.height - cs.height + if _debug: + print "%s.SetTraceback(): calculated w,h =" % c,\ + w,h,"and sys.platform = '%s'" % sys.platform + self.sizerAroundText.SetItemMinSize (c,w,h) + c.SetSize ((w,h)) + c.SetSizeHints (w,h,w,h) + c.Refresh()#.SetAutoLayout(False) + + #^ the reason we need the above seems to be to replace the + #faulty GetBestSize of wxTextCtrl... + #self.sizerAroundText.Layout() + self.topsizer.Layout() + + def GetTracebackCtrl(self): + return self.FindWindowById(wxPyError_ID_TEXTCTRL) + + def GetVersion(self): + return self.version + + def SetVersion(self,value): + self.version = value + c = self.GetVersionNumberCtrl() + if c is not None: + c.SetLabel(value) + self.topsizer.Layout() + + def GetVersionNumberCtrl(self): + return self.FindWindowById(wxPyError_ID_VERSIONNUMBER) + + def GetProgramName(self): + return self.programname + + def SetProgramName(self,value): + self.programname = value + c = self.GetProgramNameCtrl() + if c is not None: + c.SetLabel(value) + self.topsizer.Layout() + + def GetProgramNameCtrl(self): + return self.FindWindowById(wxPyError_ID_PROGRAMNAME) + + def GetContinueButton(self): + return self.FindWindowById(wxPyError_ID_CONTINUE) + + def GetMailButton(self): + return self.FindWindowById(wxPyError_ID_MAIL) + + def GetExitButton(self): + return self.FindWindowById(wxPyError_ID_EXIT) + + # write handler (which is really the guts of the thing... + # [Note that this doesn't use sys.excepthook because I already had a + # working body of code... + + def write(self,s): + if self.this_exception is not sys.last_traceback: + if not wxThread_IsMain(): + # Aquire the GUI mutex before making GUI calls. Mutex is released + # when locker is deleted at the end of this function. + locker = wxMutexGuiLocker() + + self.this_exception = sys.last_traceback + # this is meant to be done once per traceback's sys.stderr.write's + # - on the first in fact..... + try: + #from wxPython.wx import wxBell + wxBell() + + if _debug: + if sys.stdout: sys.stdout.write( + 'in %s.write(): ' % self) + + self.exceptiontype = sys.last_type + self.extraexceptioninformation = sys.last_value + c = cStringIO.StringIO() + traceback.print_last(None,c) + self.traceback = c.getvalue() + + if _debug: + #import traceback + traceback.print_last(None,sys.stdout) + + self.SetExceptionName(str(self.exceptiontype)) + self.SetExtraInformation(str(self.extraexceptioninformation)) + self.SetTraceback(str(self.traceback)) + + self.topsizer.Fit(self) + self.topsizer.SetSizeHints(self) + self.CentreOnScreen() + + if self.modal: + self.ShowModal() + else: + self.Show(True) + + except: + if not locals().has_key("c"): + c = cStringIO.StringIO() + c.write("[Exception occurred before data from " + "sys.last_traceback available]") + wxPyNonWindowingError("Warning: " + "a %s error was encountered trying to " + "handle the exception\n%s\nThis was:"#%s\n" + % (sys.exc_type, c.getvalue()),#, c2.getvalue()), + stderr=sys.stdout, + last=0) + + + # button handlers: + + def OnContinue(self, event): + try: + if self.whendismissed: + parent = self.parent # so whendismissed can refer to "parent" + if 1: + if _debug: + if sys.stdout: sys.stdout.write("exec '''%s''': " + % (self.whendismissed)) + exec self.whendismissed + if _debug: print "\n", + else: + if _debug: + if sys.stdout: sys.stdout.write("wxPythonRExec(%s).r_exec('''%s'''): " + % (self.securityhole, + self.whendismissed)) + wxPythonRExec(self.securityhole).r_exec(self.whendismissed) + if _debug: print "\n", + if self.modal: + self.EndModal(wxID_OK) + else: + self.Close () + if _debug: print "reimporting ", + for m in sys.modules.values(): + if m and m.__dict__["__name__"][0] in string.uppercase:#hack! + if _debug: + print m.__dict__["__name__"], + reload (m) + if _debug: + print ' ', + if _debug: + print '\nENDING %s.OnContinue()..\n\n' % (self,), + except: + wxPyNonWindowingError("Warning: the following exception information" + " may not be the full story.. (because " + "a %s(%s) error was encountered trying to " + "handle the exception)\n\n" + % tuple(sys.exc_info()[:2]), + stderr=sys.stdout, + last=0) + + PlainMessageTemplate = \ + "Hello,\n\n"\ + '%(programname)s'\ + " error.\n\n"\ + "I encountered the following error when running your "\ + 'program %(programname)s,'\ + "at %(date)s.\n\n"\ + "(The following has been automatically generated...)\n"\ + '%(traceback)s\n\n'\ + "More information follows:\n\n"\ + '[Insert more '\ + "information about the error here, such as what you were "\ + "trying to do at the time of the error. Please "\ + "understand that failure to fill in this field will be "\ + "interpreted as an invitation to consign this e-mail "\ + "straight to the trash can!]\n\n"\ + 'Yours sincerely,\n'\ + "[insert your name here]\n" + + HTMLMessageTemplate = \ + '\n'\ + ''\ + "

\n"\ + "Hello,\n

\n"\ + '

%(programname)s'\ + " error.

\n"\ + "I encountered the following error when running your "\ + 'program %(programname)s,'\ + "at %(date)s.\n

\n"\ + "

"\ + "

Traceback (automatically generated):

\n"\ + '

\n

%(traceback)s
\n

'\ + "\n

\n

More information follows:

\n

\n"\ + ''\ + '[Insert more '\ + "information about the error here, such as what you were "\ + "trying to do at the time of the error. Please "\ + "understand that failure to fill in this field will be "\ + "interpreted as an invitation to consign this e-mail "\ + "straight to the trash can!]\n

\n"\ + "

\n"\ + 'Yours sincerely,\n

'\ + ''\ + "[insert your name here]\n"\ + "\n"\ + "\n"\ + "\n" + # text="#000000" bgcolor="#FFFFFF">\n'\ + + def OnMail(self,event): + try: + if _debug: + print 'Attempting to write mail message.\n', + gmtdate = time.asctime(time.gmtime(time.time())) + ' GMT' + tm = time.localtime(time.time()) + date = time.asctime(tm) + ' ' +\ + time.strftime("%Z",tm) + programname = self.programname + traceback = self.traceback + mailto = self.mailto + + subject = "Un-caught exception when running %s." % programname + mailfrom = None#self.FindWindowById (wxPyError_ID_ADDRESS) + if mailfrom: + mailfrom = mailfrom.GetValue() + if _startmailerwithhtml(mailto,subject, + self.HTMLMessageTemplate % vars(), + text=self.PlainMessageTemplate % vars(), + mailfrom=mailfrom): + if not (hasattr(self,"fatal") and self.fatal): + self.OnContinue(event) # if ok, then act as if "Continue" selected + except: + wxPyNonWindowingError("Warning: the following exception information" + " may not be the full story... (because " + "a %s error was encountered trying to " + "handle the original exception)\n\n"#%s" + % (sys.exc_type,),#self.msg), + stderr=sys.stdout, + last=0) + + def OnExit(self, event): + if self.IsModal(): + self.EndModal(wxID_CANCEL) + if self.exitjustreturns: + return + wxGetApp().ExitMainLoop() + + def SetText(self,number,string): + self.FindWindowById(eval("wxPyError_ID_TEXT%d" + % number)).SetLabel(string) + self.topsizer.Layout() + +class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback): + populate_function = populate_wxPyFatalErrorDialogWithTraceback + no_continue_button = True + fatal = True + +class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback): + populate_function = populate_wxPyNonFatalErrorDialog + +class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback): + populate_function = populate_wxPyFatalErrorDialog + +def _startmailerwithhtml(mailto,subject,html,text=None,mailfrom=None): + if sys.hexversion >= 0x02000000:#\ + # and sys.platform in ["windows",'nt',"w is in32"]: + s = 'mailto:%s?subject=%s&body=%s' % (mailto, + urllib.quote(subject), + urllib.quote( + text.replace('\n','\r\n'), + "")) + + # Note that RFC 2368 requires that line breaks in the body of + # a message contained in a mailto URL MUST be encoded with + # "%0D%0A"--even on Unix/Linux. Also note that there appears + # to be no way to specify that the body of a mailto tag be + # interpreted as HTML (mailto tags shouldn't stuff around with + # the MIME-Version/Content-Type headers, the RFC says, so I + # can't even be bothered trying, as the Python + # webbrowser/urllib modules quite likely won't allow + # this... anyway, a plain text message is [at least!] fine...). + + if _debug: + t = urllib.quote(text) + if len(t) > 20 * 80: + t = t[0:20 * 80] + "..." + print "\nSummarizing (only shortened version of argument "\ + "printed here), ", + print 'webbrowser.open("' \ + 'mailto:%s?subject=%s&body=%s"' % (mailto, + urllib.quote(subject), + t) + webbrowser.open(s) + return 1 + else: + return _writehtmlmessage(mailto,subject,html,text,mailfrom=mailfrom) + +def _writehtmlmessage(mailto,subject,html,text=None,parent=None,mailfrom=None): + dlg = wxFileDialog (parent, + "Please choose a a file to save the message to...", + ".", + "bug-report", + "HTML files (*.htm,*.html)|*.htm,*.html|" + "All files (*)|*", + wxSAVE | wxHIDE_READONLY) + if dlg.ShowModal() <> wxID_CANCEL: + f = open(dlg.GetPath(),"w") + dlg.Destroy() + f.write(_createhtmlmail(html,text,subject,to=mailto,mailfrom=mailfrom)) + f.close() + return 1 + else: + return 0 + +# PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN +#(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly]. + +def _createhtmlmail (html, text, subject, to=None, mailfrom=None): + """Create a mime-message that will render HTML in popular + MUAs, text in better ones (if indeed text is not unTrue (e.g. None) + """ + import MimeWriter, mimetools, cStringIO + + out = cStringIO.StringIO() # output buffer for our message + htmlin = cStringIO.StringIO(html) + if text: + txtin = cStringIO.StringIO(text) + + writer = MimeWriter.MimeWriter(out) + # + # set up some basic headers... we put subject here + # because smtplib.sendmail expects it to be in the + # message body + # + if mailfrom: + writer.addheader("From", mailfrom) + #writer.addheader("Reply-to", mailfrom) + writer.addheader("Subject", subject) + if to: + writer.addheader("To", to) + writer.addheader("MIME-Version", "1.0") + # + # start the multipart section of the message + # multipart/alternative seems to work better + # on some MUAs than multipart/mixed + # + writer.startmultipartbody("alternative") + writer.flushheaders() + # + # the plain text section + # + if text: + subpart = writer.nextpart() + subpart.addheader("Content-Transfer-Encoding", "quoted-printable") + pout = subpart.startbody("text/plain", [("charset", 'us-ascii')]) + mimetools.encode(txtin, pout, 'quoted-printable') + txtin.close() + # + # start the html subpart of the message + # + subpart = writer.nextpart() + subpart.addheader("Content-Transfer-Encoding", "quoted-printable") + pout = subpart.startbody("text/html", [("charset", 'us-ascii')]) + mimetools.encode(htmlin, pout, 'quoted-printable') + htmlin.close() + # + # Now that we're done, close our writer and + # return the message body + # + writer.lastpart() + msg = out.getvalue() + out.close() + return msg + +def _sendmail(mailto,subject,html,text):# currently unused + """For illustration only--this function is not actually used.""" + import smtplib + message = _createhtmlmail(html, text, subject) + server = smtplib.SMTP("localhost") + server.sendmail(mailto, subject, message) + server.quit() + +def wxPyFatalError(parent,msg,**kw): + return wxPyFatalOrNonFatalError(parent,msg,fatal=1,**kw) + +def wxPyNonFatalError(parent,msg,**kw): + return wxPyFatalOrNonFatalError(parent,msg,fatal=0,**kw) + +def wxPyResizeHTMLWindowToDispelScrollbar(window, + fraction, + sizer=None, + defaultfraction=0.7): + # Try to `grow' parent window (typically a dialog), only so far as + # no scrollbar is necessary, mantaining aspect ratio of display. + # Will go no further than specified fraction of display size. + w = 200 + if type(fraction) == type(''): + fraction = int(fraction[:-1]) / 100. + ds = wxDisplaySize () + c = window.GetInternalRepresentation () + while w < ds[0] * fraction: + c.Layout(w) + if _debug: + print '(c.GetHeight() + 20, w * ds[1]/ds[0]):',\ + (c.GetHeight() + 20, w * ds[1]/ds[0]) + if c.GetHeight() + 20 < w * ds[1]/ds[0]: + size = (w,min(int ((w) * ds[1]/ds[0]), + c.GetHeight())) + break + w = w + 20 + else: + if type(defaultfraction) == type(''): + defaultfraction = int(defaultfraction[:-1]) / 100. + defaultsize = (defaultfraction * ds[0], defaultfraction * ds[1]) + if _debug: + print 'defaultsize =',defaultsize + size = defaultsize + window.SetSize(size) + if sizer is not None: + sizer.SetMinSize(size) + sizer.Fit(window) + #sizer.SetSizeHints(window) + +def wxPyFatalOrNonFatalError(parent, + msg, + fatal=0, + extraversioninformation="", + caption=None, + versionname=None, + errorname=None, + version="?", + programname="Python program", + tback=None,# backwards compatibility, and for + #possible future inclusion of ability to display + #a traceback along with the given message + #"msg"... currently ignored though... + modal=0): + if not wxThread_IsMain(): + # Aquire the GUI mutex before making GUI calls. Mutex is released + # when locker is deleted at the end of this function. + locker = wxMutexGuiLocker() + + dlg = wxDialog(parent,-1,"Error!") + + if fatal: + populate_function = populate_wxPyFatalError + else: + populate_function = populate_wxPyNonFatalError + + sizer = populate_function(dlg,False,True) + + window = dlg.FindWindowById(wxPyError_ID_HTML) + window.SetPage(msg) + wxPyResizeHTMLWindowToDispelScrollbar(window, + "85%", + sizer=dlg.sizerAroundText) + dlg.FindWindowById(wxPyError_ID_PROGRAMNAME).SetLabel(str(programname)) + if errorname: + dlg.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname)) + if versionname: + dlg.FindWindowById(wxPyError_ID_TEXT2).SetLabel(str(versionname)) + dlg.FindWindowById(wxPyError_ID_VERSIONNUMBER).SetLabel(str(version)) + dlg.FindWindowById(wxPyError_ID_EXTRA_VERSION_INFORMATION).SetLabel(str( + extraversioninformation)) + if caption: + dlg.SetTitle(caption) + sizer.Fit(dlg) + sizer.SetSizeHints(dlg) + dlg.CentreOnScreen() + + if modal: + v = dlg.ShowModal() + dlg.Destroy() + return v + else: + dlg.Show(True) -from wx import _rename -from wxPython.lib import ErrorDialogs -_rename(globals(), ErrorDialogs.__dict__, modulename='lib.ErrorDialogs') -del ErrorDialogs -del _rename