UI/EmailDialog.py

Sat, 16 Jan 2010 15:56:47 +0000

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 16 Jan 2010 15:56:47 +0000
changeset 67
4b52a61c96f3
parent 45
9a18f4dbb493
child 97
c4086afea02b
permissions
-rw-r--r--

Regenerated the translation files and did the German translations.

# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2010 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to send bug reports.
"""

import sys
import os
import mimetypes
import smtplib
import socket

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from .Ui_EmailDialog import Ui_EmailDialog

from .Info import Program, Version, BugAddress, FeatureAddress
import Preferences
import Utilities

############################################################
## This code is to work around a bug in the Python email  ##
## package for Image and Audio mime messages.             ##
############################################################
from base64 import b64encode as _bencode
def _encode_base64(msg):
    """
    Function to encode the message's payload in Base64.

    Note: It adds an appropriate Content-Transfer-Encoding header.
    
    @param msg reference to the message object (email.Message)
    """
    orig = msg.get_payload()
    encdata = str(_bencode(orig), "ASCII")
    msg.set_payload(encdata)
    msg['Content-Transfer-Encoding'] = 'base64'

from email import encoders
encoders.encode_base64 = _encode_base64     # WORK AROUND: implement our corrected encoder
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.multipart import MIMEMultipart

class EmailDialog(QDialog, Ui_EmailDialog):
    """
    Class implementing a dialog to send bug reports.
    """
    def __init__(self, mode = "bug", parent = None):
        """
        Constructor
        
        @param mode mode of this dialog (string, "bug" or "feature")
        @param parent parent widget of this dialog (QWidget)
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)
        
        self.__mode = mode
        if self.__mode == "feature":
            self.setWindowTitle(self.trUtf8("Send feature request"))
            self.msgLabel.setText(self.trUtf8(
                "Enter your &feature request below."
                " Version information is added automatically."))
            self.__toAddress = FeatureAddress
        else:
            # default is bug
            self.msgLabel.setText(self.trUtf8(
                "Enter your &bug description below."
                " Version information is added automatically."))
            self.__toAddress = BugAddress
        
        self.sendButton = \
            self.buttonBox.addButton(self.trUtf8("Send"), QDialogButtonBox.ActionRole)
        self.sendButton.setEnabled(False)
        self.sendButton.setDefault(True)
        
        height = self.height()
        self.mainSplitter.setSizes([int(0.7 * height), int(0.3 * height)])
        
        self.attachments.headerItem().setText(self.attachments.columnCount(), "")
        self.attachments.header().setResizeMode(QHeaderView.Interactive)
        
        sig = Preferences.getUser("Signature")
        if sig:
            self.message.setPlainText(sig)
            cursor = self.message.textCursor()
            cursor.setPosition(0)
            self.message.setTextCursor(cursor)
            self.message.ensureCursorVisible()
        
        self.__deleteFiles = []
        
    def keyPressEvent(self, ev):
        """
        Re-implemented to handle the user pressing the escape key.
        
        @param ev key event (QKeyEvent)
        """
        if ev.key() == Qt.Key_Escape:
            res = QMessageBox.question(self,
                self.trUtf8("Close dialog"),
                self.trUtf8("""Do you really want to close the dialog?"""),
                QMessageBox.StandardButtons(\
                    QMessageBox.No | \
                    QMessageBox.Yes),
                QMessageBox.No)
            if res == QMessageBox.Yes:
                self.reject()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.sendButton:
            self.on_sendButton_clicked()
        
    def on_buttonBox_rejected(self):
        """
        Private slot to handle the rejected signal of the button box.
        """
        res = QMessageBox.question(self,
            self.trUtf8("Close dialog"),
            self.trUtf8("""Do you really want to close the dialog?"""),
            QMessageBox.StandardButtons(\
                QMessageBox.No | \
                QMessageBox.Yes),
            QMessageBox.No)
        if res == QMessageBox.Yes:
            self.reject()
        
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the email message.
        """
        if self.attachments.topLevelItemCount():
            msg = self.__createMultipartMail()
        else:
            msg = self.__createSimpleMail()
            
        ok = self.__sendmail(msg)
        
        if ok:
            for f in self.__deleteFiles:
                try:
                    os.remove(f)
                except OSError:
                    pass
            self.accept()
        
    def __createSimpleMail(self):
        """
        Private method to create a simple mail message.
        
        @return string containing the mail message
        """
        try:
            import sipconfig
            sip_version_str = sipconfig.Configuration().sip_version_str
        except ImportError:
            sip_version_str = "sip version not available"
        
        msgtext = "%s\r\n----\r\n%s----\r\n%s----\r\n%s" % \
            (self.message.toPlainText(), 
             Utilities.generateVersionInfo("\r\n"), 
             Utilities.generatePluginsVersionInfo("\r\n"), 
             Utilities.generateDistroInfo("\r\n"))
        
        msg = MIMEText(msgtext, 
                       _charset = Preferences.getSystem("StringEncoding"))
        msg['From']    = Preferences.getUser("Email")
        msg['To']      = self.__toAddress
        msg['Subject'] = '[eric5] %s' % self.subject.text()
            
        return msg.as_string()
        
    def __createMultipartMail(self):
        """
        Private method to create a multipart mail message.
        
        @return string containing the mail message
        """
        try:
            import sipconfig
            sip_version_str = sipconfig.Configuration().sip_version_str
        except ImportError:
            sip_version_str = "sip version not available"
        
        mpPreamble = ("This is a MIME-encoded message with attachments. "
            "If you see this message, your mail client is not "
            "capable of displaying the attachments.")
        
        msgtext = "%s\r\n----\r\n%s----\r\n%s----\r\n%s" % \
            (self.message.toPlainText(), 
             Utilities.generateVersionInfo("\r\n"), 
             Utilities.generatePluginsVersionInfo("\r\n"), 
             Utilities.generateDistroInfo("\r\n"))
        
        # first part of multipart mail explains format
        msg = MIMEMultipart()
        msg['From']    = Preferences.getUser("Email")
        msg['To']      = self.__toAddress
        msg['Subject'] = '[eric5] %s' % self.subject.text()
        msg.preamble = mpPreamble
        msg.epilogue = ''
        
        # second part is intended to be read
        att = MIMEText(msgtext.encode(), 
                       _charset = Preferences.getSystem("StringEncoding"))
        msg.attach(att)
        
        # next parts contain the attachments
        for index in range(self.attachments.topLevelItemCount()):
            itm = self.attachments.topLevelItem(index)
            maintype, subtype = itm.text(1).split('/', 1)
            fname = itm.text(0)
            name = os.path.basename(fname)
            
            if maintype == 'text':
                att = MIMEText(
                    open(fname, 'r', encoding = "utf-8").read(), _subtype = subtype)
            elif maintype == 'image':
                att = MIMEImage(open(fname, 'rb').read(), _subtype = subtype)
            elif maintype == 'audio':
                att = MIMEAudio(open(fname, 'rb').read(), _subtype = subtype)
            else:
                att = MIMEBase(maintype, subtype)
                att.set_payload(open(fname, 'rb').read())
                encoders.encode_base64(att)
            att.add_header('Content-Disposition', 'attachment', filename = fname)
            msg.attach(att)
            
        return msg.as_string()

    def __sendmail(self, msg):
        """
        Private method to actually send the message.
        
        @param msg the message to be sent (string)
        @return flag indicating success (boolean)
        """
        try:
            server = smtplib.SMTP(Preferences.getUser("MailServer"), 
                                  Preferences.getUser("MailServerPort"))
            if Preferences.getUser("MailServerUseTLS"):
                server.starttls()
            if Preferences.getUser("MailServerAuthentication"):
                # mail server needs authentication
                password = Preferences.getUser("MailServerPassword")
                if not password:
                    password, ok = QInputDialog.getText(\
                        self, 
                        self.trUtf8("Mail Server Password"),
                        self.trUtf8("Enter your mail server password"),
                        QLineEdit.Password)
                    if not ok:
                        # abort
                        return False
                try:
                    server.login(Preferences.getUser("MailServerUser"),
                                 password)
                except (smtplib.SMTPException, socket.error) as e:
                    res = QMessageBox.critical(self,
                        self.trUtf8("Send bug report"),
                        self.trUtf8("""<p>Authentication failed.<br>Reason: {0}</p>""")
                            .format(str(e)),
                        QMessageBox.StandardButtons(\
                            QMessageBox.Abort | \
                            QMessageBox.Retry),
                        QMessageBox.Retry)
                    if res == QMessageBox.Retry:
                        return self.__sendmail(msg)
                    else:
                        return False

            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            QApplication.processEvents()
            result = server.sendmail(Preferences.getUser("Email"),
                self.__toAddress, msg)
            server.quit()
            QApplication.restoreOverrideCursor()
        except (smtplib.SMTPException, socket.error) as e:
            QApplication.restoreOverrideCursor()
            res = QMessageBox.critical(self,
                self.trUtf8("Send bug report"),
                self.trUtf8("""<p>Message could not be sent.<br>Reason: {0}</p>""")
                    .format(str(e)),
                QMessageBox.StandardButtons(\
                    QMessageBox.Abort | \
                    QMessageBox.Retry),
                QMessageBox.Retry)
            if res == QMessageBox.Retry:
                return self.__sendmail(msg)
            else:
                return False
        return True
        
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to handle the Add... button.
        """
        fname = QFileDialog.getOpenFileName(\
            self,
            self.trUtf8("Attach file"))
        if fname:
            self.attachFile(fname, False)
        
    def attachFile(self, fname, deleteFile):
        """
        Public method to add an attachment.
        
        @param fname name of the file to be attached (string)
        @param deleteFile flag indicating to delete the file after it has 
            been sent (boolean)
        """
        type = mimetypes.guess_type(fname)[0]
        if not type:
            type = "application/octet-stream"
        itm = QTreeWidgetItem(self.attachments, [fname, type])
        self.attachments.header().resizeSections(QHeaderView.ResizeToContents)
        self.attachments.header().setStretchLastSection(True)
        
        if deleteFile:
            self.__deleteFiles.append(fname)
        
    @pyqtSlot()
    def on_deleteButton_clicked(self):
        """
        Private slot to handle the Delete button.
        """
        itm = self.attachments.currentItem()
        if itm is not None:
            itm = self.attachments.takeTopLevelItem(\
                self.attachments.indexOfTopLevelItem(itm))
            del itm
        
    def on_subject_textChanged(self, txt):
        """
        Private slot to handle the textChanged signal of the subject edit.
        
        @param txt changed text (string)
        """
        self.sendButton.setEnabled(\
            self.subject.text() != "" and \
            self.message.toPlainText() != "")
        
    def on_message_textChanged(self):
        """
        Private slot to handle the textChanged signal of the message edit.
        
        @param txt changed text (string)
        """
        self.sendButton.setEnabled(\
            self.subject.text() != "" and \
            self.message.toPlainText() != "")

eric ide

mercurial