Skip to content

Commit 9ae08d0

Browse files
committed
App: re-work launching, detach GUI from app
- Massively re-work the application setup, so that *far* less is done in the __init__() for OpenShotApp. In particular... * All GUI setup is deferred to a separate `.gui()` method, which is now optional (the application instance can be created without automatically creating the full GUI). * A new `StartupMessage` class is created, and any message to be displayed in a QMessageBox is now stored in an object instance and placed on a queue. OpenShotApp.gui() will automatically call the app's `.show_errors()` method to process the queue, but it can also be called before/without `.gui()`. * The app instance's references to `info` and `openshot` are non-global, so they're passed into several methods which need access to them (just something to look out for) - `launch.py` is also re-worked in accordance with the new design of classes.app.OpenShotApp - Multiple media files can now be passed as separate arguments on the command line, and they will be added to the initial project in order. The number of positional arguments is no longer capped at 1 (except in the case of an `.osp` file)
1 parent f9184c5 commit 9ae08d0

File tree

2 files changed

+212
-137
lines changed

2 files changed

+212
-137
lines changed

src/classes/app.py

+137-113
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
"""
2929

3030
import atexit
31+
import sys
3132
import os
3233
import platform
33-
import sys
3434
import traceback
3535
import json
3636

@@ -40,20 +40,6 @@
4040
from PyQt5.QtGui import QPalette, QColor, QFontDatabase, QFont
4141
from PyQt5.QtWidgets import QApplication, QStyleFactory, QMessageBox
4242

43-
try:
44-
# This apparently has to be done before loading QtQuick
45-
# (via QtWebEgine) AND before creating the QApplication instance
46-
QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
47-
from OpenGL import GL # noqa
48-
except (ImportError, AttributeError):
49-
pass
50-
51-
try:
52-
# QtWebEngineWidgets must be loaded prior to creating a QApplication
53-
# But on systems with only WebKit, this will fail (and we ignore the failure)
54-
from PyQt5.QtWebEngineWidgets import QWebEngineView
55-
except ImportError:
56-
pass
5743

5844
def get_app():
5945
""" Get the current QApplication instance of OpenShot """
@@ -64,12 +50,26 @@ def get_settings():
6450
"""Get a reference to the app's settings object"""
6551
return get_app().get_settings()
6652

67-
try:
68-
# Enable High-DPI resolutions
69-
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
70-
except AttributeError:
71-
pass # Quietly fail for older Qt5 versions
7253

54+
class StartupError:
55+
""" Store and later display an error encountered during setup"""
56+
levels = {
57+
"warning": QMessageBox.warning,
58+
"error": QMessageBox.critical,
59+
}
60+
61+
def __init__(self, title="", message="", level="warning"):
62+
"""Create an error message object, populated with details"""
63+
self.title = title
64+
self.message = message
65+
self.level = level
66+
67+
def show(self):
68+
"""Display the stored error message"""
69+
box_call = self.levels[self.level]
70+
box_call(None, self.title, self.message)
71+
if self.level == "error":
72+
sys.exit()
7373

7474

7575
class OpenShotApp(QApplication):
@@ -78,42 +78,71 @@ class OpenShotApp(QApplication):
7878

7979
def __init__(self, *args, mode=None):
8080
QApplication.__init__(self, *args)
81+
self.mode = mode or "normal"
82+
self.args = list(*args)
83+
self.errors = []
8184

8285
try:
8386
# Import modules
8487
from classes import info
8588
from classes.logger import log, reroute_output
8689

8790
# Log the session's start
88-
import time
89-
log.info("------------------------------------------------")
90-
log.info(time.asctime().center(48))
91-
log.info('Starting new session'.center(48))
92-
93-
from classes import (
94-
settings, project_data, updates, language, ui_util,
95-
logger_libopenshot
96-
)
91+
if mode != "unittest":
92+
import time
93+
log.info("-" * 48)
94+
log.info(time.asctime().center(48))
95+
log.info('Starting new session'.center(48))
96+
97+
log.debug("Starting up in {} mode".format(self.mode))
98+
log.debug("Command line: {}".format(self.args))
99+
100+
from classes import settings, project_data, updates
97101
import openshot
98102

99103
# Re-route stdout and stderr to logger
100-
reroute_output()
104+
if mode != "unittest":
105+
reroute_output()
106+
101107
except ImportError as ex:
102108
tb = traceback.format_exc()
103109
log.error('OpenShotApp::Import Error', exc_info=1)
104-
QMessageBox.warning(None, "Import Error",
105-
"Module: %(name)s\n\n%(tb)s" % {"name": ex.name, "tb": tb})
106-
# Stop launching and exit
110+
self.errors.append(StartupError(
111+
"Import Error",
112+
"Module: %(name)s\n\n%(tb)s" % {"name": ex.name, "tb": tb},
113+
level="error"))
114+
# Stop launching
107115
raise
108116
except Exception:
109117
log.error('OpenShotApp::Init Error', exc_info=1)
110118
sys.exit()
111119

