← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~m2j/openlp/work into lp:openlp

 

m2j has proposed merging lp:~m2j/openlp/work into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)


Additional buttons in the exception dialog for sending a bug report per email or saving it to a file.
The report contains the traceback along with library versions.

ATM the revision number is not necessary correct. Maybe you want additional informations to be reported (language environment, phonon backend, etc).
-- 
https://code.launchpad.net/~m2j/openlp/work/+merge/43411
Your team OpenLP Core is requested to review the proposed merge of lp:~m2j/openlp/work into lp:openlp.
=== added directory 'openlp/core/lib/mailto'
=== added file 'openlp/core/lib/mailto/__init__.py'
--- openlp/core/lib/mailto/__init__.py	1970-01-01 00:00:00 +0000
+++ openlp/core/lib/mailto/__init__.py	2010-12-11 00:46:10 +0000
@@ -0,0 +1,338 @@
+#!/usr/bin/env python
+
+'''Utilities for opening files or URLs in the registered default application
+and for sending e-mail using the user's preferred composer.
+
+'''
+
+__version__ = '1.1'
+__all__ = ['open', 'mailto']
+
+import os
+import sys
+import webbrowser
+import subprocess
+
+from email.Utils import encode_rfc2231
+
+_controllers = {}
+_open = None
+
+
+class BaseController(object):
+    '''Base class for open program controllers.'''
+
+    def __init__(self, name):
+        self.name = name
+
+    def open(self, filename):
+        raise NotImplementedError
+
+
+class Controller(BaseController):
+    '''Controller for a generic open program.'''
+
+    def __init__(self, *args):
+        super(Controller, self).__init__(os.path.basename(args[0]))
+        self.args = list(args)
+
+    def _invoke(self, cmdline):
+        if sys.platform[:3] == 'win':
+            closefds = False
+            startupinfo = subprocess.STARTUPINFO()
+            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+        else:
+            closefds = True
+            startupinfo = None
+
+        if (os.environ.get('DISPLAY') or sys.platform[:3] == 'win' or
+                                                    sys.platform == 'darwin'):
+            inout = file(os.devnull, 'r+')
+        else:
+            # for TTY programs, we need stdin/out
+            inout = None
+
+        # if possible, put the child precess in separate process group,
+        # so keyboard interrupts don't affect child precess as well as
+        # Python
+        setsid = getattr(os, 'setsid', None)
+        if not setsid:
+            setsid = getattr(os, 'setpgrp', None)
+
+        pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout,
+                                stderr=inout, close_fds=closefds,
+                                preexec_fn=setsid, startupinfo=startupinfo)
+
+        # It is assumed that this kind of tools (gnome-open, kfmclient,
+        # exo-open, xdg-open and open for OSX) immediately exit after lauching
+        # the specific application
+        returncode = pipe.wait()
+        if hasattr(self, 'fixreturncode'):
+            returncode = self.fixreturncode(returncode)
+        return not returncode
+
+    def open(self, filename):
+        if isinstance(filename, basestring):
+            cmdline = self.args + [filename]
+        else:
+            # assume it is a sequence
+            cmdline = self.args + filename
+        try:
+            return self._invoke(cmdline)
+        except OSError:
+            return False
+
+
+# Platform support for Windows
+if sys.platform[:3] == 'win':
+
+    class Start(BaseController):
+        '''Controller for the win32 start progam through os.startfile.'''
+
+        def open(self, filename):
+            try:
+                os.startfile(filename)
+            except WindowsError:
+                # [Error 22] No application is associated with the specified
+                # file for this operation: '<URL>'
+                return False
+            else:
+                return True
+
+    _controllers['windows-default'] = Start('start')
+    _open = _controllers['windows-default'].open
+
+
+# Platform support for MacOS
+elif sys.platform == 'darwin':
+    _controllers['open']= Controller('open')
+    _open = _controllers['open'].open
+
+
+# Platform support for Unix
+else:
+
+    import commands
+
+    # @WARNING: use the private API of the webbrowser module
+    from webbrowser import _iscommand
+
+    class KfmClient(Controller):
+        '''Controller for the KDE kfmclient program.'''
+
+        def __init__(self, kfmclient='kfmclient'):
+            super(KfmClient, self).__init__(kfmclient, 'exec')
+            self.kde_version = self.detect_kde_version()
+
+        def detect_kde_version(self):
+            kde_version = None
+            try:
+                info = commands.getoutput('kde-config --version')
+
+                for line in info.splitlines():
+                    if line.startswith('KDE'):
+                        kde_version = line.split(':')[-1].strip()
+                        break
+            except (OSError, RuntimeError):
+                pass
+
+            return kde_version
+
+        def fixreturncode(self, returncode):
+            if returncode is not None and self.kde_version > '3.5.4':
+                return returncode
+            else:
+                return os.EX_OK
+
+    def detect_desktop_environment():
+        '''Checks for known desktop environments
+
+        Return the desktop environments name, lowercase (kde, gnome, xfce)
+        or "generic"
+
+        '''
+
+        desktop_environment = 'generic'
+
+        if os.environ.get('KDE_FULL_SESSION') == 'true':
+            desktop_environment = 'kde'
+        elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
+            desktop_environment = 'gnome'
+        else:
+            try:
+                info = commands.getoutput('xprop -root _DT_SAVE_MODE')
+                if ' = "xfce4"' in info:
+                    desktop_environment = 'xfce'
+            except (OSError, RuntimeError):
+                pass
+
+        return desktop_environment
+
+
+    def register_X_controllers():
+        if _iscommand('kfmclient'):
+            _controllers['kde-open'] = KfmClient()
+
+        for command in ('gnome-open', 'exo-open', 'xdg-open'):
+            if _iscommand(command):
+                _controllers[command] = Controller(command)
+
+    def get():
+        controllers_map = {
+            'gnome': 'gnome-open',
+            'kde': 'kde-open',
+            'xfce': 'exo-open',
+        }
+
+        desktop_environment = detect_desktop_environment()
+
+        try:
+            controller_name = controllers_map[desktop_environment]
+            return _controllers[controller_name].open
+
+        except KeyError:
+            if _controllers.has_key('xdg-open'):
+                return _controllers['xdg-open'].open
+            else:
+                return webbrowser.open
+
+
+    if os.environ.get("DISPLAY"):
+        register_X_controllers()
+    _open = get()
+
+
+def open(filename):
+    '''Open a file or an URL in the registered default application.'''
+
+    return _open(filename)
+
+
+def _fix_addersses(**kwargs):
+    for headername in ('address', 'to', 'cc', 'bcc'):
+        try:
+            headervalue = kwargs[headername]
+            if not headervalue:
+                del kwargs[headername]
+                continue
+            elif not isinstance(headervalue, basestring):
+                # assume it is a sequence
+                headervalue = ','.join(headervalue)
+
+        except KeyError:
+            pass
+        except TypeError:
+            raise TypeError('string or sequence expected for "%s", '
+                            '%s found' % (headername,
+                                          type(headervalue).__name__))
+        else:
+            translation_map = {'%': '%25', '&': '%26', '?': '%3F'}
+            for char, replacement in translation_map.items():
+                headervalue = headervalue.replace(char, replacement)
+            kwargs[headername] = headervalue
+
+    return kwargs
+
+
+def mailto_format(**kwargs):
+    # @TODO: implement utf8 option
+
+    kwargs = _fix_addersses(**kwargs)
+    parts = []
+    for headername in ('to', 'cc', 'bcc', 'subject', 'body', 'attach'):
+        if kwargs.has_key(headername):
+            headervalue = kwargs[headername]
+            if not headervalue:
+                continue
+            if headername in ('address', 'to', 'cc', 'bcc'):
+                parts.append('%s=%s' % (headername, headervalue))
+            else:
+                headervalue = encode_rfc2231(headervalue) # @TODO: check
+                parts.append('%s=%s' % (headername, headervalue))
+
+    mailto_string = 'mailto:%s' % kwargs.get('address', '')
+    if parts:
+        mailto_string = '%s?%s' % (mailto_string, '&'.join(parts))
+
+    return mailto_string
+
+
+def mailto(address, to=None, cc=None, bcc=None, subject=None, body=None,
+           attach=None):
+    '''Send an e-mail using the user's preferred composer.
+
+    Open the user's preferred e-mail composer in order to send a mail to
+    address(es) that must follow the syntax of RFC822. Multiple addresses
+    may be provided (for address, cc and bcc parameters) as separate
+    arguments.
+
+    All parameters provided are used to prefill corresponding fields in
+    the user's e-mail composer. The user will have the opportunity to
+    change any of this information before actually sending the e-mail.
+
+    address - specify the destination recipient
+    cc      - specify a recipient to be copied on the e-mail
+    bcc     - specify a recipient to be blindly copied on the e-mail
+    subject - specify a subject for the e-mail
+    body    - specify a body for the e-mail. Since the user will be able
+              to make changes before actually sending the e-mail, this
+              can be used to provide the user with a template for the
+              e-mail text may contain linebreaks
+    attach  - specify an attachment for the e-mail. file must point to
+              an existing file
+
+    '''
+
+    mailto_string = mailto_format(**locals())
+    return open(mailto_string)
+
+
+if __name__ == '__main__':
+    from optparse import OptionParser
+
+    version = '%%prog %s' % __version__
+    usage = (
+        '\n\n%prog FILENAME [FILENAME(s)] -- for opening files'
+        '\n\n%prog -m [OPTIONS] ADDRESS [ADDRESS(es)] -- for sending e-mails'
+    )
+
+    parser = OptionParser(usage=usage, version=version, description=__doc__)
+    parser.add_option('-m', '--mailto', dest='mailto_mode', default=False,
+                      action='store_true', help='set mailto mode. '
+                      'If not set any other option is ignored')
+    parser.add_option('--cc', dest='cc', help='specify a recipient to be '
+                      'copied on the e-mail')
+    parser.add_option('--bcc', dest='bcc', help='specify a recipient to be '
+                      'blindly copied on the e-mail')
+    parser.add_option('--subject', dest='subject',
+                      help='specify a subject for the e-mail')
+    parser.add_option('--body', dest='body', help='specify a body for the '
+                      'e-mail. Since the user will be able to make changes '
+                      'before actually sending the e-mail, this can be used '
+                      'to provide the user with a template for the e-mail '
+                      'text may contain linebreaks')
+    parser.add_option('--attach', dest='attach', help='specify an attachment '
+                      'for the e-mail. file must point to an existing file')
+
+    (options, args) = parser.parse_args()
+
+    if not args:
+        parser.print_usage()
+        parser.exit(1)
+
+    if options.mailto_mode:
+        if not mailto(args, None, options.cc, options.bcc, options.subject,
+                      options.body, options.attach):
+            sys.exit('Unable to open the e-mail client')
+    else:
+        for name in ('cc', 'bcc', 'subject', 'body', 'attach'):
+            if getattr(options, name):
+                parser.error('The "cc", "bcc", "subject", "body" and "attach" '
+                             'options are only accepten in mailto mode')
+        success = False
+        for arg in args:
+            if not open(arg):
+                print 'Unable to open "%s"' % arg
+            else:
+                success = True
+        sys.exit(success)

