Skip to content

Commit d54a732

Browse files
authored
Merge pull request #3663 from OpenShot/mixin_support
Creating a mixin class to support both WebKit and WebEngine
2 parents 5a146e2 + c1dcfa3 commit d54a732

File tree

6 files changed

+326
-79
lines changed

6 files changed

+326
-79
lines changed

src/timeline/app.js

+5-16
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
*/
2828

2929
// Initialize Angular application
30-
/*global App, timeline, angular*/
31-
var timeline = null;
30+
/*global App, angular, timeline, init_mixin*/
3231
var App = angular.module("openshot-timeline", ["ui.bootstrap", "ngAnimate"]);
3332

3433

@@ -37,18 +36,8 @@ $(document).ready(function () {
3736

3837
var body_object = $("body");
3938

40-
// Check for Qt Integration
41-
new QWebChannel(qt.webChannelTransport, function (channel) {
42-
timeline = channel.objects.timeline;
43-
timeline.qt_log("INFO", "Qt Ready");
44-
45-
// Only enable Qt once Angular as initialized
46-
angular.element(document).ready(function () {
47-
timeline.qt_log("INFO", "Angular Ready");
48-
body_object.scope().enableQt();
49-
});
50-
51-
});
39+
// Initialize Qt Mixin (WebEngine or WebKit)
40+
init_mixin();
5241

5342
/// Capture window resize event, and resize scrollable divs (i.e. track container)
5443
$(window).resize(function () {
@@ -64,8 +53,8 @@ $(document).ready(function () {
6453

6554
track_controls.height(new_track_height);
6655
$("#scrolling_tracks").height(new_track_height);
67-
body_object.scope().playhead_height = $("#track-container").height();
68-
$(".playhead-line").height(body_object.scope().playhead_height);
56+
body_object.scope().playhead_height = $("#track-container").height();
57+
$(".playhead-line").height(body_object.scope().playhead_height);
6958
});
7059

7160
// Bind to keydown event (to detect SHIFT)

src/timeline/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<script type="text/javascript" src="media/js/angular-animate.min.js"></script>
1818

1919
<!-- OpenShot JavaScript Sources -->
20-
<script type="text/javascript" src="js/qwebchannel.js"></script>
20+
{{MIXIN_JS_INCLUDE}}
2121
<script type="text/javascript" src="app.js"></script>
2222
<script type="text/javascript" src="js/functions.js"></script>
2323
<script type="text/javascript" src="js/controllers.js"></script>

src/timeline/js/mixin_webengine.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @file
3+
* @brief JavaScript file to initialize QtWebEngine JS mixin
4+
* @author Jonathan Thomas <[email protected]>
5+
*
6+
* @section LICENSE
7+
*
8+
* Copyright (c) 2008-2018 OpenShot Studios, LLC
9+
* <http://www.openshotstudios.com/>. This file is part of
10+
* OpenShot Video Editor, an open-source project dedicated to
11+
* delivering high quality video editing and animation solutions to the
12+
* world. For more information visit <http://www.openshot.org/>.
13+
*
14+
* OpenShot Video Editor is free software: you can redistribute it
15+
* and/or modify it under the terms of the GNU General Public License
16+
* as published by the Free Software Foundation, either version 3 of the
17+
* License, or (at your option) any later version.
18+
*
19+
* OpenShot Video Editor is distributed in the hope that it will be
20+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
* GNU General Public License for more details.
23+
*
24+
* You should have received a copy of the GNU General Public License
25+
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
26+
*/
27+
28+
/*global timeline, angular, QWebChannel*/
29+
var timeline = null;
30+
31+
function init_mixin() {
32+
33+
// Check for Qt Integration
34+
var channel = new QWebChannel(qt.webChannelTransport, function (channel) {
35+
timeline = channel.objects.timeline;
36+
timeline.qt_log("INFO", "Qt Ready");
37+
38+
// Only enable Qt once Angular as initialized
39+
angular.element(document).ready(function () {
40+
timeline.qt_log("INFO", "Angular Ready");
41+
$("body").scope().enableQt();
42+
});
43+
44+
});
45+
46+
}

src/timeline/js/mixin_webkit.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @file
3+
* @brief JavaScript file to initialize QtWebKit JS mixin
4+
* @author Jonathan Thomas <[email protected]>
5+
*
6+
* @section LICENSE
7+
*
8+
* Copyright (c) 2008-2018 OpenShot Studios, LLC
9+
* <http://www.openshotstudios.com/>. This file is part of
10+
* OpenShot Video Editor, an open-source project dedicated to
11+
* delivering high quality video editing and animation solutions to the
12+
* world. For more information visit <http://www.openshot.org/>.
13+
*
14+
* OpenShot Video Editor is free software: you can redistribute it
15+
* and/or modify it under the terms of the GNU General Public License
16+
* as published by the Free Software Foundation, either version 3 of the
17+
* License, or (at your option) any later version.
18+
*
19+
* OpenShot Video Editor is distributed in the hope that it will be
20+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
* GNU General Public License for more details.
23+
*
24+
* You should have received a copy of the GNU General Public License
25+
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
26+
*/
27+
28+
/*global timeline, angular*/
29+
30+
function init_mixin() {
31+
32+
// Only enable Qt once Angular as initialized
33+
angular.element(document).ready(function () {
34+
if (typeof timeline !== "undefined") {
35+
timeline.qt_log("INFO", "Qt Ready");
36+
$("body").scope().enableQt();
37+
}
38+
timeline.qt_log("INFO", "Angular Ready");
39+
});
40+
41+
}

src/windows/views/timeline_mixins.py

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"""
2+
@file
3+
@brief This file allows for switching between QtWebEngine and QtWebKit
4+
@author Jonathan Thomas <[email protected]>
5+
6+
@section LICENSE
7+
8+
Copyright (c) 2008-2018 OpenShot Studios, LLC
9+
(http://www.openshotstudios.com). This file is part of
10+
OpenShot Video Editor (http://www.openshot.org), an open-source project
11+
dedicated to delivering high quality video editing and animation solutions
12+
to the world.
13+
14+
OpenShot Video Editor is free software: you can redistribute it and/or modify
15+
it under the terms of the GNU General Public License as published by
16+
the Free Software Foundation, either version 3 of the License, or
17+
(at your option) any later version.
18+
19+
OpenShot Video Editor is distributed in the hope that it will be useful,
20+
but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
GNU General Public License for more details.
23+
24+
You should have received a copy of the GNU General Public License
25+
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
26+
"""
27+
28+
import os
29+
from classes import info
30+
from PyQt5.QtCore import QFileInfo, pyqtSlot, QUrl, Qt, QCoreApplication, QTimer
31+
from PyQt5.QtGui import QCursor, QKeySequence, QColor
32+
from PyQt5.QtWidgets import QMenu
33+
from classes.logger import log
34+
from functools import partial
35+
36+
37+
try:
38+
# Attempt to import QtWebEngine
39+
from PyQt5.QtWebChannel import QWebChannel
40+
from PyQt5.QtWebEngineWidgets import QWebEngineView
41+
IS_WEBENGINE_VALID = True
42+
except ImportError:
43+
QWebEngineView = object # Prevent inheritance errors
44+
IS_WEBENGINE_VALID = False
45+
46+
try:
47+
# Attempt to import QtWebKit
48+
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
49+
IS_WEBKIT_VALID = True
50+
except ImportError:
51+
QWebView = object # Prevent inheritance errors
52+
QWebPage = object
53+
IS_WEBKIT_VALID = False
54+
55+
56+
class TimelineBaseMixin(object):
57+
"""OpenShot Timeline Base Mixin Class"""
58+
def __init__(self):
59+
"""Initialization code required for parent widget"""
60+
self.document_is_ready = False
61+
self.html_path = html_path = os.path.join(info.PATH, 'timeline', 'index.html')
62+
63+
def run_js(self, code, callback=None, retries=0):
64+
"""Run javascript code snippet"""
65+
raise Exception("run_js not implemented")
66+
67+
def get_html(self):
68+
"""Get HTML for Timeline, adjusted for mixin"""
69+
raise Exception("get_html not implemented")
70+
71+
72+
class TimelineQtWebEngineMixin(TimelineBaseMixin, QWebEngineView):
73+
"""QtWebEngine Timeline Widget"""
74+
75+
def __init__(self):
76+
"""Initialization code required for widget"""
77+
TimelineBaseMixin.__init__(self)
78+
QWebEngineView.__init__(self)
79+
80+
# Set background color of timeline
81+
self.page().setBackgroundColor(QColor("#363636"))
82+
83+
# Delete the webview when closed
84+
self.setAttribute(Qt.WA_DeleteOnClose)
85+
86+
# Enable smooth scrolling on timeline
87+
self.settings().setAttribute(self.settings().ScrollAnimatorEnabled, True)
88+
89+
# Set url from configuration (QUrl takes absolute paths for file system paths, create from QFileInfo)
90+
self.webchannel = QWebChannel(self.page())
91+
self.setHtml(self.get_html(), QUrl.fromLocalFile(QFileInfo(self.html_path).absoluteFilePath()))
92+
self.page().setWebChannel(self.webchannel)
93+
94+
# Connect signal of javascript initialization to our javascript reference init function
95+
self.page().loadStarted.connect(self.setup_js_data)
96+
97+
def run_js(self, code, callback=None, retries=0):
98+
'''Run JS code async and optionally have a callback for response'''
99+
# Check if document.Ready has fired in JS
100+
if not self.document_is_ready:
101+
# Not ready, try again in a few moments
102+
if retries == 0:
103+
# Log the script contents, the first time
104+
log.debug("run_js() called before document ready event. Script queued: %s" % code)
105+
elif retries % 5 == 0:
106+
log.warning("WebEngine backend still not ready after {} retries.".format(retries))
107+
else:
108+
log.debug("Script queued, {} retries so far".format(retries))
109+
QTimer.singleShot(200, partial(self.run_js, code, callback, retries + 1))
110+
return None
111+
else:
112+
# Execute JS code
113+
if callback:
114+
return self.page().runJavaScript(code, callback)
115+
else:
116+
return self.page().runJavaScript(code)
117+
118+
def setup_js_data(self):
119+
# Export self as a javascript object in webview
120+
self.webchannel.registerObject('timeline', self)
121+
122+
def get_html(self):
123+
"""Get HTML for Timeline, adjusted for mixin"""
124+
html = open(self.html_path, 'r', encoding='utf-8').read()
125+
html = html.replace('{{MIXIN_JS_INCLUDE}}',
126+
'''
127+
<script type="text/javascript" src="js/qwebchannel.js"></script>
128+
<script type="text/javascript" src="js/mixin_webengine.js"></script>
129+
130+
''')
131+
return html
132+
133+
def keyPressEvent(self, event):
134+
""" Keypress callback for timeline """
135+
key_value = event.key()
136+
if (key_value == Qt.Key_Shift or key_value == Qt.Key_Control):
137+
138+
# Only pass a few keystrokes to the webview (CTRL and SHIFT)
139+
return QWebEngineView.keyPressEvent(self, event)
140+
141+
else:
142+
# Ignore most keypresses
143+
event.ignore()
144+
145+
146+
class LoggingWebPage(QWebPage):
147+
"""Override console.log message to display messages"""
148+
def javaScriptConsoleMessage(self, msg, line, source):
149+
log.warning('JS: %s line %d: %s' % (source, line, msg))
150+
151+
class TimelineQtWebKitMixin(TimelineBaseMixin, QWebView):
152+
"""QtWebKit Timeline Widget"""
153+
154+
def __init__(self):
155+
"""Initialization code required for widget"""
156+
TimelineBaseMixin.__init__(self)
157+
QWebView.__init__(self)
158+
159+
# Delete the webview when closed
160+
self.setAttribute(Qt.WA_DeleteOnClose)
161+
162+
# Connect logging web page (for console.log)
163+
page = LoggingWebPage()
164+
self.setPage(page)
165+
166+
# Disable image caching on timeline
167+
self.settings().setObjectCacheCapacities(0, 0, 0)
168+
169+
# Set url from configuration (QUrl takes absolute paths for file system paths, create from QFileInfo)
170+
self.setHtml(self.get_html(), QUrl.fromLocalFile(QFileInfo(self.html_path).absoluteFilePath()))
171+
172+
# Connect signal of javascript initialization to our javascript reference init function
173+
self.page().mainFrame().javaScriptWindowObjectCleared.connect(self.setup_js_data)
174+
175+
def run_js(self, code, callback=None, retries=0):
176+
'''Run JS code async and optionally have a callback for response'''
177+
# Check if document.Ready has fired in JS
178+
if not self.document_is_ready:
179+
# Not ready, try again in a few moments
180+
if retries == 0:
181+
# Log the script contents, the first time
182+
log.debug("run_js() called before document ready event. Script queued: %s" % code)
183+
elif retries % 5 == 0:
184+
log.warning("WebKit backend still not ready after {} retries.".format(retries))
185+
else:
186+
log.debug("Script queued, {} retries so far".format(retries))
187+
QTimer.singleShot(200, partial(self.run_js, code, callback, retries + 1))
188+
return None
189+
else:
190+
# Execute JS code
191+
if callback:
192+
# Pass output to callback
193+
callback(self.page().mainFrame().evaluateJavaScript(code))
194+
else:
195+
return self.page().mainFrame().evaluateJavaScript(code)
196+
197+
def setup_js_data(self):
198+
# Export self as a javascript object in webview
199+
self.page().mainFrame().addToJavaScriptWindowObject('timeline', self)
200+
self.page().mainFrame().addToJavaScriptWindowObject('mainWindow', self.window)
201+
202+
def get_html(self):
203+
"""Get HTML for Timeline, adjusted for mixin"""
204+
html = open(self.html_path, 'r', encoding='utf-8').read()
205+
html = html.replace('{{MIXIN_JS_INCLUDE}}',
206+
'''
207+
<script type="text/javascript" src="js/mixin_webkit.js"></script>
208+
209+
''')
210+
return html
211+
212+
def keyPressEvent(self, event):
213+
""" Keypress callback for timeline """
214+
key_value = event.key()
215+
if (key_value == Qt.Key_Shift or key_value == Qt.Key_Control):
216+
217+
# Only pass a few keystrokes to the webview (CTRL and SHIFT)
218+
return QWebView.keyPressEvent(self, event)
219+
220+
else:
221+
# Ignore most keypresses
222+
event.ignore()
223+
224+
# Set correct Mixin (with QtWebEngine preference)
225+
if IS_WEBENGINE_VALID:
226+
TimelineMixin = TimelineQtWebEngineMixin
227+
elif IS_WEBKIT_VALID:
228+
TimelineMixin = TimelineQtWebKitMixin
229+
else:
230+
TimelineMixin = None

0 commit comments

Comments
 (0)