src/eric7/eric7_ide.py

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

eric ide

mercurial