Merge lp:~ic90/openlp/animated-alerts into lp:openlp

Proposed by Nico Opiyo
Status: Merged
Merged at revision: 2904
Proposed branch: lp:~ic90/openlp/animated-alerts
Merge into: lp:openlp
Diff against target: 1027 lines (+784/-41)
9 files modified
openlp/core/display/html/display.css (+80/-0)
openlp/core/display/html/display.html (+9/-4)
openlp/core/display/html/display.js (+240/-13)
openlp/core/display/window.py (+3/-2)
openlp/plugins/alerts/alertsplugin.py (+3/-1)
openlp/plugins/alerts/lib/alertsmanager.py (+33/-3)
openlp/plugins/alerts/lib/alertstab.py (+69/-17)
package.json (+1/-1)
tests/js/test_display.js (+346/-0)
To merge this branch: bzr merge lp:~ic90/openlp/animated-alerts
Reviewer Review Type Date Requested Status
Tomas Groth Needs Fixing
Raoul Snyman Pending
Nico Opiyo Pending
Review via email: mp+369540@code.launchpad.net

Commit message

Added scrolling alerts

Description of the change

Refactored the tests and optimized alert display code plus fixed spacing

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/134/ for more details

Revision history for this message
Tomas Groth (tomasgroth) wrote :

Limiting needs fixing

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) wrote :

I tested a bit and found a few things:
When displaying a new scrolling alert, it does not seem to scroll slowly into view, instead it "jumps" a bit into the screen.
When using trying to activate a new alert, while an alert is already displayed, the new alert is never displayed.

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) wrote :

