--- a/src/eric7/UI/EmailDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/UI/EmailDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -15,8 +15,12 @@ from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtGui import QTextOption from PyQt6.QtWidgets import ( - QHeaderView, QLineEdit, QDialog, QInputDialog, QDialogButtonBox, - QTreeWidgetItem + QHeaderView, + QLineEdit, + QDialog, + QInputDialog, + QDialogButtonBox, + QTreeWidgetItem, ) from EricWidgets import EricMessageBox, EricFileDialog @@ -46,50 +50,58 @@ """ Class implementing a dialog to send bug reports or feature requests. """ + 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) """ super().__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.WindowType.Window) - + self.message.setWordWrapMode(QTextOption.WrapMode.WordWrap) - + self.__mode = mode if self.__mode == "feature": self.setWindowTitle(self.tr("Send feature request")) - self.msgLabel.setText(self.tr( - "Enter your &feature request below." - " Version information is added automatically.")) + self.msgLabel.setText( + self.tr( + "Enter your &feature request below." + " Version information is added automatically." + ) + ) self.__toAddress = FeatureAddress else: # default is bug - self.msgLabel.setText(self.tr( - "Enter your &bug description below." - " Version information is added automatically.")) + self.msgLabel.setText( + self.tr( + "Enter your &bug description below." + " Version information is added automatically." + ) + ) self.__toAddress = BugAddress - + self.sendButton = self.buttonBox.addButton( - self.tr("Send"), QDialogButtonBox.ButtonRole.ActionRole) + self.tr("Send"), QDialogButtonBox.ButtonRole.ActionRole + ) self.sendButton.setEnabled(False) self.sendButton.setDefault(True) - + self.googleHelpButton = self.buttonBox.addButton( - self.tr("Google Mail API Help"), - QDialogButtonBox.ButtonRole.HelpRole) - + self.tr("Google Mail API Help"), QDialogButtonBox.ButtonRole.HelpRole + ) + height = self.height() self.mainSplitter.setSizes([int(0.7 * height), int(0.3 * height)]) - - self.attachments.headerItem().setText( - self.attachments.columnCount(), "") + + self.attachments.headerItem().setText(self.attachments.columnCount(), "") self.attachments.header().setSectionResizeMode( - QHeaderView.ResizeMode.Interactive) - + QHeaderView.ResizeMode.Interactive + ) + sig = Preferences.getUser("Signature") if sig: self.message.setPlainText(sig) @@ -97,37 +109,38 @@ cursor.setPosition(0) self.message.setTextCursor(cursor) self.message.ensureCursorVisible() - + self.__deleteFiles = [] - + self.__helpDialog = None self.__googleMail = None - + def keyPressEvent(self, ev): """ Protected method to handle the user pressing the escape key. - + @param ev key event (QKeyEvent) """ if ev.key() == Qt.Key.Key_Escape: res = EricMessageBox.yesNo( self, self.tr("Close dialog"), - self.tr("""Do you really want to close the dialog?""")) + self.tr("""Do you really want to close the dialog?"""), + ) if res: 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() elif button == self.googleHelpButton: self.on_googleHelpButton_clicked() - + def on_buttonBox_rejected(self): """ Private slot to handle the rejected signal of the button box. @@ -135,10 +148,11 @@ res = EricMessageBox.yesNo( self, self.tr("Close dialog"), - self.tr("""Do you really want to close the dialog?""")) + self.tr("""Do you really want to close the dialog?"""), + ) if res: self.reject() - + @pyqtSlot() def on_googleHelpButton_clicked(self): """ @@ -147,21 +161,24 @@ if self.__helpDialog is None: try: from EricNetwork.EricGoogleMail import GoogleMailHelp + helpStr = GoogleMailHelp() except ImportError: from EricNetwork.EricGoogleMailHelpers import getInstallCommand + helpStr = self.tr( "<p>The Google Mail Client API is not installed." " Use <code>{0}</code> to install it.</p>" ).format(getInstallCommand()) - + from EricWidgets.EricSimpleHelpDialog import EricSimpleHelpDialog + self.__helpDialog = EricSimpleHelpDialog( - title=self.tr("Gmail API Help"), - helpStr=helpStr, parent=self) - + title=self.tr("Gmail API Help"), helpStr=helpStr, parent=self + ) + self.__helpDialog.show() - + @pyqtSlot() def on_sendButton_clicked(self): """ @@ -169,10 +186,10 @@ """ msg = ( self.__createMultipartMail() - if self.attachments.topLevelItemCount() else - self.__createSimpleMail() + if self.attachments.topLevelItemCount() + else self.__createSimpleMail() ) - + if Preferences.getUser("UseGoogleMailOAuth2"): self.__sendmailGoogle(msg) else: @@ -180,7 +197,7 @@ if ok: self.__deleteAttachedFiles() self.accept() - + def __deleteAttachedFiles(self): """ Private method to delete attached files. @@ -188,11 +205,11 @@ for f in self.__deleteFiles: with contextlib.suppress(OSError): os.remove(f) - + def __encodedText(self, txt): """ Private method to create a MIMEText message with correct encoding. - + @param txt text to be put into the MIMEText object (string) @return MIMEText object """ @@ -202,11 +219,11 @@ except UnicodeEncodeError: coding = Preferences.getSystem("StringEncoding") return MIMEText(txt.encode(coding), _charset=coding) - + def __encodedHeader(self, txt): """ Private method to create a correctly encoded mail header. - + @param txt header text to encode (string) @return encoded header (email.header.Header) """ @@ -216,11 +233,11 @@ except UnicodeEncodeError: coding = Preferences.getSystem("StringEncoding") return Header(txt, coding) - + def __createSimpleMail(self): """ Private method to create a simple mail message. - + @return prepared mail message @rtype email.mime.text.MIMEText """ @@ -228,81 +245,85 @@ self.message.toPlainText(), Utilities.generateVersionInfo("\r\n"), Utilities.generatePluginsVersionInfo("\r\n"), - Utilities.generateDistroInfo("\r\n")) - + Utilities.generateDistroInfo("\r\n"), + ) + msg = self.__encodedText(msgtext) - msg['From'] = Preferences.getUser("Email") - msg['To'] = self.__toAddress - subject = '[eric7] {0}'.format(self.subject.text()) - msg['Subject'] = self.__encodedHeader(subject) - + msg["From"] = Preferences.getUser("Email") + msg["To"] = self.__toAddress + subject = "[eric7] {0}".format(self.subject.text()) + msg["Subject"] = self.__encodedHeader(subject) + return msg - + def __createMultipartMail(self): """ Private method to create a multipart mail message. - + @return prepared mail message @rtype email.mime.text.MIMEMultipart """ - mpPreamble = ("This is a MIME-encoded message with attachments. " - "If you see this message, your mail client is not " - "capable of displaying the attachments.") - + 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 = "{0}\r\n----\r\n{1}\r\n----\r\n{2}\r\n----\r\n{3}".format( self.message.toPlainText(), Utilities.generateVersionInfo("\r\n"), Utilities.generatePluginsVersionInfo("\r\n"), - Utilities.generateDistroInfo("\r\n")) - + Utilities.generateDistroInfo("\r\n"), + ) + # first part of multipart mail explains format msg = MIMEMultipart() - msg['From'] = Preferences.getUser("Email") - msg['To'] = self.__toAddress - subject = '[eric7] {0}'.format(self.subject.text()) - msg['Subject'] = self.__encodedHeader(subject) + msg["From"] = Preferences.getUser("Email") + msg["To"] = self.__toAddress + subject = "[eric7] {0}".format(self.subject.text()) + msg["Subject"] = self.__encodedHeader(subject) msg.preamble = mpPreamble - msg.epilogue = '' - + msg.epilogue = "" + # second part is intended to be read att = self.__encodedText(msgtext) 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) + maintype, subtype = itm.text(1).split("/", 1) fname = itm.text(0) name = os.path.basename(fname) - - if maintype == 'text': - with open(fname, 'r', encoding="utf-8") as f: + + if maintype == "text": + with open(fname, "r", encoding="utf-8") as f: txt = f.read() try: txt.encode("us-ascii") att = MIMEText(txt, _subtype=subtype) except UnicodeEncodeError: att = MIMEText( - txt.encode("utf-8"), _subtype=subtype, - _charset="utf-8") - elif maintype == 'image': - with open(fname, 'rb') as f: + txt.encode("utf-8"), _subtype=subtype, _charset="utf-8" + ) + elif maintype == "image": + with open(fname, "rb") as f: att = MIMEImage(f.read(), _subtype=subtype) - elif maintype == 'audio': - with open(fname, 'rb') as f: + elif maintype == "audio": + with open(fname, "rb") as f: att = MIMEAudio(f.read(), _subtype=subtype) else: - with open(fname, 'rb') as f: + with open(fname, "rb") as f: att = MIMEApplication(f.read()) - att.add_header('Content-Disposition', 'attachment', filename=name) + att.add_header("Content-Disposition", "attachment", filename=name) msg.attach(att) - + return msg def __sendmail(self, msg): """ Private method to actually send the message. - + @param msg the message to be sent (string) @return flag indicating success (boolean) """ @@ -311,11 +332,13 @@ if encryption == "SSL": server = smtplib.SMTP_SSL( Preferences.getUser("MailServer"), - Preferences.getUser("MailServerPort")) + Preferences.getUser("MailServerPort"), + ) else: server = smtplib.SMTP( Preferences.getUser("MailServer"), - Preferences.getUser("MailServerPort")) + Preferences.getUser("MailServerPort"), + ) if encryption == "TLS": server.starttls() if Preferences.getUser("MailServerAuthentication"): @@ -326,13 +349,13 @@ self, self.tr("Mail Server Password"), self.tr("Enter your mail server password"), - QLineEdit.EchoMode.Password) + QLineEdit.EchoMode.Password, + ) if not ok: # abort return False try: - server.login(Preferences.getUser("MailServerUser"), - password) + server.login(Preferences.getUser("MailServerUser"), password) except (smtplib.SMTPException, OSError) as e: if isinstance(e, smtplib.SMTPResponseException): errorStr = e.smtp_error.decode() @@ -346,17 +369,17 @@ self, self.tr("Send Message"), self.tr( - """<p>Authentication failed.<br>Reason: {0}</p>""") - .format(errorStr), - EricMessageBox.Critical) + """<p>Authentication failed.<br>Reason: {0}</p>""" + ).format(errorStr), + EricMessageBox.Critical, + ) if res: return self.__sendmail(msg) else: return False with EricOverrideCursor(): - server.sendmail(Preferences.getUser("Email"), self.__toAddress, - msg) + server.sendmail(Preferences.getUser("Email"), self.__toAddress, msg) server.quit() except (smtplib.SMTPException, OSError) as e: if isinstance(e, smtplib.SMTPResponseException): @@ -370,37 +393,38 @@ res = EricMessageBox.retryAbort( self, self.tr("Send Message"), - self.tr( - """<p>Message could not be sent.<br>Reason: {0}</p>""") - .format(errorStr), - EricMessageBox.Critical) + self.tr("""<p>Message could not be sent.<br>Reason: {0}</p>""").format( + errorStr + ), + EricMessageBox.Critical, + ) if res: return self.__sendmail(msg) else: return False return True - + def __sendmailGoogle(self, msg): """ Private method to actually send the message via Google Mail. - + @param msg email message to be sent @type email.mime.text.MIMEBase """ from EricNetwork.EricGoogleMail import EricGoogleMail - + if self.__googleMail is None: self.__googleMail = EricGoogleMail(self) self.__googleMail.sendResult.connect(self.__gmailSendResult) - + self.__googleMail.sendMessage(msg) - + @pyqtSlot(bool, str) def __gmailSendResult(self, ok, message): """ Private slot handling the send result reported by the Google Mail interface. - + @param ok flag indicating success @type bool @param message message from the interface @@ -414,26 +438,24 @@ EricMessageBox.critical( self, self.tr("Send Message via Gmail"), - self.tr( - """<p>Message could not be sent.<br>Reason: {0}</p>""") - .format(message) + self.tr("""<p>Message could not be sent.<br>Reason: {0}</p>""").format( + message + ), ) - + @pyqtSlot() def on_addButton_clicked(self): """ Private slot to handle the Add... button. """ - fname = EricFileDialog.getOpenFileName( - self, - self.tr("Attach file")) + fname = EricFileDialog.getOpenFileName(self, self.tr("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) @@ -443,12 +465,13 @@ mimeType = "application/octet-stream" QTreeWidgetItem(self.attachments, [fname, mimeType]) self.attachments.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + QHeaderView.ResizeMode.ResizeToContents + ) self.attachments.header().setStretchLastSection(True) - + if deleteFile: self.__deleteFiles.append(fname) - + @pyqtSlot() def on_deleteButton_clicked(self): """ @@ -457,23 +480,24 @@ itm = self.attachments.currentItem() if itm is not None: itm = self.attachments.takeTopLevelItem( - self.attachments.indexOfTopLevelItem(itm)) + 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() != "") - + self.subject.text() != "" and self.message.toPlainText() != "" + ) + def on_message_textChanged(self): """ Private slot to handle the textChanged signal of the message edit. """ self.sendButton.setEnabled( - self.subject.text() != "" and - self.message.toPlainText() != "") + self.subject.text() != "" and self.message.toPlainText() != "" + )