Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create new dummy webview backend, fix unit-test launching #3938

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
sudo add-apt-repository ppa:openshot.developers/libopenshot-daily
sudo apt update
sudo apt install libopenshot-audio-dev libopenshot-dev python3-openshot
sudo apt install qttranslations5-l10n libssl-dev xvfb
sudo apt install qttranslations5-l10n libssl-dev
sudo apt install python3-pyqt5 python3-pyqt5.qtsvg python3-pyqt5.qtwebengine python3-pyqt5.qtopengl python3-zmq python3-xdg
pip3 install setuptools wheel
pip3 install cx_Freeze==6.1 distro defusedxml requests certifi chardet urllib3
Expand All @@ -30,4 +30,4 @@ jobs:

- name: Test
shell: bash
run: xvfb-run --auto-servernum --server-num=1 --server-args "-screen 0 1920x1080x24" python3 ./src/tests/query_tests.py
run: python3 ./src/tests/query_tests.py -platform minimal
200 changes: 95 additions & 105 deletions src/classes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,40 +39,17 @@
from PyQt5.QtGui import QPalette, QColor, QFontDatabase, QFont
from PyQt5.QtWidgets import QApplication, QStyleFactory, QMessageBox

try:
# This apparently has to be done before loading QtQuick
# (via QtWebEgine) AND before creating the QApplication instance
QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
from OpenGL import GL # noqa
except (ImportError, AttributeError):
pass

try:
# QtWebEngineWidgets must be loaded prior to creating a QApplication
# But on systems with only WebKit, this will fail (and we ignore the failure)
from PyQt5.QtWebEngineWidgets import QWebEngineView
except ImportError:
pass

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


def get_app():
""" Returns the current QApplication instance of OpenShot """
return QApplication.instance()


class OpenShotApp(QApplication):
""" This class is the primary QApplication for OpenShot.
mode=None (normal), mode=unittest (testing)"""

