← 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:
  Raoul Snyman (raoul-snyman)
  Tim Bentley (trb143)


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.

The bug report is prefilled with the traceback, the openlp version, platform informations (on Linux also KDE or GNOME), the and the versions of all used libraries.
-- 
https://code.launchpad.net/~m2j/openlp/work/+merge/43452
Your team OpenLP Core is subscribed to branch lp:openlp.
=== added directory 'openlp/core/lib/mailto'
=== added file 'openlp/core/lib/mailto/LICENSE'
--- openlp/core/lib/mailto/LICENSE	1970-01-01 00:00:00 +0000
+++ openlp/core/lib/mailto/LICENSE	2010-12-11 23:57:54 +0000
@@ -0,0 +1,38 @@
+PSF LICENSE AGREEMENT FOR PYTHON 2.7.1
+
+ 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"),
+    and the Individual or Organization ("Licensee") accessing and otherwise
+    using Python 2.7.1 software in source or binary form and its associated
+    documentation.
+ 2. Subject to the terms and conditions of this License Agreement, PSF hereby
+    grants Licensee a nonexclusive, royalty-free, world-wide license to
+    reproduce, analyze, test, perform and/or display publicly, prepare
+    derivative works, distribute, and otherwise use Python 2.7.1 alone or in any
+    derivative version, provided, however, that PSF's License Agreement and
+    PSF's notice of copyright, i.e., "Copyright (c) 2001-2010 Python Software
+    Foundation; All Rights Reserved" are retained in Python 2.7.1 alone or in
+    any derivative version prepared by Licensee.
+ 3. In the event Licensee prepares a derivative work that is based on or
+    incorporates Python 2.7.1 or any part thereof, and wants to make the
+    derivative work available to others as provided herein, then Licensee hereby
+    agrees to include in any such work a brief summary of the changes made to
+    Python 2.7.1.
+ 4. PSF is making Python 2.7.1 available to Licensee on an "AS IS" basis. PSF
+    MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
+    EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION
+    OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
+    THE USE OF PYTHON 2.7.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+ 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.1 FOR
+    ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
+    MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.1, OR ANY DERIVATIVE
+    THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+ 6. This License Agreement will automatically terminate upon a material breach
+    of its terms and conditions.
+ 7. Nothing in this License Agreement shall be deemed to create any relationship
+    of agency, partnership, or joint venture between PSF and Licensee. This
+    License Agreement does not grant permission to use PSF trademarks or trade
+    name in a trademark sense to endorse or promote products or services of
+    Licensee, or any third party.
+ 8. By copying, installing or otherwise using Python 2.7.1, Licensee agrees to
+    be bound by the terms and conditions of this License Agreement.
+