120+
self.info = info
121+
112122
# Log some basic system info
123+
self.log = log
124+
self.show_environment(info, openshot)
125+
if self.mode != "unittest":
126+
self.check_libopenshot_version(info, openshot)
127+
128+
# Init data objects
129+
self.settings = settings.SettingStore(parent=self)
130+
self.settings.load()
131+
self.project = project_data.ProjectDataStore()
132+
self.updates = updates.UpdateManager()
133+
# It is important that the project is the first listener if the key gets update
134+
self.updates.add_listener(self.project)
135+
self.updates.reset()
136+
137+
# Set location of OpenShot program (for libopenshot)
138+
openshot.Settings.Instance().PATH_OPENSHOT_INSTALL = info.PATH
139+
140+
def show_environment(self, info, openshot):
141+
log = self.log
113142
try:
114-
log.info("------------------------------------------------")
143+
log.info("-" * 48)
115144
log.info(("OpenShot (version %s)" % info.SETUP['version']).center(48))
116-
log.info("------------------------------------------------")
145+
log.info("-" * 48)
117146

118147
log.info("openshot-qt version: %s" % info.VERSION)
119148
log.info("libopenshot version: %s" % openshot.OPENSHOT_VERSION_FULL)
@@ -135,60 +164,47 @@ def __init__(self, *args, mode=None):
135164
except Exception:
136165
log.debug("Error displaying dependency/system details", exc_info=1)
137166

138-
# Setup application
139-
self.setApplicationName('openshot')
140-
self.setApplicationVersion(info.SETUP['version'])
141-
try:
142-
# Qt 5.7+ only
143-
self.setDesktopFile("org.openshot.OpenShot")
144-
except AttributeError:
145-
pass
146-
147-
# Init settings
148-
self.settings = settings.SettingStore(parent=self)
149-
self.settings.load()
150-
151167
# Init and attach exception handler
152168
from classes import exceptions
153169
sys.excepthook = exceptions.ExceptionHandler
154170

155-
# Init translation system
156-
language.init_language()
157-
158-
# Detect minimum libopenshot version
171+
def check_libopenshot_version(self, info, openshot):
172+
"""Detect minimum libopenshot version"""
159173
_ = self._tr
160-
libopenshot_version = openshot.OPENSHOT_VERSION_FULL
161-
if mode != "unittest" and libopenshot_version < info.MINIMUM_LIBOPENSHOT_VERSION:
162-
QMessageBox.warning(
163-
None,
164-
_("Wrong Version of libopenshot Detected"),
165-
_("<b>Version %(minimum_version)s is required</b>, "
166-
"but %(current_version)s was detected. "
167-
"Please update libopenshot or download our latest installer.")
168-
% {
169-
"minimum_version": info.MINIMUM_LIBOPENSHOT_VERSION,
170-
"current_version": libopenshot_version,
171-
})
172-
# Stop launching and exit
173-
sys.exit()
174-
175-
# Set location of OpenShot program (for libopenshot)
176-
openshot.Settings.Instance().PATH_OPENSHOT_INSTALL = info.PATH
177-
178-
# Tests of project data loading/saving
179-
self.project = project_data.ProjectDataStore()
174+
ver = openshot.OPENSHOT_VERSION_FULL
175+
min = info.MINIMUM_LIBOPENSHOT_VERSION
176+
if ver >= min:
177+
return True
178+
179+
self.errors.append(StartupError(
180+
_("Wrong Version of libopenshot Detected"),
181+
_("<b>Version %(minimum_version)s is required</b>, "
182+
"but %(current_version)s was detected. "
183+
"Please update libopenshot or download our latest installer.") % {
184+
"minimum_version": min,
185+
"current_version": ver,
186+
},
187+
level="error",
188+
))
189+
raise RuntimeError(
190+
"libopenshot version {} found, minimum is {}".format(ver, min))
191+
192+
def gui(self):
193+
from classes import language, ui_util, logger_libopenshot
180194

181-
# Init Update Manager
182-
self.updates = updates.UpdateManager()
195+
_ = self._tr
196+
info = self.info
197+
log = self.log
183198

184-
# It is important that the project is the first listener if the key gets update
185-
self.updates.add_listener(self.project)
199+
# Init translation system
200+
language.init_language()
186201

187202
# Load ui theme if not set by OS
188203
ui_util.load_theme()
189204

