c7fa61e5ec
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@26346 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
897 lines
35 KiB
Python
897 lines
35 KiB
Python
#----------------------------------------------------------------------------
|
|
# 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}).
|
|
|
|
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...
|
|
|
|
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 (<win> (['frame='] <frame (can be None)>,
|
|
<OPTIONS>))
|
|
...
|
|
wxPyDestroyErrorDialogIfPresent () # e.g. when top frame destroyed)
|
|
|
|
for unhandled errors, or
|
|
|
|
returnval = wxpyNonFatalError (<frame (can be None)>,
|
|
<HTML message>
|
|
[,<OPTIONS>])
|
|
or
|
|
returnval = wxPyFatalError (<HTML message>,
|
|
[,<OPTIONS, an extra one of which may be
|
|
'frame=' <frame (defaults to None)>>])
|
|
or
|
|
|
|
wxPybNonWindowingError (message)
|
|
|
|
for explicit errors.
|
|
|
|
<win> is one of
|
|
wxPyNonFatalErrorDialog
|
|
wxPyFatalErrorDialog
|
|
wxPyFatalErrorDialogWithTraceback
|
|
wxPyNonFatalErrorDialogWithTraceback
|
|
wxPyNonWindowingErrorHandler
|
|
|
|
and the OPTIONS (with defaults) are: (please note that the options for
|
|
wxPyNonWindowingErrorHandler / wxPyNonWindowingError are 'almost' a (small))
|
|
subset of these):
|
|
|
|
'modal' [default 1]: block until dismissed.
|
|
|
|
'programname' [default "Python program"]: appears inThe
|
|
caption of the dialog, amidst it's text and (by default) in mailings.
|
|
|
|
'whendismissed' option, if given, this should be a string, to be
|
|
executed in a restricted environment (see rexec module) after
|
|
dialog dismissal. Typically, this should be Python code to "clean
|
|
up" after what was presumably the partial execution of some
|
|
operation started by the user, after the unexpected interruption
|
|
by some error [which--at least the way I work, means an unexpected
|
|
error in the code, since exceptions that may be raised by some
|
|
foreseen (in a programmatic sense) should normally be handled by
|
|
try...except clauses].
|
|
NOTE THAT CURRENTLY THE rexec CODE IS NOT WORKING, SO THIS IS JUST DONE
|
|
BY exec...
|
|
|
|
'mailto': if None, give no "e-mail support" option, otherwise this
|
|
is meant to be an address for 'bug reports'; a command button will
|
|
be created for this, which pops up another dialog [Linux], or a window of
|
|
another program [Windows].
|
|
|
|
On Windows, this will launch your mailer (assuming your mailer
|
|
would start when a file with the '.eml'extension is double-clicked
|
|
in Windows Explorer--this will be the case for Microsoft Outlook
|
|
Express [tm and all those legal necessities] if installed, and the
|
|
association has not been taken over by some other program. On
|
|
Linux, it will open a file dialog to save the message, by default
|
|
as a '.html' file...{Please note that, on Windows and with current
|
|
(when I got the machine I'm writing this on--early 2000) versions
|
|
of Outlook Express, you may need to enter your e-mail address in
|
|
the box beside the "mail support" button.)
|
|
|
|
The template for the mail message is in the 'MessageTemplate'
|
|
attribute of the dialog-derived class in question (e.g.,
|
|
wxPyNonFatalErrorDialog). Search for this in the code below to
|
|
see the default (note that this template is in HTML format). This
|
|
attributes uses the '%{name}s' string conversion feature of Python
|
|
[see sec.2 of the Python Library Reference]; allowed Lance are:
|
|
programname version exceptionname exceptionvalue
|
|
extraexceptioninformation traceback
|
|
|
|
'version': str(this) will appear after 'Version' below "Error in
|
|
<programname>" 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<NUMBER>--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 = \
|
|
'<html>\n'\
|
|
'<body>'\
|
|
"<p>\n"\
|
|
"<i><b>Hello,</b></i>\n<p>\n"\
|
|
'<p><h2><font color="#CC6C00">%(programname)s</font>'\
|
|
" error.</h2>\n"\
|
|
"I encountered the following error when running your "\
|
|
'program <font color="#CC6C00">%(programname)s</font>,'\
|
|
"at %(date)s.\n<p>\n"\
|
|
"<p>"\
|
|
"<h2>Traceback (automatically generated):</h2>\n"\
|
|
'<p><font size="-1">\n<pre>%(traceback)s</pre>\n<p></font><p>'\
|
|
"\n<p>\n<h2>More information follows:</h2>\n<p>\n"\
|
|
'<font color="#CC6C00">'\
|
|
'<i>[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</i><p>\n"\
|
|
"</font><p>\n"\
|
|
'<i><b>Yours sincerely,</b></i>\n<p>'\
|
|
'<font color="#CC6C00">'\
|
|
"[insert your name here]\n"\
|
|
"</font>\n"\
|
|
"</body>\n"\
|
|
"</html>\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)
|
|
|