=== 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 23:57:54 +0000
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# Utilities for opening files or URLs in the registered default application   #
+# and for sending e-mail using the user's preferred composer.                 #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2007 Antonio Valentino                                        #
+# All rights reserved.                                                        #
+# --------------------------------------------------------------------------- #
+# This program offered under the PSF License as published by the Python       #
+# Software Foundation.                                                        #
+#                                                                             #
+# The license text can be found at http://docs.python.org/license.html        #
+#                                                                             #
+# This code is taken from: http://code.activestate.com/recipes/511443         #
+# Modified for use in OpenLP                                                  #
+###############################################################################
+
+__version__ = u'1.1'
+__all__ = [u'open', u'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] == u'win':
+            closefds = False
+            startupinfo = subprocess.STARTUPINFO()
+            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+        else:
+            closefds = True
+            startupinfo = None
+
+        if (os.environ.get(u'DISPLAY') or sys.platform[:3] == u'win' or \
+                sys.platform == u'darwin'):
+            inout = file(os.devnull, u'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, u'setsid', None)
+        if not setsid:
+            setsid = getattr(os, u'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, u'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] == u'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[u'windows-default'] = Start(u'start')
+    _open = _controllers[u'windows-default'].open
+
+
+# Platform support for MacOS
+elif sys.platform == u'darwin':
+    _controllers[u'open']= Controller(u'open')
+    _open = _controllers[u'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=u'kfmclient'):
+            super(KfmClient, self).__init__(kfmclient, u'exec')
+            self.kde_version = self.detect_kde_version()
+
+        def detect_kde_version(self):
+            kde_version = None
+            try:
+                info = commands.getoutput(u'kfmclient --version')
+
+                for line in info.splitlines():
+                    if line.startswith(u'KDE'):
+                        kde_version = line.split(u':')[-1].strip()
+                        break
+            except (OSError, RuntimeError):
+                pass
+
+            return kde_version
+
+        def fixreturncode(self, returncode):
+            if returncode is not None and self.kde_version > u'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 = u'generic'
+
+        if os.environ.get(u'KDE_FULL_SESSION') == u'true':
+            desktop_environment = u'kde'
+        elif os.environ.get(u'GNOME_DESKTOP_SESSION_ID'):
+            desktop_environment = u'gnome'
+        else:
+            try:
+                info = commands.getoutput(u'xprop -root _DT_SAVE_MODE')
+                if u' = "xfce4"' in info:
+                    desktop_environment = u'xfce'
+            except (OSError, RuntimeError):
+                pass
+
+        return desktop_environment
+
+
+    def register_X_controllers():
+        if _iscommand(u'kfmclient'):
+            _controllers[u'kde-open'] = KfmClient()
+
+        for command in (u'gnome-open', u'exo-open', u'xdg-open'):
+            if _iscommand(command):
+                _controllers[command] = Controller(command)
+
+
+    def get():
+        controllers_map = {
+            u'gnome': u'gnome-open',
+            u'kde': u'kde-open',
+            u'xfce': u'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(u'xdg-open'):
+                return _controllers[u'xdg-open'].open
+            else:
+                return webbrowser.open
+
+    if os.environ.get(u'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 (u'address', u'to', u'cc', u'bcc'):
+        try:
+            headervalue = kwargs[headername]
+            if not headervalue:
+                del kwargs[headername]
+                continue
+            elif not isinstance(headervalue, basestring):
+                # assume it is a sequence
+                headervalue = u','.join(headervalue)
+        except KeyError:
+            pass
+        except TypeError:
+            raise TypeError(u'string or sequence expected for "%s", %s '
+                u'found' % (headername, type(headervalue).__name__))
+        else:
+            translation_map = {u'%': u'%25', u'&': u'%26', u'?': u'%3F'}
+            for char, replacement in translation_map.items():
+                headervalue = headervalue.replace(char, replacement)
+            kwargs[headername] = headervalue
+
+    return kwargs
+
+
+def mailto_format(**kwargs):
+    """
+    Compile mailto string from call parameters
+    """
+    # @TODO: implement utf8 option
+
+    kwargs = _fix_addersses(**kwargs)
+    parts = []
+    for headername in (u'to', u'cc', u'bcc', u'subject', u'body', u'attach'):
+        if kwargs.has_key(headername):
+            headervalue = kwargs[headername]
+            if not headervalue:
+                continue
+            if headername in (u'address', u'to', u'cc', u'bcc'):
+                parts.append(u'%s=%s' % (headername, headervalue))
+            else:
+                headervalue = encode_rfc2231(headervalue) # @TODO: check
+                parts.append(u'%s=%s' % (headername, headervalue))
+
+    mailto_string = u'mailto:%s' % kwargs.get(u'address', '')
+    if parts:
+        mailto_string = u'%s?%s' % (mailto_string, u'&'.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)
+

=== modified file 'openlp/core/resources.py'
--- openlp/core/resources.py	2010-11-19 22:26:15 +0000
+++ openlp/core/resources.py	2010-12-11 23:57:54 +0000
@@ -59162,6 +59162,61 @@
 \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\x03\x41\
+\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\x03\x00\x00\x00\x28\x2d\x0f\x53\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\x42\x28\
+\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xda\x01\x16\x02\x14\
+\x1d\x79\x4f\xdb\xf8\x00\x00\x01\xa7\x50\x4c\x54\x45\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x02\x02\x02\x09\x09\x09\x09\x09\x09\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x02\x02\x02\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x02\x00\x00\x00\x30\x30\
+\x30\x00\x00\x00\x00\x00\x00\x3d\x3d\x3d\x00\x00\x00\x9a\x9a\x9a\
+\x10\x10\x10\x9b\x9b\x9b\x5d\x5e\x5e\x35\x36\x36\x80\x80\x81\xb0\
+\xae\xaf\xc5\xc5\xc6\xe9\xea\xea\xf1\xf1\xf1\x50\x52\x53\x51\x52\
+\x53\xfc\xfd\xff\xfd\xff\xff\xfe\xfe\xfe\x18\x18\x1a\x1c\x47\x9c\
+\x1d\x1e\x1f\x1f\x49\x9e\x2f\x56\xa4\x40\x70\xc3\x43\x71\xc4\x51\
+\x7d\xc9\x57\x8d\xe5\x59\x90\xe7\x67\x9d\xee\x74\x76\x78\x88\x9e\
+\xc8\x8a\x8b\x8d\x8a\x9f\xc9\x8d\x8d\x8e\x8d\x8d\x91\x8e\x8e\x8f\
+\x8e\xa3\xca\x90\x91\x92\x94\x95\x96\x96\x95\x97\x97\x99\x9b\x99\
+\x9a\x9b\x99\x9b\x9b\x9c\x9e\xa0\xaa\xab\xae\xcf\xd0\xd3\xd3\xd5\
+\xd9\xd6\xd5\xd2\xda\xdd\xdf\xdb\xde\xe1\xdd\xe1\xe3\xe0\xe2\xe5\
+\xe0\xe3\xe5\xe1\xe2\xe6\xe1\xe3\xe6\xe2\xe3\xe8\xe3\xe5\xe7\xe6\
+\xe8\xea\xe8\xe9\xea\xe8\xea\xec\xe8\xea\xed\xe8\xeb\xed\xe9\xea\
+\xee\xe9\xeb\xec\xea\xe8\xe4\xea\xeb\xee\xea\xed\xf3\xeb\xed\xee\
+\xeb\xee\xf5\xec\xed\xef\xec\xee\xef\xec\xef\xf5\xed\xee\xef\xed\
+\xef\xf2\xef\xf0\xf1\xef\xf1\xf1\xf0\xf2\xf2\xf1\xf1\xf1\xf1\xf2\
+\xf3\xf2\xf2\xf2\xf2\xf4\xf6\xf3\xf5\xf6\xf3\xf5\xf9\xf4\xf3\xf0\
+\xf4\xf4\xf5\xf4\xf5\xf6\xf4\xf5\xf7\xf4\xf6\xf7\xf5\xf3\xf1\xf5\
+\xf4\xf2\xf5\xf5\xf5\xf5\xf6\xf7\xf5\xf7\xf9\xf6\xf3\xf0\xf6\xf6\
+\xf7\xf6\xf8\xfa\xf7\xf7\xf8\xf8\xf9\xfa\xf8\xfa\xfc\xf9\xf7\xf4\
+\xf9\xfa\xfb\xf9\xfb\xfc\xfa\xfb\xfc\xfb\xfb\xfc\xfb\xfc\xfd\xfb\
+\xfd\xfd\xfb\xfd\xff\xfc\xfc\xfd\xfc\xfd\xff\xfd\xfd\xfe\xfd\xfe\
+\xff\xff\xff\xff\xd0\x32\x98\xa6\x00\x00\x00\x2f\x74\x52\x4e\x53\
+\x00\x01\x02\x03\x06\x08\x0d\x0f\x13\x1e\x24\x30\x33\x38\x3a\x3b\
+\x3c\x3f\x40\x46\x49\x4a\x4d\x53\x5b\x5d\x61\x63\x69\x6f\x6f\x73\
+\x91\x93\x96\x9f\xaa\xb8\xce\xde\xf2\xf7\xfe\xfe\xfe\xfe\xfe\xc1\
+\x92\xd1\x86\x00\x00\x00\x01\x62\x4b\x47\x44\x8c\x6c\x0b\xd2\x43\
+\x00\x00\x00\xd8\x49\x44\x41\x54\x18\xd3\x63\x60\x00\x02\x36\x05\
+\x25\x76\x06\x24\xc0\x22\xdd\xd3\x23\xc7\x8a\xe0\x33\x09\x68\xe8\
+\xe9\x69\x8a\x30\xc3\x05\xb8\xd4\x5a\x6a\x6b\x9b\xd5\xb9\x19\xa1\
+\x7c\x0e\xe5\xd6\x6c\x20\xa8\x53\xe5\x84\xf0\xf9\x14\xeb\x75\x13\
+\x80\x40\xa7\x47\x45\x84\x5f\x40\x80\x87\x41\xd4\x5e\x4b\xbf\x32\
+\x2c\xac\xd2\x50\xdb\xca\xa7\xa0\x34\x9f\x97\x41\x28\x2a\xc8\x35\
+\x3a\x20\x20\xc2\x23\x38\xd6\xc2\xd2\x3c\x5e\x90\x41\x38\x37\xcd\
+\x3b\xd0\xdf\xd7\xcf\x2b\xb2\xca\xc4\xcc\x34\x51\x98\x41\xac\xdd\
+\xc6\xc5\xc9\x33\x24\x34\x26\xae\xc1\xc0\xd8\x28\x45\x8c\x41\xbc\
+\xa6\xb8\xb8\x24\x3b\x35\x39\x29\xb9\xcc\xd6\xd1\xba\x46\x9c\x41\
+\xa2\x29\x3d\x3d\xa3\x22\x2f\xaf\xba\xa8\x28\x33\x27\xab\x4d\x82\
+\x41\xaa\x33\x3b\xbb\xd1\xc1\xcd\xdd\xce\xcd\x39\x3c\x3b\xbb\x5b\
+\x8a\x41\xa6\xa7\xb0\xb0\xbc\x03\x04\xba\xaa\x0b\x0b\x7b\x64\x18\
+\x64\x7b\x50\x80\x2c\x83\xa4\x3c\x0a\x90\x04\x00\x22\x75\x40\x01\
+\xd9\xb4\x9c\x69\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 +59890,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 +59917,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\xec\
+\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\x6c\x1f\
+\x00\x00\x10\xd6\x00\x00\x00\x00\x00\x01\x00\x0e\x66\x74\
 \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\xf3\
 \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 23:57:54 +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,27 @@
         self.exceptionButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Close)
         self.exceptionButtonBox.setObjectName(u'exceptionButtonBox')
         self.exceptionLayout.addWidget(self.exceptionButtonBox)
+        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.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.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.sendReportButton,
+            QtCore.SIGNAL(u'pressed()'), self.onSendReportButtonPressed)
+        QtCore.QObject.connect(self.saveReportButton,
+            QtCore.SIGNAL(u'pressed()'), self.onSaveReportButtonPressed)
         QtCore.QMetaObject.connectSlotsByName(exceptionDialog)
 
     def retranslateUi(self, exceptionDialog):
@@ -80,3 +95,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.sendReportButton.setText(translate('OpenLP.ExceptionDialog',
+            'Send E-Mail'))
+        self.saveReportButton.setText(translate('OpenLP.ExceptionDialog',
+            'Save to File'))

=== 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 23:57:54 +0000
@@ -24,7 +24,38 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 
-from PyQt4 import QtGui
+import re
+import os
+import platform
+
+import sqlalchemy
+import BeautifulSoup
+from lxml import etree
+from PyQt4 import Qt, QtCore, QtGui
+
+try:
+    from PyQt4.phonon import Phonon
+    phonon_version = Phonon.phononVersion()
+except ImportError:
+    phonon_version = u'-'
+try:
+    import chardet
+    chardet_version = chardet.__version__
+except ImportError:
+    chardet_version = u'-'
+try:
+    import enchant
+    enchant_version = enchant.__version__
+except ImportError:
+    enchant_version = u'-'
+try:
+    import sqlite
+    sqlite_version = sqlite.version
+except ImportError:
+    sqlite_version = u'-'
+
+from openlp.core.lib import translate, SettingsManager
+from openlp.core.lib.mailto import mailto
 
 from exceptiondialog import Ui_ExceptionDialog
 
@@ -35,3 +66,78 @@
     def __init__(self, parent):
         QtGui.QDialog.__init__(self, parent)
         self.setupUi(self)
+        self.settingsSection = u'crashreport'
+
+    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 = u'Python: %s\n' % platform.python_version() + \
+            u'Qt4: %s\n' % Qt.qVersion() + \
+            u'Phonon: %s\n' % phonon_version + \
+            u'PyQt4: %s\n' % Qt.PYQT_VERSION_STR + \
+            u'SQLAlchemy: %s\n' % sqlalchemy.__version__ + \
+            u'BeautifulSoup: %s\n' % BeautifulSoup.__version__ + \
+            u'lxml: %s\n' % etree.__version__ + \
+            u'Chardet: %s\n' % chardet_version + \
+            u'PyEnchant: %s\n' % enchant_version + \
+            u'PySQLite: %s\n' %  sqlite_version
+        if platform.system() == u'Linux':
+            if os.environ.get(u'KDE_FULL_SESSION') == u'true':
+                system = system + u'Desktop: KDE SC\n'
+            elif os.environ.get(u'GNOME_DESKTOP_SESSION_ID'):
+                system = system + u'Desktop: GNOME\n'
+        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.
+        """
+        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'))
+        content = self._createReport()
+        for line in content[1].split(u'\n'):
+            if re.search(r'[/\\]openlp[/\\]', line):
+                source = re.sub(r'.*[/\\]openlp[/\\](.*)".*', r'\1', line)
+            if u':' in line:
+            	exception = line.split(u'\n')[-1].split(u':')[0]
+        subject = u'Bug report: %s in %s' % (exception, source)
+        mailto(address=u'bugs@xxxxxxxxxx', subject=subject, body=body % content)

=== 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 23:57:54 +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 23:57:54 +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