=== added file 'openlp/core/lib/mailto/mailto.INFO'
--- openlp/core/lib/mailto/mailto.INFO	1970-01-01 00:00:00 +0000
+++ openlp/core/lib/mailto/mailto.INFO	2010-12-11 00:46:10 +0000
@@ -0,0 +1,4 @@
+Cross-platform startfile and mailto functions
+Author:  Antonio Valentino
+License: PSF license (http://docs.python.org/license.html)
+Source:  http://code.activestate.com/recipes/511443-cross-platform-startfile-and-mailto-functions/

=== modified file 'openlp/core/resources.py'
--- openlp/core/resources.py	2010-11-19 22:26:15 +0000
+++ openlp/core/resources.py	2010-12-11 00:46:10 +0000
@@ -59162,6 +59162,56 @@
 \xea\x24\x0f\x93\xcc\x00\x6a\x00\x7e\x03\x38\x00\x50\x3d\x94\x6f\
 \x8c\xf9\xf3\x17\xb1\x57\xd8\xfd\x23\x30\x24\x2d\x00\x00\x00\x00\
 \x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x02\xf3\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x03\x76\x00\x00\x03\x76\
+\x01\x7d\xd5\x82\xcc\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x02\x70\x49\x44\
+\x41\x54\x78\xda\xc5\x93\x4f\x48\xd4\x41\x14\xc7\xbf\x33\xf3\x1b\
+\x65\x5d\x77\x5b\x93\x4c\xd7\xe4\xb7\x89\xfd\x73\x65\x4b\x5a\x09\
+\x14\x42\x4b\x44\x89\xa0\x5b\x97\x8e\x49\xd0\xd1\x0e\x66\x81\xd4\
+\x12\x5e\x2a\x08\xa1\x93\xa7\xa0\x4b\x88\x10\x21\x78\xf1\x18\x15\
+\x91\x88\x84\x88\x10\xb9\xbb\xb5\x89\xb4\xb4\xb9\xbb\xba\xfb\xdb\
+\xdf\xfe\xe6\x35\x8e\x74\xed\xe2\xa1\x0f\x3c\x18\xbe\xbc\xef\x7b\
+\x6f\xde\x30\x8c\x88\x70\x10\x38\x0e\x08\x6b\x68\x68\xe8\xb3\x6d\
+\xbb\x05\x9a\x10\x23\xf9\x9b\x98\x0b\x8d\xa8\xa9\xb7\x94\xbb\x63\
+\xce\x41\x22\x99\x67\xfb\x3a\x97\x7e\xf9\x57\x4f\xa5\x52\x9b\x98\
+\x98\x98\x98\xfa\x99\x2b\x52\x36\x57\xa0\x4c\x4f\x8c\x8a\xbb\x0e\
+\x95\x1d\x97\x86\x1f\xbd\x23\xa5\x14\x79\xe5\x12\xfd\xe8\x8b\x93\
+\xeb\xba\x26\x2e\x3d\x78\xab\x73\xca\x94\x2f\x96\x28\x91\x48\x3c\
+\xb1\x38\xe7\x70\x1c\x17\x96\xc5\x51\x73\x3f\x01\x80\xc0\x18\x70\
+\xf7\x5a\x07\x88\x08\xc4\x05\x02\x93\x09\x73\xd6\x05\x71\xe7\xca\
+\x71\x93\x5f\x55\xb4\xa7\x29\x71\xb2\xff\xc6\xcd\x97\x2b\xac\x6b\
+\x28\x76\x04\xc1\xce\x53\xba\x90\x80\xb4\x2c\x44\x9a\xea\x8c\x89\
+\x00\x88\x48\x3b\x0a\x3b\x25\xcc\xbe\xcf\x42\x11\x47\xd0\x07\xdc\
+\x9a\x59\x03\xf3\x4a\x19\x1e\xc6\xf7\xe4\x8b\xdb\x31\x34\x06\x6a\
+\x20\x04\x87\x25\x84\x99\xc0\x98\x09\x70\x5c\x0f\xa5\x8a\x87\x6a\
+\xb5\x8a\x1d\x57\x21\xe0\xb7\xe0\x97\x0c\x33\xa3\x9d\x08\xf3\xcd\
+\x34\x67\x9a\x5a\x29\xcc\x15\x7e\x65\xb3\xe0\x0c\x7a\x44\xc7\x18\
+\xca\xe5\x12\xb4\x0c\x06\xa5\x43\xa0\xa9\x9e\x43\x0a\x86\xaa\xa7\
+\x4c\x3e\x48\x29\x2b\xfa\x2d\x19\xd9\x1d\xbe\x88\xd0\xdc\x3c\x96\
+\x97\x97\x30\x37\xf7\xd5\x14\x88\xc7\xe3\x48\xa7\xd3\x90\x52\xe2\
+\xec\xb9\x6e\x1c\x6b\xb3\x31\x10\x3d\x0c\xb7\xea\x81\x72\x39\xd0\
+\xf5\x11\x9c\x39\xd1\x69\x5b\xeb\x76\x7b\xfa\xea\xb3\xe7\x90\xfe\
+\x3a\x0c\x0e\x0e\x9a\xd1\x35\xc6\x58\xa9\x54\x50\xd5\x86\x92\x53\
+\x31\x5d\xbd\xbd\x50\x04\xd9\xd8\x88\xda\xd7\x8b\x58\x9b\x7e\x9a\
+\xb4\x88\x31\x12\x3e\x1f\x04\x67\x78\xb3\xb0\x80\x42\xa1\x80\xe6\
+\xe6\x66\x6c\x6d\x6d\x81\x73\x0e\x4b\xd6\x22\xa7\x3b\xfa\x03\x87\
+\x30\x70\x79\x68\x7f\x4f\x3a\x84\xac\x81\xd2\x5e\x2b\x43\xad\x91\
+\x91\xa9\x0f\x98\x1d\xeb\x41\x5b\xac\x1f\xdd\x91\x20\xc8\xf3\x50\
+\xf9\xf4\x11\xe8\x8e\xc3\x71\x15\x96\x37\xb6\x71\xba\xa5\x4e\xeb\
+\x0a\xe2\xf3\x12\x0a\x5d\xe7\x31\x3a\xbd\x02\x5b\xb5\x46\x78\x9b\
+\xd8\x4c\x2d\x4e\xf6\x22\xa4\xb7\x3b\xf9\x6a\x1d\x7b\x50\xd5\x45\
+\x7e\x7c\x0c\x00\x33\x23\x3f\x9e\x4f\x9a\xe7\xf5\x49\x80\x1e\x8e\
+\xe3\x68\xc8\x87\x85\x7b\x17\xd0\x42\xe9\x0d\x16\x0e\x87\x7b\xa3\
+\xd1\xa8\x0d\x8d\xaa\x6f\x0d\xf2\x62\x26\x0f\x4d\x87\xe7\x1e\xfa\
+\x22\xe4\xf6\xbf\xf4\xd5\xd5\xd5\xd4\xff\xff\x8d\x7f\x00\xba\xc6\
+\x35\x77\x1f\xae\xe5\xaf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
+\x60\x82\
 \x00\x00\x02\x74\
 \x89\
 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
@@ -59835,6 +59885,11 @@
 \x0e\x36\xec\xe7\
 \x00\x67\
 \x00\x65\x00\x6e\x00\x65\x00\x72\x00\x61\x00\x6c\x00\x5f\x00\x61\x00\x64\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x11\
+\x06\x44\xfc\x07\
+\x00\x67\
+\x00\x65\x00\x6e\x00\x65\x00\x72\x00\x61\x00\x6c\x00\x5f\x00\x65\x00\x6d\x00\x61\x00\x69\x00\x6c\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
 \x00\x12\
 \x0d\x2c\x6d\x87\
 \x00\x67\
@@ -59857,32 +59912,33 @@
 
 qt_resource_struct = "\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\x01\
-\x00\x00\x00\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x6b\
-\x00\x00\x00\x3a\x00\x02\x00\x00\x00\x04\x00\x00\x00\x67\
-\x00\x00\x00\xf4\x00\x02\x00\x00\x00\x03\x00\x00\x00\x64\
-\x00\x00\x00\x5e\x00\x02\x00\x00\x00\x10\x00\x00\x00\x54\
-\x00\x00\x00\x4e\x00\x02\x00\x00\x00\x02\x00\x00\x00\x52\
-\x00\x00\x00\x84\x00\x02\x00\x00\x00\x09\x00\x00\x00\x49\
-\x00\x00\x00\x14\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x3e\
-\x00\x00\x00\xa6\x00\x02\x00\x00\x00\x03\x00\x00\x00\x3b\
-\x00\x00\x00\xe2\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x2f\
-\x00\x00\x00\x6e\x00\x02\x00\x00\x00\x03\x00\x00\x00\x2c\
-\x00\x00\x01\x04\x00\x02\x00\x00\x00\x03\x00\x00\x00\x29\
-\x00\x00\x00\xcc\x00\x02\x00\x00\x00\x08\x00\x00\x00\x21\
-\x00\x00\x00\x26\x00\x02\x00\x00\x00\x04\x00\x00\x00\x1d\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x13\
+\x00\x00\x00\x98\x00\x02\x00\x00\x00\x06\x00\x00\x00\x6c\
+\x00\x00\x00\x3a\x00\x02\x00\x00\x00\x04\x00\x00\x00\x68\
+\x00\x00\x00\xf4\x00\x02\x00\x00\x00\x03\x00\x00\x00\x65\
+\x00\x00\x00\x5e\x00\x02\x00\x00\x00\x10\x00\x00\x00\x55\
+\x00\x00\x00\x4e\x00\x02\x00\x00\x00\x02\x00\x00\x00\x53\
+\x00\x00\x00\x84\x00\x02\x00\x00\x00\x09\x00\x00\x00\x4a\
+\x00\x00\x00\x14\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x3f\
+\x00\x00\x00\xa6\x00\x02\x00\x00\x00\x03\x00\x00\x00\x3c\
+\x00\x00\x00\xe2\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x30\
+\x00\x00\x00\x6e\x00\x02\x00\x00\x00\x03\x00\x00\x00\x2d\
+\x00\x00\x01\x04\x00\x02\x00\x00\x00\x03\x00\x00\x00\x2a\
+\x00\x00\x00\xcc\x00\x02\x00\x00\x00\x08\x00\x00\x00\x22\
+\x00\x00\x00\x26\x00\x02\x00\x00\x00\x04\x00\x00\x00\x1e\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x13\
 \x00\x00\x00\xb8\x00\x02\x00\x00\x00\x03\x00\x00\x00\x10\
 \x00\x00\x06\x26\x00\x00\x00\x00\x00\x01\x00\x08\x78\xb3\
 \x00\x00\x05\xc2\x00\x00\x00\x00\x00\x01\x00\x03\x36\xef\
 \x00\x00\x05\xf4\x00\x00\x00\x00\x00\x01\x00\x05\xd7\xd1\
 \x00\x00\x10\x3e\x00\x00\x00\x00\x00\x01\x00\x0e\x5b\xe4\
 \x00\x00\x0f\xee\x00\x00\x00\x00\x00\x01\x00\x0e\x56\x88\
-\x00\x00\x10\xd8\x00\x00\x00\x00\x00\x01\x00\x0e\x65\xa7\
+\x00\x00\x11\x00\x00\x00\x00\x00\x00\x01\x00\x0e\x68\x9e\
+\x00\x00\x10\xae\x00\x00\x00\x00\x00\x01\x00\x0e\x63\x2f\
 \x00\x00\x0f\xc4\x00\x00\x00\x00\x00\x01\x00\x0e\x53\xea\
-\x00\x00\x11\x04\x00\x00\x00\x00\x00\x01\x00\x0e\x68\xda\
-\x00\x00\x10\xae\x00\x00\x00\x00\x00\x01\x00\x0e\x63\x2f\
+\x00\x00\x11\x2c\x00\x00\x00\x00\x00\x01\x00\x0e\x6b\xd1\
+\x00\x00\x10\xd6\x00\x00\x00\x00\x00\x01\x00\x0e\x66\x26\
 \x00\x00\x10\x14\x00\x00\x00\x00\x00\x01\x00\x0e\x59\x62\
-\x00\x00\x11\x2a\x00\x00\x00\x00\x00\x01\x00\x0e\x6b\xae\
+\x00\x00\x11\x52\x00\x00\x00\x00\x00\x01\x00\x0e\x6e\xa5\
 \x00\x00\x10\x8a\x00\x00\x00\x00\x00\x01\x00\x0e\x60\x32\
 \x00\x00\x10\x64\x00\x00\x00\x00\x00\x01\x00\x0e\x5e\x1b\
 \x00\x00\x0d\x50\x00\x00\x00\x00\x00\x01\x00\x0e\x2d\xb0\

=== modified file 'openlp/core/ui/exceptiondialog.py'
--- openlp/core/ui/exceptiondialog.py	2010-09-19 08:47:00 +0000
+++ openlp/core/ui/exceptiondialog.py	2010-12-11 00:46:10 +0000
@@ -26,7 +26,7 @@
 
 from PyQt4 import QtCore, QtGui
 
-from openlp.core.lib import translate
+from openlp.core.lib import translate, build_icon
 
 class Ui_ExceptionDialog(object):
     def setupUi(self, exceptionDialog):
@@ -63,12 +63,26 @@
         self.exceptionButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Close)
         self.exceptionButtonBox.setObjectName(u'exceptionButtonBox')
         self.exceptionLayout.addWidget(self.exceptionButtonBox)
