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