1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2006 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a starter for the system tray. 8""" 9 10import sys 11import os 12import contextlib 13 14from PyQt5.QtCore import QProcess, QSettings, QFileInfo 15from PyQt5.QtGui import QCursor 16from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QDialog, QApplication 17 18from E5Gui import E5MessageBox 19from E5Gui.E5Application import e5App 20 21import Globals 22import UI.PixmapCache 23from UI.Info import Version, Program 24 25import Utilities 26import Preferences 27 28from eric6config import getConfig 29 30 31class 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) 574