Merge lp:~ic90/openlp/animated-alerts into lp:openlp
- animated-alerts
- Merge into trunk
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 |
Related bugs: |
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 : | # |
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Linting failed, please see https:/
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:/
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); |
Linux tests passed!