This patch makes the queueing of alerts work: https://bin.snyman.info/mmm2y5nx

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'openlp/core/display/html/display.css'
2--- openlp/core/display/html/display.css 1970-01-01 00:00:00 +0000
3+++ openlp/core/display/html/display.css 2019-07-01 17:51:00 +0000
4@@ -0,0 +1,80 @@
5+@keyframes alert-scrolling-text {
6+ 0% { transform: translateX(100%); opacity: 1; }
7+ 99% { opacity: 1; }
8+ 100% { transform: translateX(-101%); opacity: 0;}
9+}
10+
11+/* ALERT BACKGROUND STYLING */
12+.bg-default {
13+ position: absolute;
14+ margin: 0;
15+ padding: 0;
16+ left: 0;
17+ z-index: 11;
18+ width: 100%;
19+ height: 0;
20+ min-height: 0;
21+ overflow: hidden;
22+ transform: translate(0,0);
23+ transition: min-height 1s ease-out .5s;
24+ white-space: nowrap;
25+ display: flex;
26+ flex-direction: row;
27+ align-items: center;
28+ /* align-content: center; */
29+}
30+
31+.bg-default span {
32+ display: inline-block;
33+ padding-left: 120%;
34+}
35+
36+.show-bg {
37+ /* height: auto; */
38+ min-height: 25%;
39+ transition: min-height 1s ease-in .5s;
40+}
41+
42+.middle {
43+ align-items: center;
44+}
45+
46+.alert-container {
47+ position: absolute;
48+ display: flex;
49+ flex-direction: row;
50+ height: 100vh;
51+ width: 100vw;
52+}
53+
54+.top {
55+ align-items: flex-start;
56+}
57+
58+.bottom {
59+ align-items: flex-end;
60+}
61+
62+/* ALERT TEXT STYLING */
63+#alert {
64+ z-index: 100;
65+ overflow: visible;
66+ color: #ffffff;
67+ font-size: 40pt;
68+ padding: 0;
69+ margin: 0;
70+ opacity: 0;
71+ transition: opacity .5s linear;
72+}
73+
74+#alert.hide-text {
75+ opacity: 0;
76+}
77+
78+#alert.show-text {
79+ transform: none;
80+ transition: none;
81+ animation: none;
82+ padding: auto 5px;
83+ opacity: 1;
84+}
85
86=== modified file 'openlp/core/display/html/display.html'
87--- openlp/core/display/html/display.html 2019-03-17 10:36:12 +0000
88+++ openlp/core/display/html/display.html 2019-07-01 17:51:00 +0000
89@@ -2,7 +2,7 @@
90 <html>
91 <head>
92 <title>Display Window</title>
93- <link href="reveal.css" rel="stylesheet">
94+ <link href="reveal.css" rel="stylesheet">
95 <style type="text/css">
96 body {
97 background: transparent !important;
98@@ -24,16 +24,21 @@
99 visibility: visible;
100 z-index: -1;
101 }
102- </style>
103+ </style>
104+ <link rel="stylesheet" type="text/css" href="display.css"></link>
105 <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
106 <script type="text/javascript" src="reveal.js"></script>
107 <script type="text/javascript" src="display.js"></script>
108 </head>
109+
110 <body>
111+ <div class="alert-container">
112+ <div id="alert-background" class="bg-default"><span id="alert">Testing alerts</span></div>
113+ </div>
114 <div class="reveal">
115- <div id="global-background" class="slide-background present" data-loaded="true"></div>
116+ <div id="global-background" class="slide-background present" data-loaded="true"></div>
117 <div class="slides"></div>
118 <div class="footer"></div>
119- </div>
120+ </div>
121 </body>
122 </html>
123
124=== modified file 'openlp/core/display/html/display.js'
125--- openlp/core/display/html/display.js 2019-06-21 20:53:42 +0000
126+++ openlp/core/display/html/display.js 2019-07-01 17:51:00 +0000
127@@ -53,6 +53,50 @@
128 };
129
130 /**
131+ * Transition state enumeration
132+ */
133+var TransitionState = {
134+ EntranceTransition: "entranceTransition",
135+ NoTransition: "noTransition",
136+ ExitTransition: "exitTransition"
137+};
138+
139+/**
140+ * Animation state enumeration
141+ */
142+var AnimationState = {
143+ NoAnimation: "noAnimation",
144+ ScrollingText: "scrollingText",
145+ NonScrollingText: "noScrollingText"
146+};
147+
148+/**
149+ * Alert location enumeration
150+ */
151+var AlertLocation = {
152+ Top: 0,
153+ Middle: 1,
154+ Bottom: 2
155+};
156+
157+/**
158+ * Alert state enumeration
159+ */
160+var AlertState = {
161+ Displaying: "displaying",
162+ NotDisplaying: "notDisplaying"
163+}
164+
165+/**
166+ * Alert delay enumeration
167+ */
168+var AlertDelay = {
169+ FiftyMilliseconds: 50,
170+ OneSecond: 1000,
171+ OnePointFiveSeconds: 1500
172+}
173+
174+/**
175 * Return an array of elements based on the selector query
176 * @param {string} selector - The selector to find elements
177 * @returns {array} An array of matching elements
178@@ -234,7 +278,12 @@
179 * The Display object is what we use from OpenLP
180 */
181 var Display = {
182+ _alerts: [],
183 _slides: {},
184+ _alertSettings: {},
185+ _alertState: AlertState.NotDisplaying,
186+ _transitionState: TransitionState.NoTransition,
187+ _animationState: AnimationState.NoAnimation,
188 _revealConfig: {
189 margin: 0.0,
190 minScale: 1.0,
191@@ -356,19 +405,197 @@
192 /**
193 * Display an alert
194 * @param {string} text - The alert text
195- * @param {int} location - The location of the text (top, middle or bottom)
196- */
197- alert: function (text, location) {
198- console.debug(" alert text: " + text, ", location: " + location);
199- /*
200- * The implementation should show an alert.
201- * It should be able to handle receiving a new alert before a previous one is "finished", basically queueing it.
202- */
203- return;
204-},
205-
206- /**
207- * Add a slides. If the slide exists but the HTML is different, update the slide.
208+ * @param {string} JSON object - The settings for the alert object e.g '{"backgroundColor": "rgb(255, 85, 0)",
209+ * "location": 1, "fontFace": "Open Sans Condensed", "fontSize": 90, "fontColor": "rgb(255, 255, 255)",
210+ * "timeout": 10, "repeat": 2, "scroll": true}'
211+ */
212+ alert: function (text, alertSettings) {
213+ var alertBackground = $('#alert-background')[0];
214+ var alertText = $('#alert')[0];
215+ if (text == "") {
216+ return null;
217+ }
218+ else {
219+ if (this._alertState === AlertState.Displaying) {
220+ Display.addAlertToQueue(text, alert_settings);
221+ }
222+ }
223+ var settings = JSON.parse(alertSettings);
224+ this._alertSettings = settings;
225+ Display.setAlertText(text, settings.fontColor, settings.fontFace, settings.fontSize);
226+ Display.setAlertLocation(settings.location);
227+ /* Check if the alert is a queued alert */
228+ if (Display._alertState !== AlertState.DisplayingFromQueue) {
229+ Display._alertState = AlertState.Displaying;
230+ }
231+
232+ alertBackground.addEventListener('transitionend', Display.alertTransitionEndEvent, false);
233+ alertText.addEventListener('animationend', Display.alertAnimationEndEvent, false);
234+
235+ Display.showAlertBackground(settings.backgroundColor);
236+ },
237+ /**
238+ * Add an alert to the alert queue
239+ * @param {string} text - The alert text to be displayed
240+ * @param {string} setttings - JSON object containing the settings for the alert
241+ */
242+ addAlertToQueue: function (text, settings) {
243+ var alertObject = {text: text, settings: settings};
244+ this._alerts.push(JSON.stringify(alertObject));
245+ return null;
246+ },
247+ /**
248+ * Set Alert Text
249+ * @param {string} text - The alert text to display
250+ */
251+ setAlertText: function (text, color, fontFace, fontSize) {
252+ var alertText = $("#alert")[0];
253+ alertText.textContent = text;
254+ alertText.style.color = color;
255+ alertText.style.fontFamily = fontFace;
256+ alertText.style.fontSize = fontSize + "pt";
257+ },
258+ /**
259+ * The alertTransitionEndEvent called after a transition has ended
260+ */
261+ alertTransitionEndEvent: function (e) {
262+ e.stopPropagation();
263+ console.debug("Transition end event reached");
264+ if (Display._transitionState === TransitionState.EntranceTransition) {
265+ Display._transitionState = TransitionState.NoTransition;
266+ Display.showAlertText(Display._alertSettings);
267+ }
268+ else if (Display._transitionState === TransitionState.ExitTransition) {
269+ Display._transitionState = TransitionState.NoTransition;
270+ Display.removeAlertLocation(Display._alertSettings.location);
271+ Display.clearAlertSettings();
272+ setTimeout(function () {
273+ Display.showNextAlert();
274+ }, AlertDelay.OnePointFiveSeconds);
275+
276+ }
277+ },
278+ /**
279+ * The alertAnimationEndEvent called after an animation has ended
280+ */
281+ alertAnimationEndEvent: function (e) {
282+ e.stopPropagation();
283+ Display.hideAlertText();
284+ },
285+ /**
286+ * Start background entrance transition for display of alert
287+ * @param {string} hex_color - The background color of the alert
288+ */
289+ showAlertBackground: function (bg_color) {
290+ var alertBackground = $("#alert-background")[0];
291+ alertBackground.classList.add("show-bg");
292+ alertBackground.style.backgroundColor = bg_color;
293+ this._transitionState = TransitionState.EntranceTransition;
294+ },
295+ /**
296+ * Set the location of the alert
297+ * @param {int} location - Integer number with the location of the alert on screen
298+ */
299+ setAlertLocation: function (location) {
300+ var alertContainer = $(".alert-container")[0];
301+
302+ switch (location) {
303+ case AlertLocation.Top:
304+ alertContainer.classList.add("top");
305+ break;
306+ case AlertLocation.Middle:
307+ alertContainer.classList.add("middle");
308+ break;
309+ case AlertLocation.Bottom:
310+ default:
311+ alertContainer.classList.add("bottom");
312+ break;
313+ }
314+ },
315+ /**
316+ * Remove the location class set after displaying the alert
317+ * @param {int} location - Integer number with the location of the alert on screen
318+ */
319+ removeAlertLocation: function (location) {
320+ var alertContainer = $(".alert-container")[0];
321+ console.debug("The value of location for removal is: " + location);
322+
323+ switch (location) {
324+ case AlertLocation.Top:
325+ alertContainer.classList.remove("top");
326+ break;
327+ case AlertLocation.Middle:
328+ alertContainer.classList.remove("middle");
329+ break;
330+ case AlertLocation.Bottom:
331+ default:
332+ alertContainer.classList.remove("bottom");
333+ break;
334+ }
335+ },
336+ /**
337+ * Hide the alert background after the alert has been shown
338+ */
339+ hideAlertBackground: function () {
340+ var alertBackground = $("#alert-background")[0];
341+ alertBackground.classList.remove("show-bg");
342+ this._transitionState = TransitionState.ExitTransition;
343+ this._alertState = AlertState.NotDisplaying;
344+ },
345+ /**
346+ * Sets the alert text styles correctly after the entrance transition has ended.
347+ * @param {json} settings object - The settings to use for the animation
348+ */
349+ showAlertText: function (settings) {
350+ var alertText = $("#alert")[0];
351+
352+ if (settings.scroll) {
353+ var animationSettings = "alert-scrolling-text " + settings.timeout +
354+ "s linear 0.6s " + settings.repeat + " normal";
355+ alertText.style.animation = animationSettings;
356+ Display._animationState = AnimationState.ScrollingText;
357+ }
358+ else {
359+ Display._animationState = AnimationState.NonScrollingText;
360+ alertText.classList.add("show-text");
361+ setTimeout (function () {
362+ Display._animationState = AnimationState.NoAnimation;
363+ alertText.classList.add("hide-text");
364+ alertText.classList.remove("show-text");
365+ Display.hideAlertText();
366+ }, settings.timeout * AlertDelay.OneSecond);
367+ }
368+ },
369+ /**
370+ * Reset styling and hide the alert text after displaying the animation
371+ */
372+ hideAlertText: function () {
373+ var alertText = $('#alert')[0];
374+ Display._animationState = AnimationState.NoAnimation;
375+ alertText.style.animation = "";
376+ Display.hideAlertBackground();
377+ },
378+ /**
379+ * Display the next alert in the queue
380+ */
381+ showNextAlert: function () {
382+ if (Display._alerts.length > 0) {
383+ var alertObject = JSON.parse(this._alerts.shift());
384+ this._alertState = AlertState.DisplayingFromQueue;
385+ Display.alert(alertObject.text, alertObject.settings);
386+ }
387+ else {
388+ return null;
389+ }
390+ },
391+ /**
392+ * Clears the alert settings after displaying an alert
393+ */
394+ clearAlertSettings: function () {
395+ this._alertSettings = {};
396+ },
397+ /**
398+ * Add a slide. If the slide exists but the HTML is different, update the slide.
399 * @param {string} verse - The verse number, e.g. "v1"
400 * @param {string} text - The HTML for the verse, e.g. "line1<br>line2"
401 * @param {string} footer_text - The HTML for the footer"
402
403=== modified file 'openlp/core/display/window.py'
404--- openlp/core/display/window.py 2019-06-21 22:09:36 +0000
405+++ openlp/core/display/window.py 2019-07-01 17:51:00 +0000
406@@ -397,8 +397,9 @@
407 self.scale = scale
408 self.run_javascript('Display.setScale({scale});'.format(scale=scale * 100))
409
410- def alert(self, text, location):
411+ def alert(self, text, settings):
412 """
413 Set an alert
414 """
415- self.run_javascript('Display.alert({text}, {location});'.format(text=text, location=location))
416+ self.run_javascript('Display.alert("{text}", \'{settings}\');'.format(text=text, settings=settings))
417+ # TODO: Add option to prevent scrolling
418
419=== modified file 'openlp/plugins/alerts/alertsplugin.py'
420--- openlp/plugins/alerts/alertsplugin.py 2019-04-13 13:00:22 +0000
421+++ openlp/plugins/alerts/alertsplugin.py 2019-07-01 17:51:00 +0000
422@@ -126,7 +126,9 @@
423 'alerts/location': AlertLocation.Bottom,
424 'alerts/background color': '#660000',
425 'alerts/font color': '#ffffff',
426- 'alerts/timeout': 5
427+ 'alerts/timeout': 10,
428+ 'alerts/repeat': 1,
429+ 'alerts/scroll': True
430 }
431
432
433
434=== modified file 'openlp/plugins/alerts/lib/alertsmanager.py'
435--- openlp/plugins/alerts/lib/alertsmanager.py 2019-04-13 13:00:22 +0000
436+++ openlp/plugins/alerts/lib/alertsmanager.py 2019-07-01 17:51:00 +0000
437@@ -23,7 +23,9 @@
438 The :mod:`~openlp.plugins.alerts.lib.alertsmanager` module contains the part of the plugin which manages storing and
439 displaying of alerts.
440 """
441-from PyQt5 import QtCore
442+import json
443+
444+from PyQt5 import QtCore, QtGui
445
446 from openlp.core.common.i18n import translate
447 from openlp.core.common.mixins import LogMixin, RegistryProperties
448@@ -83,8 +85,26 @@
449 not Settings().value('core/display on monitor')):
450 return
451 text = self.alert_list.pop(0)
452- alert_tab = self.parent().settings_tab
453- self.live_controller.displays[0].alert(text, alert_tab.location)
454+
455+ # Get the rgb color format of the font & background hex colors from settings
456+ rgb_font_color = self.hex_to_rgb(QtGui.QColor(Settings().value('alerts/font color')))
457+ rgb_background_color = self.hex_to_rgb(QtGui.QColor(Settings().value('alerts/background color')))
458+
459+ # Put alert settings together in dict that will be passed to Display in Javascript
460+ alert_settings = {
461+ 'backgroundColor': rgb_background_color,
462+ 'location': Settings().value('alerts/location'),
463+ 'fontFace': Settings().value('alerts/font face'),
464+ 'fontSize': Settings().value('alerts/font size'),
465+ 'fontColor': rgb_font_color,
466+ 'timeout': Settings().value('alerts/timeout'),
467+ 'repeat': Settings().value('alerts/repeat'),
468+ 'scroll': Settings().value('alerts/scroll')
469+ }
470+ self.live_controller.displays[0].alert(text, json.dumps(alert_settings))
471+ # Check to see if we have a timer running.
472+ # if self.timer_id == 0:
473+ # self.timer_id = self.startTimer(int(alert_tab.timeout) * 1000)
474
475 def timerEvent(self, event):
476 """
477@@ -98,3 +118,13 @@
478 self.killTimer(self.timer_id)
479 self.timer_id = 0
480 self.generate_alert()
481+
482+ def hex_to_rgb(self, rgb_values):
483+ """
484+ Converts rgb color values from QColor to rgb string
485+
486+ :param rgb_values:
487+ :return: rgb color string
488+ :rtype: string
489+ """
490+ return "rgb(" + str(rgb_values.red()) + ", " + str(rgb_values.green()) + ", " + str(rgb_values.blue()) + ")"
491
492=== modified file 'openlp/plugins/alerts/lib/alertstab.py'
493--- openlp/plugins/alerts/lib/alertstab.py 2019-04-13 13:00:22 +0000
494+++ openlp/plugins/alerts/lib/alertstab.py 2019-07-01 17:51:00 +0000
495@@ -47,35 +47,56 @@
496 self.font_layout.addRow(self.font_label, self.font_combo_box)
497 self.font_color_label = QtWidgets.QLabel(self.font_group_box)
498 self.font_color_label.setObjectName('font_color_label')
499- self.color_layout = QtWidgets.QHBoxLayout()
500- self.color_layout.setObjectName('color_layout')
501 self.font_color_button = ColorButton(self.font_group_box)
502 self.font_color_button.setObjectName('font_color_button')
503- self.color_layout.addWidget(self.font_color_button)
504- self.color_layout.addSpacing(20)
505- self.background_color_label = QtWidgets.QLabel(self.font_group_box)
506- self.background_color_label.setObjectName('background_color_label')
507- self.color_layout.addWidget(self.background_color_label)
508- self.background_color_button = ColorButton(self.font_group_box)
509- self.background_color_button.setObjectName('background_color_button')
510- self.color_layout.addWidget(self.background_color_button)
511- self.font_layout.addRow(self.font_color_label, self.color_layout)
512+ self.font_layout.addRow(self.font_color_label, self.font_color_button)
513 self.font_size_label = QtWidgets.QLabel(self.font_group_box)
514 self.font_size_label.setObjectName('font_size_label')
515 self.font_size_spin_box = QtWidgets.QSpinBox(self.font_group_box)
516 self.font_size_spin_box.setObjectName('font_size_spin_box')
517 self.font_layout.addRow(self.font_size_label, self.font_size_spin_box)
518- self.timeout_label = QtWidgets.QLabel(self.font_group_box)
519+ self.left_layout.addWidget(self.font_group_box)
520+ # Background Settings
521+ self.background_group_box = QtWidgets.QGroupBox(self.left_column)
522+ self.background_group_box.setObjectName('background_group_box')
523+ self.background_layout = QtWidgets.QFormLayout(self.background_group_box)
524+ self.background_layout.setObjectName('background_settings_layout')
525+ self.background_color_label = QtWidgets.QLabel(self.background_group_box)
526+ self.background_color_label.setObjectName('background_color_label')
527+ self.background_color_button = ColorButton(self.background_group_box)
528+ self.background_color_button.setObjectName('background_color_button')
529+ self.background_layout.addRow(self.background_color_label,self.background_color_button)
530+ self.left_layout.addWidget(self.background_group_box)
531+ # Scroll Settings
532+ self.scroll_group_box = QtWidgets.QGroupBox(self.left_column)
533+ self.scroll_group_box.setObjectName('scroll_group_box')
534+ self.scroll_group_layout = QtWidgets.QFormLayout(self.scroll_group_box)
535+ self.scroll_group_layout.setObjectName('scroll_group_layout')
536+ self.scroll_check_box = QtWidgets.QCheckBox(self.scroll_group_box)
537+ self.scroll_check_box.setObjectName('scroll_check_box')
538+ self.scroll_group_layout.addRow(self.scroll_check_box)
539+ self.repeat_label = QtWidgets.QLabel(self.scroll_group_box)
540+ self.repeat_label.setObjectName('repeat_label')
541+ self.repeat_spin_box = QtWidgets.QSpinBox(self.scroll_group_box)
542+ self.repeat_spin_box.setObjectName('repeat_spin_box')
543+ self.scroll_group_layout.addRow(self.repeat_label, self.repeat_spin_box)
544+ self.left_layout.addWidget(self.scroll_group_box)
545+ # Other Settings
546+ self.settings_group_box = QtWidgets.QGroupBox(self.left_column)
547+ self.settings_group_box.setObjectName('settings_group_box')
548+ self.settings_layout = QtWidgets.QFormLayout(self.settings_group_box)
549+ self.settings_layout.setObjectName('settings_layout')
550+ self.timeout_label = QtWidgets.QLabel(self.settings_group_box)
551 self.timeout_label.setObjectName('timeout_label')
552- self.timeout_spin_box = QtWidgets.QSpinBox(self.font_group_box)
553+ self.timeout_spin_box = QtWidgets.QSpinBox(self.settings_group_box)
554 self.timeout_spin_box.setMaximum(180)
555 self.timeout_spin_box.setObjectName('timeout_spin_box')
556- self.font_layout.addRow(self.timeout_label, self.timeout_spin_box)
557+ self.settings_layout.addRow(self.timeout_label, self.timeout_spin_box)
558 self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self.font_group_box)
559 self.vertical_label.setObjectName('vertical_label')
560 self.vertical_combo_box.setObjectName('vertical_combo_box')
561- self.font_layout.addRow(self.vertical_label, self.vertical_combo_box)
562- self.left_layout.addWidget(self.font_group_box)
563+ self.settings_layout.addRow(self.vertical_label, self.vertical_combo_box)
564+ self.left_layout.addWidget(self.settings_group_box)
565 self.left_layout.addStretch()
566 self.preview_group_box = QtWidgets.QGroupBox(self.right_column)
567 self.preview_group_box.setObjectName('preview_group_box')
568@@ -92,16 +113,22 @@
569 self.font_combo_box.activated.connect(self.on_font_combo_box_clicked)
570 self.timeout_spin_box.valueChanged.connect(self.on_timeout_spin_box_changed)
571 self.font_size_spin_box.valueChanged.connect(self.on_font_size_spin_box_changed)
572+ self.repeat_spin_box.valueChanged.connect(self.on_repeat_spin_box_changed)
573+ self.scroll_check_box.toggled.connect(self.scroll_check_box_toggled)
574
575 def retranslate_ui(self):
576- self.font_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Font'))
577+ self.font_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Font Settings'))
578 self.font_label.setText(translate('AlertsPlugin.AlertsTab', 'Font name:'))
579 self.font_color_label.setText(translate('AlertsPlugin.AlertsTab', 'Font color:'))
580 self.background_color_label.setText(UiStrings().BackgroundColorColon)
581 self.font_size_label.setText(translate('AlertsPlugin.AlertsTab', 'Font size:'))
582 self.font_size_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit))
583+ self.background_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Background Settings'))
584+ self.settings_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Other Settings'))
585 self.timeout_label.setText(translate('AlertsPlugin.AlertsTab', 'Alert timeout:'))
586 self.timeout_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds))
587+ self.repeat_label.setText(translate('AlertsPlugin.AlertsTab', 'Repeat (no. of times):'))
588+ self.scroll_check_box.setText(translate('AlertsPlugin.AlertsTab', 'Enable Scrolling'))
589 self.preview_group_box.setTitle(UiStrings().Preview)
590 self.font_preview.setText(UiStrings().OpenLP)
591
592@@ -140,6 +167,24 @@
593 self.font_size = self.font_size_spin_box.value()
594 self.update_display()
595
596+ def on_repeat_spin_box_changed(self):
597+ """
598+ The repeat spin box has changed
599+ """
600+ self.repeat = self.repeat_spin_box.value()
601+ self.changed = True
602+
603+ def scroll_check_box_toggled(self):
604+ """
605+ The scrolling checkbox has been toggled
606+ """
607+ if self.scroll_check_box.isChecked():
608+ self.repeat_spin_box.setEnabled(True)
609+ else:
610+ self.repeat_spin_box.setEnabled(False)
611+ self.scroll = self.scroll_check_box.isChecked()
612+ self.changed = True
613+
614 def load(self):
615 """
616 Load the settings into the UI.
617@@ -152,12 +197,17 @@
618 self.background_color = settings.value('background color')
619 self.font_face = settings.value('font face')
620 self.location = settings.value('location')
621+ self.repeat = settings.value('repeat')
622+ self.scroll = settings.value('scroll')
623 settings.endGroup()
624 self.font_size_spin_box.setValue(self.font_size)
625 self.timeout_spin_box.setValue(self.timeout)
626 self.font_color_button.color = self.font_color
627 self.background_color_button.color = self.background_color
628+ self.repeat_spin_box.setValue(self.repeat)
629+ self.repeat_spin_box.setEnabled(self.scroll)
630 self.vertical_combo_box.setCurrentIndex(self.location)
631+ self.scroll_check_box.setChecked(self.scroll)
632 font = QtGui.QFont()
633 font.setFamily(self.font_face)
634 self.font_combo_box.setCurrentFont(font)
635@@ -181,6 +231,8 @@
636 settings.setValue('timeout', self.timeout)
637 self.location = self.vertical_combo_box.currentIndex()
638 settings.setValue('location', self.location)
639+ settings.setValue('repeat', self.repeat)
640+ settings.setValue('scroll', self.scroll_check_box.isChecked())
641 settings.endGroup()
642 if self.changed:
643 self.settings_form.register_post_process('update_display_css')
644
645=== modified file 'package.json'
646--- package.json 2019-04-13 13:00:22 +0000
647+++ package.json 2019-07-01 17:51:00 +0000
648@@ -15,7 +15,7 @@
649 "phantomjs-prebuilt": "^2.1.16"
650 },
651 "scripts": {
652- "test": "karma start"
653+ "test": "karma start --single-run"
654 },
655 "author": "OpenLP Developers",
656 "license": "GPL-3.0-or-later",
657
658=== modified file 'tests/js/test_display.js'
659--- tests/js/test_display.js 2019-01-16 06:15:21 +0000
660+++ tests/js/test_display.js 2019-07-01 17:51:00 +0000
661@@ -18,6 +18,14 @@
662 it("AudioState should exist", function () {
663 expect(AudioState).toBeDefined();
664 });
665+
666+ it("TransitionState should exist", function(){
667+ expect(TransitionState).toBeDefined();
668+ });
669+
670+ it("AnimationState should exist", function(){
671+ expect(AnimationState).toBeDefined();
672+ });
673 });
674
675 describe("The function", function () {
676@@ -138,6 +146,343 @@
677 Display.goToSlide("v1");
678 expect(Reveal.slide).toHaveBeenCalledWith(0);
679 });
680+
681+ it("should have an alert() method", function () {
682+ expect(Display.alert).toBeDefined();
683+ });
684+
685+});
686+
687+describe("Display.alert", function () {
688+ var alertBackground, alertText, settings;
689+
690+ beforeEach(function () {
691+ document.body.innerHTML = "";
692+ alertContainer = document.createElement("div");
693+ alertContainer.setAttribute("class", "alert-container");
694+ document.body.appendChild(alertContainer);
695+ alertBackground = document.createElement("div");
696+ alertBackground.setAttribute("id", "alert-background");
697+ alertContainer.appendChild(alertBackground);
698+ alertText = document.createElement("span");
699+ alertText.setAttribute("id","alert");
700+ alertBackground.appendChild(alertText);
701+ settings = '{ \
702+ "location": 1, "fontFace": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif", \
703+ "fontSize": 40, "fontColor": "#ffffff", "backgroundColor": "#660000", \
704+ "timeout": 5, "repeat": 1, "scroll": true \
705+ }';
706+ });
707+
708+ it("should return null if called without any text", function () {
709+ expect(Display.alert("", settings)).toBeNull();
710+ });
711+
712+ it("should set the correct alert text", function () {
713+ spyOn(Display, "setAlertText");
714+ spyOn(Display, "setAlertLocation");
715+ Display.alert("OPEN-LP-3.0 Alert Test", settings);
716+
717+ expect(Display.setAlertText).toHaveBeenCalled();
718+ expect(Display.setAlertLocation).toHaveBeenCalled();
719+ });
720+
721+ it("should call the addAlertToQueue method if an alert is displaying", function () {
722+ spyOn(Display, "addAlertToQueue");
723+ Display._alerts = [];
724+ Display._alertState = AlertState.Displaying;
725+ var text = "Testing alert queue";
726+
727+ Display.alert(text, settings);
728+
729+ expect(Display.addAlertToQueue).toHaveBeenCalledWith(text, settings);
730+ });
731+
732+ it("should set the alert settings correctly", function() {
733+ Display.alert("Testing settings", settings);
734+
735+ expect(Display._alertSettings).toEqual(JSON.parse(settings));
736+ });
737+});
738+
739+describe("Display.showAlertBackground", function () {
740+
741+ var alertBackground, bg_color;
742+ beforeEach(function () {
743+ document.body.innerHTML = "";
744+ bg_color = "rgb(102, 0, 0)";
745+ alertBackground = document.createElement("div");
746+ alertBackground.setAttribute("id", "alert-background");
747+ alertBackground.setAttribute("class", "bg-default");
748+ document.body.appendChild(alertBackground);
749+ });
750+
751+ it("should set the correct transition state", function () {
752+ Display.showAlertBackground(bg_color);
753+ expect(Display._transitionState).toEqual(TransitionState.EntranceTransition);
754+ });
755+
756+ it("should apply the styles correctly when showAlertBackground is called", function () {
757+ Display.showAlertBackground(bg_color);
758+
759+ expect(alertBackground.style.backgroundColor).toEqual(bg_color);
760+ expect(alertBackground.className).toEqual("bg-default show-bg");
761+ });
762+});
763+
764+describe("Display.hideAlertBackground", function () {
765+ var alertBackground;
766+ beforeEach( function() {
767+ document.body.innerHTML = "";
768+ alertBackground = document.createElement("div");
769+ alertBackground.setAttribute("id", "alert-background");
770+ alertBackground.setAttribute("class", "bg-default show-bg");
771+ document.body.appendChild(alertBackground);
772+ });
773+
774+ it("reset the background to default once an alert has been displayed", function() {
775+ Display.hideAlertBackground();
776+
777+ expect(Display._transitionState).toEqual(TransitionState.ExitTransition);
778+ expect(Display._alertState).toEqual(AlertState.NotDisplaying);
779+ expect(alertBackground.className).toEqual("bg-default");
780+ });
781+});
782+
783+describe("Display.setAlertText", function() {
784+ var alertText;
785+ beforeEach( function() {
786+ document.body.innerHTML = "";
787+ alertText = document.createElement("span");
788+ alertText.setAttribute("id", "alert");
789+ document.body.appendChild(alertText);
790+ });
791+ it("should set the alert text correctly", function () {
792+ Display.setAlertText("OpenLP Alert Text", "#ffffff", "Tahoma", 40);
793+
794+ expect(alertText.textContent).toEqual("OpenLP Alert Text");
795+ expect(alertText.style.color).toEqual("rgb(255, 255, 255)");
796+ expect(alertText.style.fontFamily).toEqual("Tahoma");
797+ expect(alertText.style.fontSize).toEqual("40pt");
798+ });
799+});
800+
801+describe("Display.setAlertLocation", function() {
802+ beforeEach(function() {
803+ document.body.innerHTML = "";
804+ alertContainer = document.createElement("div");
805+ alertContainer.setAttribute("class", "alert-container");
806+ document.body.appendChild(alertContainer);
807+ });
808+ it("should set the correct class when location is top of the page", function () {
809+ Display.setAlertLocation(0);
810+
811+ expect(alertContainer.className).toEqual("alert-container top");
812+ });
813+
814+ it("should set the correct class when location is middle of the page", function () {
815+ Display.setAlertLocation(1);
816+
817+ expect(alertContainer.className).toEqual("alert-container middle");
818+ });
819+
820+ it("should set the correct class when location is bottom of the page", function () {
821+ Display.setAlertLocation(2);
822+
823+ expect(alertContainer.className).toEqual("alert-container bottom");
824+ });
825+});
826+
827+describe("Display.removeAlertLocation", function () {
828+ beforeEach(function() {
829+ document.body.innerHTML = "";
830+ alertContainer = document.createElement("div");
831+ alertContainer.setAttribute("class", "alert-container");
832+ document.body.appendChild(alertContainer);
833+ });
834+ it("should remove the correct class when location is top of the page", function () {
835+ alertContainer.classList.add("top");
836+ Display.removeAlertLocation(0);
837+
838+ expect(alertContainer.className).toEqual("alert-container");
839+ });
840+
841+ it("should remove the correct class when location is middle of the page", function () {
842+ alertContainer.classList.add("middle");
843+ Display.removeAlertLocation(1);
844+
845+ expect(alertContainer.className).toEqual("alert-container");
846+ });
847+
848+ it("should remove the correct class when location is bottom of the page", function () {
849+ alertContainer.classList.add("bottom");
850+ Display.removeAlertLocation(2);
851+
852+ expect(alertContainer.className).toEqual("alert-container");
853+ });
854+});
855+
856+describe("Display.showAlertText", function () {
857+ var alertText, settings;
858+ beforeEach(function () {
859+ document.body.innerHTML = "";
860+ alertText = document.createElement("span");
861+ alertText.setAttribute("id", "alert");
862+ document.body.appendChild(alertText);
863+ settings = {
864+ "location": 2, "fontFace": "Tahoma", "fontSize": 40,
865+ "fontColor": "rgb(255, 255, 255)", "backgroundColor": "rgb(102, 0, 0)",
866+ "timeout": 0.01, "repeat": 1, "scroll": true
867+ };
868+ Display._transitionState = TransitionState.EntranceTransition;
869+ });
870+
871+ it("should set the correct animation when text is set to scroll)", function () {
872+ Display.showAlertText(settings);
873+
874+ expect(alertText.style.animation).toEqual("alert-scrolling-text " + settings.timeout + "s linear 0.6s 1 normal");
875+ expect(Display._animationState).toEqual(AnimationState.ScrollingText);
876+ });
877+
878+ it("should set the correct styles when text is not scrolling", function (done) {
879+ settings.scroll = false;
880+ Display._transitionState = TransitionState.EntranceTransition;
881+ spyOn(Display, "hideAlertText");
882+ Display.showAlertText(settings);
883+
884+ // expect(alertText.style.animation).toEqual("");
885+ expect(Display._animationState).toEqual(AnimationState.NonScrollingText);
886+ expect(alertText.classList.contains('show-text')).toBe(true);
887+ setTimeout (function () {
888+ expect(Display._animationState).toEqual(AnimationState.NoAnimation);
889+ expect(Display.hideAlertText).toHaveBeenCalled();
890+ done();
891+ }, settings.timeout * 1000);
892+ });
893+});
894+
895+describe("Display.hideAlertText", function() {
896+ var alertBackground, alertText, keyframeStyle;
897+ beforeEach(function () {
898+ document.body.innerHTML = "";
899+ alertBackground = document.createElement("div");
900+ alertBackground.setAttribute("id", "alert-background");
901+ alertBackground.setAttribute("class", "bg-default show-bg");
902+ document.body.appendChild(alertBackground);
903+ alertText = document.createElement("span");
904+ alertText.setAttribute("id", "alert");
905+ alertText.style.opacity = 1;
906+ alertText.style.animation = "alert-scrolling-text 5s linear 0s 1 bg-default";
907+ alertBackground.appendChild(alertText);
908+ Display._animationState = AnimationState.ScrollingText;
909+ });
910+
911+ it("should reset the text styles and animation state after the text has scrolled", function() {
912+ spyOn(Display, "hideAlertBackground");
913+ Display.hideAlertText();
914+
915+ expect(alertText.style.animation).toEqual("");
916+ expect(Display._animationState).toEqual(AnimationState.NoAnimation);
917+ });
918+
919+ it("should call the hideAlertBackground method", function() {
920+ spyOn(Display, "hideAlertBackground");
921+ Display.hideAlertText();
922+
923+
924+ expect(Display.hideAlertBackground).toHaveBeenCalled();
925+ });
926+});
927+
928+describe("Display.addAlertToQueue", function () {
929+ it("should add an alert to the queue if one is displaying already", function() {
930+ Display._alerts = [];
931+ Display._alertState = AlertState.Displaying;
932+ settings = '{ \
933+ "location": 1, "fontFace": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif", \
934+ "fontSize": 40, "fontColor": "#ffffff", "backgroundColor": "#660000", \
935+ "timeout": 5, "repeat": 1, "scrolling_text": true \
936+ }';
937+ var alertObject = {text: "Testing alert queue", settings: settings};
938+ var queuedAlert = JSON.stringify(alertObject);
939+
940+ Display.addAlertToQueue("Testing alert queue", settings);
941+
942+ expect(Display._alerts.length).toEqual(1);
943+ expect(Display._alerts[0]).toEqual(queuedAlert);
944+ });
945+});
946+
947+describe("Display.showNextAlert", function () {
948+ Display.showNextAlert();
949+
950+ it("should return null if there are no alerts in the queue", function () {
951+ Display._alerts = [];
952+ Display.showNextAlert();
953+
954+ expect(Display.showNextAlert()).toBeNull();
955+ });
956+
957+ it("should call the alert function correctly if there is an alert in the queue", function () {
958+ var settings = {
959+ "location": 2, "fontFace": "Tahoma", "fontSize": 40,
960+ "fontColor": "rgb(255, 255, 255)", "backgroundColor": "rgb(102, 0, 0)",
961+ "timeout": 5, "repeat": 1, "scrolling_text": true
962+ };
963+ var alertObject = {text: "Queued Alert", settings: settings};
964+ Display._alerts.push(JSON.stringify(alertObject));
965+ spyOn(Display, "alert");
966+ Display.showNextAlert();
967+
968+ expect(Display.alert).toHaveBeenCalled();
969+ expect(Display.alert).toHaveBeenCalledWith("Queued Alert",alertObject.settings);
970+ });
971+});
972+
973+describe("Display.alertTransitionEndEvent", function() {
974+ beforeEach( function() {
975+
976+ });
977+
978+ it("should set the correct state and call showAlertText after the alert entrance transition", function() {
979+ var fake_settings = {test: "fake_settings"};
980+ var e = jasmine.createSpyObj('e', ['stopPropagation']);
981+ Display._alertSettings = fake_settings;
982+ spyOn(Display, "showAlertText");
983+ Display._transitionState = TransitionState.EntranceTransition;
984+ Display.alertTransitionEndEvent();
985+
986+ expect(Display._transitionState).toEqual(TransitionState.NoTransition);
987+ expect(Display.showAlertText).toHaveBeenCalledWith(fake_settings);
988+ });
989+
990+ it("should set the correct state after the alert exit transition", function() {
991+ spyOn(Display, "showNextAlert");
992+ Display._transitionState = TransitionState.ExitTransition;
993+ Display.alertTransitionEndEvent();
994+
995+ expect(Display._transitionState).toEqual(TransitionState.NoTransition);
996+ });
997+});
998+
999+describe("Display.alertAnimationEndEvent", function () {
1000+ it("should call the hideAlertText method", function() {
1001+ spyOn(Display, "hideAlertText");
1002+
1003+ Display.alertAnimationEndEvent();
1004+
1005+ expect(Display.hideAlertText).toHaveBeenCalled();
1006+ });
1007+});
1008+
1009+describe("Display.clearAlertSettings", function () {
1010+ it("should clear the alert settings once an alert has been displayed", function () {
1011+ var fake_settings = {test: "fake_settings"};
1012+ Display._alertSettings = fake_settings;
1013+ Display.clearAlertSettings();
1014+
1015+ expect(Display._alertSettings).toEqual({});
1016+ });
1017 });
1018
1019 describe("Display.addTextSlide", function () {
1020@@ -249,6 +594,7 @@
1021 };
1022
1023 spyOn(Display, "reinit");
1024+ spyOn(Reveal, "slide");
1025
1026 Display.setTextSlides(slides);
1027 Display.setTheme(theme);