44 |
48 |
45 class EmailDialog(QDialog, Ui_EmailDialog): |
49 class EmailDialog(QDialog, Ui_EmailDialog): |
46 """ |
50 """ |
47 Class implementing a dialog to send bug reports or feature requests. |
51 Class implementing a dialog to send bug reports or feature requests. |
48 """ |
52 """ |
|
53 |
49 def __init__(self, mode="bug", parent=None): |
54 def __init__(self, mode="bug", parent=None): |
50 """ |
55 """ |
51 Constructor |
56 Constructor |
52 |
57 |
53 @param mode mode of this dialog (string, "bug" or "feature") |
58 @param mode mode of this dialog (string, "bug" or "feature") |
54 @param parent parent widget of this dialog (QWidget) |
59 @param parent parent widget of this dialog (QWidget) |
55 """ |
60 """ |
56 super().__init__(parent) |
61 super().__init__(parent) |
57 self.setupUi(self) |
62 self.setupUi(self) |
58 self.setWindowFlags(Qt.WindowType.Window) |
63 self.setWindowFlags(Qt.WindowType.Window) |
59 |
64 |
60 self.message.setWordWrapMode(QTextOption.WrapMode.WordWrap) |
65 self.message.setWordWrapMode(QTextOption.WrapMode.WordWrap) |
61 |
66 |
62 self.__mode = mode |
67 self.__mode = mode |
63 if self.__mode == "feature": |
68 if self.__mode == "feature": |
64 self.setWindowTitle(self.tr("Send feature request")) |
69 self.setWindowTitle(self.tr("Send feature request")) |
65 self.msgLabel.setText(self.tr( |
70 self.msgLabel.setText( |
66 "Enter your &feature request below." |
71 self.tr( |
67 " Version information is added automatically.")) |
72 "Enter your &feature request below." |
|
73 " Version information is added automatically." |
|
74 ) |
|
75 ) |
68 self.__toAddress = FeatureAddress |
76 self.__toAddress = FeatureAddress |
69 else: |
77 else: |
70 # default is bug |
78 # default is bug |
71 self.msgLabel.setText(self.tr( |
79 self.msgLabel.setText( |
72 "Enter your &bug description below." |
80 self.tr( |
73 " Version information is added automatically.")) |
81 "Enter your &bug description below." |
|
82 " Version information is added automatically." |
|
83 ) |
|
84 ) |
74 self.__toAddress = BugAddress |
85 self.__toAddress = BugAddress |
75 |
86 |
76 self.sendButton = self.buttonBox.addButton( |
87 self.sendButton = self.buttonBox.addButton( |
77 self.tr("Send"), QDialogButtonBox.ButtonRole.ActionRole) |
88 self.tr("Send"), QDialogButtonBox.ButtonRole.ActionRole |
|
89 ) |
78 self.sendButton.setEnabled(False) |
90 self.sendButton.setEnabled(False) |
79 self.sendButton.setDefault(True) |
91 self.sendButton.setDefault(True) |
80 |
92 |
81 self.googleHelpButton = self.buttonBox.addButton( |
93 self.googleHelpButton = self.buttonBox.addButton( |
82 self.tr("Google Mail API Help"), |
94 self.tr("Google Mail API Help"), QDialogButtonBox.ButtonRole.HelpRole |
83 QDialogButtonBox.ButtonRole.HelpRole) |
95 ) |
84 |
96 |
85 height = self.height() |
97 height = self.height() |
86 self.mainSplitter.setSizes([int(0.7 * height), int(0.3 * height)]) |
98 self.mainSplitter.setSizes([int(0.7 * height), int(0.3 * height)]) |
87 |
99 |
88 self.attachments.headerItem().setText( |
100 self.attachments.headerItem().setText(self.attachments.columnCount(), "") |
89 self.attachments.columnCount(), "") |
|
90 self.attachments.header().setSectionResizeMode( |
101 self.attachments.header().setSectionResizeMode( |
91 QHeaderView.ResizeMode.Interactive) |
102 QHeaderView.ResizeMode.Interactive |
92 |
103 ) |
|
104 |
93 sig = Preferences.getUser("Signature") |
105 sig = Preferences.getUser("Signature") |
94 if sig: |
106 if sig: |
95 self.message.setPlainText(sig) |
107 self.message.setPlainText(sig) |
96 cursor = self.message.textCursor() |
108 cursor = self.message.textCursor() |
97 cursor.setPosition(0) |
109 cursor.setPosition(0) |
98 self.message.setTextCursor(cursor) |
110 self.message.setTextCursor(cursor) |
99 self.message.ensureCursorVisible() |
111 self.message.ensureCursorVisible() |
100 |
112 |
101 self.__deleteFiles = [] |
113 self.__deleteFiles = [] |
102 |
114 |
103 self.__helpDialog = None |
115 self.__helpDialog = None |
104 self.__googleMail = None |
116 self.__googleMail = None |
105 |
117 |
106 def keyPressEvent(self, ev): |
118 def keyPressEvent(self, ev): |
107 """ |
119 """ |
108 Protected method to handle the user pressing the escape key. |
120 Protected method to handle the user pressing the escape key. |
109 |
121 |
110 @param ev key event (QKeyEvent) |
122 @param ev key event (QKeyEvent) |
111 """ |
123 """ |
112 if ev.key() == Qt.Key.Key_Escape: |
124 if ev.key() == Qt.Key.Key_Escape: |
113 res = EricMessageBox.yesNo( |
125 res = EricMessageBox.yesNo( |
114 self, |
126 self, |
115 self.tr("Close dialog"), |
127 self.tr("Close dialog"), |
116 self.tr("""Do you really want to close the dialog?""")) |
128 self.tr("""Do you really want to close the dialog?"""), |
|
129 ) |
117 if res: |
130 if res: |
118 self.reject() |
131 self.reject() |
119 |
132 |
120 def on_buttonBox_clicked(self, button): |
133 def on_buttonBox_clicked(self, button): |
121 """ |
134 """ |
122 Private slot called by a button of the button box clicked. |
135 Private slot called by a button of the button box clicked. |
123 |
136 |
124 @param button button that was clicked (QAbstractButton) |
137 @param button button that was clicked (QAbstractButton) |
125 """ |
138 """ |
126 if button == self.sendButton: |
139 if button == self.sendButton: |
127 self.on_sendButton_clicked() |
140 self.on_sendButton_clicked() |
128 elif button == self.googleHelpButton: |
141 elif button == self.googleHelpButton: |
129 self.on_googleHelpButton_clicked() |
142 self.on_googleHelpButton_clicked() |
130 |
143 |
131 def on_buttonBox_rejected(self): |
144 def on_buttonBox_rejected(self): |
132 """ |
145 """ |
133 Private slot to handle the rejected signal of the button box. |
146 Private slot to handle the rejected signal of the button box. |
134 """ |
147 """ |
135 res = EricMessageBox.yesNo( |
148 res = EricMessageBox.yesNo( |
136 self, |
149 self, |
137 self.tr("Close dialog"), |
150 self.tr("Close dialog"), |
138 self.tr("""Do you really want to close the dialog?""")) |
151 self.tr("""Do you really want to close the dialog?"""), |
|
152 ) |
139 if res: |
153 if res: |
140 self.reject() |
154 self.reject() |
141 |
155 |
142 @pyqtSlot() |
156 @pyqtSlot() |
143 def on_googleHelpButton_clicked(self): |
157 def on_googleHelpButton_clicked(self): |
144 """ |
158 """ |
145 Private slot to show some help text "how to turn on the Gmail API". |
159 Private slot to show some help text "how to turn on the Gmail API". |
146 """ |
160 """ |
147 if self.__helpDialog is None: |
161 if self.__helpDialog is None: |
148 try: |
162 try: |
149 from EricNetwork.EricGoogleMail import GoogleMailHelp |
163 from EricNetwork.EricGoogleMail import GoogleMailHelp |
|
164 |
150 helpStr = GoogleMailHelp() |
165 helpStr = GoogleMailHelp() |
151 except ImportError: |
166 except ImportError: |
152 from EricNetwork.EricGoogleMailHelpers import getInstallCommand |
167 from EricNetwork.EricGoogleMailHelpers import getInstallCommand |
|
168 |
153 helpStr = self.tr( |
169 helpStr = self.tr( |
154 "<p>The Google Mail Client API is not installed." |
170 "<p>The Google Mail Client API is not installed." |
155 " Use <code>{0}</code> to install it.</p>" |
171 " Use <code>{0}</code> to install it.</p>" |
156 ).format(getInstallCommand()) |
172 ).format(getInstallCommand()) |
157 |
173 |
158 from EricWidgets.EricSimpleHelpDialog import EricSimpleHelpDialog |
174 from EricWidgets.EricSimpleHelpDialog import EricSimpleHelpDialog |
|
175 |
159 self.__helpDialog = EricSimpleHelpDialog( |
176 self.__helpDialog = EricSimpleHelpDialog( |
160 title=self.tr("Gmail API Help"), |
177 title=self.tr("Gmail API Help"), helpStr=helpStr, parent=self |
161 helpStr=helpStr, parent=self) |
178 ) |
162 |
179 |
163 self.__helpDialog.show() |
180 self.__helpDialog.show() |
164 |
181 |
165 @pyqtSlot() |
182 @pyqtSlot() |
166 def on_sendButton_clicked(self): |
183 def on_sendButton_clicked(self): |
167 """ |
184 """ |
168 Private slot to send the email message. |
185 Private slot to send the email message. |
169 """ |
186 """ |
170 msg = ( |
187 msg = ( |
171 self.__createMultipartMail() |
188 self.__createMultipartMail() |
172 if self.attachments.topLevelItemCount() else |
189 if self.attachments.topLevelItemCount() |
173 self.__createSimpleMail() |
190 else self.__createSimpleMail() |
174 ) |
191 ) |
175 |
192 |
176 if Preferences.getUser("UseGoogleMailOAuth2"): |
193 if Preferences.getUser("UseGoogleMailOAuth2"): |
177 self.__sendmailGoogle(msg) |
194 self.__sendmailGoogle(msg) |
178 else: |
195 else: |
179 ok = self.__sendmail(msg.as_string()) |
196 ok = self.__sendmail(msg.as_string()) |
180 if ok: |
197 if ok: |
181 self.__deleteAttachedFiles() |
198 self.__deleteAttachedFiles() |
182 self.accept() |
199 self.accept() |
183 |
200 |
184 def __deleteAttachedFiles(self): |
201 def __deleteAttachedFiles(self): |
185 """ |
202 """ |
186 Private method to delete attached files. |
203 Private method to delete attached files. |
187 """ |
204 """ |
188 for f in self.__deleteFiles: |
205 for f in self.__deleteFiles: |
189 with contextlib.suppress(OSError): |
206 with contextlib.suppress(OSError): |
190 os.remove(f) |
207 os.remove(f) |
191 |
208 |
192 def __encodedText(self, txt): |
209 def __encodedText(self, txt): |
193 """ |
210 """ |
194 Private method to create a MIMEText message with correct encoding. |
211 Private method to create a MIMEText message with correct encoding. |
195 |
212 |
196 @param txt text to be put into the MIMEText object (string) |
213 @param txt text to be put into the MIMEText object (string) |
197 @return MIMEText object |
214 @return MIMEText object |
198 """ |
215 """ |
199 try: |
216 try: |
200 txt.encode("us-ascii") |
217 txt.encode("us-ascii") |
201 return MIMEText(txt) |
218 return MIMEText(txt) |
202 except UnicodeEncodeError: |
219 except UnicodeEncodeError: |
203 coding = Preferences.getSystem("StringEncoding") |
220 coding = Preferences.getSystem("StringEncoding") |
204 return MIMEText(txt.encode(coding), _charset=coding) |
221 return MIMEText(txt.encode(coding), _charset=coding) |
205 |
222 |
206 def __encodedHeader(self, txt): |
223 def __encodedHeader(self, txt): |
207 """ |
224 """ |
208 Private method to create a correctly encoded mail header. |
225 Private method to create a correctly encoded mail header. |
209 |
226 |
210 @param txt header text to encode (string) |
227 @param txt header text to encode (string) |
211 @return encoded header (email.header.Header) |
228 @return encoded header (email.header.Header) |
212 """ |
229 """ |
213 try: |
230 try: |
214 txt.encode("us-ascii") |
231 txt.encode("us-ascii") |
215 return Header(txt) |
232 return Header(txt) |
216 except UnicodeEncodeError: |
233 except UnicodeEncodeError: |
217 coding = Preferences.getSystem("StringEncoding") |
234 coding = Preferences.getSystem("StringEncoding") |
218 return Header(txt, coding) |
235 return Header(txt, coding) |
219 |
236 |
220 def __createSimpleMail(self): |
237 def __createSimpleMail(self): |
221 """ |
238 """ |
222 Private method to create a simple mail message. |
239 Private method to create a simple mail message. |
223 |
240 |
224 @return prepared mail message |
241 @return prepared mail message |
225 @rtype email.mime.text.MIMEText |
242 @rtype email.mime.text.MIMEText |
226 """ |
243 """ |
227 msgtext = "{0}\r\n----\r\n{1}\r\n----\r\n{2}\r\n----\r\n{3}".format( |
244 msgtext = "{0}\r\n----\r\n{1}\r\n----\r\n{2}\r\n----\r\n{3}".format( |
228 self.message.toPlainText(), |
245 self.message.toPlainText(), |
229 Utilities.generateVersionInfo("\r\n"), |
246 Utilities.generateVersionInfo("\r\n"), |
230 Utilities.generatePluginsVersionInfo("\r\n"), |
247 Utilities.generatePluginsVersionInfo("\r\n"), |
231 Utilities.generateDistroInfo("\r\n")) |
248 Utilities.generateDistroInfo("\r\n"), |
232 |
249 ) |
|
250 |
233 msg = self.__encodedText(msgtext) |
251 msg = self.__encodedText(msgtext) |
234 msg['From'] = Preferences.getUser("Email") |
252 msg["From"] = Preferences.getUser("Email") |
235 msg['To'] = self.__toAddress |
253 msg["To"] = self.__toAddress |
236 subject = '[eric7] {0}'.format(self.subject.text()) |
254 subject = "[eric7] {0}".format(self.subject.text()) |
237 msg['Subject'] = self.__encodedHeader(subject) |
255 msg["Subject"] = self.__encodedHeader(subject) |
238 |
256 |
239 return msg |
257 return msg |
240 |
258 |
241 def __createMultipartMail(self): |
259 def __createMultipartMail(self): |
242 """ |
260 """ |
243 Private method to create a multipart mail message. |
261 Private method to create a multipart mail message. |
244 |
262 |
245 @return prepared mail message |
263 @return prepared mail message |
246 @rtype email.mime.text.MIMEMultipart |
264 @rtype email.mime.text.MIMEMultipart |
247 """ |
265 """ |
248 mpPreamble = ("This is a MIME-encoded message with attachments. " |
266 mpPreamble = ( |
249 "If you see this message, your mail client is not " |
267 "This is a MIME-encoded message with attachments. " |
250 "capable of displaying the attachments.") |
268 "If you see this message, your mail client is not " |
251 |
269 "capable of displaying the attachments." |
|
270 ) |
|
271 |
252 msgtext = "{0}\r\n----\r\n{1}\r\n----\r\n{2}\r\n----\r\n{3}".format( |
272 msgtext = "{0}\r\n----\r\n{1}\r\n----\r\n{2}\r\n----\r\n{3}".format( |
253 self.message.toPlainText(), |
273 self.message.toPlainText(), |
254 Utilities.generateVersionInfo("\r\n"), |
274 Utilities.generateVersionInfo("\r\n"), |
255 Utilities.generatePluginsVersionInfo("\r\n"), |
275 Utilities.generatePluginsVersionInfo("\r\n"), |
256 Utilities.generateDistroInfo("\r\n")) |
276 Utilities.generateDistroInfo("\r\n"), |
257 |
277 ) |
|
278 |
258 # first part of multipart mail explains format |
279 # first part of multipart mail explains format |
259 msg = MIMEMultipart() |
280 msg = MIMEMultipart() |
260 msg['From'] = Preferences.getUser("Email") |
281 msg["From"] = Preferences.getUser("Email") |
261 msg['To'] = self.__toAddress |
282 msg["To"] = self.__toAddress |
262 subject = '[eric7] {0}'.format(self.subject.text()) |
283 subject = "[eric7] {0}".format(self.subject.text()) |
263 msg['Subject'] = self.__encodedHeader(subject) |
284 msg["Subject"] = self.__encodedHeader(subject) |
264 msg.preamble = mpPreamble |
285 msg.preamble = mpPreamble |
265 msg.epilogue = '' |
286 msg.epilogue = "" |
266 |
287 |
267 # second part is intended to be read |
288 # second part is intended to be read |
268 att = self.__encodedText(msgtext) |
289 att = self.__encodedText(msgtext) |
269 msg.attach(att) |
290 msg.attach(att) |
270 |
291 |
271 # next parts contain the attachments |
292 # next parts contain the attachments |
272 for index in range(self.attachments.topLevelItemCount()): |
293 for index in range(self.attachments.topLevelItemCount()): |
273 itm = self.attachments.topLevelItem(index) |
294 itm = self.attachments.topLevelItem(index) |
274 maintype, subtype = itm.text(1).split('/', 1) |
295 maintype, subtype = itm.text(1).split("/", 1) |
275 fname = itm.text(0) |
296 fname = itm.text(0) |
276 name = os.path.basename(fname) |
297 name = os.path.basename(fname) |
277 |
298 |
278 if maintype == 'text': |
299 if maintype == "text": |
279 with open(fname, 'r', encoding="utf-8") as f: |
300 with open(fname, "r", encoding="utf-8") as f: |
280 txt = f.read() |
301 txt = f.read() |
281 try: |
302 try: |
282 txt.encode("us-ascii") |
303 txt.encode("us-ascii") |
283 att = MIMEText(txt, _subtype=subtype) |
304 att = MIMEText(txt, _subtype=subtype) |
284 except UnicodeEncodeError: |
305 except UnicodeEncodeError: |
285 att = MIMEText( |
306 att = MIMEText( |
286 txt.encode("utf-8"), _subtype=subtype, |
307 txt.encode("utf-8"), _subtype=subtype, _charset="utf-8" |
287 _charset="utf-8") |
308 ) |
288 elif maintype == 'image': |
309 elif maintype == "image": |
289 with open(fname, 'rb') as f: |
310 with open(fname, "rb") as f: |
290 att = MIMEImage(f.read(), _subtype=subtype) |
311 att = MIMEImage(f.read(), _subtype=subtype) |
291 elif maintype == 'audio': |
312 elif maintype == "audio": |
292 with open(fname, 'rb') as f: |
313 with open(fname, "rb") as f: |
293 att = MIMEAudio(f.read(), _subtype=subtype) |
314 att = MIMEAudio(f.read(), _subtype=subtype) |
294 else: |
315 else: |
295 with open(fname, 'rb') as f: |
316 with open(fname, "rb") as f: |
296 att = MIMEApplication(f.read()) |
317 att = MIMEApplication(f.read()) |
297 att.add_header('Content-Disposition', 'attachment', filename=name) |
318 att.add_header("Content-Disposition", "attachment", filename=name) |
298 msg.attach(att) |
319 msg.attach(att) |
299 |
320 |
300 return msg |
321 return msg |
301 |
322 |
302 def __sendmail(self, msg): |
323 def __sendmail(self, msg): |
303 """ |
324 """ |
304 Private method to actually send the message. |
325 Private method to actually send the message. |
305 |
326 |
306 @param msg the message to be sent (string) |
327 @param msg the message to be sent (string) |
307 @return flag indicating success (boolean) |
328 @return flag indicating success (boolean) |
308 """ |
329 """ |
309 try: |
330 try: |
310 encryption = Preferences.getUser("MailServerEncryption") |
331 encryption = Preferences.getUser("MailServerEncryption") |
311 if encryption == "SSL": |
332 if encryption == "SSL": |
312 server = smtplib.SMTP_SSL( |
333 server = smtplib.SMTP_SSL( |
313 Preferences.getUser("MailServer"), |
334 Preferences.getUser("MailServer"), |
314 Preferences.getUser("MailServerPort")) |
335 Preferences.getUser("MailServerPort"), |
|
336 ) |
315 else: |
337 else: |
316 server = smtplib.SMTP( |
338 server = smtplib.SMTP( |
317 Preferences.getUser("MailServer"), |
339 Preferences.getUser("MailServer"), |
318 Preferences.getUser("MailServerPort")) |
340 Preferences.getUser("MailServerPort"), |
|
341 ) |
319 if encryption == "TLS": |
342 if encryption == "TLS": |
320 server.starttls() |
343 server.starttls() |
321 if Preferences.getUser("MailServerAuthentication"): |
344 if Preferences.getUser("MailServerAuthentication"): |
322 # mail server needs authentication |
345 # mail server needs authentication |
323 password = Preferences.getUser("MailServerPassword") |
346 password = Preferences.getUser("MailServerPassword") |
324 if not password: |
347 if not password: |
325 password, ok = QInputDialog.getText( |
348 password, ok = QInputDialog.getText( |
326 self, |
349 self, |
327 self.tr("Mail Server Password"), |
350 self.tr("Mail Server Password"), |
328 self.tr("Enter your mail server password"), |
351 self.tr("Enter your mail server password"), |
329 QLineEdit.EchoMode.Password) |
352 QLineEdit.EchoMode.Password, |
|
353 ) |
330 if not ok: |
354 if not ok: |
331 # abort |
355 # abort |
332 return False |
356 return False |
333 try: |
357 try: |
334 server.login(Preferences.getUser("MailServerUser"), |
358 server.login(Preferences.getUser("MailServerUser"), password) |
335 password) |
|
336 except (smtplib.SMTPException, OSError) as e: |
359 except (smtplib.SMTPException, OSError) as e: |
337 if isinstance(e, smtplib.SMTPResponseException): |
360 if isinstance(e, smtplib.SMTPResponseException): |
338 errorStr = e.smtp_error.decode() |
361 errorStr = e.smtp_error.decode() |
339 elif isinstance(e, OSError): |
362 elif isinstance(e, OSError): |
340 errorStr = e.strerror |
363 errorStr = e.strerror |