def __init__(self, *args, mode=None):
QApplication.__init__(self, *args)
""" This class is the primary QApplication for OpenShot."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
# Import modules
from classes import info
Expand Down Expand Up @@ -143,7 +120,7 @@ def __init__(self, *args, mode=None):
# Detect minimum libopenshot version
_ = self._tr
libopenshot_version = openshot.OPENSHOT_VERSION_FULL
if mode != "unittest" and libopenshot_version < info.MINIMUM_LIBOPENSHOT_VERSION:
if info.LAUNCH_MODE == "gui" and libopenshot_version < info.MINIMUM_LIBOPENSHOT_VERSION:
QMessageBox.warning(
None,
_("Wrong Version of libopenshot Detected"),
Expand All @@ -169,32 +146,31 @@ def __init__(self, *args, mode=None):
# It is important that the project is the first listener if the key gets update
self.updates.add_listener(self.project)

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

# Test for permission issues (and display message if needed)
try:
# Create test paths
TEST_PATH_DIR = os.path.join(info.USER_PATH, 'PERMISSION')
TEST_PATH_FILE = os.path.join(TEST_PATH_DIR, 'test.osp')
os.makedirs(TEST_PATH_DIR, exist_ok=True)
with open(TEST_PATH_FILE, 'w') as f:
f.write('{}')
f.flush()
# Delete test paths
os.unlink(TEST_PATH_FILE)
os.rmdir(TEST_PATH_DIR)
except PermissionError as ex:
log.error('Failed to create file %s', TEST_PATH_FILE, exc_info=1)
QMessageBox.warning(
None, _("Permission Error"),
_("%(error)s. Please delete <b>%(path)s</b> and launch OpenShot again." % {
"error": str(ex),
"path": info.USER_PATH,
}))
# Stop launching and exit
raise
sys.exit()
if info.LAUNCH_MODE == "gui":
try:
# Create test paths
TEST_PATH_DIR = os.path.join(info.USER_PATH, 'PERMISSION')
TEST_PATH_FILE = os.path.join(TEST_PATH_DIR, 'test.osp')
os.makedirs(TEST_PATH_DIR, exist_ok=True)
with open(TEST_PATH_FILE, 'w') as f:
f.write('{}')
f.flush()
# Delete test paths
os.unlink(TEST_PATH_FILE)
os.rmdir(TEST_PATH_DIR)
except PermissionError as ex:
log.error('Failed to create file %s', TEST_PATH_FILE, exc_info=1)
QMessageBox.warning(
None,
_("Permission Error"),
_("%(error)s. Please delete <b>%(path)s</b> and launch OpenShot again." % {
"error": str(ex),
"path": info.USER_PATH,
}))
# Stop launching and exit
raise
sys.exit()

# Start libopenshot logging thread
self.logger_libopenshot = logger_libopenshot.LoggerLibOpenShot()
Expand All @@ -203,11 +179,77 @@ def __init__(self, *args, mode=None):
# Track which dockable window received a context menu
self.context_menu_object = None

if info.LAUNCH_MODE == "gui":
self.configure_gui()

# Create main window
from windows.main_window import MainWindow
self.window = MainWindow()

# Reset undo/redo history
self.updates.reset()
self.window.updateStatusChanged(False, False)

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

log.info('Process command-line arguments: %s' % args)
if len(args[0]) == 2:
path = args[0][1]
if ".osp" in path:
# Auto load project passed as argument
self.window.OpenProjectSignal.emit(path)
else:
# Apply the default settings and Auto import media file
self.project.load("")
self.window.filesView.add_file(path)
else:
if info.LAUNCH_MODE != "unittest":
# Recover backup file (this can't happen until after the Main Window has completely loaded)
self.window.RecoverBackup.emit()

def settings_load_error(self, filepath=None):
"""Use QMessageBox to warn the user of a settings load issue"""
_ = self._tr
from classes import info
if info.LAUNCH_MODE == "gui":
QMessageBox.warning(
None,
_("Settings Error"),
_("Error loading settings file: %(file_path)s. Settings will be reset.")
% {"file_path": filepath}
)

def _tr(self, message):
return self.translate("", message)

# Start event loop
def run(self):
""" Start the primary Qt event loop for the interface """
return self.exec_()

@pyqtSlot()
def cleanup(self):
"""aboutToQuit signal handler for application exit"""
try:
from classes.logger import log
self.settings.save()
except Exception:
log.error("Couldn't save user settings on exit.", exc_info=1)

def configure_gui(self):
"""Apply application GUI settings for fonts and colors"""
from classes import info, ui_util
from classes.logger import log

# Load ui theme if not set by OS
ui_util.load_theme(self.settings)

# Set Font for any theme
if self.settings.get("theme") != "No Theme":
# Load embedded font
try:
log.info("Setting font to %s" % os.path.join(info.IMAGES_PATH, "fonts", "Ubuntu-R.ttf"))
log.info("Setting font to %s", os.path.join(info.IMAGES_PATH, "fonts", "Ubuntu-R.ttf"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still need the %s placeholder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. logging's formatter processes template strings with % escapes, the remaining arguments are the inputs to the formatting.

There's one key difference between passing the format string and inputs as arguments, and doing the string interpolation up front: logging calls benefit from lazy evaluation. If logging is disabled (or the logging.INFO level isn't configured for output), not only will the final, formatted string not be generated, but the os.path.join() won't even be executed, since its output isn't needed.

"Setting font to %s" % os.path.join(info.IMAGES_PATH, "fonts", "Ubuntu-R.ttf") always has to do the os.path.join() and generate the string, in order to pass it to the log.info() call — whether or not it will ultimately be logged.

font_id = QFontDatabase.addApplicationFont(os.path.join(info.IMAGES_PATH, "fonts", "Ubuntu-R.ttf"))
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
font = QFont(font_family)
Expand Down Expand Up @@ -252,58 +294,6 @@ def __init__(self, *args, mode=None):
self.setPalette(darkPalette)
self.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 0px solid white; }")

# Create main window
from windows.main_window import MainWindow
self.window = MainWindow(mode=mode)

# Reset undo/redo history
self.updates.reset()
self.window.updateStatusChanged(False, False)

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

log.info('Process command-line arguments: %s' % args)
if len(args[0]) == 2:
path = args[0][1]
if ".osp" in path:
# Auto load project passed as argument
self.window.OpenProjectSignal.emit(path)
else:
# Apply the default settings and Auto import media file
self.project.load("")
self.window.filesView.add_file(path)
else:
# Recover backup file (this can't happen until after the Main Window has completely loaded)
self.window.RecoverBackup.emit()

def settings_load_error(self, filepath=None):
"""Use QMessageBox to warn the user of a settings load issue"""
_ = self._tr
QMessageBox.warning(
None,
_("Settings Error"),
_("Error loading settings file: %(file_path)s. Settings will be reset.")
% {"file_path": filepath}
)

def _tr(self, message):
return self.translate("", message)

# Start event loop
def run(self):
""" Start the primary Qt event loop for the interface """
return self.exec_()

@pyqtSlot()
def cleanup(self):
"""aboutToQuit signal handler for application exit"""
try:
from classes.logger import log
self.settings.save()
except Exception:
log.error("Couldn't save user settings on exit.", exc_info=1)


# Log the session's end
@atexit.register
Expand Down
3 changes: 3 additions & 0 deletions src/classes/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
# Desktop launcher ID, for Linux
DESKTOP_ID = "org.openshot.OpenShot.desktop"

# Mode of execution, will be adjusted to 'unittest' by test runner
LAUNCH_MODE = 'gui'

# Blender minimum version required (a string value)
BLENDER_MIN_VERSION = "2.80"

Expand Down
7 changes: 2 additions & 5 deletions src/classes/ui_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,17 @@
from PyQt5 import uic

from classes.logger import log
from classes import settings

from . import openshot_rc # noqa

DEFAULT_THEME_NAME = "Humanity"


def load_theme():
def load_theme(settings):
""" Load the current OS theme, or fallback to a default one """

s = settings.get_settings()

# If theme not reported by OS
if QIcon.themeName() == '' and s.get("theme") != "No Theme":
if QIcon.themeName() == '' and settings.get("theme") != "No Theme":

# Address known Ubuntu bug of not reporting configured theme name, use default ubuntu theme
if os.getenv('DESKTOP_SESSION') == 'ubuntu':
Expand Down
22 changes: 21 additions & 1 deletion src/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@
sys.path.append(openshot_qt.OPENSHOT_PATH)
from classes import info

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication

try:
# This apparently has to be done before loading QtQuick
# (via QtWebEgine) AND before creating the QApplication instance
QGuiApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
from OpenGL import GL # noqa
# Enable High-DPI resolutions
QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
except (ImportError, AttributeError):
pass

try:
# QtWebEngineWidgets must be loaded prior to creating a QApplication
# But on systems with only WebKit, this will fail (and we ignore the failure)
from PyQt5.QtWebEngineWidgets import QWebEngineView # noqa
except ImportError:
pass


def main():
""""Initialize settings (not implemented) and create main window/application."""
Expand All @@ -70,7 +90,7 @@ def main():
help="Load Qt's QAbstractItemModelTester into data models "
'(requires Qt 5.11+)')
parser.add_argument('-b', '--web-backend', action='store',
choices=['auto', 'webkit', 'webengine'], default='auto',
choices=['auto', 'webkit', 'webengine', 'dummy'], default='auto',
help="Web backend to use for Timeline")
parser.add_argument('-d', '--debug', action='store_true',
help='Enable debugging output')
Expand Down
39 changes: 25 additions & 14 deletions src/tests/query_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,40 @@
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""