-
+        self.saveReportButton = QtGui.QPushButton(exceptionDialog)
+        self.saveReportButton.setIcon(build_icon(u':/general/general_save.png'))
+        self.saveReportButton.setObjectName(u'saveReportButton')
+        self.exceptionButtonBox.addButton(self.saveReportButton,
+            QtGui.QDialogButtonBox.ActionRole)
+        self.sendReportButton = QtGui.QPushButton(exceptionDialog)
+        self.sendReportButton.setIcon(build_icon(
+            u':/general/general_email.png'))
+        self.sendReportButton.setObjectName(u'sendReportButton')
+        self.exceptionButtonBox.addButton(self.sendReportButton,
+            QtGui.QDialogButtonBox.ActionRole)
         self.retranslateUi(exceptionDialog)
         QtCore.QObject.connect(self.exceptionButtonBox,
             QtCore.SIGNAL(u'accepted()'), exceptionDialog.accept)
         QtCore.QObject.connect(self.exceptionButtonBox,
             QtCore.SIGNAL(u'rejected()'), exceptionDialog.reject)
+        QtCore.QObject.connect(self.saveReportButton,
+            QtCore.SIGNAL(u'pressed()'), self.onSaveReportButtonPressed)
+        QtCore.QObject.connect(self.sendReportButton,
+            QtCore.SIGNAL(u'pressed()'), self.onSendReportButtonPressed)
         QtCore.QMetaObject.connectSlotsByName(exceptionDialog)
 
     def retranslateUi(self, exceptionDialog):
