]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ErrorDialogs.py
Don't scroll too far if the child getting the focus is large.
[wxWidgets.git] / wxPython / wx / lib / ErrorDialogs.py
1 #----------------------------------------------------------------------------
2 # Name: ErrorDialogs.py
3 # Version: 1.0
4 # Created: September--October 2001
5 # Author: Chris Fama of Wholly Snakes Software,
6 # Chris.Fama@whollysnakes.com
7 #----------------------------------------------------------------------------
8 """
9 ErrorDialogs.py: by Christopher J. Fama (trading under the name
10 Wholly Snakes Software {Australian Business Number: 28379514278}).
11
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...
20
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.
37
38
39 DOCUMENTATION:
40
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.]
46
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...):
50
51 wxPyNewErrorDialog (<win> (['frame='] <frame (can be None)>,
52 <OPTIONS>))
53 ...
54 wxPyDestroyErrorDialogIfPresent () # e.g. when top frame destroyed)
55
56 for unhandled errors, or
57
58 returnval = wxpyNonFatalError (<frame (can be None)>,
59 <HTML message>
60 [,<OPTIONS>])
61 or
62 returnval = wxPyFatalError (<HTML message>,
63 [,<OPTIONS, an extra one of which may be
64 'frame=' <frame (defaults to None)>>])
65 or
66
67 wxPybNonWindowingError (message)
68
69 for explicit errors.
70
71 <win> is one of
72 wxPyNonFatalErrorDialog
73 wxPyFatalErrorDialog
74 wxPyFatalErrorDialogWithTraceback
75 wxPyNonFatalErrorDialogWithTraceback
76 wxPyNonWindowingErrorHandler
77
78 and the OPTIONS (with defaults) are: (please note that the options for
79 wxPyNonWindowingErrorHandler / wxPyNonWindowingError are 'almost' a (small))
80 subset of these):
81
82 'modal' [default 1]: block until dismissed.
83
84 'programname' [default "Python program"]: appears inThe
85 caption of the dialog, amidst it's text and (by default) in mailings.
86
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
97 BY exec...
98
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].
103
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.)
114
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
123
124 'version': str(this) will appear after 'Version' below "Error in
125 <programname>" at the top of the dialog.
126
127 EXAMPLES:
128
129 sys.stderr = wxPyNonFatalErrorWindowWithTraceback (
130 parentframe,
131 programname='sumthing',
132 mailto='me@sumwear',
133 whendismissed="from wxPython.wx import * ; wxBell()")
134
135 FOR INTERNATIONAL [NON-ENGLISH-SPEAKING] USE:
136 wxPyNonFatalErrorDialog and relatives have the method
137 SetText(Number NUMBER, STRING)
138
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...
142
143 """
144
145
146 #----------------------------------------------------------------------
147 import warnings
148
149 warningmsg = r"""\
150
151 ##############################################################\
152 # THIS MODULE IS DEPRECATED and NOT MAINTAINED |
153 ##############################################################/
154
155 """
156
157 warnings.warn(warningmsg, DeprecationWarning, stacklevel=2)
158
159 #----------------------------------------------------------------------
160
161
162 _debug = 0
163 #_debug = 1 # uncomment to display some information (to stdout)
164 Version = 1.3
165
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
171
172 from ErrorDialogs_wdr import *
173
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"...
193
194 def wxPyDestroyErrorDialogIfPresent():
195 if isinstance(sys.stderr,wxPyNonFatalErrorDialog):
196 sys.stderr.Destroy()
197 sys.stderr = None
198
199 def wxPyNewErrorDialog(dlg):
200 wxPyDestroyErrorDialogIfPresent()
201 sys.stderr = dlg
202
203 class wxPyNonWindowingErrorHandler:
204 this_exception = 0
205 softspace = 0
206 def __init__(self,fatal=0,file=sys.__stderr__):
207 self.fatal = fatal
208 self.file = file
209 def write(self,s):
210 import sys
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.",
216 fatal=self.fatal,
217 stderr=self.file,
218 last=1)
219 self.this_exception = sys.last_traceback
220
221 def wxPyNonWindowingError(msg,#output=None,errors=None,
222 stderr=sys.__stderr__,
223 fatal=1,
224 last=None):
225 if os.path.exists("wxPyNonWindowingErrors.txt"):
226 mode = 'a+'
227 else:
228 mode = 'w'
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...)
234 else:
235 l = [fl]
236 for f in l:
237 f.write(time.ctime (time.time ()) + ": ")
238 f.write(msg)
239 #f.flush()
240 if sys.exc_info () [0] is not None:
241 if last:
242 f.write('Currently handled exception:\n')
243 f.flush()
244 traceback.print_exc(file=f)
245 if last:
246 f.write('\nPrevious (?) error:\n')
247 elif last or sys.last_traceback:
248 f.write("\n\n(For wizards only) ")
249 if last:
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],
255 None,f)
256 #f.flush()
257 if f is sys.__stderr__:
258 s = ' (see the file "wxPyNonWindowingErrors.txt")'
259 else:
260 s = ""
261 f.write("Please contact the author with a copy of this\n"
262 "message%s.\n" % s)
263 #f.flush()
264 fl.close()
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.")
269 sys.exit()
270
271 class wxPythonRExec (rexec.RExec):
272 def __init__(self,securityhole=0,*args,**kwargs):
273 apply(rexec.RExec.__init__, (self,) + args, kwargs)
274 if securityhole:
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!
286
287 ##def wxPyFatalError(msg,frame=None,**kwargs):
288 ## kwargs.update({'fatal' : 1})
289 ## apply(wxPyNonFatalError,
290 ## (frame,msg),
291 ## kwargs)
292
293 class wxPyNonFatalErrorDialogWithTraceback(wxDialog):
294 this_exception = 0
295 populate_function = populate_wxPyNonFatalErrorDialogWithTraceback
296 no_continue_button = False
297 fatal = False
298 modal = True
299 exitjustreturns = False # really only for testing!
300
301 def __init__(self, parent, id,
302 pos=wxDefaultPosition,
303 size=wxDefaultSize,
304 style=wxDEFAULT_DIALOG_STYLE,
305 programname="Python program",
306 version="?",
307 mailto=None,
308 whendismissed="",
309 extraversioninformation="",
310 caption="Python error!",
311 versionname=None,
312 errorname=None,
313 disable_exit_button=False):
314
315 if self.fatal:
316 whetherNF = ""
317 else:
318 whetherNF = "non-"
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
326
327 wxDialog.__init__(self, parent, id, title, pos, size, style)
328
329 self.topsizer = self.populate_function( False,True )
330
331 self.SetProgramName(programname)
332 self.SetVersion(version)
333 if errorname:
334 self.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname))
335 if versionname:
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))
340 if caption:
341 self.SetTitle(caption)
342
343 if not self.no_continue_button:
344 EVT_BUTTON(self, wxPyError_ID_CONTINUE, self.OnContinue)
345 if mailto:
346 disable_mail_button = 0
347 else:
348 disable_mail_button = 1
349 if not disable_mail_button:
350 EVT_BUTTON(self, wxPyError_ID_MAIL, self.OnMail)
351 else:
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)
358
359 def GetExtraInformation(self):
360 return self.extraexceptioninformation
361
362 def SetExtraInformation(self,value):
363 self.extraexceptioninformation = value
364 c = self.GetExtraInformationCtrl()
365 if c is not None:
366 c.SetLabel(str(value))
367 self.topsizer.Layout()
368
369 def GetExtraInformationCtrl(self):
370 return self.FindWindowById(wxPyError_ID_EXTRAINFORMATION)
371
372 def GetExceptionName(self):
373 return str(self.exceptiontype)
374
375 def SetExceptionName(self,value):
376 self.exceptiontype = str(value)
377 c = self.GetExceptionNameCtrl()
378 if c is not None:
379 c.SetLabel(str(value))
380 self.topsizer.Layout()
381
382 def GetExceptionNameCtrl(self):
383 return self.FindWindowById(wxPyError_ID_EXCEPTIONNAME)
384
385 def GetTraceback(self):
386 try:
387 return self.traceback
388 except AttributeError:
389 return None
390
391 def SetTraceback(self,value):
392 self.traceback = value
393 c = self.GetTracebackCtrl()
394 if c is not None:
395 s,cs = c.GetSize(), c.GetClientSize()
396 if value[-1] == '\n':
397 value = value[:-1]
398 if _debug:
399 print "%s.SetTraceback(): ...SetValue('%s' (^M=\\r; ^J=\\n))"\
400 % (self,value.replace('\n',"^J"))
401 c.SetValue(value)
402
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
412 # clicks...)
413
414 if _debug:
415 size = c.GetBestSize()
416 print "%s.SetTraceback(): %s.GetBestSize() = (%s,%s)"\
417 % (self,c,size.width,size.height)
418 w,h = 0,0
419 for v in value.split("\n"):
420 pw,ph,d,e = t = c.GetFullTextExtent(v)
421 if _debug:
422 print v, t
423 h = h + ph + e# + d
424 pw = pw + wxSystemSettings_GetSystemMetric(wxSYS_VSCROLL_X)
425 if pw > w:
426 w = pw
427 w = w + s.width - cs.width
428 h = h + s.height - cs.height
429 if _debug:
430 print "%s.SetTraceback(): calculated w,h =" % c,\
431 w,h,"and sys.platform = '%s'" % sys.platform
432 self.sizerAroundText.SetItemMinSize (c,w,h)
433 c.SetSize ((w,h))
434 c.SetSizeHints (w,h,w,h)
435 c.Refresh()#.SetAutoLayout(False)
436
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()
441
442 def GetTracebackCtrl(self):
443 return self.FindWindowById(wxPyError_ID_TEXTCTRL)
444
445 def GetVersion(self):
446 return self.version
447
448 def SetVersion(self,value):
449 self.version = value
450 c = self.GetVersionNumberCtrl()
451 if c is not None:
452 c.SetLabel(value)
453 self.topsizer.Layout()
454
455 def GetVersionNumberCtrl(self):
456 return self.FindWindowById(wxPyError_ID_VERSIONNUMBER)
457
458 def GetProgramName(self):
459 return self.programname
460
461 def SetProgramName(self,value):
462 self.programname = value
463 c = self.GetProgramNameCtrl()
464 if c is not None:
465 c.SetLabel(value)
466 self.topsizer.Layout()
467
468 def GetProgramNameCtrl(self):
469 return self.FindWindowById(wxPyError_ID_PROGRAMNAME)
470
471 def GetContinueButton(self):
472 return self.FindWindowById(wxPyError_ID_CONTINUE)
473
474 def GetMailButton(self):
475 return self.FindWindowById(wxPyError_ID_MAIL)
476
477 def GetExitButton(self):
478 return self.FindWindowById(wxPyError_ID_EXIT)
479
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...
483
484 def write(self,s):
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()
490
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.....
494 try:
495 #from wxPython.wx import wxBell
496 wxBell()
497
498 if _debug:
499 if sys.stdout: sys.stdout.write(
500 'in %s.write(): ' % self)
501
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()
507
508 if _debug:
509 #import traceback
510 traceback.print_last(None,sys.stdout)
511
512 self.SetExceptionName(str(self.exceptiontype))
513 self.SetExtraInformation(str(self.extraexceptioninformation))
514 self.SetTraceback(str(self.traceback))
515
516 self.topsizer.Fit(self)
517 self.topsizer.SetSizeHints(self)
518 self.CentreOnScreen()
519
520 if self.modal:
521 self.ShowModal()
522 else:
523 self.Show(True)
524
525 except:
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()),
534 stderr=sys.stdout,
535 last=0)
536
537
538 # button handlers:
539
540 def OnContinue(self, event):
541 try:
542 if self.whendismissed:
543 parent = self.parent # so whendismissed can refer to "parent"
544 if 1:
545 if _debug:
546 if sys.stdout: sys.stdout.write("exec '''%s''': "
547 % (self.whendismissed))
548 exec self.whendismissed
549 if _debug: print "\n",
550 else:
551 if _debug:
552 if sys.stdout: sys.stdout.write("wxPythonRExec(%s).r_exec('''%s'''): "
553 % (self.securityhole,
554 self.whendismissed))
555 wxPythonRExec(self.securityhole).r_exec(self.whendismissed)
556 if _debug: print "\n",
557 if self.modal:
558 self.EndModal(wxID_OK)
559 else:
560 self.Close ()
561 if _debug: print "reimporting ",
562 for m in sys.modules.values():
563 if m and m.__dict__["__name__"][0] in string.uppercase:#hack!
564 if _debug:
565 print m.__dict__["__name__"],
566 reload (m)
567 if _debug:
568 print ' ',
569 if _debug:
570 print '\nENDING %s.OnContinue()..\n\n' % (self,),
571 except:
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]),
577 stderr=sys.stdout,
578 last=0)
579
580 PlainMessageTemplate = \
581 "Hello,\n\n"\
582 '%(programname)s'\
583 " error.\n\n"\
584 "I encountered the following error when running your "\
585 'program %(programname)s,'\
586 "at %(date)s.\n\n"\
587 "(The following has been automatically generated...)\n"\
588 '%(traceback)s\n\n'\
589 "More information follows:\n\n"\
590 '[Insert more '\
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"
598
599 HTMLMessageTemplate = \
600 '<html>\n'\
601 '<body>'\
602 "<p>\n"\
603 "<i><b>Hello,</b></i>\n<p>\n"\
604 '<p><h2><font color="#CC6C00">%(programname)s</font>'\
605 " error.</h2>\n"\
606 "I encountered the following error when running your "\
607 'program <font color="#CC6C00">%(programname)s</font>,'\
608 "at %(date)s.\n<p>\n"\
609 "<p>"\
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">'\
614 '<i>[Insert more '\
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"\
620 "</font><p>\n"\
621 '<i><b>Yours sincerely,</b></i>\n<p>'\
622 '<font color="#CC6C00">'\
623 "[insert your name here]\n"\
624 "</font>\n"\
625 "</body>\n"\
626 "</html>\n"
627 # text="#000000" bgcolor="#FFFFFF">\n'\
628
629 def OnMail(self,event):
630 try:
631 if _debug:
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
639 mailto = self.mailto
640
641 subject = "Un-caught exception when running %s." % programname
642 mailfrom = None#self.FindWindowById (wxPyError_ID_ADDRESS)
643 if mailfrom:
644 mailfrom = mailfrom.GetValue()
645 if _startmailerwithhtml(mailto,subject,
646 self.HTMLMessageTemplate % vars(),
647 text=self.PlainMessageTemplate % vars(),
648 mailfrom=mailfrom):
649 if not (hasattr(self,"fatal") and self.fatal):
650 self.OnContinue(event) # if ok, then act as if "Continue" selected
651 except:
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),
657 stderr=sys.stdout,
658 last=0)
659
660 def OnExit(self, event):
661 if self.IsModal():
662 self.EndModal(wxID_CANCEL)
663 if self.exitjustreturns:
664 return
665 wxGetApp().ExitMainLoop()
666
667 def SetText(self,number,string):
668 self.FindWindowById(eval("wxPyError_ID_TEXT%d"
669 % number)).SetLabel(string)
670 self.topsizer.Layout()
671
672 class wxPyFatalErrorDialogWithTraceback(wxPyNonFatalErrorDialogWithTraceback):
673 populate_function = populate_wxPyFatalErrorDialogWithTraceback
674 no_continue_button = True
675 fatal = True
676
677 class wxPyNonFatalErrorDialog(wxPyNonFatalErrorDialogWithTraceback):
678 populate_function = populate_wxPyNonFatalErrorDialog
679
680 class wxPyFatalErrorDialog(wxPyFatalErrorDialogWithTraceback):
681 populate_function = populate_wxPyFatalErrorDialog
682
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),
688 urllib.quote(
689 text.replace('\n','\r\n'),
690 ""))
691
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...).
701
702 if _debug:
703 t = urllib.quote(text)
704 if len(t) > 20 * 80:
705 t = t[0:20 * 80] + "..."
706 print "\nSummarizing (only shortened version of argument "\
707 "printed here), ",
708 print 'webbrowser.open("' \
709 'mailto:%s?subject=%s&body=%s"' % (mailto,
710 urllib.quote(subject),
711 t)
712 webbrowser.open(s)
713 return 1
714 else:
715 return _writehtmlmessage(mailto,subject,html,text,mailfrom=mailfrom)
716
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...",
720 ".",
721 "bug-report",
722 "HTML files (*.htm,*.html)|*.htm,*.html|"
723 "All files (*)|*",
724 wxSAVE | wxHIDE_READONLY)
725 if dlg.ShowModal() <> wxID_CANCEL:
726 f = open(dlg.GetPath(),"w")
727 dlg.Destroy()
728 f.write(_createhtmlmail(html,text,subject,to=mailto,mailfrom=mailfrom))
729 f.close()
730 return 1
731 else:
732 return 0
733
734 # PLEASE NOTE THAT THE CODE BELOW FOR WRITING A GIVEN
735 #(HTML) MESSAGE IS BY ART GILLESPIE [with slight modifications by yours truly].
736
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)
740 """
741 import MimeWriter, mimetools, cStringIO
742
743 out = cStringIO.StringIO() # output buffer for our message
744 htmlin = cStringIO.StringIO(html)
745 if text:
746 txtin = cStringIO.StringIO(text)
747
748 writer = MimeWriter.MimeWriter(out)
749 #
750 # set up some basic headers... we put subject here
751 # because smtplib.sendmail expects it to be in the
752 # message body
753 #
754 if mailfrom:
755 writer.addheader("From", mailfrom)
756 #writer.addheader("Reply-to", mailfrom)
757 writer.addheader("Subject", subject)
758 if to:
759 writer.addheader("To", to)
760 writer.addheader("MIME-Version", "1.0")
761 #
762 # start the multipart section of the message
763 # multipart/alternative seems to work better
764 # on some MUAs than multipart/mixed
765 #
766 writer.startmultipartbody("alternative")
767 writer.flushheaders()
768 #
769 # the plain text section
770 #
771 if text:
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')
776 txtin.close()
777 #
778 # start the html subpart of the message
779 #
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')
784 htmlin.close()
785 #
786 # Now that we're done, close our writer and
787 # return the message body
788 #
789 writer.lastpart()
790 msg = out.getvalue()
791 out.close()
792 return msg
793
794 def _sendmail(mailto,subject,html,text):# currently unused
795 """For illustration only--this function is not actually used."""
796 import smtplib
797 message = _createhtmlmail(html, text, subject)
798 server = smtplib.SMTP("localhost")
799 server.sendmail(mailto, subject, message)
800 server.quit()
801
802 def wxPyFatalError(parent,msg,**kw):
803 return wxPyFatalOrNonFatalError(parent,msg,fatal=1,**kw)
804
805 def wxPyNonFatalError(parent,msg,**kw):
806 return wxPyFatalOrNonFatalError(parent,msg,fatal=0,**kw)
807
808 def wxPyResizeHTMLWindowToDispelScrollbar(window,
809 fraction,
810 sizer=None,
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.
815 w = 200
816 if type(fraction) == type(''):
817 fraction = int(fraction[:-1]) / 100.
818 ds = wxDisplaySize ()
819 c = window.GetInternalRepresentation ()
820 while w < ds[0] * fraction:
821 c.Layout(w)
822 if _debug:
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]),
827 c.GetHeight()))
828 break
829 w = w + 20
830 else:
831 if type(defaultfraction) == type(''):
832 defaultfraction = int(defaultfraction[:-1]) / 100.
833 defaultsize = (defaultfraction * ds[0], defaultfraction * ds[1])
834 if _debug:
835 print 'defaultsize =',defaultsize
836 size = defaultsize
837 window.SetSize(size)
838 if sizer is not None:
839 sizer.SetMinSize(size)
840 sizer.Fit(window)
841 #sizer.SetSizeHints(window)
842
843 def wxPyFatalOrNonFatalError(parent,
844 msg,
845 fatal=0,
846 extraversioninformation="",
847 caption=None,
848 versionname=None,
849 errorname=None,
850 version="?",
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...
856 modal=0):
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()
861
862 dlg = wxDialog(parent,-1,"Error!")
863
864 if fatal:
865 populate_function = populate_wxPyFatalError
866 else:
867 populate_function = populate_wxPyNonFatalError
868
869 sizer = populate_function(dlg,False,True)
870
871 window = dlg.FindWindowById(wxPyError_ID_HTML)
872 window.SetPage(msg)
873 wxPyResizeHTMLWindowToDispelScrollbar(window,
874 "85%",
875 sizer=dlg.sizerAroundText)
876 dlg.FindWindowById(wxPyError_ID_PROGRAMNAME).SetLabel(str(programname))
877 if errorname:
878 dlg.FindWindowById(wxPyError_ID_TEXT1).SetLabel(str(errorname))
879 if versionname:
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))
884 if caption:
885 dlg.SetTitle(caption)
886 sizer.Fit(dlg)
887 sizer.SetSizeHints(dlg)
888 dlg.CentreOnScreen()
889
890 if modal:
891 v = dlg.ShowModal()
892 dlg.Destroy()
893 return v
894 else:
895 dlg.Show(True)
896