import sys, os
# Import parent folder (so it can find other imports)
PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if PATH not in sys.path:
sys.path.append(PATH)

import random
import sys
import os
import unittest
import uuid
from PyQt5.QtGui import QGuiApplication
from classes.app import OpenShotApp
from classes import info
import openshot # Python module for libopenshot (required video editing module installed separately)
import json
import logging

import openshot

from PyQt5.QtWidgets import QApplication

# Import parent folder (so it can find other imports)
try:
from classes import info
except ImportError:
PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if PATH not in sys.path:
sys.path.append(PATH)
from classes import info

# Configure for unit testing, no Timeline web view and minimal log output
info.LAUNCH_MODE = "unittest"
info.WEB_BACKEND = "dummy"
info.LOG_LEVEL_CONSOLE = logging.WARNING

class TestQueryClass(unittest.TestCase):
""" Unit test class for Query class """

@classmethod
def setUpClass(TestQueryClass):
""" Init unit test data """
from classes.app import OpenShotApp

# Create Qt application
TestQueryClass.app = OpenShotApp([], mode="unittest")
TestQueryClass.app = OpenShotApp(sys.argv)
TestQueryClass.clip_ids = []
TestQueryClass.file_ids = []
TestQueryClass.transition_ids = []
Expand Down Expand Up @@ -319,5 +330,5 @@ def test_add_file(self):


if __name__ == '__main__':
app = QGuiApplication(sys.argv)
app = QApplication(sys.argv)
unittest.main()
Loading