@@ -80,3 +94,7 @@
             'developers, so please e-mail it to bugs@xxxxxxxxxx, along with a '
             'detailed description of what you were doing when the problem '
             'occurred.'))
+        self.saveReportButton.setText(translate('OpenLP.ExceptionDialog',
+            'Save Report to File'))
+        self.sendReportButton.setText(translate('OpenLP.ExceptionDialog',
+            'Send Report Mail'))

=== modified file 'openlp/core/ui/exceptionform.py'
--- openlp/core/ui/exceptionform.py	2010-10-21 15:31:22 +0000
+++ openlp/core/ui/exceptionform.py	2010-12-11 00:46:10 +0000
@@ -24,7 +24,23 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 
-from PyQt4 import QtGui
+import os
+import platform
+
+import sqlalchemy
+import BeautifulSoup
+import enchant
+import chardet
+try:
+    import sqlite
+    sqlite_version = sqlite.version
+except ImportError:
+    sqlite_version = u'-'
+
+from lxml import etree
+from PyQt4 import Qt, QtCore, QtGui
+
+from openlp.core.lib import translate, SettingsManager, mailto
 
 from exceptiondialog import Ui_ExceptionDialog
 
@@ -35,3 +51,71 @@
     def __init__(self, parent):
         QtGui.QDialog.__init__(self, parent)
         self.setupUi(self)
