src/eric7/UI/UserInterface.py

branch
eric7-maintenance
changeset 10873
4e8e63df7893
parent 10817
cbdafa2fc879
parent 10862
c14dae55e9d8
child 10892
409d010d7cae
equal deleted inserted replaced
10826:0f9c86561033 10873:4e8e63df7893
10 import contextlib 10 import contextlib
11 import datetime 11 import datetime
12 import enum 12 import enum
13 import functools 13 import functools
14 import getpass 14 import getpass
15 import glob
16 import importlib
15 import json 17 import json
16 import logging 18 import logging
17 import os 19 import os
18 import pathlib 20 import pathlib
19 import shutil 21 import shutil
20 import sys 22 import sys
23
24 import psutil
21 25
22 from PyQt6 import sip 26 from PyQt6 import sip
23 from PyQt6.Qsci import QSCINTILLA_VERSION_STR 27 from PyQt6.Qsci import QSCINTILLA_VERSION_STR
24 from PyQt6.QtCore import ( 28 from PyQt6.QtCore import (
25 PYQT_VERSION_STR, 29 PYQT_VERSION_STR,
43 QKeySequence, 47 QKeySequence,
44 QSessionManager, 48 QSessionManager,
45 ) 49 )
46 from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkProxyFactory 50 from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkProxyFactory
47 from PyQt6.QtWidgets import ( 51 from PyQt6.QtWidgets import (
52 QAbstractItemView,
48 QApplication, 53 QApplication,
49 QDialog, 54 QDialog,
50 QDockWidget, 55 QDockWidget,
51 QLabel, 56 QLabel,
52 QMenu, 57 QMenu,
73 proxyAuthenticationRequired, 78 proxyAuthenticationRequired,
74 ) 79 )
75 from eric7.EricWidgets import EricErrorMessage, EricFileDialog, EricMessageBox 80 from eric7.EricWidgets import EricErrorMessage, EricFileDialog, EricMessageBox
76 from eric7.EricWidgets.EricApplication import ericApp 81 from eric7.EricWidgets.EricApplication import ericApp
77 from eric7.EricWidgets.EricClickableLabel import EricClickableLabel 82 from eric7.EricWidgets.EricClickableLabel import EricClickableLabel
83 from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
78 from eric7.EricWidgets.EricMainWindow import EricMainWindow 84 from eric7.EricWidgets.EricMainWindow import EricMainWindow
79 from eric7.EricWidgets.EricSingleApplication import EricSingleApplicationServer 85 from eric7.EricWidgets.EricSingleApplication import EricSingleApplicationServer
80 from eric7.EricWidgets.EricToolBarManager import EricToolBarManager 86 from eric7.EricWidgets.EricToolBarManager import EricToolBarManager
81 from eric7.EricWidgets.EricZoomWidget import EricZoomWidget 87 from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
82 from eric7.Globals import getConfig 88 from eric7.Globals import getConfig
252 self.profiles = Preferences.getUI("ViewProfiles") 258 self.profiles = Preferences.getUI("ViewProfiles")
253 259
254 splash.showMessage(self.tr("Initializing Basic Services...")) 260 splash.showMessage(self.tr("Initializing Basic Services..."))
255 logging.getLogger(__name__).debug("Initializing Basic Services...") 261 logging.getLogger(__name__).debug("Initializing Basic Services...")
256 262
257 # Generate the redirection helpers
258 self.stdout = EricStdRedirector(False, self)
259 self.stdout.stdoutString.connect(self.appendToStdout)
260 self.stderr = EricStdRedirector(True, self)
261 self.stderr.stderrString.connect(self.appendToStderr)
262 # Redirect sys.stdout and/or sys.stderr if those are None 263 # Redirect sys.stdout and/or sys.stderr if those are None
263 if sys.stdout is None or UserInterface.ReleaseMode: 264 if sys.stdout is None or UserInterface.ReleaseMode:
264 sys.stdout = self.stdout 265 self.__stdout = EricStdRedirector(False, self)
266 self.__stdout.stdoutString.connect(self.appendToStdout)
267 sys.stdout = self.__stdout
265 if sys.stderr is None or UserInterface.ReleaseMode: 268 if sys.stderr is None or UserInterface.ReleaseMode:
266 sys.stderr = self.stderr 269 self.__stderr = EricStdRedirector(True, self)
270 self.__stderr.stderrString.connect(self.appendToStderr)
271 sys.stderr = self.__stderr
267 272
268 # create the remote server interface 273 # create the remote server interface
269 logging.getLogger(__name__).debug("Creating 'eric-ide' Server Interface...") 274 logging.getLogger(__name__).debug("Creating 'eric-ide' Server Interface...")
270 self.__ericServerInterface = EricServerInterface(self) 275 self.__ericServerInterface = EricServerInterface(self)
271 # register it early because it is needed very soon 276 # register it early because it is needed very soon
3106 ) 3111 )
3107 ) 3112 )
3108 self.webBrowserAct.triggered.connect(self.__startWebBrowser) 3113 self.webBrowserAct.triggered.connect(self.__startWebBrowser)
3109 self.actions.append(self.webBrowserAct) 3114 self.actions.append(self.webBrowserAct)
3110 3115
3116 if importlib.util.find_spec("fido2"):
3117 self.securityKeyMgmtAct = EricAction(
3118 self.tr("FIDO2 Security Key Management"),
3119 EricPixmapCache.getIcon("securityKey"),
3120 self.tr("FIDO2 Security Key Management..."),
3121 0,
3122 0,
3123 self,
3124 "fido2_security_key_mgmt",
3125 )
3126 self.securityKeyMgmtAct.setStatusTip(
3127 self.tr("Start the FIDO2 Security Key Management tool")
3128 )
3129 self.securityKeyMgmtAct.setWhatsThis(
3130 self.tr(
3131 """<b>FIDO2 Security Key Management</b>"""
3132 """<p>Start a tool to manage FIDO2 securit y keys.</p>"""
3133 )
3134 )
3135 self.securityKeyMgmtAct.triggered.connect(self.__startFido2SecurityKeyMgmt)
3136 self.actions.append(self.securityKeyMgmtAct)
3137 else:
3138 self.securityKeyMgmtAct = None
3139
3111 self.iconEditorAct = EricAction( 3140 self.iconEditorAct = EricAction(
3112 self.tr("Icon Editor"), 3141 self.tr("Icon Editor"),
3113 EricPixmapCache.getIcon("iconEditor"), 3142 EricPixmapCache.getIcon("iconEditor"),
3114 self.tr("&Icon Editor..."), 3143 self.tr("&Icon Editor..."),
3115 0, 3144 0,
3818 ############################################################## 3847 ##############################################################
3819 3848
3820 self.__menus["server"] = self.__ericServerInterface.initMenu() 3849 self.__menus["server"] = self.__ericServerInterface.initMenu()
3821 3850
3822 ############################################################## 3851 ##############################################################
3852 ## Sessions menu
3853 ##############################################################
3854
3855 self.__menus["sessions"] = QMenu(self.tr("Sessions"))
3856 self.__menus["sessions"].aboutToShow.connect(self.__showSessionsMenu)
3857
3858 ##############################################################
3823 ## File menu 3859 ## File menu
3824 ############################################################## 3860 ##############################################################
3825 3861
3826 self.__menus["file"] = self.viewmanager.initFileMenu() 3862 self.__menus["file"] = self.viewmanager.initFileMenu()
3827 mb.addMenu(self.__menus["file"]) 3863 mb.addMenu(self.__menus["file"])
3828 self.__menus["file"].addSeparator() 3864 self.__menus["file"].addSeparator()
3829 self.__menus["file"].addAction(self.saveSessionAct) 3865 self.__menus["file"].addMenu(self.__menus["sessions"])
3830 self.__menus["file"].addAction(self.loadSessionAct)
3831 self.__menus["file"].addSeparator() 3866 self.__menus["file"].addSeparator()
3832 self.__menus["file"].addAction(self.restartAct) 3867 self.__menus["file"].addAction(self.restartAct)
3833 self.__menus["file"].addAction(self.exitAct) 3868 self.__menus["file"].addAction(self.exitAct)
3834 act = self.__menus["file"].actions()[0] 3869 act = self.__menus["file"].actions()[0]
3835 sep = self.__menus["file"].insertSeparator(act) 3870 sep = self.__menus["file"].insertSeparator(act)
4186 toolstb.addAction(self.hexEditorAct) 4221 toolstb.addAction(self.hexEditorAct)
4187 toolstb.addAction(self.iconEditorAct) 4222 toolstb.addAction(self.iconEditorAct)
4188 if self.snapshotAct is not None: 4223 if self.snapshotAct is not None:
4189 toolstb.addAction(self.snapshotAct) 4224 toolstb.addAction(self.snapshotAct)
4190 toolstb.addAction(self.pdfViewerAct) 4225 toolstb.addAction(self.pdfViewerAct)
4191 if self.webBrowserAct: 4226 toolstb.addSeparator()
4192 toolstb.addSeparator() 4227 toolstb.addAction(self.webBrowserAct)
4193 toolstb.addAction(self.webBrowserAct) 4228 if self.securityKeyMgmtAct is not None:
4229 toolstb.addAction(self.securityKeyMgmtAct)
4194 self.toolbarManager.addToolBar(toolstb, toolstb.windowTitle()) 4230 self.toolbarManager.addToolBar(toolstb, toolstb.windowTitle())
4195 4231
4196 # setup the settings toolbar 4232 # setup the settings toolbar
4197 settingstb.addAction(self.prefAct) 4233 settingstb.addAction(self.prefAct)
4198 settingstb.addAction(self.configViewProfilesAct) 4234 settingstb.addAction(self.configViewProfilesAct)
5168 btMenu.addAction(self.hexEditorAct) 5204 btMenu.addAction(self.hexEditorAct)
5169 btMenu.addAction(self.iconEditorAct) 5205 btMenu.addAction(self.iconEditorAct)
5170 if self.snapshotAct is not None: 5206 if self.snapshotAct is not None:
5171 btMenu.addAction(self.snapshotAct) 5207 btMenu.addAction(self.snapshotAct)
5172 btMenu.addAction(self.pdfViewerAct) 5208 btMenu.addAction(self.pdfViewerAct)
5173 if self.webBrowserAct: 5209 btMenu.addAction(self.webBrowserAct)
5174 btMenu.addAction(self.webBrowserAct) 5210 if self.securityKeyMgmtAct is not None:
5211 btMenu.addAction(self.securityKeyMgmtAct)
5175 5212
5176 ptMenu = QMenu(self.tr("&Plugin Tools"), self) 5213 ptMenu = QMenu(self.tr("&Plugin Tools"), self)
5177 ptMenu.aboutToShow.connect(self.__showPluginToolsMenu) 5214 ptMenu.aboutToShow.connect(self.__showPluginToolsMenu)
5178 5215
5179 utMenu = QMenu(self.tr("&User Tools"), self) 5216 utMenu = QMenu(self.tr("&User Tools"), self)
6257 def __startWebBrowser(self): 6294 def __startWebBrowser(self):
6258 """ 6295 """
6259 Private slot to start the eric web browser. 6296 Private slot to start the eric web browser.
6260 """ 6297 """
6261 self.launchHelpViewer("") 6298 self.launchHelpViewer("")
6299
6300 @pyqtSlot()
6301 def __startFido2SecurityKeyMgmt(self):
6302 """
6303 Private slot to start the FIDO2 Security Key Management.
6304 """
6305 fido2Mgmt = os.path.join(os.path.dirname(__file__), "..", "eric7_fido2.py")
6306 QProcess.startDetached(
6307 PythonUtilities.getPythonExecutable(), [fido2Mgmt]
6308 )
6262 6309
6263 def __customViewer(self, home=None): 6310 def __customViewer(self, home=None):
6264 """ 6311 """
6265 Private slot to start a custom viewer. 6312 Private slot to start a custom viewer.
6266 6313
7693 """ 7740 """
7694 fn = os.path.join(Globals.getConfigDir(), "eric7tasks.etj") 7741 fn = os.path.join(Globals.getConfigDir(), "eric7tasks.etj")
7695 if os.path.exists(fn): 7742 if os.path.exists(fn):
7696 self.__tasksFile.readFile(fn) 7743 self.__tasksFile.readFile(fn)
7697 7744
7698 def __writeSession(self, filename="", crashSession=False): 7745 @pyqtSlot()
7746 def __showSessionsMenu(self):
7747 """
7748 Private slot to mofify the state of some session actions.
7749 """
7750 crashSessionsAvailable = bool(self.__getCrashedSessions())
7751
7752 menu = self.__menus["sessions"]
7753 menu.clear()
7754 menu.addAction(self.saveSessionAct)
7755 menu.addAction(self.loadSessionAct)
7756 menu.addSeparator()
7757 act = menu.addAction(self.tr("Load crash session..."), self.__loadCrashSession)
7758 act.setEnabled(crashSessionsAvailable)
7759 act = menu.addAction(
7760 self.tr("Clean crash sessions..."), self.__cleanCrashSessions
7761 )
7762 act.setEnabled(crashSessionsAvailable)
7763
7764 def __writeSession(self, filename=""):
7699 """ 7765 """
7700 Private slot to write the session data to a JSON file (.esj). 7766 Private slot to write the session data to a JSON file (.esj).
7701 7767
7702 @param filename name of a session file to write 7768 @param filename name of a session file to write
7703 @type str 7769 @type str
7704 @param crashSession flag indicating to write a crash session file
7705 @type bool
7706 @return flag indicating success 7770 @return flag indicating success
7707 @rtype bool 7771 @rtype bool
7708 """ 7772 """
7709 if filename: 7773 fn = (
7710 fn = filename 7774 filename
7711 elif crashSession: 7775 if filename
7712 fn = os.path.join(Globals.getConfigDir(), "eric7_crash_session.esj") 7776 else os.path.join(Globals.getConfigDir(), "eric7session.esj")
7713 else: 7777 )
7714 fn = os.path.join(Globals.getConfigDir(), "eric7session.esj")
7715 7778
7716 return self.__sessionFile.writeFile(fn) 7779 return self.__sessionFile.writeFile(fn)
7717 7780
7718 def __readSession(self, filename=""): 7781 def __readSession(self, filename=""):
7719 """ 7782 """
7786 if not sessionFile: 7849 if not sessionFile:
7787 return 7850 return
7788 7851
7789 self.__readSession(filename=sessionFile) 7852 self.__readSession(filename=sessionFile)
7790 7853
7854 def __crashSessionFilePath(self, globPattern=False):
7855 """
7856 Private method to generate a path name for a unique crash session file.
7857
7858 @param globPattern flag indicating to get the glob pattern for crash
7859 session files (defaults to False)
7860 @type bool (optional)
7861 @return crash session file path
7862 @rtype str
7863 """
7864 if globPattern:
7865 return os.path.join(Globals.getConfigDir(), "eric7_crash_session_*.esj")
7866 else:
7867 return os.path.join(
7868 Globals.getConfigDir(), f"eric7_crash_session_{os.getpid()}.esj"
7869 )
7870
7871 def __getCrashedSessions(self):
7872 """
7873 Private method to get a list of crash session file paths of crashed sessions.
7874
7875 Note: Crashed sessions are those, whose PID does not exist anymore.
7876
7877 @return list of crashed session file paths
7878 @rtype list of str
7879 """
7880 crashedSessionsList = []
7881
7882 crashSessionPattern = self.__crashSessionFilePath(globPattern=True)
7883 crashSessionPatternParts = crashSessionPattern.split("*", 1)
7884 # crashSessionPatternParts is used to extract the PID from the crash session
7885 # file path
7886 crashSessionsList = glob.glob(crashSessionPattern)
7887 if crashSessionsList:
7888 for crashSession in crashSessionsList:
7889 pid = crashSession.replace(crashSessionPatternParts[0], "").replace(
7890 crashSessionPatternParts[1], ""
7891 )
7892 if not psutil.pid_exists(int(pid)):
7893 # it is a real crash session
7894 crashedSessionsList.append(crashSession)
7895
7896 return crashedSessionsList
7897
7898 def __checkCrashSessionExists(self):
7899 """
7900 Private method to check for the existence of crash session files and
7901 select the one to open.
7902
7903 @return file path of the crash session file to open. An empty string indicates
7904 that no crash session file should be opened or exists.
7905 @rtype str
7906 """
7907 selectedCrashSessionFile = ""
7908 crashedSessionsList = self.__getCrashedSessions()
7909 if crashedSessionsList:
7910 dlg = EricListSelectionDialog(
7911 sorted(crashedSessionsList),
7912 selectionMode=QAbstractItemView.SelectionMode.SingleSelection,
7913 title=self.tr("Found Crash Sessions"),
7914 message=self.tr(
7915 "These crash session files were found. Select the one to"
7916 " open. Select 'Cancel' to not open a crash session."
7917 ),
7918 doubleClickOk=True,
7919 parent=self,
7920 )
7921 if dlg.exec() == QDialog.DialogCode.Accepted:
7922 selectedCrashSessionFile = dlg.getSelection()[0]
7923
7924 return selectedCrashSessionFile
7925
7791 def __deleteCrashSession(self): 7926 def __deleteCrashSession(self):
7792 """ 7927 """
7793 Private slot to delete the crash session file. 7928 Private slot to delete the crash session file.
7794 """ 7929 """
7795 fn = os.path.join(Globals.getConfigDir(), "eric7_crash_session.esj") 7930 fn = self.__crashSessionFilePath()
7796 if os.path.exists(fn): 7931 if os.path.exists(fn):
7797 with contextlib.suppress(OSError): 7932 with contextlib.suppress(OSError):
7798 os.remove(fn) 7933 os.remove(fn)
7799 7934
7800 def __writeCrashSession(self): 7935 def __writeCrashSession(self):
7804 if ( 7939 if (
7805 not self.__readingSession 7940 not self.__readingSession
7806 and not self.__disableCrashSession 7941 and not self.__disableCrashSession
7807 and Preferences.getUI("CrashSessionEnabled") 7942 and Preferences.getUI("CrashSessionEnabled")
7808 ): 7943 ):
7809 self.__writeSession(crashSession=True) 7944 self.__writeSession(filename=self.__crashSessionFilePath())
7810 7945
7811 def __readCrashSession(self): 7946 def __readCrashSession(self):
7812 """ 7947 """
7813 Private method to check for and read a crash session. 7948 Private method to check for and read a crash session.
7814 7949
7819 if ( 7954 if (
7820 not self.__disableCrashSession 7955 not self.__disableCrashSession
7821 and not self.__noCrashOpenAtStartup 7956 and not self.__noCrashOpenAtStartup
7822 and Preferences.getUI("OpenCrashSessionOnStartup") 7957 and Preferences.getUI("OpenCrashSessionOnStartup")
7823 ): 7958 ):
7824 fn = os.path.join(Globals.getConfigDir(), "eric7_crash_session.esj") 7959 fn = self.__checkCrashSessionExists()
7825 if os.path.exists(fn): 7960 if fn:
7826 yes = EricMessageBox.yesNo( 7961 res = self.__readSession(filename=fn)
7827 self, 7962 if res and Preferences.getUI("DeleteLoadedCrashSession"):
7828 self.tr("Crash Session found!"), 7963 os.remove(fn)
7829 self.tr(
7830 """A session file of a crashed session was"""
7831 """ found. Shall this session be restored?"""
7832 ),
7833 )
7834 if yes:
7835 res = self.__readSession(filename=fn)
7836 7964
7837 return res 7965 return res
7966
7967 @pyqtSlot()
7968 def __loadCrashSession(self):
7969 """
7970 Private slot to load a crash session.
7971 """
7972 fn = self.__checkCrashSessionExists()
7973 if fn:
7974 self.__readSession(filename=fn)
7975
7976 @pyqtSlot()
7977 def __cleanCrashSessions(self):
7978 """
7979 Private slot to clean all stale crash sessions.
7980 """
7981 from .DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
7982
7983 crashedSessionsList = self.__getCrashedSessions()
7984 if crashedSessionsList:
7985 dlg = DeleteFilesConfirmationDialog(
7986 parent=self,
7987 caption=self.tr("Clean stale crash sessions"),
7988 message=self.tr(
7989 "Do you really want to delete these stale crash session files?"
7990 ),
7991 files=sorted(crashedSessionsList),
7992 )
7993 if dlg.exec() == QDialog.DialogCode.Accepted:
7994 for crashSession in crashedSessionsList:
7995 os.remove(crashSession)
7838 7996
7839 def showFindFileByNameDialog(self): 7997 def showFindFileByNameDialog(self):
7840 """ 7998 """
7841 Public slot to show the Find File by Name dialog. 7999 Public slot to show the Find File by Name dialog.
7842 """ 8000 """

eric ide

mercurial