src/eric7/eric7.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8973
ad4848b7fd9b
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
5 #
6
7 """
8 eric Python IDE.
9
10 This is the main Python script that performs the necessary initialization
11 of the IDE and starts the Qt event loop.
12 """
13
14 import contextlib
15 import io
16 import logging
17 import os
18 import sys
19 import traceback
20 import time
21
22 originalPathString = os.getenv("PATH")
23
24 # generate list of arguments to be remembered for a restart
25 restartArgsList = ["--no-splash", "--plugin", "--debug", "--config",
26 "--settings", "--disable-crash", "--disable-plugin"]
27 restartArgs = [arg for arg in sys.argv[1:]
28 if arg.split("=", 1)[0] in restartArgsList]
29
30 sys.path.insert(1, os.path.dirname(__file__))
31
32 try:
33 from PyQt6.QtCore import qWarning, QLibraryInfo, QTimer, QCoreApplication
34 except ImportError:
35 try:
36 from tkinter import messagebox
37 except ImportError:
38 sys.exit(100)
39 messagebox.showerror(
40 "eric7 Error",
41 "PyQt could not be imported. Please make sure"
42 " it is installed and accessible.")
43 sys.exit(100)
44
45 try:
46 from PyQt6 import QtWebEngineWidgets
47 # __IGNORE_WARNING__ __IGNORE_EXCEPTION__
48 from PyQt6.QtWebEngineCore import QWebEngineUrlScheme
49 WEBENGINE_AVAILABLE = True
50 except ImportError:
51 WEBENGINE_AVAILABLE = False
52
53 # some global variables needed to start the application
54 args = None
55 mainWindow = None
56 splash = None
57 inMainLoop = None
58 app = None
59
60 if "--debug" in sys.argv:
61 del sys.argv[sys.argv.index("--debug")]
62 logging.basicConfig(level=logging.DEBUG)
63
64 for arg in sys.argv[:]:
65 if arg.startswith("--config="):
66 import Globals
67 configDir = arg.replace("--config=", "")
68 Globals.setConfigDir(configDir)
69 sys.argv.remove(arg)
70 elif arg.startswith("--settings="):
71 from PyQt6.QtCore import QSettings
72 settingsDir = os.path.expanduser(arg.replace("--settings=", ""))
73 if not os.path.isdir(settingsDir):
74 os.makedirs(settingsDir)
75 QSettings.setPath(
76 QSettings.Format.IniFormat, QSettings.Scope.UserScope, settingsDir)
77 sys.argv.remove(arg)
78
79 # make Third-Party package available as a packages repository
80 sys.path.insert(2, os.path.join(os.path.dirname(__file__),
81 "ThirdParty", "Jasy"))
82 sys.path.insert(2, os.path.join(os.path.dirname(__file__),
83 "DebugClients", "Python"))
84
85 from EricWidgets.EricApplication import EricApplication
86
87
88 def handleSingleApplication(ddindex):
89 """
90 Global function to handle the single application mode.
91
92 @param ddindex index of a '--' option in the options list
93 """
94 from EricWidgets.EricSingleApplication import EricSingleApplicationClient
95
96 client = EricSingleApplicationClient()
97 res = client.connect()
98 if res > 0:
99 if (
100 "--no-splash" in sys.argv and
101 sys.argv.index("--no-splash") < ddindex
102 ):
103 sys.argv.remove("--no-splash")
104 ddindex -= 1
105 if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex:
106 sys.argv.remove("--no-open")
107 ddindex -= 1
108 if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex:
109 sys.argv.remove("--no-crash")
110 if (
111 "--disable-crash" in sys.argv and
112 sys.argv.index("--disable-crash") < ddindex
113 ):
114 sys.argv.remove("--disable-crash")
115 ddindex -= 1
116 if "--debug" in sys.argv and sys.argv.index("--debug") < ddindex:
117 sys.argv.remove("--debug")
118 ddindex -= 1
119 for arg in sys.argv:
120 if (
121 arg.startswith("--config=") and
122 sys.argv.index(arg) < ddindex
123 ):
124 sys.argv.remove(arg)
125 ddindex -= 1
126 break
127 for arg in sys.argv:
128 if (
129 arg.startswith("--plugin=") and
130 sys.argv.index(arg) < ddindex
131 ):
132 sys.argv.remove(arg)
133 ddindex -= 1
134 break
135 for arg in sys.argv[:]:
136 if (
137 arg.startswith("--disable-plugin=") and
138 sys.argv.index(arg) < ddindex
139 ):
140 sys.argv.remove(arg)
141 ddindex -= 1
142
143 if len(sys.argv) > 1:
144 client.processArgs(sys.argv[1:])
145 sys.exit(0)
146 elif res < 0:
147 print("eric7: {0}".format(client.errstr()))
148 # __IGNORE_WARNING_M801__
149 sys.exit(res)
150
151
152 def excepthook(excType, excValue, tracebackobj):
153 """
154 Global function to catch unhandled exceptions.
155
156 @param excType exception type
157 @param excValue exception value
158 @param tracebackobj traceback object
159 """
160 from UI.Info import BugAddress
161 import Utilities
162 import Globals
163
164 # Workaround for a strange issue with QScintilla
165 if str(excValue) == "unable to convert a QVariant back to a Python object":
166 return
167
168 separator = '-' * 80
169 logFile = os.path.join(Globals.getConfigDir(), "eric7_error.log")
170 notice = (
171 """An unhandled exception occurred. Please report the problem\n"""
172 """using the error reporting dialog or via email to <{0}>.\n"""
173 """A log has been written to "{1}".\n\nError information:\n""".format(
174 BugAddress, logFile)
175 )
176 timeString = time.strftime("%Y-%m-%d, %H:%M:%S")
177
178 versionInfo = "\n{0}\n{1}".format(
179 separator, Utilities.generateVersionInfo())
180 pluginVersionInfo = Utilities.generatePluginsVersionInfo()
181 if pluginVersionInfo:
182 versionInfo += "\n{0}\n{1}".format(separator, pluginVersionInfo)
183 distroInfo = Utilities.generateDistroInfo()
184 if distroInfo:
185 versionInfo += "\n{0}\n{1}".format(separator, distroInfo)
186
187 if isinstance(excType, str):
188 tbinfo = tracebackobj
189 else:
190 tbinfofile = io.StringIO()
191 traceback.print_tb(tracebackobj, None, tbinfofile)
192 tbinfofile.seek(0)
193 tbinfo = tbinfofile.read()
194 errmsg = '{0}: \n{1}'.format(str(excType), str(excValue))
195 sections = ['', separator, timeString, separator, errmsg, separator,
196 tbinfo]
197 msg = '\n'.join(sections)
198 with contextlib.suppress(OSError), \
199 open(logFile, "w", encoding="utf-8") as f:
200 f.write(msg)
201 f.write(versionInfo)
202
203 if inMainLoop is None:
204 warning = notice + msg + versionInfo
205 print(warning) # __IGNORE_WARNING_M801__
206 else:
207 warning = notice + msg + versionInfo
208 # Escape &<> otherwise it's not visible in the error dialog
209 warning = (
210 warning
211 .replace("&", "&amp;")
212 .replace(">", "&gt;")
213 .replace("<", "&lt;")
214 )
215 qWarning(warning)
216
217
218 def uiStartUp():
219 """
220 Global function to finalize the start up of the main UI.
221
222 Note: It is activated by a zero timeout single-shot timer.
223 """
224 global args, mainWindow, splash
225
226 if splash:
227 splash.finish(mainWindow)
228 del splash
229
230 mainWindow.checkForErrorLog()
231 mainWindow.processArgs(args)
232 mainWindow.processInstallInfoFile()
233 mainWindow.checkProjectsWorkspace()
234 mainWindow.checkConfigurationStatus()
235 mainWindow.performVersionCheck()
236 mainWindow.checkPluginUpdatesAvailable()
237 mainWindow.autoConnectIrc()
238
239
240 def main():
241 """
242 Main entry point into the application.
243 """
244 from Globals import AppInfo
245 import Globals
246
247 global app, args, mainWindow, splash, restartArgs, inMainLoop
248
249 sys.excepthook = excepthook
250
251 from PyQt6.QtGui import QGuiApplication
252 QGuiApplication.setDesktopFileName("eric7.desktop")
253
254 options = [
255 ("--config=configDir",
256 "use the given directory as the one containing the config files"),
257 ("--debug", "activate debugging output to the console"),
258 ("--no-splash", "don't show the splash screen"),
259 ("--no-open",
260 "don't open anything at startup except that given in command"),
261 ("--no-crash", "don't check for a crash session file on startup"),
262 ("--disable-crash", "disable the support for crash sessions"),
263 ("--disable-plugin=<plug-in name>",
264 "disable the given plug-in (may be repeated)"),
265 ("--plugin=plugin-file",
266 "load the given plugin file (plugin development)"),
267 ("--settings=settingsDir",
268 "use the given directory to store the settings files"),
269 ("--small-screen",
270 "adjust the interface for screens smaller than FHD"),
271 ("--start-file", "load the most recently opened file"),
272 ("--start-multi", "load the most recently opened multi-project"),
273 ("--start-project", "load the most recently opened project"),
274 ("--start-session", "load the global session file"),
275 ("--",
276 "indicate that there are options for the program to be debugged"),
277 ("",
278 "(everything after that is considered arguments for this program)")
279 ]
280 appinfo = AppInfo.makeAppInfo(sys.argv,
281 "Eric7",
282 "[project | files... [--] [debug-options]]",
283 "A Python IDE",
284 options)
285
286 if "__PYVENV_LAUNCHER__" in os.environ:
287 del os.environ["__PYVENV_LAUNCHER__"]
288
289 # make sure our executable directory (i.e. that of the used Python
290 # interpreter) is included in the executable search path
291 pathList = os.environ["PATH"].split(os.pathsep)
292 exeDir = os.path.dirname(sys.executable)
293 if exeDir not in pathList:
294 pathList.insert(0, exeDir)
295 os.environ["PATH"] = os.pathsep.join(pathList)
296
297 from Toolbox import Startup
298 # set the library paths for plugins
299 Startup.setLibraryPaths()
300
301 if WEBENGINE_AVAILABLE:
302 scheme = QWebEngineUrlScheme(b"qthelp")
303 scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path)
304 scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme)
305 QWebEngineUrlScheme.registerScheme(scheme)
306
307 app = EricApplication(sys.argv)
308 ddindex = Startup.handleArgs(sys.argv, appinfo)
309
310 logging.debug("Importing Preferences")
311 import Preferences
312
313 if Preferences.getUI("SingleApplicationMode"):
314 handleSingleApplication(ddindex)
315
316 # set the application style sheet
317 app.setStyleSheetFile(Preferences.getUI("StyleSheet"))
318
319 # set the search path for icons
320 Startup.initializeResourceSearchPath(app)
321
322 # generate and show a splash window, if not suppressed
323 from UI.SplashScreen import SplashScreen, NoneSplashScreen
324 if "--no-splash" in sys.argv and sys.argv.index("--no-splash") < ddindex:
325 sys.argv.remove("--no-splash")
326 ddindex -= 1
327 splash = NoneSplashScreen()
328 elif not Preferences.getUI("ShowSplash"):
329 splash = NoneSplashScreen()
330 else:
331 splash = SplashScreen()
332 QCoreApplication.processEvents()
333
334 # modify the executable search path for the PyQt5 installer
335 if Globals.isWindowsPlatform():
336 pyqtDataDir = Globals.getPyQt6ModulesDirectory()
337 if os.path.exists(os.path.join(pyqtDataDir, "bin")):
338 path = os.path.join(pyqtDataDir, "bin")
339 else:
340 path = pyqtDataDir
341 os.environ["PATH"] = path + os.pathsep + os.environ["PATH"]
342
343 pluginFile = None
344 noopen = False
345 nocrash = False
346 disablecrash = False
347 disabledPlugins = []
348 if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex:
349 sys.argv.remove("--no-open")
350 ddindex -= 1
351 noopen = True
352 if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex:
353 sys.argv.remove("--no-crash")
354 ddindex -= 1
355 nocrash = True
356 if (
357 "--disable-crash" in sys.argv and
358 sys.argv.index("--disable-crash") < ddindex
359 ):
360 sys.argv.remove("--disable-crash")
361 ddindex -= 1
362 disablecrash = True
363 for arg in sys.argv[:]:
364 if (
365 arg.startswith("--disable-plugin=") and
366 sys.argv.index(arg) < ddindex
367 ):
368 # extract the plug-in name
369 pluginName = arg.replace("--disable-plugin=", "")
370 sys.argv.remove(arg)
371 ddindex -= 1
372 disabledPlugins.append(pluginName)
373 for arg in sys.argv:
374 if arg.startswith("--plugin=") and sys.argv.index(arg) < ddindex:
375 # extract the plugin development option
376 pluginFile = arg.replace("--plugin=", "").replace('"', "")
377 sys.argv.remove(arg)
378 ddindex -= 1
379 pluginFile = os.path.expanduser(pluginFile)
380 pluginFile = os.path.abspath(pluginFile)
381 break
382
383 # is there a set of filenames or options on the command line,
384 # if so, pass them to the UI
385 if len(sys.argv) > 1:
386 args = sys.argv[1:]
387
388 # get the Qt translations directory
389 qtTransDir = Preferences.getQtTranslationsDir()
390 if not qtTransDir:
391 qtTransDir = QLibraryInfo.path(
392 QLibraryInfo.LibraryPath.TranslationsPath)
393
394 # Load translation files and install them
395 loc = Startup.loadTranslators(qtTransDir, app, ("qscintilla",))
396
397 # Initialize SSL stuff
398 from EricNetwork.EricSslUtilities import initSSL
399 initSSL()
400
401 splash.showMessage(QCoreApplication.translate("eric7", "Starting..."))
402 # We can only import these after creating the EricApplication because they
403 # make Qt calls that need the EricApplication to exist.
404 from UI.UserInterface import UserInterface
405
406 splash.showMessage(
407 QCoreApplication.translate("eric7", "Generating Main Window..."))
408 mainWindow = UserInterface(app, loc, splash, pluginFile, disabledPlugins,
409 noopen, nocrash, disablecrash, restartArgs,
410 originalPathString)
411 app.lastWindowClosed.connect(app.quit)
412 mainWindow.show()
413
414 QTimer.singleShot(0, uiStartUp)
415
416 # generate a graphical error handler
417 from EricWidgets import EricErrorMessage
418 eMsg = EricErrorMessage.qtHandler()
419 eMsg.setMinimumSize(600, 400)
420
421 # start the event loop
422 inMainLoop = True
423 res = app.exec()
424 logging.debug("Shutting down, result %d", res)
425 logging.shutdown()
426 sys.exit(res)
427
428 if __name__ == '__main__':
429 main()

eric ide

mercurial