+        self.settingsSection = u'crashreport'
+        #TODO: Icons
+
+    def _createReport(self):
+        openlp_version = self.parent().applicationVersion[u'full']
+        traceback = unicode(self.exceptionTextEdit.toPlainText()) 
+        system = unicode(translate('OpenLP.ExceptionForm',
+            'Platform: %s\n')) % (platform.platform())
+        libraries = unicode(translate('OpenLP.ExceptionForm',
+            'Python: %s\n'
+            'PyQt4: %s\n'
+            'Qt4: %s\n'
+            'SQLAlchemy: %s\n'
+            'lxml: %s\n'
+            'BeautifulSoup: %s\n'
+            'PyEnchant: %s\n'
+            'Chardet: %s\n'
+            'PySQLite: %s\n')) % (platform.python_version(),
+             Qt.PYQT_VERSION_STR, Qt.qVersion(), sqlalchemy.__version__,
+             etree.__version__, BeautifulSoup.__version__ , enchant.__version__,
+             chardet.__version__, sqlite_version)
+        return (openlp_version, traceback, system, libraries)
+ 
+    def onSaveReportButtonPressed(self):
+        """
+        Saving exception log and system informations to a file.
+        """
+        report = unicode(translate('OpenLP.ExceptionForm',
+            '**OpenLP Bug Report**\n'
+            'Version: %s\n\n'
+            '--- Exception Traceback ---\n%s\n'
+            '--- System information ---\n%s\n'
+            '--- Library Versions ---\n%s\n'))
+        filename = QtGui.QFileDialog.getSaveFileName(self,
+            translate('OpenLP.ExceptionForm', 'Save Crash Report'),
+            SettingsManager.get_last_dir(self.settingsSection),
+            translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
+        if filename:
+            filename = unicode(QtCore.QDir.toNativeSeparators(filename))
+            SettingsManager.set_last_dir(self.settingsSection, os.path.dirname(
+                filename))
+            report = report % self._createReport()
+            try:
+                file = open(filename, u'w')
+                try:
+                    file.write(report)
+                except UnicodeError:
+                    file.close()
+                    file = open(filename, u'wb')
+                    file.write(report.encode(u'utf-8'))
+                file.close()
+            except IOError:
+                log.exception(u'Failed to write crash report')
+
+    def onSendReportButtonPressed(self):
+        """
+        Opening systems default email client and inserting exception log and
+        system informations.
+        """
+        email_body = unicode(translate('OpenLP.ExceptionForm',
+            '*OpenLP Bug Report*\n'
+            'Version: %s\n\n'
+            '--- Please enter the report below this line. ---\n\n\n'
+            '--- Exception Traceback ---\n%s\n'
+            '--- System information ---\n%s\n'
+            '--- Library Versions ---\n%s\n'))
+        mailto.mailto(address=u'bugs@xxxxxxxxxx', subject=u'OpenLP Bug Report',
+            body=email_body % self._createReport())

=== added file 'resources/images/general_email.png'
Binary files resources/images/general_email.png	1970-01-01 00:00:00 +0000 and resources/images/general_email.png	2010-12-11 00:46:10 +0000 differ
=== modified file 'resources/images/openlp-2.qrc'
--- resources/images/openlp-2.qrc	2010-11-19 22:26:15 +0000
+++ resources/images/openlp-2.qrc	2010-12-11 00:46:10 +0000
@@ -39,6 +39,7 @@
     <file>general_new.png</file>
     <file>general_open.png</file>
     <file>general_save.png</file>
+    <file>general_email.png</file>
   </qresource>
   <qresource prefix="slides">
     <file>slide_close.png</file>


Follow ups