|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2006 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a starter for the system tray. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import os |
|
12 import contextlib |
|
13 |
|
14 from PyQt5.QtCore import QProcess, QSettings, QFileInfo |
|
15 from PyQt5.QtGui import QCursor |
|
16 from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QDialog, QApplication |
|
17 |
|
18 from E5Gui import E5MessageBox |
|
19 from E5Gui.E5Application import e5App |
|
20 |
|
21 import Globals |
|
22 import UI.PixmapCache |
|
23 from UI.Info import Version, Program |
|
24 |
|
25 import Utilities |
|
26 import Preferences |
|
27 |
|
28 from eric6config import getConfig |
|
29 |
|
30 |
|
31 class TrayStarter(QSystemTrayIcon): |
|
32 """ |
|
33 Class implementing a starter for the system tray. |
|
34 """ |
|
35 def __init__(self, settingsDir): |
|
36 """ |
|
37 Constructor |
|
38 |
|
39 @param settingsDir directory to be used for the settings files |
|
40 @type str |
|
41 """ |
|
42 super().__init__( |
|
43 UI.PixmapCache.getIcon( |
|
44 Preferences.getTrayStarter("TrayStarterIcon"))) |
|
45 |
|
46 self.settingsDir = settingsDir |
|
47 |
|
48 self.maxMenuFilePathLen = 75 |
|
49 |
|
50 self.rsettings = QSettings( |
|
51 QSettings.Format.IniFormat, |
|
52 QSettings.Scope.UserScope, |
|
53 Globals.settingsNameOrganization, |
|
54 Globals.settingsNameRecent) |
|
55 |
|
56 self.recentProjects = [] |
|
57 self.__loadRecentProjects() |
|
58 self.recentMultiProjects = [] |
|
59 self.__loadRecentMultiProjects() |
|
60 self.recentFiles = [] |
|
61 self.__loadRecentFiles() |
|
62 |
|
63 self.activated.connect(self.__activated) |
|
64 |
|
65 self.__menu = QMenu(self.tr("eric tray starter")) |
|
66 |
|
67 self.recentProjectsMenu = QMenu( |
|
68 self.tr('Recent Projects'), self.__menu) |
|
69 self.recentProjectsMenu.aboutToShow.connect( |
|
70 self.__showRecentProjectsMenu) |
|
71 self.recentProjectsMenu.triggered.connect(self.__openRecent) |
|
72 |
|
73 self.recentMultiProjectsMenu = QMenu( |
|
74 self.tr('Recent Multiprojects'), self.__menu) |
|
75 self.recentMultiProjectsMenu.aboutToShow.connect( |
|
76 self.__showRecentMultiProjectsMenu) |
|
77 self.recentMultiProjectsMenu.triggered.connect(self.__openRecent) |
|
78 |
|
79 self.recentFilesMenu = QMenu(self.tr('Recent Files'), self.__menu) |
|
80 self.recentFilesMenu.aboutToShow.connect(self.__showRecentFilesMenu) |
|
81 self.recentFilesMenu.triggered.connect(self.__openRecent) |
|
82 |
|
83 act = self.__menu.addAction( |
|
84 self.tr("eric tray starter"), self.__about) |
|
85 font = act.font() |
|
86 font.setBold(True) |
|
87 act.setFont(font) |
|
88 self.__menu.addSeparator() |
|
89 |
|
90 self.__menu.addAction( |
|
91 self.tr("Show Versions"), self.__showVersions) |
|
92 self.__menu.addSeparator() |
|
93 |
|
94 self.__menu.addAction( |
|
95 self.tr("QRegularExpression editor"), |
|
96 self.__startQRegularExpression) |
|
97 self.__menu.addAction( |
|
98 self.tr("Python re editor"), self.__startPyRe) |
|
99 self.__menu.addSeparator() |
|
100 |
|
101 self.__menu.addAction( |
|
102 UI.PixmapCache.getIcon("uiPreviewer"), |
|
103 self.tr("UI Previewer"), self.__startUIPreviewer) |
|
104 self.__menu.addAction( |
|
105 UI.PixmapCache.getIcon("trPreviewer"), |
|
106 self.tr("Translations Previewer"), self.__startTRPreviewer) |
|
107 self.__menu.addAction( |
|
108 UI.PixmapCache.getIcon("unittest"), |
|
109 self.tr("Unittest"), self.__startUnittest) |
|
110 self.__menu.addSeparator() |
|
111 |
|
112 self.__menu.addAction( |
|
113 UI.PixmapCache.getIcon("diffFiles"), |
|
114 self.tr("Compare Files"), self.__startDiff) |
|
115 self.__menu.addAction( |
|
116 UI.PixmapCache.getIcon("compareFiles"), |
|
117 self.tr("Compare Files side by side"), self.__startCompare) |
|
118 self.__menu.addSeparator() |
|
119 |
|
120 self.__menu.addAction( |
|
121 UI.PixmapCache.getIcon("sqlBrowser"), |
|
122 self.tr("SQL Browser"), self.__startSqlBrowser) |
|
123 self.__menu.addSeparator() |
|
124 |
|
125 self.__menu.addAction( |
|
126 UI.PixmapCache.getIcon("ericSnap"), |
|
127 self.tr("Snapshot"), self.__startSnapshot) |
|
128 self.__menu.addAction( |
|
129 UI.PixmapCache.getIcon("iconEditor"), |
|
130 self.tr("Icon Editor"), self.__startIconEditor) |
|
131 self.__menu.addSeparator() |
|
132 |
|
133 self.__menu.addAction( |
|
134 UI.PixmapCache.getIcon("pluginInstall"), |
|
135 self.tr("Install Plugin"), self.__startPluginInstall) |
|
136 self.__menu.addAction( |
|
137 UI.PixmapCache.getIcon("pluginUninstall"), |
|
138 self.tr("Uninstall Plugin"), self.__startPluginUninstall) |
|
139 self.__menu.addAction( |
|
140 UI.PixmapCache.getIcon("pluginRepository"), |
|
141 self.tr("Plugin Repository"), self.__startPluginRepository) |
|
142 self.__menu.addSeparator() |
|
143 |
|
144 self.__menu.addAction( |
|
145 UI.PixmapCache.getIcon("configure"), |
|
146 self.tr('Preferences'), self.__startPreferences) |
|
147 self.__menu.addSeparator() |
|
148 |
|
149 self.__menu.addAction( |
|
150 UI.PixmapCache.getIcon("editor"), |
|
151 self.tr("eric Mini Editor"), self.__startMiniEditor) |
|
152 self.__menu.addAction( |
|
153 UI.PixmapCache.getIcon("hexEditor"), |
|
154 self.tr("eric Hex Editor"), self.__startHexEditor) |
|
155 self.__menu.addAction( |
|
156 UI.PixmapCache.getIcon("shell"), |
|
157 self.tr("eric Shell Window"), self.__startShell) |
|
158 self.__menu.addSeparator() |
|
159 |
|
160 self.__menu.addAction( |
|
161 UI.PixmapCache.getIcon("ericWeb"), |
|
162 self.tr("eric Web Browser"), self.__startWebBrowser) |
|
163 self.__menu.addAction( |
|
164 UI.PixmapCache.getIcon("ericWeb"), |
|
165 self.tr("eric Web Browser (with QtHelp)"), |
|
166 self.__startWebBrowserQtHelp) |
|
167 self.__menu.addAction( |
|
168 UI.PixmapCache.getIcon("ericWeb"), |
|
169 self.tr("eric Web Browser (Private Mode)"), |
|
170 self.__startWebBrowserPrivate) |
|
171 self.__menu.addSeparator() |
|
172 |
|
173 # recent files |
|
174 self.menuRecentFilesAct = self.__menu.addMenu(self.recentFilesMenu) |
|
175 # recent multi projects |
|
176 self.menuRecentMultiProjectsAct = self.__menu.addMenu( |
|
177 self.recentMultiProjectsMenu) |
|
178 # recent projects |
|
179 self.menuRecentProjectsAct = self.__menu.addMenu( |
|
180 self.recentProjectsMenu) |
|
181 self.__menu.addSeparator() |
|
182 |
|
183 self.__menu.addAction( |
|
184 UI.PixmapCache.getIcon("erict"), |
|
185 self.tr("eric IDE"), self.__startEric) |
|
186 self.__menu.addSeparator() |
|
187 |
|
188 self.__menu.addAction( |
|
189 UI.PixmapCache.getIcon("configure"), |
|
190 self.tr('Configure Tray Starter'), self.__showPreferences) |
|
191 self.__menu.addSeparator() |
|
192 |
|
193 self.__menu.addAction( |
|
194 UI.PixmapCache.getIcon("exit"), |
|
195 self.tr('Quit'), e5App().quit) |
|
196 |
|
197 def __loadRecentProjects(self): |
|
198 """ |
|
199 Private method to load the recently opened project filenames. |
|
200 """ |
|
201 rp = self.rsettings.value(Globals.recentNameProject) |
|
202 if rp is not None: |
|
203 for f in rp: |
|
204 if QFileInfo(f).exists(): |
|
205 self.recentProjects.append(f) |
|
206 |
|
207 def __loadRecentMultiProjects(self): |
|
208 """ |
|
209 Private method to load the recently opened multi project filenames. |
|
210 """ |
|
211 rmp = self.rsettings.value(Globals.recentNameMultiProject) |
|
212 if rmp is not None: |
|
213 for f in rmp: |
|
214 if QFileInfo(f).exists(): |
|
215 self.recentMultiProjects.append(f) |
|
216 |
|
217 def __loadRecentFiles(self): |
|
218 """ |
|
219 Private method to load the recently opened filenames. |
|
220 """ |
|
221 rf = self.rsettings.value(Globals.recentNameFiles) |
|
222 if rf is not None: |
|
223 for f in rf: |
|
224 if QFileInfo(f).exists(): |
|
225 self.recentFiles.append(f) |
|
226 |
|
227 def __activated(self, reason): |
|
228 """ |
|
229 Private slot to handle the activated signal. |
|
230 |
|
231 @param reason reason code of the signal |
|
232 (QSystemTrayIcon.ActivationReason) |
|
233 """ |
|
234 if reason in ( |
|
235 QSystemTrayIcon.ActivationReason.Context, |
|
236 QSystemTrayIcon.ActivationReason.MiddleClick |
|
237 ): |
|
238 self.__showContextMenu() |
|
239 elif reason == QSystemTrayIcon.ActivationReason.DoubleClick: |
|
240 self.__startEric() |
|
241 |
|
242 def __showContextMenu(self): |
|
243 """ |
|
244 Private slot to show the context menu. |
|
245 """ |
|
246 self.menuRecentProjectsAct.setEnabled(len(self.recentProjects) > 0) |
|
247 self.menuRecentMultiProjectsAct.setEnabled( |
|
248 len(self.recentMultiProjects) > 0) |
|
249 self.menuRecentFilesAct.setEnabled(len(self.recentFiles) > 0) |
|
250 |
|
251 pos = QCursor.pos() |
|
252 x = pos.x() - self.__menu.sizeHint().width() |
|
253 pos.setX(x > 0 and x or 0) |
|
254 y = pos.y() - self.__menu.sizeHint().height() |
|
255 pos.setY(y > 0 and y or 0) |
|
256 self.__menu.popup(pos) |
|
257 |
|
258 def __startProc(self, applName, *applArgs): |
|
259 """ |
|
260 Private method to start an eric application. |
|
261 |
|
262 @param applName name of the eric application script (string) |
|
263 @param *applArgs variable list of application arguments |
|
264 """ |
|
265 proc = QProcess() |
|
266 applPath = os.path.join(getConfig("ericDir"), applName) |
|
267 |
|
268 args = [] |
|
269 args.append(applPath) |
|
270 args.append("--config={0}".format(Utilities.getConfigDir())) |
|
271 if self.settingsDir: |
|
272 args.append("--settings={0}".format(self.settingsDir)) |
|
273 for arg in applArgs: |
|
274 args.append(arg) |
|
275 |
|
276 if ( |
|
277 not os.path.isfile(applPath) or |
|
278 not proc.startDetached(sys.executable, args) |
|
279 ): |
|
280 E5MessageBox.critical( |
|
281 self, |
|
282 self.tr('Process Generation Error'), |
|
283 self.tr( |
|
284 '<p>Could not start the process.<br>' |
|
285 'Ensure that it is available as <b>{0}</b>.</p>' |
|
286 ).format(applPath), |
|
287 self.tr('OK')) |
|
288 |
|
289 def __startMiniEditor(self): |
|
290 """ |
|
291 Private slot to start the eric Mini Editor. |
|
292 """ |
|
293 self.__startProc("eric6_editor.py") |
|
294 |
|
295 def __startEric(self): |
|
296 """ |
|
297 Private slot to start the eric IDE. |
|
298 """ |
|
299 self.__startProc("eric6.py") |
|
300 |
|
301 def __startPreferences(self): |
|
302 """ |
|
303 Private slot to start the eric configuration dialog. |
|
304 """ |
|
305 self.__startProc("eric6_configure.py") |
|
306 |
|
307 def __startPluginInstall(self): |
|
308 """ |
|
309 Private slot to start the eric plugin installation dialog. |
|
310 """ |
|
311 self.__startProc("eric6_plugininstall.py") |
|
312 |
|
313 def __startPluginUninstall(self): |
|
314 """ |
|
315 Private slot to start the eric plugin uninstallation dialog. |
|
316 """ |
|
317 self.__startProc("eric6_pluginuninstall.py") |
|
318 |
|
319 def __startPluginRepository(self): |
|
320 """ |
|
321 Private slot to start the eric plugin repository dialog. |
|
322 """ |
|
323 self.__startProc("eric6_pluginrepository.py") |
|
324 |
|
325 def __startWebBrowser(self): |
|
326 """ |
|
327 Private slot to start the eric web browser. |
|
328 """ |
|
329 variant = Globals.getWebBrowserSupport() |
|
330 if variant == "QtWebEngine": |
|
331 self.__startProc("eric6_browser.py") |
|
332 |
|
333 def __startWebBrowserQtHelp(self): |
|
334 """ |
|
335 Private slot to start the eric web browser with QtHelp support. |
|
336 """ |
|
337 variant = Globals.getWebBrowserSupport() |
|
338 if variant == "QtWebEngine": |
|
339 self.__startProc("eric6_browser.py", "--qthelp") |
|
340 |
|
341 def __startWebBrowserPrivate(self): |
|
342 """ |
|
343 Private slot to start the eric web browser in private mode. |
|
344 """ |
|
345 variant = Globals.getWebBrowserSupport() |
|
346 if variant == "QtWebEngine": |
|
347 self.__startProc("eric6_browser.py", "--private") |
|
348 |
|
349 def __startUIPreviewer(self): |
|
350 """ |
|
351 Private slot to start the eric UI previewer. |
|
352 """ |
|
353 self.__startProc("eric6_uipreviewer.py") |
|
354 |
|
355 def __startTRPreviewer(self): |
|
356 """ |
|
357 Private slot to start the eric translations previewer. |
|
358 """ |
|
359 self.__startProc("eric6_trpreviewer.py") |
|
360 |
|
361 def __startUnittest(self): |
|
362 """ |
|
363 Private slot to start the eric unittest dialog. |
|
364 """ |
|
365 self.__startProc("eric6_unittest.py") |
|
366 |
|
367 def __startDiff(self): |
|
368 """ |
|
369 Private slot to start the eric diff dialog. |
|
370 """ |
|
371 self.__startProc("eric6_diff.py") |
|
372 |
|
373 def __startCompare(self): |
|
374 """ |
|
375 Private slot to start the eric compare dialog. |
|
376 """ |
|
377 self.__startProc("eric6_compare.py") |
|
378 |
|
379 def __startSqlBrowser(self): |
|
380 """ |
|
381 Private slot to start the eric sql browser dialog. |
|
382 """ |
|
383 self.__startProc("eric6_sqlbrowser.py") |
|
384 |
|
385 def __startIconEditor(self): |
|
386 """ |
|
387 Private slot to start the eric icon editor dialog. |
|
388 """ |
|
389 self.__startProc("eric6_iconeditor.py") |
|
390 |
|
391 def __startSnapshot(self): |
|
392 """ |
|
393 Private slot to start the eric snapshot dialog. |
|
394 """ |
|
395 self.__startProc("eric6_snap.py") |
|
396 |
|
397 def __startQRegularExpression(self): |
|
398 """ |
|
399 Private slot to start the eric QRegularExpression editor dialog. |
|
400 """ |
|
401 self.__startProc("eric6_qregularexpression.py") |
|
402 |
|
403 def __startPyRe(self): |
|
404 """ |
|
405 Private slot to start the eric Python re editor dialog. |
|
406 """ |
|
407 self.__startProc("eric6_re.py") |
|
408 |
|
409 def __startHexEditor(self): |
|
410 """ |
|
411 Private slot to start the eric hex editor dialog. |
|
412 """ |
|
413 self.__startProc("eric6_hexeditor.py") |
|
414 |
|
415 def __startShell(self): |
|
416 """ |
|
417 Private slot to start the eric Shell window. |
|
418 """ |
|
419 self.__startProc("eric6_shell.py") |
|
420 |
|
421 def __showRecentProjectsMenu(self): |
|
422 """ |
|
423 Private method to set up the recent projects menu. |
|
424 """ |
|
425 self.recentProjects = [] |
|
426 self.rsettings.sync() |
|
427 self.__loadRecentProjects() |
|
428 |
|
429 self.recentProjectsMenu.clear() |
|
430 |
|
431 for idx, rp in enumerate(self.recentProjects, start=1): |
|
432 formatStr = '&{0:d}. {1}' if idx < 10 else '{0:d}. {1}' |
|
433 act = self.recentProjectsMenu.addAction( |
|
434 formatStr.format( |
|
435 idx, Utilities.compactPath(rp, self.maxMenuFilePathLen))) |
|
436 act.setData(rp) |
|
437 |
|
438 def __showRecentMultiProjectsMenu(self): |
|
439 """ |
|
440 Private method to set up the recent multi projects menu. |
|
441 """ |
|
442 self.recentMultiProjects = [] |
|
443 self.rsettings.sync() |
|
444 self.__loadRecentMultiProjects() |
|
445 |
|
446 self.recentMultiProjectsMenu.clear() |
|
447 |
|
448 for idx, rmp in enumerate(self.recentMultiProjects, start=1): |
|
449 formatStr = '&{0:d}. {1}' if idx < 10 else '{0:d}. {1}' |
|
450 act = self.recentMultiProjectsMenu.addAction( |
|
451 formatStr.format( |
|
452 idx, Utilities.compactPath(rmp, self.maxMenuFilePathLen))) |
|
453 act.setData(rmp) |
|
454 |
|
455 def __showRecentFilesMenu(self): |
|
456 """ |
|
457 Private method to set up the recent files menu. |
|
458 """ |
|
459 self.recentFiles = [] |
|
460 self.rsettings.sync() |
|
461 self.__loadRecentFiles() |
|
462 |
|
463 self.recentFilesMenu.clear() |
|
464 |
|
465 for idx, rf in enumerate(self.recentFiles, start=1): |
|
466 formatStr = '&{0:d}. {1}' if idx < 10 else '{0:d}. {1}' |
|
467 act = self.recentFilesMenu.addAction( |
|
468 formatStr.format( |
|
469 idx, Utilities.compactPath(rf, self.maxMenuFilePathLen))) |
|
470 act.setData(rf) |
|
471 |
|
472 def __openRecent(self, act): |
|
473 """ |
|
474 Private method to open a project or file from the list of recently |
|
475 opened projects or files. |
|
476 |
|
477 @param act reference to the action that triggered (QAction) |
|
478 """ |
|
479 filename = act.data() |
|
480 if filename: |
|
481 self.__startProc( |
|
482 "eric6.py", |
|
483 filename) |
|
484 |
|
485 def __showPreferences(self): |
|
486 """ |
|
487 Private slot to set the preferences. |
|
488 """ |
|
489 from Preferences.ConfigurationDialog import ( |
|
490 ConfigurationDialog, ConfigurationMode |
|
491 ) |
|
492 dlg = ConfigurationDialog( |
|
493 None, 'Configuration', True, fromEric=True, |
|
494 displayMode=ConfigurationMode.TRAYSTARTERMODE) |
|
495 dlg.preferencesChanged.connect(self.preferencesChanged) |
|
496 dlg.show() |
|
497 dlg.showConfigurationPageByName("trayStarterPage") |
|
498 dlg.exec() |
|
499 QApplication.processEvents() |
|
500 if dlg.result() == QDialog.DialogCode.Accepted: |
|
501 dlg.setPreferences() |
|
502 Preferences.syncPreferences() |
|
503 self.preferencesChanged() |
|
504 |
|
505 def preferencesChanged(self): |
|
506 """ |
|
507 Public slot to handle a change of preferences. |
|
508 """ |
|
509 self.setIcon( |
|
510 UI.PixmapCache.getIcon( |
|
511 Preferences.getTrayStarter("TrayStarterIcon"))) |
|
512 |
|
513 def __about(self): |
|
514 """ |
|
515 Private slot to handle the About dialog. |
|
516 """ |
|
517 from Plugins.AboutPlugin.AboutDialog import AboutDialog |
|
518 dlg = AboutDialog() |
|
519 dlg.exec() |
|
520 |
|
521 def __showVersions(self): |
|
522 """ |
|
523 Private slot to handle the Versions dialog. |
|
524 """ |
|
525 from PyQt5.QtCore import qVersion, PYQT_VERSION_STR |
|
526 from PyQt5.Qsci import QSCINTILLA_VERSION_STR |
|
527 |
|
528 try: |
|
529 try: |
|
530 from PyQt5 import sip |
|
531 except ImportError: |
|
532 import sip |
|
533 sip_version_str = sip.SIP_VERSION_STR |
|
534 except (ImportError, AttributeError): |
|
535 sip_version_str = "sip version not available" |
|
536 |
|
537 versionText = self.tr( |
|
538 """<h3>Version Numbers</h3>""" |
|
539 """<table>""") |
|
540 versionText += ( |
|
541 """<tr><td><b>Python</b></td><td>{0}</td></tr>""" |
|
542 .format(sys.version.split()[0]) |
|
543 ) |
|
544 versionText += ( |
|
545 """<tr><td><b>Qt</b></td><td>{0}</td></tr>""" |
|
546 .format(qVersion()) |
|
547 ) |
|
548 versionText += ( |
|
549 """<tr><td><b>PyQt</b></td><td>{0}</td></tr>""" |
|
550 .format(PYQT_VERSION_STR) |
|
551 ) |
|
552 versionText += ( |
|
553 """<tr><td><b>sip</b></td><td>{0}</td></tr>""" |
|
554 .format(sip_version_str) |
|
555 ) |
|
556 versionText += ( |
|
557 """<tr><td><b>QScintilla</b></td><td>{0}</td></tr>""" |
|
558 .format(QSCINTILLA_VERSION_STR) |
|
559 ) |
|
560 with contextlib.suppress(ImportError): |
|
561 from WebBrowser.Tools import WebBrowserTools |
|
562 chromeVersion = WebBrowserTools.getWebEngineVersions()[0] |
|
563 versionText += ( |
|
564 """<tr><td><b>WebEngine</b></td><td>{0}</td></tr>""" |
|
565 .format(chromeVersion) |
|
566 ) |
|
567 versionText += ( |
|
568 """<tr><td><b>{0}</b></td><td>{1}</td></tr>""" |
|
569 .format(Program, Version) |
|
570 ) |
|
571 versionText += self.tr("""</table>""") |
|
572 |
|
573 E5MessageBox.about(None, Program, versionText) |