190205
# Test for permission issues (and display message if needed)
191206
try:
207+
log.debug("Testing write access to user directory")
192208
# Create test paths
193209
TEST_PATH_DIR = os.path.join(info.USER_PATH, 'PERMISSION')
194210
TEST_PATH_FILE = os.path.join(TEST_PATH_DIR, 'test.osp')
@@ -201,15 +217,17 @@ def __init__(self, *args, mode=None):
201217
os.rmdir(TEST_PATH_DIR)
202218
except PermissionError as ex:
203219
log.error('Failed to create file %s', TEST_PATH_FILE, exc_info=1)
204-
QMessageBox.warning(
205-
None, _("Permission Error"),
206-
_("%(error)s. Please delete <b>%(path)s</b> and launch OpenShot again." % {
220+
self.errors.append(StartupError(
221+
_("Permission Error"),
222+
_("%(error)s. Please delete <b>%(path)s</b> and launch OpenShot again.") % {
207223
"error": str(ex),
208224
"path": info.USER_PATH,
209-
}))
210-
# Stop launching and exit
211-
raise
212-
sys.exit()
225+
},
226+
level="error",
227+
))
228+
229+
# Display any outstanding startup messages
230+
self.show_errors()
213231

214232
# Start libopenshot logging thread
215233
self.logger_libopenshot = logger_libopenshot.LoggerLibOpenShot()
@@ -219,6 +237,7 @@ def __init__(self, *args, mode=None):
219237
self.context_menu_object = None
220238

221239
# Set Font for any theme
240+
log.debug("Loading UI theme")
222241
if self.settings.get("theme") != "No Theme":
223242
# Load embedded font
224243
try:
@@ -269,72 +288,77 @@ def __init__(self, *args, mode=None):
269288

270289
# Create main window
271290
from windows.main_window import MainWindow
272-
self.window = MainWindow(mode=mode)
291+
log.debug("Creating main interface window")
292+
self.window = MainWindow(mode=self.mode)
273293

274-
# Reset undo/redo history
275-
self.updates.reset()
294+
# Clear undo/redo history
276295
self.window.updateStatusChanged(False, False)
277296

278297
# Connect our exit signals
279298
self.aboutToQuit.connect(self.cleanup)
280299

281-
log.info('Process command-line arguments: %s' % args)
282-
if len(args[0]) == 2:
283-
path = args[0][1]
284-
if ".osp" in path:
285-
# Auto load project passed as argument
286-
self.window.OpenProjectSignal.emit(path)
287-
else:
288-
# Apply the default settings and Auto import media file
289-
self.project.load("")
290-
self.window.filesView.add_file(path)
291-
else:
300+
args = self.args
301+
if len(args) < 2:
292302
# Recover backup file (this can't happen until after the Main Window has completely loaded)
293303
self.window.RecoverBackup.emit()
304+
return
305+
306+
log.info('Process command-line arguments: %s' % args)
307+
308+
# Auto load project if passed as argument
309+
if args[1].endswith(".osp"):
310+
self.window.OpenProjectSignal.emit(args[1])
311+
return
312+
313+
# Start a new project and auto import any media files
314+
self.project.load("")
315+
for arg in args[1:]:
316+
self.window.filesView.add_file(arg)
294317

295318
def settings_load_error(self, filepath=None):
296319
"""Use QMessageBox to warn the user of a settings load issue"""
297320
_ = self._tr
298-
QMessageBox.warning(
299-
None,
321+
self.errors.append(StartupError(
300322
_("Settings Error"),
301-
_("Error loading settings file: %(file_path)s. Settings will be reset.")
302-
% {"file_path": filepath}
303-
)
323+
_("Error loading settings file: %(file_path)s. Settings will be reset.") % {
324+
"file_path": filepath
325+
},
326+
level="warning",
327+
))
304328

305329
def get_settings(self):
306330
if not hasattr(self, "settings"):
307331
return None
308332
return self.settings
309333

334+
def show_errors(self):
335+
count = len(self.errors)
336+
if count > 0:
337+
self.log.warning("Displaying {} startup messages".format(count))
338+
for error in self.errors:
339+
error.show()
340+
310341
def _tr(self, message):
311342
return self.translate("", message)
312343

313-
# Start event loop
314-
def run(self):
315-
""" Start the primary Qt event loop for the interface """
316-
return self.exec_()
317-
318344
@pyqtSlot()
319345
def cleanup(self):
320346
"""aboutToQuit signal handler for application exit"""
321347
try:
322-
from classes.logger import log
323348
self.settings.save()
324349
except Exception:
325-
log.error("Couldn't save user settings on exit.", exc_info=1)
350+
self.log.error("Couldn't save user settings on exit.", exc_info=1)
326351

327352

328-
# Log the session's end
329353
@atexit.register
330354
def onLogTheEnd():
331355
""" Log when the primary Qt event loop ends """
332356
try:
333357
from classes.logger import log
334358
import time
335-
log.info('OpenShot\'s session ended'.center(48))
359+
log.info("OpenShot's session ended".center(48))
336360
log.info(time.asctime().center(48))
337-
log.info("================================================")
361+
log.info("=" * 48)
338362
except Exception:
339-
from classes.logger import log
363+
from logging import log
340364
log.debug('Failed to write session ended log')

0 commit comments

Comments
 (0)