--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UI/EmailDialog.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,345 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2003 - 2009 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 + +from email import Encoders +from email.MIMEBase import MIMEBase +from email.MIMEText import MIMEText +from email.MIMEImage import MIMEImage +from email.MIMEAudio import MIMEAudio +from email.MIMEMultipart 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, + _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 = str(itm.text(1)).split('/', 1) + fname = itm.text(0) + name = os.path.basename(fname) + + if maintype == 'text': + att = MIMEText(open(fname, 'rb').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(str(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), e: + res = QMessageBox.critical(self, + self.trUtf8("Send bug report"), + self.trUtf8("""<p>Authentication failed.<br>Reason: {0}</p>""") + .format(unicode(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), 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(unicode(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() != "")