Merge branch 'master' of gitlab.com:openlp/openlp

This commit is contained in:
Tim 2019-12-14 17:03:12 +00:00
commit 691b119afb
No known key found for this signature in database
GPG Key ID: 3D454289AF831A6D
30 changed files with 4055 additions and 1597 deletions

View File

@ -26,8 +26,9 @@ import faulthandler
import logging import logging
import multiprocessing import multiprocessing
import sys import sys
import os
# from OpenGL import GL # from OpenGL import GL # noqa
from openlp.core.app import main from openlp.core.app import main
from openlp.core.common import is_macosx, is_win from openlp.core.common import is_macosx, is_win
@ -78,6 +79,11 @@ def start():
# to avoid any potential conflicts. # to avoid any potential conflicts.
if is_macosx(): if is_macosx():
sys.argv = [x for x in sys.argv if not x.startswith('-psn')] sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
if getattr(sys, 'frozen', False):
os.environ['QTWEBENGINEPROCESS_PATH'] = os.path.normpath(os.path.join(
sys._MEIPASS, '..', 'MacOS', 'PyQt5', 'Qt', 'lib', 'QtWebEngineCore.framework',
'Versions', '5', 'Helpers', 'QtWebEngineProcess.app', 'Contents', 'MacOS', 'QtWebEngineProcess'
))
main() main()

View File

@ -98,3 +98,387 @@ sup {
transition: opacity 0.5s linear; transition: opacity 0.5s linear;
z-index: 100; z-index: 100;
} }
/*********************************************
* Transition overrides to allow different directions
*********************************************/
/*********************************************
* SLIDE TRANSITION
* Aliased 'linear' for backwards compatibility
*********************************************/
.reveal .slides section[data-transition~=concave].stack,
.reveal[class~=slide] .slides section.stack {
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.reveal .slides > section[data-transition=slide].past,
.reveal .slides > section[data-transition~=slide-out].past,
.reveal.slide .slides > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=slide-horizontal].past,
.reveal .slides > section > section[data-transition~=slide-horizontal-out].past,
.reveal.slide-horizontal .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=slide-horizontal-reverse].future,
.reveal .slides > section > section[data-transition~=slide-horizontal-reverse-in].future,
.reveal.slide-horizontal-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate(-150%, 0);
transform: translate(-150%, 0); }
.reveal .slides > section[data-transition=slide].future,
.reveal .slides > section[data-transition~=slide-in].future,
.reveal.slide .slides > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=slide-horizontal].future,
.reveal .slides > section > section[data-transition~=slide-horizontal-in].future,
.reveal.slide-horizontal .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=slide-horizontal-reverse].past,
.reveal .slides > section > section[data-transition~=slide-horizontal-reverse-out].past,
.reveal.slide-horizontal-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate(150%, 0);
transform: translate(150%, 0); }
.reveal .slides > section > section[data-transition=slide-vertical].past,
.reveal .slides > section > section[data-transition~=slide-vertical-out].past,
.reveal.slide-vertical .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=slide-vertical-reverse].future,
.reveal .slides > section > section[data-transition~=slide-vertical-reverse-in].future,
.reveal.slide-vertical-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate(0, -150%);
transform: translate(0, -150%); }
.reveal .slides > section > section[data-transition=slide-vertical].future,
.reveal .slides > section > section[data-transition~=slide-vertical-in].future,
.reveal.slide-vertical .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=slide-vertical-reverse].past,
.reveal .slides > section > section[data-transition~=slide-vertical-reverse-out].past,
.reveal.slide-vertical-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate(0, 150%);
transform: translate(0, 150%); }
/*********************************************
* CONVEX TRANSITION
* Aliased 'default' for backwards compatibility
*********************************************/
.reveal .slides section[data-transition~=convex].stack,
.reveal[class~=convex] .slides section.stack {
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.reveal .slides > section[data-transition=convex].past,
.reveal .slides > section[data-transition~=convex-out].past,
.reveal.convex .slides > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=convex-horizontal].past,
.reveal .slides > section > section[data-transition~=convex-horizontal-out].past,
.reveal.convex-horizontal .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=convex-horizontal-reverse].future,
.reveal .slides > section > section[data-transition~=convex-horizontal-reverse-in].future,
.reveal.convex-horizontal-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
.reveal .slides > section[data-transition=convex].future,
.reveal .slides > section[data-transition~=convex-in].future,
.reveal.convex .slides > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=convex-horizontal].future,
.reveal .slides > section > section[data-transition~=convex-horizontal-in].future,
.reveal.convex-horizontal .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=convex-horizontal-reverse].past,
.reveal .slides > section > section[data-transition~=convex-horizontal-reverse-out].past,
.reveal.convex-horizontal-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
.reveal .slides > section > section[data-transition=convex-vertical].past,
.reveal .slides > section > section[data-transition~=convex-vertical-out].past,
.reveal.convex-vertical .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=convex-vertical-reverse].future,
.reveal .slides > section > section[data-transition~=convex-vertical-reverse-in].future,
.reveal.convex-vertical-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
.reveal .slides > section > section[data-transition=convex-vertical].future,
.reveal .slides > section > section[data-transition~=convex-vertical-in].future,
.reveal.convex-vertical .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=convex-vertical-reverse].past,
.reveal .slides > section > section[data-transition~=convex-vertical-reverse-out].past,
.reveal.convex-vertical-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
/*********************************************
* CONCAVE TRANSITION
*********************************************/
.reveal .slides section[data-transition~=concave].stack,
.reveal[class~=concave] .slides section.stack {
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.reveal .slides > section[data-transition=concave-vertical].past,
.reveal .slides > section[data-transition~=concave-vertical-out].past,
.reveal.concave-vertical .slides > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=concave-horizontal].past,
.reveal .slides > section > section[data-transition~=concave-horizontal-out].past,
.reveal.concave-horizontal .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=concave-horizontal-reverse].future,
.reveal .slides > section > section[data-transition~=concave-horizontal-reverse-in].future,
.reveal.concave-horizontal-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
.reveal .slides > section[data-transition=concave-vertical].future,
.reveal .slides > section[data-transition~=concave-vertical-in].future,
.reveal.concave-vertical .slides > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=concave-horizontal].future,
.reveal .slides > section > section[data-transition~=concave-horizontal-in].future,
.reveal.concave-horizontal .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=concave-horizontal-reverse].past,
.reveal .slides > section > section[data-transition~=concave-horizontal-reverse-out].past,
.reveal.concave-horizontal-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
.reveal .slides > section > section[data-transition=concave-vertical].past,
.reveal .slides > section > section[data-transition~=concave-vertical-out].past,
.reveal.concave-vertical .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=concave-vertical-reverse].future,
.reveal .slides > section > section[data-transition~=concave-vertical-reverse-in].future,
.reveal.concave-vertical-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0); }
.reveal .slides > section > section[data-transition=concave-vertical].future,
.reveal .slides > section > section[data-transition~=concave-vertical-in].future,
.reveal.concave-vertical .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=concave-vertical-reverse].past,
.reveal .slides > section > section[data-transition~=concave-vertical-reverse-out].past,
.reveal.concave-vertical-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0); }
/*********************************************
* ZOOM TRANSITION
*********************************************/
.reveal .slides section[data-transition~=zoom],
.reveal[class~=zoom] .slides section:not([data-transition]) {
transition-timing-function: ease; }
.reveal .slides > section[data-transition=zoom].past,
.reveal .slides > section[data-transition~=zoom-out].past,
.reveal.zoom .slides > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=zoom-horizontal].past,
.reveal .slides > section > section[data-transition~=zoom-horizontal-out].past,
.reveal.zoom-horizontal .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=zoom-horizontal-reverse].future,
.reveal .slides > section > section[data-transition~=zoom-horizontal-reverse-in].future,
.reveal.zoom-horizontal-reverse .slides > section > section:not([data-transition]).future {
visibility: hidden;
-webkit-transform: scale(16);
transform: scale(16); }
.reveal .slides > section[data-transition=zoom].future,
.reveal .slides > section[data-transition~=zoom-in].future,
.reveal.zoom .slides > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=zoom-horizontal].future,
.reveal .slides > section > section[data-transition~=zoom-horizontal-in].future,
.reveal.zoom-horizontal .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=zoom-horizontal-reverse].past,
.reveal .slides > section > section[data-transition~=zoom-horizontal-reverse-out].past,
.reveal.zoom-horizontal-reverse .slides > section > section:not([data-transition]).past {
visibility: hidden;
-webkit-transform: scale(0.2);
transform: scale(0.2); }
.reveal .slides > section > section[data-transition=zoom-vertical].past,
.reveal .slides > section > section[data-transition~=zoom-vertical-out].past,
.reveal.zoom-vertical .slides > section > section:not([data-transition]).past,
.reveal .slides > section > section[data-transition=zoom-vertical-reverse].future,
.reveal .slides > section > section[data-transition~=zoom-vertical-reverse-in].future,
.reveal.zoom-vertical-reverse .slides > section > section:not([data-transition]).future {
-webkit-transform: scale(16);
transform: scale(16); }
.reveal .slides > section > section[data-transition=zoom-vertical].future,
.reveal .slides > section > section[data-transition~=zoom-vertical-in].future,
.reveal.zoom-vertical .slides > section > section:not([data-transition]).future,
.reveal .slides > section > section[data-transition=zoom-vertical-reverse].past,
.reveal .slides > section > section[data-transition~=zoom-vertical-reverse-out].past,
.reveal.zoom-vertical-reverse .slides > section > section:not([data-transition]).past {
-webkit-transform: scale(0.2);
transform: scale(0.2); }
/*********************************************
* CUBE TRANSITION
*
* WARNING:
* this is deprecated and will be removed in a
* future version.
*********************************************/
.reveal[class~=cube] .slides {
-webkit-perspective: 1300px;
perspective: 1300px; }
.reveal[class~=cube] .slides section {
padding: 30px;
min-height: 700px;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
box-sizing: border-box;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.reveal.center[class~=cube] .slides section {
min-height: 0; }
.reveal[class~=cube] .slides section:not(.stack):before {
content: '';
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
-webkit-transform: translateZ(-20px);
transform: translateZ(-20px); }
.reveal[class~=cube] .slides section:not(.stack):after {
content: '';
position: absolute;
display: block;
width: 90%;
height: 30px;
left: 5%;
bottom: 0;
background: none;
z-index: 1;
border-radius: 4px;
box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
-webkit-transform: translateZ(-90px) rotateX(65deg);
transform: translateZ(-90px) rotateX(65deg); }
.reveal[class~=cube] .slides > section.stack {
padding: 0;
background: none; }
.reveal.cube .slides > section.past,
.reveal.cube-horizontal .slides > section > section.past,
.reveal.cube-horizontal-reverse .slides > section > section.future {
-webkit-transform-origin: 100% 0%;
transform-origin: 100% 0%;
-webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
transform: translate3d(-100%, 0, 0) rotateY(-90deg); }
.reveal.cube .slides > section.future,
.reveal.cube-horizontal .slides > section > section.future,
.reveal.cube-horizontal-reverse .slides > section > section.past {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
transform: translate3d(100%, 0, 0) rotateY(90deg); }
.reveal.cube-vertical .slides > section > section.past,
.reveal.cube-vertical-reverse .slides > section > section.future {
-webkit-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
transform: translate3d(0, -100%, 0) rotateX(90deg); }
.reveal.cube-vertical .slides > section > section.future,
.reveal.cube-vertical-reverse .slides > section > section.past {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
transform: translate3d(0, 100%, 0) rotateX(-90deg); }
/*********************************************
* PAGE TRANSITION
*
* WARNING:
* this is deprecated and will be removed in a
* future version.
*********************************************/
.reveal[class~=page] .slides {
-webkit-perspective-origin: 0% 50%;
perspective-origin: 0% 50%;
-webkit-perspective: 3000px;
perspective: 3000px; }
.reveal[class~=page] .slides section {
padding: 30px;
min-height: 700px;
box-sizing: border-box;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.reveal[class~=page] .slides section.past {
z-index: 12; }
.reveal[class~=page] .slides section:not(.stack):before {
content: '';
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.1);
-webkit-transform: translateZ(-20px);
transform: translateZ(-20px); }
.reveal[class~=page] .slides section:not(.stack):after {
content: '';
position: absolute;
display: block;
width: 90%;
height: 30px;
left: 5%;
bottom: 0;
background: none;
z-index: 1;
border-radius: 4px;
box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
-webkit-transform: translateZ(-90px) rotateX(65deg); }
.reveal[class~=page] .slides > section.stack {
padding: 0;
background: none; }
.reveal.page .slides > section.past,
.reveal.page-horizontal .slides > section > section.past,
.reveal.page-horizontal-reverse .slides > section > section.future {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
transform: translate3d(-40%, 0, 0) rotateY(-80deg); }
.reveal.page .slides > section.future,
.reveal.page-horizontal .slides > section > section.future,
.reveal.page-horizontal-reverse .slides > section > section.past {
-webkit-transform-origin: 100% 0%;
transform-origin: 100% 0%;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.reveal.page-vertical .slides > section > section.past,
.reveal.page-vertical-reverse .slides > section > section.future {
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
-webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
transform: translate3d(0, -40%, 0) rotateX(80deg); }
.reveal.page-vertical .slides > section > section.future,
.reveal.page-vertical-reverse .slides > section > section.past {
-webkit-transform-origin: 0% 100%;
transform-origin: 0% 100%;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
/*********************************************
* FADE TRANSITION
*********************************************/
.reveal .slides section[data-transition~=fade],
.reveal[class~=fade] .slides section:not([data-transition]),
.reveal[class~=fade] .slides > section > section:not([data-transition]) {
-webkit-transform: none;
transform: none;
transition: opacity 0.5s; }

View File

@ -63,6 +63,15 @@ var TransitionSpeed = {
Slow: 2 Slow: 2
}; };
/**
* Transition direction enumeration
*/
var TransitionDirection = {
Horizontal: 0,
Vertical: 1
};
/** /**
* Audio state enumeration * Audio state enumeration
*/ */
@ -931,7 +940,19 @@ var Display = {
default: default:
new_transition_speed = "default"; new_transition_speed = "default";
} }
switch (theme.display_slide_transition_direction) {
case TransitionDirection.Vertical:
new_transition_type += "-vertical";
break;
case TransitionDirection.Horizontal:
default:
new_transition_type += "-horizontal";
}
if (theme.display_slide_transition_reverse) {
new_transition_type += "-reverse";
}
} }
Display.setTransition(new_transition_type, new_transition_speed); Display.setTransition(new_transition_type, new_transition_speed);
// Set the background // Set the background
var globalBackground = $("#global-background")[0]; var globalBackground = $("#global-background")[0];

View File

@ -33,7 +33,7 @@ from PyQt5 import QtWidgets, QtGui
from openlp.core.common import ThemeLevel from openlp.core.common import ThemeLevel
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.mixins import LogMixin
from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList from openlp.core.display.screens import ScreenList
@ -797,7 +797,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
return pixmap return pixmap
class Renderer(RegistryBase, RegistryProperties, ThemePreviewRenderer): class Renderer(RegistryBase, ThemePreviewRenderer):
""" """
A virtual display used for rendering thumbnails and other offscreen tasks A virtual display used for rendering thumbnails and other offscreen tasks
""" """

View File

@ -25,6 +25,7 @@ import json
import logging import logging
import os import os
import copy import copy
import time
from PyQt5 import QtCore, QtWebChannel, QtWidgets from PyQt5 import QtCore, QtWebChannel, QtWidgets
@ -35,6 +36,7 @@ from openlp.core.common.registry import Registry
from openlp.core.common.applocation import AppLocation from openlp.core.common.applocation import AppLocation
from openlp.core.ui import HideMode from openlp.core.ui import HideMode
from openlp.core.display.screens import ScreenList from openlp.core.display.screens import ScreenList
from openlp.core.common.mixins import RegistryProperties
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -100,7 +102,7 @@ class MediaWatcher(QtCore.QObject):
self.muted.emit(is_muted) self.muted.emit(is_muted)
class DisplayWindow(QtWidgets.QWidget): class DisplayWindow(QtWidgets.QWidget, RegistryProperties):
""" """
This is a window to show the output This is a window to show the output
""" """
@ -142,6 +144,8 @@ class DisplayWindow(QtWidgets.QWidget):
self.is_display = False self.is_display = False
self.scale = 1 self.scale = 1
self.hide_mode = None self.hide_mode = None
self.__script_done = True
self.__script_result = None
if screen and screen.is_display: if screen and screen.is_display:
Registry().register_function('live_display_hide', self.hide_display) Registry().register_function('live_display_hide', self.hide_display)
Registry().register_function('live_display_show', self.show_display) Registry().register_function('live_display_show', self.show_display)
@ -218,6 +222,14 @@ class DisplayWindow(QtWidgets.QWidget):
:param is_sync: Run the script synchronously. Defaults to False :param is_sync: Run the script synchronously. Defaults to False
""" """
log.debug(script) log.debug(script)
# Wait for other scripts to finish
end_time = time.time() + 10
while not self.__script_done:
if time.time() > end_time:
log.error('Timed out waiting for preivous javascript script to finish')
break
time.sleep(0.1)
self.application.process_events()
if not is_sync: if not is_sync:
self.webview.page().runJavaScript(script) self.webview.page().runJavaScript(script)
else: else:
@ -232,9 +244,14 @@ class DisplayWindow(QtWidgets.QWidget):
self.__script_result = result self.__script_result = result
self.webview.page().runJavaScript(script, handle_result) self.webview.page().runJavaScript(script, handle_result)
end_time = time.time() + 10
while not self.__script_done: while not self.__script_done:
# TODO: Figure out how to break out of a potentially infinite loop if time.time() > end_time:
QtWidgets.QApplication.instance().processEvents() self.__script_done = True
log.error('Timed out waiting for javascript script to finish')
break
time.sleep(0.001)
self.application.process_events()
return self.__script_result return self.__script_result
def go_to_slide(self, verse): def go_to_slide(self, verse):

View File

@ -13,6 +13,8 @@
"slide_transition": false, "slide_transition": false,
"slide_transition_type": 0, "slide_transition_type": 0,
"slide_transition_speed": 0, "slide_transition_speed": 0,
"slide_transition_direction": 0,
"slide_transition_reverse": false,
"vertical_align": 0 "vertical_align": 0
}, },
"font": { "font": {

View File

@ -203,6 +203,34 @@ class TransitionSpeed(object):
return TransitionSpeed.Slow return TransitionSpeed.Slow
class TransitionDirection(object):
"""
Type enumeration for transition types.
"""
Horizontal = 0
Vertical = 1
@staticmethod
def to_string(transition_direction):
"""
Return a string representation of a transition type.
"""
if transition_direction == TransitionDirection.Horizontal:
return 'horizontal'
elif transition_direction == TransitionDirection.Vertical:
return 'vertical'
@staticmethod
def from_string(type_string):
"""
Return a transition type for the given string.
"""
if type_string == 'horizontal':
return TransitionDirection.Horizontal
if type_string == 'vertical':
return TransitionDirection.Vertical
class HorizontalType(object): class HorizontalType(object):
""" """
Type enumeration for horizontal alignment. Type enumeration for horizontal alignment.
@ -214,6 +242,20 @@ class HorizontalType(object):
Names = ['left', 'right', 'center', 'justify'] Names = ['left', 'right', 'center', 'justify']
@staticmethod
def to_string(align):
"""
Return a string representation of the alignment
"""
return HorizontalType.Names[align]
@staticmethod
def from_string(align):
"""
Return an alignment for a given string
"""
return HorizontalType.Names.index(align)
class VerticalType(object): class VerticalType(object):
""" """
@ -225,11 +267,26 @@ class VerticalType(object):
Names = ['top', 'middle', 'bottom'] Names = ['top', 'middle', 'bottom']
@staticmethod
def to_string(align):
"""
Return a string representation of the alignment
"""
return VerticalType.Names[align]
BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition'] @staticmethod
def from_string(align):
"""
Return an alignment for a given string
"""
return VerticalType.Names.index(align)
BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition', 'slide_transition_reverse']
INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_size', 'outline_size', INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_size', 'outline_size',
'horizontal_align', 'vertical_align', 'wrap_style', 'slide_transition_type', 'slide_transition_speed'] 'horizontal_align', 'vertical_align', 'wrap_style', 'slide_transition_type', 'slide_transition_speed',
'slide_transition_direction']
class Theme(object): class Theme(object):

View File

@ -31,6 +31,7 @@ from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib import build_icon from openlp.core.lib import build_icon
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.labels import FormLabel
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -306,7 +307,7 @@ def create_valign_selection_widgets(parent):
:param parent: The parent object. This should be a ``QWidget`` descendant. :param parent: The parent object. This should be a ``QWidget`` descendant.
""" """
label = QtWidgets.QLabel(parent) label = FormLabel(parent)
label.setText(translate('OpenLP.Ui', '&Vertical Align:')) label.setText(translate('OpenLP.Ui', '&Vertical Align:'))
combo_box = QtWidgets.QComboBox(parent) combo_box = QtWidgets.QComboBox(parent)
combo_box.addItems([UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]) combo_box.addItems([UiStrings().Top, UiStrings().Middle, UiStrings().Bottom])

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.pages` module contains wizard pages used in OpenLP
"""
from PyQt5 import QtWidgets
class GridLayoutPage(QtWidgets.QWizardPage):
"""
A class that has a QGridLayout for its layout which automatically ensure all columns are equal width
"""
def __init__(self, parent=None):
super().__init__(parent)
self._column_width = 0
self.layout = QtWidgets.QGridLayout(self)
self.setup_ui()
self.retranslate_ui()
self.resize_columns()
def resizeEvent(self, event):
"""
Override inherited resize method
"""
super().resizeEvent(event)
self.resize_columns()
def resize_columns(self):
"""
Resize all the column widths
"""
width = self.layout.contentsRect().width()
spacing = self.layout.horizontalSpacing()
column_count = self.layout.columnCount()
self._column_width = (width - (spacing * (column_count - 1))) // column_count
for column_number in range(column_count):
self.layout.setColumnMinimumWidth(column_number, self._column_width)
def setup_ui(self):
raise NotImplementedError('Descendant pages need to implement setup_ui')
def retranslate_ui(self):
raise NotImplementedError('Descendant pages need to implement retranslate_ui')

View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.pages.alignment` module contains the alignment page used in the theme wizard
"""
from PyQt5 import QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.lib.theme import HorizontalType, VerticalType, TransitionType, TransitionSpeed, TransitionDirection
from openlp.core.lib.ui import create_valign_selection_widgets
from openlp.core.pages import GridLayoutPage
from openlp.core.widgets.labels import FormLabel
class AlignmentTransitionsPage(GridLayoutPage):
"""
A widget containing the alignment and transitions options
"""
def setup_ui(self):
"""
Set up the UI
"""
# Alignment
self.horizontal_label = FormLabel(self)
self.horizontal_label.setObjectName('horizontal_label')
self.layout.addWidget(self.horizontal_label, 0, 0)
self.horizontal_combo_box = QtWidgets.QComboBox(self)
self.horizontal_combo_box.addItems(['', '', '', ''])
self.horizontal_combo_box.setObjectName('horizontal_combo_box')
self.layout.addWidget(self.horizontal_combo_box, 0, 1, 1, 3)
self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self)
self.vertical_label.setObjectName('vertical_label')
self.layout.addWidget(self.vertical_label, 1, 0)
self.vertical_combo_box.setObjectName('vertical_combo_box')
self.layout.addWidget(self.vertical_combo_box, 1, 1, 1, 3)
# Line
self.line = QtWidgets.QFrame(self)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setObjectName('line')
self.layout.addWidget(self.line, 2, 0, 1, 4)
# Transitions
self.transitions_enabled_check_box = QtWidgets.QCheckBox(self)
self.transitions_enabled_check_box.setObjectName('transitions_enabled_check_box')
self.layout.addWidget(self.transitions_enabled_check_box, 3, 1)
self.transition_effect_label = FormLabel(self)
self.transition_effect_label.setObjectName('transition_effect_label')
self.layout.addWidget(self.transition_effect_label, 4, 0)
self.transition_effect_combo_box = QtWidgets.QComboBox(self)
self.transition_effect_combo_box.setObjectName('transition_effect_combo_box')
self.transition_effect_combo_box.addItems(['', '', '', '', ''])
self.layout.addWidget(self.transition_effect_combo_box, 4, 1)
self.transition_speed_label = FormLabel(self)
self.transition_speed_label.setObjectName('transition_speed_label')
self.layout.addWidget(self.transition_speed_label, 5, 0)
self.transition_speed_combo_box = QtWidgets.QComboBox(self)
self.transition_speed_combo_box.setObjectName('transition_speed_combo_box')
self.transition_speed_combo_box.addItems(['', '', ''])
self.layout.addWidget(self.transition_speed_combo_box, 5, 1)
self.transition_direction_label = FormLabel(self)
self.transition_direction_label.setObjectName('transition_direction_label')
self.layout.addWidget(self.transition_direction_label, 4, 2)
self.transition_direction_combo_box = QtWidgets.QComboBox(self)
self.transition_direction_combo_box.setObjectName('transition_direction_combo_box')
self.transition_direction_combo_box.addItems(['', ''])
self.layout.addWidget(self.transition_direction_combo_box, 4, 3)
self.transition_reverse_check_box = QtWidgets.QCheckBox(self)
self.transition_reverse_check_box.setObjectName('transition_reverse_check_box')
self.layout.addWidget(self.transition_reverse_check_box, 5, 3)
# Connect slots
self.transitions_enabled_check_box.stateChanged.connect(self._on_transition_enabled_changed)
def retranslate_ui(self):
"""
Translate the widgets
"""
self.horizontal_label.setText(translate('OpenLP.ThemeWizard', 'Horizontal Align:'))
self.horizontal_combo_box.setItemText(HorizontalType.Left, translate('OpenLP.ThemeWizard', 'Left'))
self.horizontal_combo_box.setItemText(HorizontalType.Right, translate('OpenLP.ThemeWizard', 'Right'))
self.horizontal_combo_box.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center'))
self.horizontal_combo_box.setItemText(HorizontalType.Justify, translate('OpenLP.ThemeWizard', 'Justify'))
self.transitions_enabled_check_box.setText(translate('OpenLP.ThemeWizard', 'Enable transitions'))
self.transition_effect_label.setText(translate('OpenLP.ThemeWizard', 'Effect:'))
self.transition_effect_combo_box.setItemText(TransitionType.Fade, translate('OpenLP.ThemeWizard', 'Fade'))
self.transition_effect_combo_box.setItemText(TransitionType.Slide, translate('OpenLP.ThemeWizard', 'Slide'))
self.transition_effect_combo_box.setItemText(TransitionType.Concave, translate('OpenLP.ThemeWizard', 'Concave'))
self.transition_effect_combo_box.setItemText(TransitionType.Convex, translate('OpenLP.ThemeWizard', 'Convex'))
self.transition_effect_combo_box.setItemText(TransitionType.Zoom, translate('OpenLP.ThemeWizard', 'Zoom'))
self.transition_speed_label.setText(translate('OpenLP.ThemeWizard', 'Speed:'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Normal, translate('OpenLP.ThemeWizard', 'Normal'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Fast, translate('OpenLP.ThemeWizard', 'Fast'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Slow, translate('OpenLP.ThemeWizard', 'Slow'))
self.transition_direction_label.setText(translate('OpenLP.ThemeWizard', 'Direction:'))
self.transition_direction_combo_box.setItemText(TransitionDirection.Horizontal, translate('OpenLP.ThemeWizard',
'Horizontal'))
self.transition_direction_combo_box.setItemText(TransitionDirection.Vertical, translate('OpenLP.ThemeWizard',
'Vertical'))
self.transition_reverse_check_box.setText(translate('OpenLP.ThemeWizard', 'Reverse'))
def _on_transition_enabled_changed(self, value):
"""
Update the UI widgets when the transition is enabled or disabled
"""
self.transition_effect_label.setEnabled(value)
self.transition_effect_combo_box.setEnabled(value)
self.transition_speed_label.setEnabled(value)
self.transition_speed_combo_box.setEnabled(value)
self.transition_direction_combo_box.setEnabled(value)
self.transition_direction_label.setEnabled(value)
self.transition_reverse_check_box.setEnabled(value)
@property
def horizontal_align(self):
return self.horizontal_combo_box.currentIndex()
@horizontal_align.setter
def horizontal_align(self, value):
if isinstance(value, str):
self.horizontal_combo_box.setCurrentIndex(HorizontalType.from_string(value))
elif isinstance(value, int):
self.horizontal_combo_box.setCurrentIndex(value)
else:
raise TypeError('horizontal_align must either be a string or an int')
@property
def vertical_align(self):
return self.vertical_combo_box.currentIndex()
@vertical_align.setter
def vertical_align(self, value):
if isinstance(value, str):
self.vertical_combo_box.setCurrentIndex(VerticalType.from_string(value))
elif isinstance(value, int):
self.vertical_combo_box.setCurrentIndex(value)
else:
raise TypeError('vertical_align must either be a string or an int')
@property
def is_transition_enabled(self):
return self.transitions_enabled_check_box.isChecked()
@is_transition_enabled.setter
def is_transition_enabled(self, value):
self.transitions_enabled_check_box.setChecked(value)
self._on_transition_enabled_changed(value)
@property
def transition_type(self):
return self.transition_effect_combo_box.currentIndex()
@transition_type.setter
def transition_type(self, value):
if isinstance(value, str):
self.transition_effect_combo_box.setCurrentIndex(TransitionType.from_string(value))
elif isinstance(value, int):
self.transition_effect_combo_box.setCurrentIndex(value)
else:
raise TypeError('transition_type must either be a string or an int')
@property
def transition_speed(self):
return self.transition_speed_combo_box.currentIndex()
@transition_speed.setter
def transition_speed(self, value):
if isinstance(value, str):
self.transition_speed_combo_box.setCurrentIndex(TransitionSpeed.from_string(value))
elif isinstance(value, int):
self.transition_speed_combo_box.setCurrentIndex(value)
else:
raise TypeError('transition_speed must either be a string or an int')
@property
def transition_direction(self):
return self.transition_direction_combo_box.currentIndex()
@transition_direction.setter
def transition_direction(self, value):
if isinstance(value, str):
self.transition_direction_combo_box.setCurrentIndex(TransitionDirection.from_string(value))
elif isinstance(value, int):
self.transition_direction_combo_box.setCurrentIndex(value)
else:
raise TypeError('transition_direction must either be a string or an int')
@property
def is_transition_reverse_enabled(self):
return self.transition_reverse_check_box.isChecked()
@is_transition_reverse_enabled.setter
def is_transition_reverse_enabled(self, value):
self.transition_reverse_check_box.setChecked(value)

View File

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.pages.areaposition` module contains the area position page used in the theme wizard
"""
from PyQt5 import QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.pages import GridLayoutPage
from openlp.core.widgets.labels import FormLabel
class AreaPositionPage(GridLayoutPage):
"""
A wizard page for the area positioning widgets in the theme wizard
"""
def setup_ui(self):
"""
Set up the UI
"""
# Main area position
self.main_position_group_box = QtWidgets.QGroupBox(self)
self.main_position_group_box.setObjectName('main_position_group_box')
self.main_position_layout = QtWidgets.QGridLayout(self.main_position_group_box)
self.main_position_layout.setObjectName('main_position_layout')
self.main_position_check_box = QtWidgets.QCheckBox(self.main_position_group_box)
self.main_position_check_box.setObjectName('main_position_check_box')
self.main_position_layout.addWidget(self.main_position_check_box, 0, 0, 1, 2)
self.main_x_label = FormLabel(self.main_position_group_box)
self.main_x_label.setObjectName('main_x_label')
self.main_position_layout.addWidget(self.main_x_label, 1, 0)
self.main_x_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_x_spin_box.setMaximum(9999)
self.main_x_spin_box.setObjectName('main_x_spin_box')
self.main_position_layout.addWidget(self.main_x_spin_box, 1, 1)
self.main_y_label = FormLabel(self.main_position_group_box)
self.main_y_label.setObjectName('main_y_label')
self.main_position_layout.addWidget(self.main_y_label, 2, 0)
self.main_y_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_y_spin_box.setMaximum(9999)
self.main_y_spin_box.setObjectName('main_y_spin_box')
self.main_position_layout.addWidget(self.main_y_spin_box, 2, 1)
self.main_width_label = FormLabel(self.main_position_group_box)
self.main_width_label.setObjectName('main_width_label')
self.main_width_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_width_spin_box.setMaximum(9999)
self.main_width_spin_box.setObjectName('main_width_spin_box')
self.main_position_layout.addWidget(self.main_width_label, 3, 0)
self.main_position_layout.addWidget(self.main_width_spin_box, 3, 1)
self.main_height_label = FormLabel(self.main_position_group_box)
self.main_height_label.setObjectName('main_height_label')
self.main_height_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_height_spin_box.setMaximum(9999)
self.main_height_spin_box.setObjectName('main_height_spin_box')
self.main_position_layout.addWidget(self.main_height_label, 4, 0)
self.main_position_layout.addWidget(self.main_height_spin_box, 4, 1)
self.layout.addWidget(self.main_position_group_box, 0, 0)
# Footer area position
self.footer_position_group_box = QtWidgets.QGroupBox(self)
self.footer_position_group_box.setObjectName('footer_position_group_box')
self.footer_position_layout = QtWidgets.QGridLayout(self.footer_position_group_box)
self.footer_position_layout.setObjectName('footer_position_layout')
self.footer_position_check_box = QtWidgets.QCheckBox(self.footer_position_group_box)
self.footer_position_check_box.setObjectName('footer_position_check_box')
self.footer_position_layout.addWidget(self.footer_position_check_box, 0, 0, 1, 2)
self.footer_x_label = FormLabel(self.footer_position_group_box)
self.footer_x_label.setObjectName('footer_x_label')
self.footer_x_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_x_spin_box.setMaximum(9999)
self.footer_x_spin_box.setObjectName('footer_x_spin_box')
self.footer_position_layout.addWidget(self.footer_x_label, 1, 0)
self.footer_position_layout.addWidget(self.footer_x_spin_box, 1, 1)
self.footer_y_label = FormLabel(self.footer_position_group_box)
self.footer_y_label.setObjectName('footer_y_label')
self.footer_y_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_y_spin_box.setMaximum(9999)
self.footer_y_spin_box.setObjectName('footer_y_spin_box')
self.footer_position_layout.addWidget(self.footer_y_label, 2, 0)
self.footer_position_layout.addWidget(self.footer_y_spin_box, 2, 1)
self.footer_width_label = FormLabel(self.footer_position_group_box)
self.footer_width_label.setObjectName('footer_width_label')
self.footer_width_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_width_spin_box.setMaximum(9999)
self.footer_width_spin_box.setObjectName('footer_width_spin_box')
self.footer_position_layout.addWidget(self.footer_width_label, 3, 0)
self.footer_position_layout.addWidget(self.footer_width_spin_box, 3, 1)
self.footer_height_label = FormLabel(self.footer_position_group_box)
self.footer_height_label.setObjectName('footer_height_label')
self.footer_height_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_height_spin_box.setMaximum(9999)
self.footer_height_spin_box.setObjectName('footer_height_spin_box')
self.footer_position_layout.addWidget(self.footer_height_label, 4, 0)
self.footer_position_layout.addWidget(self.footer_height_spin_box, 4, 1)
self.layout.addWidget(self.footer_position_group_box, 0, 1)
# Connect signals to slots
self.main_position_check_box.toggled.connect(self.main_x_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_y_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_width_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_height_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_x_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_y_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_width_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_height_spin_box.setDisabled)
def retranslate_ui(self):
"""
Translate the UI
"""
self.main_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Main Area'))
self.main_position_check_box.setText(translate('OpenLP.ThemeWizard', '&Use default location'))
self.main_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:'))
self.main_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:'))
self.main_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:'))
self.main_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Footer Area'))
self.footer_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:'))
self.footer_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:'))
self.footer_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:'))
self.footer_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location'))
@property
def use_main_default_location(self):
return self.main_position_check_box.isChecked()
@use_main_default_location.setter
def use_main_default_location(self, value):
self.main_position_check_box.setChecked(value)
@property
def main_x(self):
return self.main_x_spin_box.value()
@main_x.setter
def main_x(self, value):
self.main_x_spin_box.setValue(value)
@property
def main_y(self):
return self.main_y_spin_box.value()
@main_y.setter
def main_y(self, value):
self.main_y_spin_box.setValue(value)
@property
def main_width(self):
return self.main_width_spin_box.value()
@main_width.setter
def main_width(self, value):
self.main_width_spin_box.setValue(value)
@property
def main_height(self):
return self.main_height_spin_box.value()
@main_height.setter
def main_height(self, value):
self.main_height_spin_box.setValue(value)
@property
def use_footer_default_location(self):
return self.footer_position_check_box.isChecked()
@use_footer_default_location.setter
def use_footer_default_location(self, value):
self.footer_position_check_box.setChecked(value)
@property
def footer_x(self):
return self.footer_x_spin_box.value()
@footer_x.setter
def footer_x(self, value):
self.footer_x_spin_box.setValue(value)
@property
def footer_y(self):
return self.footer_y_spin_box.value()
@footer_y.setter
def footer_y(self, value):
self.footer_y_spin_box.setValue(value)
@property
def footer_width(self):
return self.footer_width_spin_box.value()
@footer_width.setter
def footer_width(self, value):
self.footer_width_spin_box.setValue(value)
@property
def footer_height(self):
return self.footer_height_spin_box.value()
@footer_height.setter
def footer_height(self, value):
self.footer_height_spin_box.setValue(value)

View File

@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.pages.background` module contains the background page used in the theme wizard
"""
from PyQt5 import QtWidgets
from openlp.core.common import get_images_filter
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib.theme import BackgroundGradientType, BackgroundType
from openlp.core.pages import GridLayoutPage
from openlp.core.ui.media import VIDEO_EXT
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.labels import FormLabel
class BackgroundPage(GridLayoutPage):
"""
A background selection widget
"""
Color = 'color'
Gradient = 'gradient'
Image = 'image'
Video = 'video'
def setup_ui(self):
"""
Set up the ui
"""
# background type
self.background_label = FormLabel(self)
self.background_label.setObjectName('background_label')
self.layout.addWidget(self.background_label, 0, 0)
self.background_combo_box = QtWidgets.QComboBox(self)
self.background_combo_box.addItems(['', '', '', ''])
self.background_combo_box.setObjectName('background_combo_box')
self.layout.addWidget(self.background_combo_box, 0, 1, 1, 3)
# color
self.color_label = FormLabel(self)
self.color_label.setObjectName('color_label')
self.layout.addWidget(self.color_label, 1, 0)
self.color_button = ColorButton(self)
self.color_button.setObjectName('color_button')
self.layout.addWidget(self.color_button, 1, 1)
self.color_widgets = [self.color_label, self.color_button]
# gradient
self.gradient_type_label = FormLabel(self)
self.gradient_type_label.setObjectName('gradient_type_label')
self.layout.addWidget(self.gradient_type_label, 2, 0)
self.gradient_combo_box = QtWidgets.QComboBox(self)
self.gradient_combo_box.setObjectName('gradient_combo_box')
self.gradient_combo_box.addItems(['', '', '', '', ''])
self.layout.addWidget(self.gradient_combo_box, 2, 1, 1, 3)
self.gradient_start_label = FormLabel(self)
self.gradient_start_label.setObjectName('gradient_start_label')
self.layout.addWidget(self.gradient_start_label, 3, 0)
self.gradient_start_button = ColorButton(self)
self.gradient_start_button.setObjectName('gradient_start_button')
self.layout.addWidget(self.gradient_start_button, 3, 1)
self.gradient_end_label = FormLabel(self)
self.gradient_end_label.setObjectName('gradient_end_label')
self.layout.addWidget(self.gradient_end_label, 3, 2)
self.gradient_end_button = ColorButton(self)
self.gradient_end_button.setObjectName('gradient_end_button')
self.layout.addWidget(self.gradient_end_button, 3, 3)
self.gradient_widgets = [self.gradient_type_label, self.gradient_combo_box, self.gradient_start_label,
self.gradient_start_button, self.gradient_end_label, self.gradient_end_button]
# image
self.image_label = FormLabel(self)
self.image_label.setObjectName('image_label')
self.layout.addWidget(self.image_label, 4, 0)
self.image_path_edit = PathEdit(self, dialog_caption=translate('OpenLP.ThemeWizard', 'Select Image'),
show_revert=False)
self.layout.addWidget(self.image_path_edit, 4, 1, 1, 3)
self.image_color_label = FormLabel(self)
self.image_color_label.setObjectName('image_color_label')
self.layout.addWidget(self.image_color_label, 5, 0)
self.image_color_button = ColorButton(self)
self.image_color_button.color = '#000000'
self.image_color_button.setObjectName('image_color_button')
self.layout.addWidget(self.image_color_button, 5, 1)
self.image_widgets = [self.image_label, self.image_path_edit, self.image_color_label, self.image_color_button]
# video
self.video_label = FormLabel(self)
self.video_label.setObjectName('video_label')
self.layout.addWidget(self.video_label, 6, 0)
self.video_path_edit = PathEdit(self, dialog_caption=translate('OpenLP.ThemeWizard', 'Select Video'),
show_revert=False)
self.layout.addWidget(self.video_path_edit, 6, 1, 1, 3)
self.video_color_label = FormLabel(self)
self.video_color_label.setObjectName('video_color_label')
self.layout.addWidget(self.video_color_label, 7, 0)
self.video_color_button = ColorButton(self)
self.video_color_button.color = '#000000'
self.video_color_button.setObjectName('video_color_button')
self.layout.addWidget(self.video_color_button, 7, 1)
self.video_widgets = [self.video_label, self.video_path_edit, self.video_color_label, self.video_color_button]
# Force everything up
self.layout_spacer = QtWidgets.QSpacerItem(1, 1)
self.layout.addItem(self.layout_spacer, 8, 0, 1, 4)
# Connect slots
self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed)
# Force the first set of widgets to show
self._on_background_type_index_changed(0)
def retranslate_ui(self):
"""
Translate the text elements of the widget
"""
self.background_label.setText(translate('OpenLP.ThemeWizard', 'Background type:'))
self.background_combo_box.setItemText(BackgroundType.Solid, translate('OpenLP.ThemeWizard', 'Solid color'))
self.background_combo_box.setItemText(BackgroundType.Gradient, translate('OpenLP.ThemeWizard', 'Gradient'))
self.background_combo_box.setItemText(BackgroundType.Image, UiStrings().Image)
self.background_combo_box.setItemText(BackgroundType.Transparent,
translate('OpenLP.ThemeWizard', 'Transparent'))
self.color_label.setText(translate('OpenLP.ThemeWizard', 'Color:'))
self.gradient_start_label.setText(translate('OpenLP.ThemeWizard', 'Starting color:'))
self.gradient_end_label.setText(translate('OpenLP.ThemeWizard', 'Ending color:'))
self.gradient_type_label.setText(translate('OpenLP.ThemeWizard', 'Gradient:'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Horizontal,
translate('OpenLP.ThemeWizard', 'Horizontal'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Vertical,
translate('OpenLP.ThemeWizard', 'Vertical'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Circular,
translate('OpenLP.ThemeWizard', 'Circular'))
self.gradient_combo_box.setItemText(BackgroundGradientType.LeftTop,
translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right'))
self.gradient_combo_box.setItemText(BackgroundGradientType.LeftBottom,
translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right'))
self.image_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:'))
self.image_label.setText('{text}:'.format(text=UiStrings().Image))
self.video_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:'))
self.video_label.setText('{text}:'.format(text=UiStrings().Video))
self.image_path_edit.filters = \
'{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles)
visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT))
actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT))
video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'),
visible=visible_formats, actual=actual_formats)
self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles)
def _on_background_type_index_changed(self, index):
"""
Hide and show widgets based on index
"""
widget_sets = [self.color_widgets, self.gradient_widgets, self.image_widgets, [], self.video_widgets]
for widgets in widget_sets:
for widget in widgets:
widget.hide()
if index < len(widget_sets):
for widget in widget_sets[index]:
widget.show()
@property
def background_type(self):
return BackgroundType.to_string(self.background_combo_box.currentIndex())
@background_type.setter
def background_type(self, value):
if isinstance(value, str):
self.background_combo_box.setCurrentIndex(BackgroundType.from_string(value))
elif isinstance(value, int):
self.background_combo_box.setCurrentIndex(value)
else:
raise TypeError('background_type must either be a string or an int')
@property
def color(self):
return self.color_button.color
@color.setter
def color(self, value):
self.color_button.color = value
@property
def gradient_type(self):
return BackgroundGradientType.to_string(self.gradient_combo_box.currentIndex())
@gradient_type.setter
def gradient_type(self, value):
if isinstance(value, str):
self.gradient_combo_box.setCurrentIndex(BackgroundGradientType.from_string(value))
elif isinstance(value, int):
self.gradient_combo_box.setCurrentIndex(value)
else:
raise TypeError('gradient_type must either be a string or an int')
@property
def gradient_start(self):
return self.gradient_start_button.color
@gradient_start.setter
def gradient_start(self, value):
self.gradient_start_button.color = value
@property
def gradient_end(self):
return self.gradient_end_button.color
@gradient_end.setter
def gradient_end(self, value):
self.gradient_end_button.color = value
@property
def image_color(self):
return self.image_color_button.color
@image_color.setter
def image_color(self, value):
self.image_color_button.color = value
@property
def image_path(self):
return self.image_path_edit.path
@image_path.setter
def image_path(self, value):
self.image_path_edit.path = value
@property
def video_color(self):
return self.video_color_button.color
@video_color.setter
def video_color(self, value):
self.video_color_button.color = value
@property
def video_path(self):
return self.video_path_edit.path
@video_path.setter
def video_path(self, value):
self.video_path_edit.path = value

View File

@ -0,0 +1,345 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
The :mod:`~openlp.core.pages.fontselect` module contains the font selection page used in the theme wizard
"""
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.pages import GridLayoutPage
from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.labels import FormLabel
class FontSelectPage(GridLayoutPage):
"""
A font selection widget
"""
Outline = 'outline'
Shadow = 'shadow'
LineSpacing = 'line_spacing'
font_name_changed = QtCore.pyqtSignal(str)
font_size_changed = QtCore.pyqtSignal(int)
font_color_changed = QtCore.pyqtSignal(str)
is_bold_changed = QtCore.pyqtSignal(bool)
is_italic_changed = QtCore.pyqtSignal(bool)
line_spacing_changed = QtCore.pyqtSignal(int)
is_outline_enabled_changed = QtCore.pyqtSignal(bool)
outline_color_changed = QtCore.pyqtSignal(str)
outline_size_changed = QtCore.pyqtSignal(int)
is_shadow_enabled_changed = QtCore.pyqtSignal(bool)
shadow_color_changed = QtCore.pyqtSignal(str)
shadow_size_changed = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.feature_widgets = {
FontSelectPage.Outline: [self.outline_groupbox],
FontSelectPage.Shadow: [self.shadow_groupbox],
FontSelectPage.LineSpacing: [self.line_spacing_label, self.line_spacing_spinbox]
}
def setup_ui(self):
# Font name
self.font_name_label = FormLabel(self)
self.font_name_label.setObjectName('font_name_label')
self.layout.addWidget(self.font_name_label, 0, 0)
self.font_name_combobox = QtWidgets.QFontComboBox(self)
self.font_name_combobox.setObjectName('font_name_combobox')
self.layout.addWidget(self.font_name_combobox, 0, 1, 1, 3)
# Font color
self.font_color_label = FormLabel(self)
self.font_color_label.setObjectName('font_color_label')
self.layout.addWidget(self.font_color_label, 1, 0)
self.font_color_button = ColorButton(self)
self.font_color_button.setObjectName('font_color_button')
self.layout.addWidget(self.font_color_button, 1, 1)
# Font style
self.font_style_label = FormLabel(self)
self.font_style_label.setObjectName('font_style_label')
self.layout.addWidget(self.font_style_label, 1, 2)
self.style_layout = QtWidgets.QHBoxLayout()
self.style_bold_button = QtWidgets.QToolButton(self)
self.style_bold_button.setCheckable(True)
self.style_bold_button.setIcon(UiIcons().bold)
self.style_bold_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Bold))
self.style_bold_button.setObjectName('style_bold_button')
self.style_layout.addWidget(self.style_bold_button)
self.style_italic_button = QtWidgets.QToolButton(self)
self.style_italic_button.setCheckable(True)
self.style_italic_button.setIcon(UiIcons().italic)
self.style_italic_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Italic))
self.style_italic_button.setObjectName('style_italic_button')
self.style_layout.addWidget(self.style_italic_button)
self.style_layout.addStretch(1)
self.layout.addLayout(self.style_layout, 1, 3)
# Font size
self.font_size_label = FormLabel(self)
self.font_size_label.setObjectName('font_size_label')
self.layout.addWidget(self.font_size_label, 2, 0)
self.font_size_spinbox = QtWidgets.QSpinBox(self)
self.font_size_spinbox.setMaximum(999)
self.font_size_spinbox.setValue(16)
self.font_size_spinbox.setObjectName('font_size_spinbox')
self.layout.addWidget(self.font_size_spinbox, 2, 1)
# Line spacing
self.line_spacing_label = FormLabel(self)
self.line_spacing_label.setObjectName('line_spacing_label')
self.layout.addWidget(self.line_spacing_label, 2, 2)
self.line_spacing_spinbox = QtWidgets.QSpinBox(self)
self.line_spacing_spinbox.setMinimum(-250)
self.line_spacing_spinbox.setMaximum(250)
self.line_spacing_spinbox.setObjectName('line_spacing_spinbox')
self.layout.addWidget(self.line_spacing_spinbox, 2, 3)
# Outline
self.outline_groupbox = QtWidgets.QGroupBox(self)
self.outline_groupbox.setCheckable(True)
self.outline_groupbox.setChecked(False)
self.outline_groupbox.setObjectName('outline_groupbox')
self.outline_layout = QtWidgets.QGridLayout(self.outline_groupbox)
self.layout.addWidget(self.outline_groupbox, 3, 0, 1, 2)
# Outline colour
self.outline_color_label = FormLabel(self.outline_groupbox)
self.outline_color_label.setObjectName('outline_color_label')
self.outline_layout.addWidget(self.outline_color_label, 0, 0)
self.outline_color_button = ColorButton(self.outline_groupbox)
self.outline_color_button.setObjectName('outline_color_button')
self.outline_layout.addWidget(self.outline_color_button, 0, 1)
# Outline size
self.outline_size_label = FormLabel(self.outline_groupbox)
self.outline_size_label.setObjectName('outline_size_label')
self.outline_layout.addWidget(self.outline_size_label, 1, 0)
self.outline_size_spinbox = QtWidgets.QSpinBox(self.outline_groupbox)
self.outline_size_spinbox.setMaximum(9999)
self.outline_size_spinbox.setObjectName('outline_size_spinbox')
self.outline_layout.addWidget(self.outline_size_spinbox, 1, 1)
# Shadow
self.shadow_groupbox = QtWidgets.QGroupBox(self)
self.shadow_groupbox.setCheckable(True)
self.shadow_groupbox.setChecked(False)
self.shadow_groupbox.setObjectName('shadow_groupbox')
self.shadow_layout = QtWidgets.QGridLayout(self.shadow_groupbox)
self.layout.addWidget(self.shadow_groupbox, 3, 2, 1, 2)
# Shadow color
self.shadow_color_label = FormLabel(self.shadow_groupbox)
self.shadow_color_label.setObjectName('shadow_color_label')
self.shadow_layout.addWidget(self.shadow_color_label, 0, 0)
self.shadow_color_button = ColorButton(self.shadow_groupbox)
self.shadow_color_button.setObjectName('shadow_color_button')
self.shadow_layout.addWidget(self.shadow_color_button, 0, 1)
# Shadow size
self.shadow_size_label = FormLabel(self.shadow_groupbox)
self.shadow_size_label.setObjectName('shadow_size_label')
self.shadow_layout.addWidget(self.shadow_size_label, 1, 0)
self.shadow_size_spinbox = QtWidgets.QSpinBox(self.shadow_groupbox)
self.shadow_size_spinbox.setMaximum(9999)
self.shadow_size_spinbox.setObjectName('shadow_size_spinbox')
self.shadow_layout.addWidget(self.shadow_size_spinbox, 1, 1)
# Connect all the signals
self.font_name_combobox.activated.connect(self._on_font_name_changed)
self.font_color_button.colorChanged.connect(self._on_font_color_changed)
self.style_bold_button.toggled.connect(self._on_style_bold_toggled)
self.style_italic_button.toggled.connect(self._on_style_italic_toggled)
self.font_size_spinbox.valueChanged.connect(self._on_font_size_changed)
self.line_spacing_spinbox.valueChanged.connect(self._on_line_spacing_changed)
self.outline_groupbox.toggled.connect(self._on_outline_toggled)
self.outline_color_button.colorChanged.connect(self._on_outline_color_changed)
self.outline_size_spinbox.valueChanged.connect(self._on_outline_size_changed)
self.shadow_groupbox.toggled.connect(self._on_shadow_toggled)
self.shadow_color_button.colorChanged.connect(self._on_shadow_color_changed)
self.shadow_size_spinbox.valueChanged.connect(self._on_shadow_size_changed)
def retranslate_ui(self):
self.font_name_label.setText(translate('OpenLP.FontSelectWidget', 'Font:'))
self.font_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.font_style_label.setText(translate('OpenLP.FontSelectWidget', 'Style:'))
self.style_bold_button.setToolTip('{name} ({shortcut})'.format(
name=translate('OpenLP.FontSelectWidget', 'Bold'),
shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Bold).toString()
))
self.style_italic_button.setToolTip('{name} ({shortcut})'.format(
name=translate('OpenLP.FontSelectWidget', 'Italic'),
shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Italic).toString()
))
self.font_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
self.font_size_spinbox.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit))
self.line_spacing_label.setText(translate('OpenLP.FontSelectWidget', 'Line Spacing:'))
self.outline_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Outline'))
self.outline_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.outline_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
self.shadow_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Shadow'))
self.shadow_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.shadow_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
def _on_font_name_changed(self, name):
if isinstance(name, str):
self.font_name_changed.emit(name)
def _on_font_color_changed(self, color):
self.font_color_changed.emit(color)
def _on_style_bold_toggled(self, is_bold):
self.is_bold_changed.emit(is_bold)
def _on_style_italic_toggled(self, is_italic):
self.is_italic_changed.emit(is_italic)
def _on_font_size_changed(self, size):
self.font_size_changed.emit(size)
def _on_line_spacing_changed(self, spacing):
self.line_spacing_changed.emit(spacing)
def _on_outline_toggled(self, is_enabled):
self.is_outline_enabled_changed.emit(is_enabled)
def _on_outline_color_changed(self, color):
self.outline_color_changed.emit(color)
def _on_outline_size_changed(self, size):
self.outline_size_changed.emit(size)
def _on_shadow_toggled(self, is_enabled):
self.is_shadow_enabled_changed.emit(is_enabled)
def _on_shadow_color_changed(self, color):
self.shadow_color_changed.emit(color)
def _on_shadow_size_changed(self, size):
self.shadow_size_changed.emit(size)
def enable_features(self, *features):
"""
Enable a feature
"""
for feature_name in features:
if feature_name not in self.feature_widgets.keys():
raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name))
for widget in self.feature_widgets[feature_name]:
widget.show()
def disable_features(self, *features):
"""
Disable a feature
"""
for feature_name in features:
if feature_name not in self.feature_widgets.keys():
raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name))
for widget in self.feature_widgets[feature_name]:
widget.hide()
@property
def font_name(self):
return self.font_name_combobox.currentFont().family()
@font_name.setter
def font_name(self, font):
self.font_name_combobox.setCurrentFont(QtGui.QFont(font))
@property
def font_color(self):
return self.font_color_button.color
@font_color.setter
def font_color(self, color):
self.font_color_button.color = color
@property
def is_bold(self):
return self.style_bold_button.isChecked()
@is_bold.setter
def is_bold(self, is_bold):
self.style_bold_button.setChecked(is_bold)
@property
def is_italic(self):
return self.style_italic_button.isChecked()
@is_italic.setter
def is_italic(self, is_italic):
self.style_italic_button.setChecked(is_italic)
@property
def font_size(self):
return self.font_size_spinbox.value()
@font_size.setter
def font_size(self, size):
self.font_size_spinbox.setValue(size)
@property
def line_spacing(self):
return self.line_spacing_spinbox.value()
@line_spacing.setter
def line_spacing(self, line_spacing):
self.line_spacing_spinbox.setValue(line_spacing)
@property
def is_outline_enabled(self):
return self.outline_groupbox.isChecked()
@is_outline_enabled.setter
def is_outline_enabled(self, is_enabled):
self.outline_groupbox.setChecked(is_enabled)
@property
def outline_color(self):
return self.outline_color_button.color
@outline_color.setter
def outline_color(self, color):
self.outline_color_button.color = color
@property
def outline_size(self):
return self.outline_size_spinbox.value()
@outline_size.setter
def outline_size(self, size):
self.outline_size_spinbox.setValue(size)
@property
def is_shadow_enabled(self):
return self.shadow_groupbox.isChecked()
@is_shadow_enabled.setter
def is_shadow_enabled(self, is_enabled):
self.shadow_groupbox.setChecked(is_enabled)
@property
def shadow_color(self):
return self.shadow_color_button.color
@shadow_color.setter
def shadow_color(self, color):
self.shadow_color_button.color = color
@property
def shadow_size(self):
return self.shadow_size_spinbox.value()
@shadow_size.setter
def shadow_size(self, size):
self.shadow_size_spinbox.setValue(size)

View File

@ -25,13 +25,12 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import get_images_filter, is_not_image_file from openlp.core.common import is_not_image_file
from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.mixins import RegistryProperties from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundGradientType, BackgroundType from openlp.core.lib.theme import BackgroundType
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import VIDEO_EXT
from openlp.core.ui.themelayoutform import ThemeLayoutForm from openlp.core.ui.themelayoutform import ThemeLayoutForm
from openlp.core.ui.themewizard import Ui_ThemeWizard from openlp.core.ui.themewizard import Ui_ThemeWizard
@ -60,42 +59,21 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
Set up the class. This method is mocked out by the tests. Set up the class. This method is mocked out by the tests.
""" """
self.setup_ui(self) self.setup_ui(self)
self.registerFields() self.can_update_theme = True
self.update_theme_allowed = True
self.temp_background_filename = None self.temp_background_filename = None
self.theme_layout_form = ThemeLayoutForm(self) self.theme_layout_form = ThemeLayoutForm(self)
self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed)
self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed)
self.color_button.colorChanged.connect(self.on_color_changed)
self.image_color_button.colorChanged.connect(self.on_image_color_changed)
self.video_color_button.colorChanged.connect(self.on_video_color_changed)
self.gradient_start_button.colorChanged.connect(self.on_gradient_start_color_changed)
self.gradient_end_button.colorChanged.connect(self.on_gradient_end_color_changed)
self.image_path_edit.filters = \
'{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles)
self.image_path_edit.pathChanged.connect(self.on_image_path_edit_path_changed)
visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT))
actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT))
video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'),
visible=visible_formats, actual=actual_formats)
self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles)
self.video_path_edit.pathChanged.connect(self.on_video_path_edit_path_changed)
self.footer_color_button.colorChanged.connect(self.on_footer_color_changed)
self.customButtonClicked.connect(self.on_custom_1_button_clicked) self.customButtonClicked.connect(self.on_custom_1_button_clicked)
self.main_position_check_box.stateChanged.connect(self.on_main_position_check_box_state_changed)
self.footer_position_check_box.stateChanged.connect(self.on_footer_position_check_box_state_changed)
self.currentIdChanged.connect(self.on_current_id_changed) self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function('theme_line_count', self.update_lines_text) Registry().register_function('theme_line_count', self.update_lines_text)
self.main_font.font_name_changed.connect(self.calculate_lines) self.main_area_page.font_name_changed.connect(self.calculate_lines)
self.main_font.font_size_changed.connect(self.calculate_lines) self.main_area_page.font_size_changed.connect(self.calculate_lines)
self.main_font.line_spacing_changed.connect(self.calculate_lines) self.main_area_page.line_spacing_changed.connect(self.calculate_lines)
self.main_font.is_outline_enabled_changed.connect(self.on_outline_toggled) self.main_area_page.is_outline_enabled_changed.connect(self.on_outline_toggled)
self.main_font.outline_size_changed.connect(self.calculate_lines) self.main_area_page.outline_size_changed.connect(self.calculate_lines)
self.main_font.is_shadow_enabled_changed.connect(self.on_shadow_toggled) self.main_area_page.is_shadow_enabled_changed.connect(self.on_shadow_toggled)
self.main_font.shadow_size_changed.connect(self.calculate_lines) self.main_area_page.shadow_size_changed.connect(self.calculate_lines)
self.footer_font_combo_box.activated.connect(self.update_theme) self.footer_area_page.font_name_changed.connect(self.update_theme)
self.footer_size_spin_box.valueChanged.connect(self.update_theme) self.footer_area_page.font_size_changed.connect(self.update_theme)
self.transitions_check_box.stateChanged.connect(self.on_transitions_check_box_state_changed)
def set_defaults(self): def set_defaults(self):
""" """
@ -109,33 +87,6 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
self.set_position_page_values() self.set_position_page_values()
self.set_preview_page_values() self.set_preview_page_values()
def registerFields(self):
"""
Map field names to screen names,
"""
self.background_page.registerField('background_type', self.background_combo_box)
self.background_page.registerField('color', self.color_button)
self.background_page.registerField('gradient_start', self.gradient_start_button)
self.background_page.registerField('gradient_end', self.gradient_end_button)
self.background_page.registerField('background_image', self.image_path_edit,
'path', self.image_path_edit.pathChanged)
self.background_page.registerField('gradient', self.gradient_combo_box)
self.main_area_page.registerField('footer_size_spin_box', self.footer_size_spin_box)
self.area_position_page.registerField('main_position_x', self.main_x_spin_box)
self.area_position_page.registerField('main_position_y', self.main_y_spin_box)
self.area_position_page.registerField('main_position_width', self.main_width_spin_box)
self.area_position_page.registerField('main_position_height', self.main_height_spin_box)
self.area_position_page.registerField('footer_position_x', self.footer_x_spin_box)
self.area_position_page.registerField('footer_position_y', self.footer_y_spin_box)
self.area_position_page.registerField('footer_position_width', self.footer_width_spin_box)
self.area_position_page.registerField('footer_position_height', self.footer_height_spin_box)
self.background_page.registerField('horizontal', self.horizontal_combo_box)
self.background_page.registerField('vertical', self.vertical_combo_box)
self.background_page.registerField('slide_transition', self.transitions_check_box)
self.background_page.registerField('slide_transition_type', self.transition_combo_box)
self.background_page.registerField('slide_transition_speed', self.transition_speed_combo_box)
self.background_page.registerField('name', self.theme_name_edit)
def calculate_lines(self, *args): def calculate_lines(self, *args):
""" """
Calculate the number of lines on a page by rendering text Calculate the number of lines on a page by rendering text
@ -175,7 +126,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
""" """
background_image = BackgroundType.to_string(BackgroundType.Image) background_image = BackgroundType.to_string(BackgroundType.Image)
if self.page(self.currentId()) == self.background_page and \ if self.page(self.currentId()) == self.background_page and \
self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename): self.background_page.background_type == background_image and \
is_not_image_file(self.background_page.image_path):
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'), QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
translate('OpenLP.ThemeWizard', 'You have not selected a ' translate('OpenLP.ThemeWizard', 'You have not selected a '
'background image. Please select one before continuing.')) 'background image. Please select one before continuing.'))
@ -229,7 +181,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
""" """
Change state as Outline check box changed Change state as Outline check box changed
""" """
if self.update_theme_allowed: if self.can_update_theme:
self.theme.font_main_outline = is_enabled self.theme.font_main_outline = is_enabled
self.calculate_lines() self.calculate_lines()
@ -237,45 +189,19 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
""" """
Change state as Shadow check box changed Change state as Shadow check box changed
""" """
if self.update_theme_allowed: if self.can_update_theme:
self.theme.font_main_shadow = is_enabled self.theme.font_main_shadow = is_enabled
self.calculate_lines() self.calculate_lines()
def on_main_position_check_box_state_changed(self, value):
"""
Change state as Main Area _position check box changed
NOTE the font_main_override is the inverse of the check box value
"""
if self.update_theme_allowed:
self.theme.font_main_override = (value != QtCore.Qt.Checked)
def on_footer_position_check_box_state_changed(self, value):
"""
Change state as Footer Area _position check box changed
NOTE the font_footer_override is the inverse of the check box value
"""
if self.update_theme_allowed:
self.theme.font_footer_override = (value != QtCore.Qt.Checked)
def on_transitions_check_box_state_changed(self, state):
"""
Change state as Transitions check box is changed
"""
if self.update_theme_allowed:
self.theme.display_slide_transition = state == QtCore.Qt.Checked
self.transition_combo_box.setEnabled(self.theme.display_slide_transition)
self.transition_speed_combo_box.setEnabled(self.theme.display_slide_transition)
self.calculate_lines()
def exec(self, edit=False): def exec(self, edit=False):
""" """
Run the wizard. Run the wizard.
""" """
log.debug('Editing theme {name}'.format(name=self.theme.theme_name)) log.debug('Editing theme {name}'.format(name=self.theme.theme_name))
self.temp_background_filename = self.theme.background_source self.temp_background_filename = self.theme.background_source
self.update_theme_allowed = False self.can_update_theme = False
self.set_defaults() self.set_defaults()
self.update_theme_allowed = True self.can_update_theme = True
self.theme_name_label.setVisible(not edit) self.theme_name_label.setVisible(not edit)
self.theme_name_edit.setVisible(not edit) self.theme_name_edit.setVisible(not edit)
self.edit_mode = edit self.edit_mode = edit
@ -308,228 +234,154 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
""" """
Handle the display and state of the Background page. Handle the display and state of the Background page.
""" """
self.background_page.background_type = self.theme.background_type
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid): if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
self.color_button.color = self.theme.background_color self.background_page.color = self.theme.background_color
self.setField('background_type', 0)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient):
self.gradient_start_button.color = self.theme.background_start_color self.background_page.gradient_start = self.theme.background_start_color
self.gradient_end_button.color = self.theme.background_end_color self.background_page.gradient_end = self.theme.background_end_color
self.setField('background_type', 1) self.background_page.gradient_type = self.theme.background_direction
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.image_color_button.color = self.theme.background_border_color self.background_page.image_color = self.theme.background_border_color
self.image_path_edit.path = self.theme.background_source if self.theme.background_source and self.theme.background_source.exists():
self.setField('background_type', 2) self.background_page.image_path = self.theme.background_source
else:
self.background_page.image_path = self.theme.background_filename
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
self.video_color_button.color = self.theme.background_border_color self.background_page.video_color = self.theme.background_border_color
self.video_path_edit.path = self.theme.background_source if self.theme.background_source and self.theme.background_source.exists():
self.setField('background_type', 4) self.background_page.video_path = self.theme.background_source
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Stream): else:
self.setField('background_type', 5) self.background_page.video_path = self.theme.background_filename
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
self.setField('background_type', 3)
if self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal):
self.setField('gradient', 0)
elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical):
self.setField('gradient', 1)
elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Circular):
self.setField('gradient', 2)
elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop):
self.setField('gradient', 3)
else:
self.setField('gradient', 4)
def set_main_area_page_values(self): def set_main_area_page_values(self):
""" """
Handle the display and state of the Main Area page. Handle the display and state of the Main Area page.
""" """
self.main_font.font_name = self.theme.font_main_name self.main_area_page.font_name = self.theme.font_main_name
self.main_font.font_color = self.theme.font_main_color self.main_area_page.font_color = self.theme.font_main_color
self.main_font.font_size = self.theme.font_main_size self.main_area_page.font_size = self.theme.font_main_size
self.main_font.line_spacing = self.theme.font_main_line_adjustment self.main_area_page.line_spacing = self.theme.font_main_line_adjustment
self.main_font.is_outline_enabled = self.theme.font_main_outline self.main_area_page.is_outline_enabled = self.theme.font_main_outline
self.main_font.outline_color = self.theme.font_main_outline_color self.main_area_page.outline_color = self.theme.font_main_outline_color
self.main_font.outline_size = self.theme.font_main_outline_size self.main_area_page.outline_size = self.theme.font_main_outline_size
self.main_font.is_shadow_enabled = self.theme.font_main_shadow self.main_area_page.is_shadow_enabled = self.theme.font_main_shadow
self.main_font.shadow_color = self.theme.font_main_shadow_color self.main_area_page.shadow_color = self.theme.font_main_shadow_color
self.main_font.shadow_size = self.theme.font_main_shadow_size self.main_area_page.shadow_size = self.theme.font_main_shadow_size
self.main_font.is_bold = self.theme.font_main_bold self.main_area_page.is_bold = self.theme.font_main_bold
self.main_font.is_italic = self.theme.font_main_italics self.main_area_page.is_italic = self.theme.font_main_italics
def set_footer_area_page_values(self): def set_footer_area_page_values(self):
""" """
Handle the display and state of the Footer Area page. Handle the display and state of the Footer Area page.
""" """
self.footer_font_combo_box.setCurrentFont(QtGui.QFont(self.theme.font_footer_name)) self.footer_area_page.font_name = self.theme.font_footer_name
self.footer_color_button.color = self.theme.font_footer_color self.footer_area_page.font_color = self.theme.font_footer_color
self.setField('footer_size_spin_box', self.theme.font_footer_size)
def set_position_page_values(self): def set_position_page_values(self):
""" """
Handle the display and state of the _position page. Handle the display and state of the _position page.
""" """
# Main Area # Main Area
self.main_position_check_box.setChecked(not self.theme.font_main_override) self.area_position_page.use_main_default_location = not self.theme.font_main_override
self.setField('main_position_x', self.theme.font_main_x) self.area_position_page.main_x = self.theme.font_main_x
self.setField('main_position_y', self.theme.font_main_y) self.area_position_page.main_y = self.theme.font_main_y
self.setField('main_position_height', self.theme.font_main_height) self.area_position_page.main_height = self.theme.font_main_height
self.setField('main_position_width', self.theme.font_main_width) self.area_position_page.main_width = self.theme.font_main_width
# Footer # Footer
self.footer_position_check_box.setChecked(not self.theme.font_footer_override) self.area_position_page.use_footer_default_location = not self.theme.font_footer_override
self.setField('footer_position_x', self.theme.font_footer_x) self.area_position_page.footer_x = self.theme.font_footer_x
self.setField('footer_position_y', self.theme.font_footer_y) self.area_position_page.footer_y = self.theme.font_footer_y
self.setField('footer_position_height', self.theme.font_footer_height) self.area_position_page.footer_height = self.theme.font_footer_height
self.setField('footer_position_width', self.theme.font_footer_width) self.area_position_page.footer_width = self.theme.font_footer_width
def set_alignment_page_values(self): def set_alignment_page_values(self):
""" """
Handle the display and state of the Alignments page. Handle the display and state of the Alignments page.
""" """
self.setField('horizontal', self.theme.display_horizontal_align) self.alignment_page.horizontal_align = self.theme.display_horizontal_align
self.setField('vertical', self.theme.display_vertical_align) self.alignment_page.vertical_align = self.theme.display_vertical_align
self.setField('slide_transition', self.theme.display_slide_transition) self.alignment_page.is_transition_enabled = self.theme.display_slide_transition
self.setField('slide_transition_type', self.theme.display_slide_transition_type) self.alignment_page.transition_type = self.theme.display_slide_transition_type
self.setField('slide_transition_speed', self.theme.display_slide_transition_speed) self.alignment_page.transition_speed = self.theme.display_slide_transition_speed
self.alignment_page.transition_direction = self.theme.display_slide_transition_direction
self.alignment_page.is_transition_reverse_enabled = self.theme.display_slide_transition_reverse
def set_preview_page_values(self): def set_preview_page_values(self):
""" """
Handle the display and state of the Preview page. Handle the display and state of the Preview page.
""" """
self.setField('name', self.theme.theme_name) self.theme_name_edit.setText(self.theme.theme_name)
self.preview_box.set_theme(self.theme) self.preview_box.set_theme(self.theme)
def on_background_combo_box_current_index_changed(self, index):
"""
Background style Combo box has changed.
"""
# do not allow updates when screen is building for the first time.
if self.update_theme_allowed:
self.theme.background_type = BackgroundType.to_string(index)
if self.theme.background_type != BackgroundType.to_string(BackgroundType.Image) and \
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video) and \
self.temp_background_filename is None:
self.temp_background_filename = self.theme.background_filename
self.theme.background_filename = None
if (self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video)) and \
self.temp_background_filename is not None:
self.theme.background_filename = self.temp_background_filename
self.temp_background_filename = None
self.set_background_page_values()
def on_gradient_combo_box_current_index_changed(self, index):
"""
Background gradient Combo box has changed.
"""
if self.update_theme_allowed:
self.theme.background_direction = BackgroundGradientType.to_string(index)
self.set_background_page_values()
def on_color_changed(self, color):
"""
Background / Gradient 1 _color button pushed.
"""
self.theme.background_color = color
def on_image_color_changed(self, color):
"""
Background / Gradient 1 _color button pushed.
"""
self.theme.background_border_color = color
def on_video_color_changed(self, color):
"""
Background / Gradient 1 _color button pushed.
"""
self.theme.background_border_color = color
def on_gradient_start_color_changed(self, color):
"""
Gradient 2 _color button pushed.
"""
self.theme.background_start_color = color
def on_gradient_end_color_changed(self, color):
"""
Gradient 2 _color button pushed.
"""
self.theme.background_end_color = color
def on_image_path_edit_path_changed(self, new_path):
"""
Handle the `pathEditChanged` signal from image_path_edit
:param pathlib.Path new_path: Path to the new image
:rtype: None
"""
self.theme.background_source = new_path
self.theme.background_filename = new_path
self.set_background_page_values()
def on_video_path_edit_path_changed(self, new_path):
"""
Handle the `pathEditChanged` signal from video_path_edit
:param pathlib.Path new_path: Path to the new video
:rtype: None
"""
self.theme.background_source = new_path
self.theme.background_filename = new_path
self.set_background_page_values()
def on_footer_color_changed(self, color):
"""
Set the footer colour value
"""
self.theme.font_footer_color = color
def update_theme(self): def update_theme(self):
""" """
Update the theme object from the UI for fields not already updated Update the theme object from the UI for fields not already updated
when the are changed. when the are changed.
""" """
if not self.update_theme_allowed: if not self.can_update_theme:
return return
log.debug('update_theme') log.debug('update_theme')
# background page
self.theme.background_type = self.background_page.background_type
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
self.theme.background_color = self.background_page.color
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient):
self.theme.background_direction = self.background_page.gradient_type
self.theme.background_start_color = self.background_page.gradient_start
self.theme.background_end_color = self.background_page.gradient_end
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.theme.background_border_color = self.background_page.image_color
self.theme.background_source = self.background_page.image_path
self.theme.background_filename = self.background_page.image_path
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
self.theme.background_border_color = self.background_page.video_color
self.theme.background_source = self.background_page.video_path
self.theme.background_filename = self.background_page.video_path
# main page # main page
self.theme.font_main_name = self.main_font.font_name self.theme.font_main_name = self.main_area_page.font_name
self.theme.font_main_size = self.main_font.font_size self.theme.font_main_size = self.main_area_page.font_size
self.theme.font_main_line_adjustment = self.main_font.line_spacing self.theme.font_main_line_adjustment = self.main_area_page.line_spacing
self.theme.font_main_outline_size = self.main_font.outline_size self.theme.font_main_outline_size = self.main_area_page.outline_size
self.theme.font_main_shadow_size = self.main_font.shadow_size self.theme.font_main_shadow_size = self.main_area_page.shadow_size
self.theme.font_main_bold = self.main_font.is_bold self.theme.font_main_bold = self.main_area_page.is_bold
self.theme.font_main_italics = self.main_font.is_italic self.theme.font_main_italics = self.main_area_page.is_italic
# footer page # footer page
self.theme.font_footer_name = self.footer_font_combo_box.currentFont().family() self.theme.font_footer_name = self.footer_area_page.font_name
self.theme.font_footer_size = self.field('footer_size_spin_box') self.theme.font_footer_size = self.footer_area_page.font_size
# position page (main) # position page (main)
self.theme.font_main_override = not self.area_position_page.use_main_default_location
if self.theme.font_main_override: if self.theme.font_main_override:
self.theme.font_main_x = self.field('main_position_x') self.theme.font_main_x = self.area_position_page.main_x
self.theme.font_main_y = self.field('main_position_y') self.theme.font_main_y = self.area_position_page.main_y
self.theme.font_main_height = self.field('main_position_height') self.theme.font_main_height = self.area_position_page.main_height
self.theme.font_main_width = self.field('main_position_width') self.theme.font_main_width = self.area_position_page.main_width
else: else:
self.theme.set_default_header() self.theme.set_default_header()
# position page (footer) # position page (footer)
self.theme.font_footer_override = not self.area_position_page.use_footer_default_location
if self.theme.font_footer_override: if self.theme.font_footer_override:
self.theme.font_footer_x = self.field('footer_position_x') self.theme.font_footer_x = self.area_position_page.footer_x
self.theme.font_footer_y = self.field('footer_position_y') self.theme.font_footer_y = self.area_position_page.footer_y
self.theme.font_footer_height = self.field('footer_position_height') self.theme.font_footer_height = self.area_position_page.footer_height
self.theme.font_footer_width = self.field('footer_position_width') self.theme.font_footer_width = self.area_position_page.footer_width
else: else:
self.theme.set_default_footer() self.theme.set_default_footer()
# position page # alignment page
self.theme.display_horizontal_align = self.horizontal_combo_box.currentIndex() self.theme.display_horizontal_align = self.alignment_page.horizontal_align
self.theme.display_vertical_align = self.vertical_combo_box.currentIndex() self.theme.display_vertical_align = self.alignment_page.vertical_align
self.theme.display_slide_transition = self.field('slide_transition') self.theme.display_slide_transition = self.alignment_page.is_transition_enabled
self.theme.display_slide_transition_type = self.field('slide_transition_type') self.theme.display_slide_transition_type = self.alignment_page.transition_type
self.theme.display_slide_transition_speed = self.field('slide_transition_speed') self.theme.display_slide_transition_speed = self.alignment_page.transition_speed
self.theme.display_slide_transition_direction = self.alignment_page.transition_direction
self.theme.display_slide_transition_reverse = self.alignment_page.is_transition_reverse_enabled
def accept(self): def accept(self):
""" """
Lets save the theme as Finish has been triggered Lets save the theme as Finish has been triggered
""" """
# Save the theme name # Save the theme name
self.theme.theme_name = self.field('name') self.theme.theme_name = self.theme_name_edit.text()
if not self.theme.theme_name: if not self.theme.theme_name:
critical_error_message_box( critical_error_message_box(
translate('OpenLP.ThemeWizard', 'Theme Name Missing'), translate('OpenLP.ThemeWizard', 'Theme Name Missing'),
@ -542,7 +394,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
return return
destination_path = None destination_path = None
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \ if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \
self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
file_name = self.theme.background_filename.name file_name = self.theme.background_filename.name
destination_path = self.path / self.theme.theme_name / file_name destination_path = self.path / self.theme.theme_name / file_name
if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name): if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name):

View File

@ -24,16 +24,15 @@ The Create/Edit theme wizard
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_macosx from openlp.core.common import is_macosx
from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.i18n import translate
from openlp.core.display.render import ThemePreviewRenderer from openlp.core.display.render import ThemePreviewRenderer
from openlp.core.lib.theme import BackgroundGradientType, BackgroundType, HorizontalType, TransitionType, \ from openlp.core.lib.ui import add_welcome_page
TransitionSpeed from openlp.core.pages.alignment import AlignmentTransitionsPage
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets from openlp.core.pages.areaposition import AreaPositionPage
from openlp.core.pages.background import BackgroundPage
from openlp.core.pages.fontselect import FontSelectPage
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.layouts import AspectRatioLayout from openlp.core.widgets.layouts import AspectRatioLayout
from openlp.core.widgets.widgets import FontSelectWidget
class Ui_ThemeWizard(object): class Ui_ThemeWizard(object):
@ -58,234 +57,26 @@ class Ui_ThemeWizard(object):
# Welcome Page # Welcome Page
add_welcome_page(theme_wizard, ':/wizards/wizard_createtheme.bmp') add_welcome_page(theme_wizard, ':/wizards/wizard_createtheme.bmp')
# Background Page # Background Page
self.background_page = QtWidgets.QWizardPage() self.background_page = BackgroundPage()
self.background_page.setObjectName('background_page') self.background_page.setObjectName('background_page')
self.background_layout = QtWidgets.QVBoxLayout(self.background_page)
self.background_layout.setObjectName('background_layout')
self.background_type_layout = QtWidgets.QFormLayout()
self.background_type_layout.setObjectName('background_type_layout')
self.background_label = QtWidgets.QLabel(self.background_page)
self.background_label.setObjectName('background_label')
self.background_combo_box = QtWidgets.QComboBox(self.background_page)
self.background_combo_box.addItems(['', '', '', ''])
self.background_combo_box.setObjectName('background_combo_box')
self.background_type_layout.addRow(self.background_label, self.background_combo_box)
self.background_type_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.background_layout.addLayout(self.background_type_layout)
self.background_stack = QtWidgets.QStackedLayout()
self.background_stack.setObjectName('background_stack')
self.color_widget = QtWidgets.QWidget(self.background_page)
self.color_widget.setObjectName('color_widget')
self.color_layout = QtWidgets.QFormLayout(self.color_widget)
self.color_layout.setContentsMargins(0, 0, 0, 0)
self.color_layout.setObjectName('color_layout')
self.color_label = QtWidgets.QLabel(self.color_widget)
self.color_label.setObjectName('color_label')
self.color_button = ColorButton(self.color_widget)
self.color_button.setObjectName('color_button')
self.color_layout.addRow(self.color_label, self.color_button)
self.color_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.background_stack.addWidget(self.color_widget)
self.gradient_widget = QtWidgets.QWidget(self.background_page)
self.gradient_widget.setObjectName('Gradient_widget')
self.gradient_layout = QtWidgets.QFormLayout(self.gradient_widget)
self.gradient_layout.setContentsMargins(0, 0, 0, 0)
self.gradient_layout.setObjectName('gradient_layout')
self.gradient_start_label = QtWidgets.QLabel(self.gradient_widget)
self.gradient_start_label.setObjectName('gradient_start_label')
self.gradient_start_button = ColorButton(self.gradient_widget)
self.gradient_start_button.setObjectName('gradient_start_button')
self.gradient_layout.addRow(self.gradient_start_label, self.gradient_start_button)
self.gradient_end_label = QtWidgets.QLabel(self.gradient_widget)
self.gradient_end_label.setObjectName('gradient_end_label')
self.gradient_end_button = ColorButton(self.gradient_widget)
self.gradient_end_button.setObjectName('gradient_end_button')
self.gradient_layout.addRow(self.gradient_end_label, self.gradient_end_button)
self.gradient_type_label = QtWidgets.QLabel(self.gradient_widget)
self.gradient_type_label.setObjectName('Gradient_type_label')
self.gradient_combo_box = QtWidgets.QComboBox(self.gradient_widget)
self.gradient_combo_box.setObjectName('gradient_combo_box')
self.gradient_combo_box.addItems(['', '', '', '', ''])
self.gradient_layout.addRow(self.gradient_type_label, self.gradient_combo_box)
self.gradient_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.background_stack.addWidget(self.gradient_widget)
self.image_widget = QtWidgets.QWidget(self.background_page)
self.image_widget.setObjectName('image_widget')
self.image_layout = QtWidgets.QFormLayout(self.image_widget)
self.image_layout.setContentsMargins(0, 0, 0, 0)
self.image_layout.setObjectName('image_layout')
self.image_color_label = QtWidgets.QLabel(self.color_widget)
self.image_color_label.setObjectName('image_color_label')
self.image_color_button = ColorButton(self.color_widget)
self.image_color_button.setObjectName('image_color_button')
self.image_layout.addRow(self.image_color_label, self.image_color_button)
self.image_label = QtWidgets.QLabel(self.image_widget)
self.image_label.setObjectName('image_label')
self.image_path_edit = PathEdit(self.image_widget,
dialog_caption=translate('OpenLP.ThemeWizard', 'Select Image'),
show_revert=False)
self.image_layout.addRow(self.image_label, self.image_path_edit)
self.image_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.background_stack.addWidget(self.image_widget)
self.transparent_widget = QtWidgets.QWidget(self.background_page)
self.transparent_widget.setObjectName('TransparentWidget')
self.transparent_layout = QtWidgets.QFormLayout(self.transparent_widget)
self.transparent_layout.setContentsMargins(0, 0, 0, 0)
self.transparent_layout.setObjectName('Transparent_layout')
self.background_stack.addWidget(self.transparent_widget)
self.background_layout.addLayout(self.background_stack)
self.video_widget = QtWidgets.QWidget(self.background_page)
self.video_widget.setObjectName('video_widget')
self.video_layout = QtWidgets.QFormLayout(self.video_widget)
self.video_layout.setContentsMargins(0, 0, 0, 0)
self.video_layout.setObjectName('video_layout')
self.video_color_label = QtWidgets.QLabel(self.color_widget)
self.video_color_label.setObjectName('video_color_label')
self.video_color_button = ColorButton(self.color_widget)
self.video_color_button.setObjectName('video_color_button')
self.video_layout.addRow(self.video_color_label, self.video_color_button)
self.video_label = QtWidgets.QLabel(self.video_widget)
self.video_label.setObjectName('video_label')
self.video_path_edit = PathEdit(self.video_widget,
dialog_caption=translate('OpenLP.ThemeWizard', 'Select Video'),
show_revert=False)
self.video_layout.addRow(self.video_label, self.video_path_edit)
self.video_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.background_stack.addWidget(self.video_widget)
theme_wizard.addPage(self.background_page) theme_wizard.addPage(self.background_page)
# Main Area Page # Main Area Page
self.main_area_page = QtWidgets.QWizardPage() self.main_area_page = FontSelectPage()
self.main_area_page.setObjectName('main_area_page') self.main_area_page.setObjectName('main_area_page')
self.main_area_layout = QtWidgets.QVBoxLayout(self.main_area_page)
self.main_area_layout.setObjectName('main_area_layout')
self.main_font = FontSelectWidget(self.main_area_page)
self.main_area_layout.addWidget(self.main_font)
theme_wizard.addPage(self.main_area_page) theme_wizard.addPage(self.main_area_page)
# Footer Area Page # Footer Area Page
self.footer_area_page = QtWidgets.QWizardPage() self.footer_area_page = FontSelectPage()
self.footer_area_page.setObjectName('footer_area_page') self.footer_area_page.setObjectName('footer_area_page')
self.footer_area_layout = QtWidgets.QFormLayout(self.footer_area_page) self.footer_area_page.disable_features(FontSelectPage.Outline, FontSelectPage.Shadow,
self.footer_area_layout.setObjectName('footer_area_layout') FontSelectPage.LineSpacing)
self.footer_font_label = QtWidgets.QLabel(self.footer_area_page)
self.footer_font_label.setObjectName('FooterFontLabel')
self.footer_font_combo_box = QtWidgets.QFontComboBox(self.footer_area_page)
self.footer_font_combo_box.setObjectName('footer_font_combo_box')
self.footer_area_layout.addRow(self.footer_font_label, self.footer_font_combo_box)
self.footer_color_label = QtWidgets.QLabel(self.footer_area_page)
self.footer_color_label.setObjectName('footer_color_label')
self.footer_color_button = ColorButton(self.footer_area_page)
self.footer_color_button.setObjectName('footer_color_button')
self.footer_area_layout.addRow(self.footer_color_label, self.footer_color_button)
self.footer_size_label = QtWidgets.QLabel(self.footer_area_page)
self.footer_size_label.setObjectName('footer_size_label')
self.footer_size_spin_box = QtWidgets.QSpinBox(self.footer_area_page)
self.footer_size_spin_box.setMaximum(999)
self.footer_size_spin_box.setValue(10)
self.footer_size_spin_box.setObjectName('FooterSizeSpinBox')
self.footer_area_layout.addRow(self.footer_size_label, self.footer_size_spin_box)
self.footer_area_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer)
theme_wizard.addPage(self.footer_area_page) theme_wizard.addPage(self.footer_area_page)
# Alignment Page # Alignment Page
self.alignment_page = QtWidgets.QWizardPage() self.alignment_page = AlignmentTransitionsPage()
self.alignment_page.setObjectName('alignment_page') self.alignment_page.setObjectName('alignment_page')
self.alignment_layout = QtWidgets.QFormLayout(self.alignment_page)
self.alignment_layout.setObjectName('alignment_layout')
self.horizontal_label = QtWidgets.QLabel(self.alignment_page)
self.horizontal_label.setObjectName('horizontal_label')
self.horizontal_combo_box = QtWidgets.QComboBox(self.alignment_page)
self.horizontal_combo_box.addItems(['', '', '', ''])
self.horizontal_combo_box.setObjectName('horizontal_combo_box')
self.alignment_layout.addRow(self.horizontal_label, self.horizontal_combo_box)
self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self.alignment_page)
self.vertical_label.setObjectName('vertical_label')
self.vertical_combo_box.setObjectName('vertical_combo_box')
self.alignment_layout.addRow(self.vertical_label, self.vertical_combo_box)
self.transitions_check_box = QtWidgets.QCheckBox(self.alignment_page)
self.transitions_check_box.setObjectName('transitions_check_box')
self.transition_layout = QtWidgets.QHBoxLayout()
self.transition_layout.setObjectName("transition_layout")
self.transition_combo_box = QtWidgets.QComboBox(self.alignment_page)
self.transition_combo_box.setObjectName("transition_combo_box")
self.transition_combo_box.addItems(['', '', '', '', ''])
self.transition_layout.addWidget(self.transition_combo_box)
self.transition_speed_label = QtWidgets.QLabel(self.alignment_page)
self.transition_speed_label.setObjectName("transition_speed_label")
self.transition_layout.addWidget(self.transition_speed_label)
self.transition_speed_combo_box = QtWidgets.QComboBox(self.alignment_page)
self.transition_speed_combo_box.setObjectName("transition_speed_combo_box")
self.transition_speed_combo_box.addItems(['', '', ''])
self.transition_layout.addWidget(self.transition_speed_combo_box)
self.alignment_layout.addRow(self.transitions_check_box, self.transition_layout)
theme_wizard.addPage(self.alignment_page) theme_wizard.addPage(self.alignment_page)
# Area Position Page # Area Position Page
self.area_position_page = QtWidgets.QWizardPage() self.area_position_page = AreaPositionPage()
self.area_position_page.setObjectName('area_position_page') self.area_position_page.setObjectName('area_position_page')
self.area_position_layout = QtWidgets.QHBoxLayout(self.area_position_page)
self.area_position_layout.setObjectName('area_position_layout')
self.main_position_group_box = QtWidgets.QGroupBox(self.area_position_page)
self.main_position_group_box.setObjectName('main_position_group_box')
self.main_position_layout = QtWidgets.QFormLayout(self.main_position_group_box)
self.main_position_layout.setObjectName('main_position_layout')
self.main_position_check_box = QtWidgets.QCheckBox(self.main_position_group_box)
self.main_position_check_box.setObjectName('main_position_check_box')
self.main_position_layout.addRow(self.main_position_check_box)
self.main_x_label = QtWidgets.QLabel(self.main_position_group_box)
self.main_x_label.setObjectName('main_x_label')
self.main_x_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_x_spin_box.setMaximum(9999)
self.main_x_spin_box.setObjectName('main_x_spin_box')
self.main_position_layout.addRow(self.main_x_label, self.main_x_spin_box)
self.main_y_label = QtWidgets.QLabel(self.main_position_group_box)
self.main_y_label.setObjectName('main_y_label')
self.main_y_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_y_spin_box.setMaximum(9999)
self.main_y_spin_box.setObjectName('main_y_spin_box')
self.main_position_layout.addRow(self.main_y_label, self.main_y_spin_box)
self.main_width_label = QtWidgets.QLabel(self.main_position_group_box)
self.main_width_label.setObjectName('main_width_label')
self.main_width_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_width_spin_box.setMaximum(9999)
self.main_width_spin_box.setObjectName('main_width_spin_box')
self.main_position_layout.addRow(self.main_width_label, self.main_width_spin_box)
self.main_height_label = QtWidgets.QLabel(self.main_position_group_box)
self.main_height_label.setObjectName('main_height_label')
self.main_height_spin_box = QtWidgets.QSpinBox(self.main_position_group_box)
self.main_height_spin_box.setMaximum(9999)
self.main_height_spin_box.setObjectName('main_height_spin_box')
self.main_position_layout.addRow(self.main_height_label, self.main_height_spin_box)
self.area_position_layout.addWidget(self.main_position_group_box)
self.footer_position_group_box = QtWidgets.QGroupBox(self.area_position_page)
self.footer_position_group_box.setObjectName('footer_position_group_box')
self.footer_position_layout = QtWidgets.QFormLayout(self.footer_position_group_box)
self.footer_position_layout.setObjectName('footer_position_layout')
self.footer_position_check_box = QtWidgets.QCheckBox(self.footer_position_group_box)
self.footer_position_check_box.setObjectName('footer_position_check_box')
self.footer_position_layout.addRow(self.footer_position_check_box)
self.footer_x_label = QtWidgets.QLabel(self.footer_position_group_box)
self.footer_x_label.setObjectName('footer_x_label')
self.footer_x_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_x_spin_box.setMaximum(9999)
self.footer_x_spin_box.setObjectName('footer_x_spin_box')
self.footer_position_layout.addRow(self.footer_x_label, self.footer_x_spin_box)
self.footer_y_label = QtWidgets.QLabel(self.footer_position_group_box)
self.footer_y_label.setObjectName('footer_y_label')
self.footer_y_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_y_spin_box.setMaximum(9999)
self.footer_y_spin_box.setObjectName('footer_y_spin_box')
self.footer_position_layout.addRow(self.footer_y_label, self.footer_y_spin_box)
self.footer_width_label = QtWidgets.QLabel(self.footer_position_group_box)
self.footer_width_label.setObjectName('footer_width_label')
self.footer_width_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_width_spin_box.setMaximum(9999)
self.footer_width_spin_box.setObjectName('footer_width_spin_box')
self.footer_position_layout.addRow(self.footer_width_label, self.footer_width_spin_box)
self.footer_height_label = QtWidgets.QLabel(self.footer_position_group_box)
self.footer_height_label.setObjectName('footer_height_label')
self.footer_height_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box)
self.footer_height_spin_box.setMaximum(9999)
self.footer_height_spin_box.setObjectName('footer_height_spin_box')
self.footer_position_layout.addRow(self.footer_height_label, self.footer_height_spin_box)
self.area_position_layout.addWidget(self.footer_position_group_box)
theme_wizard.addPage(self.area_position_page) theme_wizard.addPage(self.area_position_page)
# Preview Page # Preview Page
self.preview_page = QtWidgets.QWizardPage() self.preview_page = QtWidgets.QWizardPage()
@ -313,15 +104,6 @@ class Ui_ThemeWizard(object):
self.preview_layout.addWidget(self.preview_area) self.preview_layout.addWidget(self.preview_area)
theme_wizard.addPage(self.preview_page) theme_wizard.addPage(self.preview_page)
self.retranslate_ui(theme_wizard) self.retranslate_ui(theme_wizard)
self.background_combo_box.currentIndexChanged.connect(self.background_stack.setCurrentIndex)
self.main_position_check_box.toggled.connect(self.main_x_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_y_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_width_spin_box.setDisabled)
self.main_position_check_box.toggled.connect(self.main_height_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_x_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_y_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_width_spin_box.setDisabled)
self.footer_position_check_box.toggled.connect(self.footer_height_spin_box.setDisabled)
def retranslate_ui(self, theme_wizard): def retranslate_ui(self, theme_wizard):
""" """
@ -336,87 +118,20 @@ class Ui_ThemeWizard(object):
self.background_page.setTitle(translate('OpenLP.ThemeWizard', 'Set Up Background')) self.background_page.setTitle(translate('OpenLP.ThemeWizard', 'Set Up Background'))
self.background_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Set up your theme\'s background ' self.background_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Set up your theme\'s background '
'according to the parameters below.')) 'according to the parameters below.'))
self.background_label.setText(translate('OpenLP.ThemeWizard', 'Background type:'))
self.background_combo_box.setItemText(BackgroundType.Solid, translate('OpenLP.ThemeWizard', 'Solid color'))
self.background_combo_box.setItemText(BackgroundType.Gradient, translate('OpenLP.ThemeWizard', 'Gradient'))
self.background_combo_box.setItemText(BackgroundType.Image, UiStrings().Image)
self.background_combo_box.setItemText(BackgroundType.Transparent,
translate('OpenLP.ThemeWizard', 'Transparent'))
self.color_label.setText(translate('OpenLP.ThemeWizard', 'color:'))
self.gradient_start_label.setText(translate('OpenLP.ThemeWizard', 'Starting color:'))
self.gradient_end_label.setText(translate('OpenLP.ThemeWizard', 'Ending color:'))
self.gradient_type_label.setText(translate('OpenLP.ThemeWizard', 'Gradient:'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Horizontal,
translate('OpenLP.ThemeWizard', 'Horizontal'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Vertical,
translate('OpenLP.ThemeWizard', 'Vertical'))
self.gradient_combo_box.setItemText(BackgroundGradientType.Circular,
translate('OpenLP.ThemeWizard', 'Circular'))
self.gradient_combo_box.setItemText(BackgroundGradientType.LeftTop,
translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right'))
self.gradient_combo_box.setItemText(BackgroundGradientType.LeftBottom,
translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right'))
self.image_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:'))
self.image_label.setText('{text}:'.format(text=UiStrings().Image))
self.video_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:'))
self.video_label.setText('{text}:'.format(text=UiStrings().Video))
self.main_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Main Area Font Details')) self.main_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Main Area Font Details'))
self.main_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display ' self.main_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display '
'characteristics for the Display text')) 'characteristics for the Display text'))
self.footer_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Footer Area Font Details')) self.footer_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Footer Area Font Details'))
self.footer_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display ' self.footer_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display '
'characteristics for the Footer text')) 'characteristics for the Footer text'))
self.footer_font_label.setText(translate('OpenLP.ThemeWizard', 'Font:'))
self.footer_color_label.setText(translate('OpenLP.ThemeWizard', 'color:'))
self.footer_size_label.setText(translate('OpenLP.ThemeWizard', 'Size:'))
self.footer_size_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit))
self.alignment_page.setTitle(translate('OpenLP.ThemeWizard', 'Text Formatting Details')) self.alignment_page.setTitle(translate('OpenLP.ThemeWizard', 'Text Formatting Details'))
self.alignment_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows additional display ' self.alignment_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows additional display '
'formatting information to be defined')) 'formatting information to be defined'))
self.horizontal_label.setText(translate('OpenLP.ThemeWizard', 'Horizontal Align:'))
self.horizontal_combo_box.setItemText(HorizontalType.Left, translate('OpenLP.ThemeWizard', 'Left'))
self.horizontal_combo_box.setItemText(HorizontalType.Right, translate('OpenLP.ThemeWizard', 'Right'))
self.horizontal_combo_box.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center'))
self.horizontal_combo_box.setItemText(HorizontalType.Justify, translate('OpenLP.ThemeWizard', 'Justify'))
self.transitions_check_box.setText(translate('OpenLP.ThemeWizard', 'Transitions:'))
self.transition_combo_box.setItemText(TransitionType.Fade, translate('OpenLP.ThemeWizard', 'Fade'))
self.transition_combo_box.setItemText(TransitionType.Slide, translate('OpenLP.ThemeWizard', 'Slide'))
self.transition_combo_box.setItemText(TransitionType.Concave, translate('OpenLP.ThemeWizard', 'Concave'))
self.transition_combo_box.setItemText(TransitionType.Convex, translate('OpenLP.ThemeWizard', 'Convex'))
self.transition_combo_box.setItemText(TransitionType.Zoom, translate('OpenLP.ThemeWizard', 'Zoom'))
self.transition_speed_label.setText(translate('OpenLP.ThemeWizard', 'Speed:'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Normal, translate('OpenLP.ThemeWizard', 'Normal'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Fast, translate('OpenLP.ThemeWizard', 'Fast'))
self.transition_speed_combo_box.setItemText(TransitionSpeed.Slow, translate('OpenLP.ThemeWizard', 'Slow'))
self.area_position_page.setTitle(translate('OpenLP.ThemeWizard', 'Output Area Locations')) self.area_position_page.setTitle(translate('OpenLP.ThemeWizard', 'Output Area Locations'))
self.area_position_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows you to change and move the' self.area_position_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows you to change and move the'
' Main and Footer areas.')) ' Main and Footer areas.'))
self.main_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Main Area'))
self.main_position_check_box.setText(translate('OpenLP.ThemeWizard', '&Use default location'))
self.main_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:'))
self.main_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:'))
self.main_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:'))
self.main_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.main_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Footer Area'))
self.footer_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:'))
self.footer_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:'))
self.footer_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:'))
self.footer_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location'))
theme_wizard.setOption(QtWidgets.QWizard.HaveCustomButton1, False) theme_wizard.setOption(QtWidgets.QWizard.HaveCustomButton1, False)
theme_wizard.setButtonText(QtWidgets.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview')) theme_wizard.setButtonText(QtWidgets.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview'))
self.preview_page.setTitle(translate('OpenLP.ThemeWizard', 'Preview and Save')) self.preview_page.setTitle(translate('OpenLP.ThemeWizard', 'Preview and Save'))
self.preview_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Preview the theme and save it.')) self.preview_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Preview the theme and save it.'))
self.theme_name_label.setText(translate('OpenLP.ThemeWizard', 'Theme name:')) self.theme_name_label.setText(translate('OpenLP.ThemeWizard', 'Theme name:'))
# Align all QFormLayouts towards each other.
label_width = max(self.background_label.minimumSizeHint().width(),
self.horizontal_label.minimumSizeHint().width())
self.spacer.changeSize(label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

0
openlp/core/widgets/dialogs.py Executable file → Normal file
View File

View File

@ -19,34 +19,15 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
########################################################################## ##########################################################################
""" """
Package to test the openlp.core.ui.themeform package. The :mod:`~openlp.core.widgets.labels` module contains specialised labels
""" """
from pathlib import Path from PyQt5 import QtCore, QtWidgets
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core.ui.themeform import ThemeForm
class TestThemeForm(TestCase): class FormLabel(QtWidgets.QLabel):
""" """
Test the functions in the ThemeForm Class A label that is prepped for forms, right aligned and vertically centered
""" """
def setUp(self): def __init__(self, parent=None):
with patch('openlp.core.ui.themeform.ThemeForm._setup'): super().__init__(parent)
self.instance = ThemeForm(None) self.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
def test_on_image_path_edit_path_changed(self):
"""
Test the `image_path_edit.pathChanged` handler
"""
# GIVEN: An instance of Theme Form
with patch.object(self.instance, 'set_background_page_values') as mocked_set_background_page_values:
self.instance.theme = MagicMock()
# WHEN: `on_image_path_edit_path_changed` is clicked
self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
# THEN: The theme background file should be set and `set_background_page_values` should have been called
assert self.instance.theme.background_filename == Path('/', 'new', 'pat.h')
mocked_set_background_page_values.assert_called_once_with()

View File

@ -21,13 +21,11 @@
""" """
The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP
""" """
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.i18n import translate
from openlp.core.common.settings import ProxyMode, Settings from openlp.core.common.settings import ProxyMode, Settings
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.buttons import ColorButton
SCREENS_LAYOUT_STYLE = """ SCREENS_LAYOUT_STYLE = """
@ -443,350 +441,3 @@ class ScreenSelectionWidget(QtWidgets.QWidget):
self._setup_spin_box(self.height_spin_box, 0, screen.display_geometry.height(), self._setup_spin_box(self.height_spin_box, 0, screen.display_geometry.height(),
screen.display_geometry.height()) screen.display_geometry.height())
self.current_screen = screen self.current_screen = screen
class FontSelectWidget(QtWidgets.QWidget):
"""
A font selection widget
"""
Outline = 'outline'
Shadow = 'shadow'
LineSpacing = 'line_spacing'
font_name_changed = QtCore.pyqtSignal(str)
font_size_changed = QtCore.pyqtSignal(int)
font_color_changed = QtCore.pyqtSignal(str)
is_bold_changed = QtCore.pyqtSignal(bool)
is_italic_changed = QtCore.pyqtSignal(bool)
line_spacing_changed = QtCore.pyqtSignal(int)
is_outline_enabled_changed = QtCore.pyqtSignal(bool)
outline_color_changed = QtCore.pyqtSignal(str)
outline_size_changed = QtCore.pyqtSignal(int)
is_shadow_enabled_changed = QtCore.pyqtSignal(bool)
shadow_color_changed = QtCore.pyqtSignal(str)
shadow_size_changed = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._column_width = 0
self.setup_ui()
self.feature_widgets = {
FontSelectWidget.Outline: [self.outline_groupbox],
FontSelectWidget.Shadow: [self.shadow_groupbox],
FontSelectWidget.LineSpacing: [self.line_spacing_label, self.line_spacing_spinbox]
}
def setup_ui(self):
self.layout = QtWidgets.QGridLayout(self)
# Font name
self.font_name_label = QtWidgets.QLabel(self)
self.font_name_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.font_name_label.setObjectName('font_name_label')
self.layout.addWidget(self.font_name_label, 0, 0)
self.font_name_combobox = QtWidgets.QFontComboBox(self)
self.font_name_combobox.setObjectName('font_name_combobox')
self.layout.addWidget(self.font_name_combobox, 0, 1, 1, 3)
# Font color
self.font_color_label = QtWidgets.QLabel(self)
self.font_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.font_color_label.setObjectName('font_color_label')
self.layout.addWidget(self.font_color_label, 1, 0)
self.font_color_button = ColorButton(self)
self.font_color_button.setObjectName('font_color_button')
self.layout.addWidget(self.font_color_button, 1, 1)
# Font style
self.font_style_label = QtWidgets.QLabel(self)
self.font_style_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.font_style_label.setObjectName('font_style_label')
self.layout.addWidget(self.font_style_label, 1, 2)
self.style_layout = QtWidgets.QHBoxLayout()
self.style_bold_button = QtWidgets.QToolButton(self)
self.style_bold_button.setIcon(UiIcons().bold)
self.style_bold_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Bold))
self.style_bold_button.setObjectName('style_bold_button')
self.style_layout.addWidget(self.style_bold_button)
self.style_italic_button = QtWidgets.QToolButton(self)
self.style_italic_button.setIcon(UiIcons().italic)
self.style_italic_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Italic))
self.style_italic_button.setObjectName('style_italic_button')
self.style_layout.addWidget(self.style_italic_button)
self.style_layout.addStretch(1)
self.layout.addLayout(self.style_layout, 1, 3)
# Font size
self.font_size_label = QtWidgets.QLabel(self)
self.font_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.font_size_label.setObjectName('font_size_label')
self.layout.addWidget(self.font_size_label, 2, 0)
self.font_size_spinbox = QtWidgets.QSpinBox(self)
self.font_size_spinbox.setMaximum(999)
self.font_size_spinbox.setValue(16)
self.font_size_spinbox.setObjectName('font_size_spinbox')
self.layout.addWidget(self.font_size_spinbox, 2, 1)
# Line spacing
self.line_spacing_label = QtWidgets.QLabel(self)
self.line_spacing_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.line_spacing_label.setObjectName('line_spacing_label')
self.layout.addWidget(self.line_spacing_label, 2, 2)
self.line_spacing_spinbox = QtWidgets.QSpinBox(self)
self.line_spacing_spinbox.setMinimum(-250)
self.line_spacing_spinbox.setMaximum(250)
self.line_spacing_spinbox.setObjectName('line_spacing_spinbox')
self.layout.addWidget(self.line_spacing_spinbox, 2, 3)
# Outline
self.outline_groupbox = QtWidgets.QGroupBox(self)
self.outline_groupbox.setCheckable(True)
self.outline_groupbox.setChecked(False)
self.outline_groupbox.setObjectName('outline_groupbox')
self.outline_layout = QtWidgets.QGridLayout(self.outline_groupbox)
self.layout.addWidget(self.outline_groupbox, 3, 0, 1, 2)
# Outline colour
self.outline_color_label = QtWidgets.QLabel(self.outline_groupbox)
self.outline_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.outline_color_label.setObjectName('outline_color_label')
self.outline_layout.addWidget(self.outline_color_label, 0, 0)
self.outline_color_button = ColorButton(self.outline_groupbox)
self.outline_color_button.setObjectName('outline_color_button')
self.outline_layout.addWidget(self.outline_color_button, 0, 1)
# Outline size
self.outline_size_label = QtWidgets.QLabel(self.outline_groupbox)
self.outline_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.outline_size_label.setObjectName('outline_size_label')
self.outline_layout.addWidget(self.outline_size_label, 1, 0)
self.outline_size_spinbox = QtWidgets.QSpinBox(self.outline_groupbox)
self.outline_size_spinbox.setMaximum(9999)
self.outline_size_spinbox.setObjectName('outline_size_spinbox')
self.outline_layout.addWidget(self.outline_size_spinbox, 1, 1)
# Shadow
self.shadow_groupbox = QtWidgets.QGroupBox(self)
self.shadow_groupbox.setCheckable(True)
self.shadow_groupbox.setChecked(False)
self.shadow_groupbox.setObjectName('shadow_groupbox')
self.shadow_layout = QtWidgets.QGridLayout(self.shadow_groupbox)
self.layout.addWidget(self.shadow_groupbox, 3, 2, 1, 2)
# Shadow color
self.shadow_color_label = QtWidgets.QLabel(self.shadow_groupbox)
self.shadow_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.shadow_color_label.setObjectName('shadow_color_label')
self.shadow_layout.addWidget(self.shadow_color_label, 0, 0)
self.shadow_color_button = ColorButton(self.shadow_groupbox)
self.shadow_color_button.setObjectName('shadow_color_button')
self.shadow_layout.addWidget(self.shadow_color_button, 0, 1)
# Shadow size
self.shadow_size_label = QtWidgets.QLabel(self.shadow_groupbox)
self.shadow_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.shadow_size_label.setObjectName('shadow_size_label')
self.shadow_layout.addWidget(self.shadow_size_label, 1, 0)
self.shadow_size_spinbox = QtWidgets.QSpinBox(self.shadow_groupbox)
self.shadow_size_spinbox.setMaximum(9999)
self.shadow_size_spinbox.setObjectName('shadow_size_spinbox')
self.shadow_layout.addWidget(self.shadow_size_spinbox, 1, 1)
# Fix the size
self.resize_widgets()
# Connect all the signals
self.font_name_combobox.activated.connect(self._on_font_name_changed)
self.font_color_button.colorChanged.connect(self._on_font_color_changed)
self.style_bold_button.toggled.connect(self._on_style_bold_toggled)
self.style_italic_button.toggled.connect(self._on_style_italic_toggled)
self.font_size_spinbox.valueChanged.connect(self._on_font_size_changed)
self.line_spacing_spinbox.valueChanged.connect(self._on_line_spacing_changed)
self.outline_groupbox.toggled.connect(self._on_outline_toggled)
self.outline_color_button.colorChanged.connect(self._on_outline_color_changed)
self.outline_size_spinbox.valueChanged.connect(self._on_outline_size_changed)
self.shadow_groupbox.toggled.connect(self._on_shadow_toggled)
self.shadow_color_button.colorChanged.connect(self._on_shadow_color_changed)
self.shadow_size_spinbox.valueChanged.connect(self._on_shadow_size_changed)
# Translate everything
self.retranslate_ui()
def retranslate_ui(self):
self.font_name_label.setText(translate('OpenLP.FontSelectWidget', 'Font:'))
self.font_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.font_style_label.setText(translate('OpenLP.FontSelectWidget', 'Style:'))
self.style_bold_button.setToolTip('{name} ({shortcut})'.format(
name=translate('OpenLP.FontSelectWidget', 'Bold'),
shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Bold).toString()
))
self.style_italic_button.setToolTip('{name} ({shortcut})'.format(
name=translate('OpenLP.FontSelectWidget', 'Italic'),
shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Italic).toString()
))
self.font_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
self.font_size_spinbox.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit))
self.line_spacing_label.setText(translate('OpenLP.FontSelectWidget', 'Line Spacing:'))
self.outline_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Outline'))
self.outline_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.outline_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
self.shadow_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Shadow'))
self.shadow_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:'))
self.shadow_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:'))
def resizeEvent(self, event):
"""
Override inherited resize method
"""
super().resizeEvent(event)
self.resize_widgets()
def _on_font_name_changed(self, name):
if isinstance(name, str):
self.font_name_changed.emit(name)
def _on_font_color_changed(self, color):
self.font_color_changed.emit(color)
def _on_style_bold_toggled(self, is_bold):
self.is_bold_changed.emit(is_bold)
def _on_style_italic_toggled(self, is_italic):
self.is_italic_changed.emit(is_italic)
def _on_font_size_changed(self, size):
self.font_size_changed.emit(size)
def _on_line_spacing_changed(self, spacing):
self.line_spacing_changed.emit(spacing)
def _on_outline_toggled(self, is_enabled):
self.is_outline_enabled_changed.emit(is_enabled)
def _on_outline_color_changed(self, color):
self.outline_color_changed.emit(color)
def _on_outline_size_changed(self, size):
self.outline_size_changed.emit(size)
def _on_shadow_toggled(self, is_enabled):
self.is_shadow_enabled_changed.emit(is_enabled)
def _on_shadow_color_changed(self, color):
self.shadow_color_changed.emit(color)
def _on_shadow_size_changed(self, size):
self.shadow_size_changed.emit(size)
def resize_widgets(self):
"""
Resize all the widgets and set the column widths
"""
width = self.geometry().width()
margins = self.layout.contentsMargins()
spacing = self.layout.horizontalSpacing()
self._column_width = (width - margins.left() - margins.right() - (spacing * 3)) // 4
for column_number in range(4):
self.layout.setColumnMinimumWidth(column_number, self._column_width)
def enable_features(self, *features):
"""
Enable a feature
"""
for feature_name in features:
if feature_name not in self.feature_widgets.keys():
raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name))
for widget in self.feature_widgets[feature_name]:
widget.show()
def disable_features(self, *features):
"""
Disable a feature
"""
for feature_name in features:
if feature_name not in self.feature_widgets.keys():
raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name))
for widget in self.feature_widgets[feature_name]:
widget.hide()
@property
def font_name(self):
return self.font_name_combobox.currentFont().family()
@font_name.setter
def font_name(self, font):
self.font_name_combobox.setCurrentFont(QtGui.QFont(font))
@property
def font_color(self):
return self.font_color_button.color
@font_color.setter
def font_color(self, color):
self.font_color_button.color = color
@property
def is_bold(self):
return self.style_bold_button.isChecked()
@is_bold.setter
def is_bold(self, is_bold):
self.style_bold_button.setChecked(is_bold)
@property
def is_italic(self):
return self.style_italic_button.isChecked()
@is_italic.setter
def is_italic(self, is_italic):
self.style_italic_button.setChecked(is_italic)
@property
def font_size(self):
return self.font_size_spinbox.value()
@font_size.setter
def font_size(self, size):
self.font_size_spinbox.setValue(size)
@property
def line_spacing(self):
return self.line_spacing_spinbox.value()
@line_spacing.setter
def line_spacing(self, line_spacing):
self.line_spacing_spinbox.setValue(line_spacing)
@property
def is_outline_enabled(self):
return self.outline_groupbox.isChecked()
@is_outline_enabled.setter
def is_outline_enabled(self, is_enabled):
self.outline_groupbox.setChecked(is_enabled)
@property
def outline_color(self):
return self.outline_color_button.color
@outline_color.setter
def outline_color(self, color):
self.outline_color_button.color = color
@property
def outline_size(self):
return self.outline_size_spinbox.value()
@outline_size.setter
def outline_size(self, size):
self.outline_size_spinbox.setValue(size)
@property
def is_shadow_enabled(self):
return self.shadow_groupbox.isChecked()
@is_shadow_enabled.setter
def is_shadow_enabled(self, is_enabled):
self.shadow_groupbox.setChecked(is_enabled)
@property
def shadow_color(self):
return self.shadow_color_button.color
@shadow_color.setter
def shadow_color(self, color):
self.shadow_color_button.color = color
@property
def shadow_size(self):
return self.shadow_size_spinbox.value()
@shadow_size.setter
def shadow_size(self, size):
self.shadow_size_spinbox.setValue(size)

View File

@ -22,6 +22,7 @@
Package to test the openlp.core.display.window package. Package to test the openlp.core.display.window package.
""" """
import sys import sys
import time
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -104,3 +105,42 @@ class TestDisplayWindow(TestCase, TestMixin):
# THEN: javascript should not be run # THEN: javascript should not be run
display_window.run_javascript.assert_called_once_with('Display.setScale(50.0);') display_window.run_javascript.assert_called_once_with('Display.setScale(50.0);')
@patch.object(time, 'time')
def test_run_javascript_no_sync_no_wait(self, MockSettings, mocked_webengine, mocked_addWidget, mock_time):
"""
test a script is run on the webview
"""
# GIVEN: A (fake) webengine page
display_window = DisplayWindow()
webengine_page = MagicMock()
display_window.webview.page = MagicMock(return_value=webengine_page)
# WHEN: javascript is requested to run
display_window.run_javascript('javascript to execute')
# THEN: javascript should be run with no delay
webengine_page.runJavaScript.assert_called_once_with('javascript to execute')
mock_time.sleep.assert_not_called()
@patch.object(time, 'time')
def test_run_javascript_sync_no_wait(self, MockSettings, mocked_webengine, mocked_addWidget, mock_time):
"""
test a synced script is run on the webview and immediately returns a result
"""
# GIVEN: A (fake) webengine page with a js callback fn
def save_callback(script, callback):
callback(1234)
display_window = DisplayWindow()
display_window.webview = MagicMock()
webengine_page = MagicMock()
webengine_page.runJavaScript.side_effect = save_callback
display_window.webview.page.return_value = webengine_page
# WHEN: javascript is requested to run
result = display_window.run_javascript('javascript to execute', True)
# THEN: javascript should be run with no delay and return with the correct result
assert result == 1234
webengine_page.runJavaScript.assert_called_once()
mock_time.sleep.assert_not_called()

View File

@ -25,78 +25,160 @@ from pathlib import Path
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.lib.theme import BackgroundType, Theme from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, TransitionType, TransitionSpeed, Theme
class TestBackgroundType(TestCase): class ThemeEnumerationTypes(TestCase):
""" """
Test the BackgroundType enum methods. Test the theme enum methods.
""" """
def test_solid_to_string(self): def test_background_type_to_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the to_string method of :class:`BackgroundType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The BackgroundType members
background_type = BackgroundType.Solid background_type_solid = BackgroundType.Solid
background_type_gradient = BackgroundType.Gradient
background_type_image = BackgroundType.Image
background_type_transparent = BackgroundType.Transparent
background_type_video = BackgroundType.Video
background_type_stream = BackgroundType.Stream
# WHEN: Calling BackgroundType.to_string # WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned # THEN: The string equivalents should be returned
assert BackgroundType.to_string(background_type) == 'solid' assert BackgroundType.to_string(background_type_solid) == 'solid'
assert BackgroundType.to_string(background_type_gradient) == 'gradient'
assert BackgroundType.to_string(background_type_image) == 'image'
assert BackgroundType.to_string(background_type_transparent) == 'transparent'
assert BackgroundType.to_string(background_type_video) == 'video'
assert BackgroundType.to_string(background_type_stream) == 'stream'
def test_gradient_to_string(self): def test_background_type_from_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the from_string method of :class:`BackgroundType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The BackgroundType strings
background_type = BackgroundType.Gradient background_type_solid = 'solid'
background_type_gradient = 'gradient'
background_type_image = 'image'
background_type_transparent = 'transparent'
background_type_video = 'video'
background_type_stream = 'stream'
# WHEN: Calling BackgroundType.to_string # WHEN: Calling BackgroundType.from_string
# THEN: The string equivalent should have been returned # THEN: The enum equivalents should be returned
assert BackgroundType.to_string(background_type) == 'gradient' assert BackgroundType.from_string(background_type_solid) == BackgroundType.Solid
assert BackgroundType.from_string(background_type_gradient) == BackgroundType.Gradient
assert BackgroundType.from_string(background_type_image) == BackgroundType.Image
assert BackgroundType.from_string(background_type_transparent) == BackgroundType.Transparent
assert BackgroundType.from_string(background_type_video) == BackgroundType.Video
assert BackgroundType.from_string(background_type_stream) == BackgroundType.Stream
def test_image_to_string(self): def test_background_gradient_type_to_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the to_string method of :class:`BackgroundGradientType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The BackgroundGradientType member
background_type = BackgroundType.Image background_gradient_horizontal = BackgroundGradientType.Horizontal
background_gradient_vertical = BackgroundGradientType.Vertical
background_gradient_circular = BackgroundGradientType.Circular
background_gradient_left_top = BackgroundGradientType.LeftTop
background_gradient_left_bottom = BackgroundGradientType.LeftBottom
# WHEN: Calling BackgroundType.to_string # WHEN: Calling BackgroundGradientType.to_string
# THEN: The string equivalent should have been returned # THEN: The string equivalents should be returned
assert BackgroundType.to_string(background_type) == 'image' assert BackgroundGradientType.to_string(background_gradient_horizontal) == 'horizontal'
assert BackgroundGradientType.to_string(background_gradient_vertical) == 'vertical'
assert BackgroundGradientType.to_string(background_gradient_circular) == 'circular'
assert BackgroundGradientType.to_string(background_gradient_left_top) == 'leftTop'
assert BackgroundGradientType.to_string(background_gradient_left_bottom) == 'leftBottom'
def test_transparent_to_string(self): def test_background_gradient_type_from_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the from_string method of :class:`BackgroundGradientType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The BackgroundGradientType strings
background_type = BackgroundType.Transparent background_gradient_horizontal = 'horizontal'
background_gradient_vertical = 'vertical'
background_gradient_circular = 'circular'
background_gradient_left_top = 'leftTop'
background_gradient_left_bottom = 'leftBottom'
# WHEN: Calling BackgroundType.to_string # WHEN: Calling BackgroundGradientType.from_string
# THEN: The string equivalent should have been returned # THEN: The enum equivalents should be returned
assert BackgroundType.to_string(background_type) == 'transparent' assert BackgroundGradientType.from_string(background_gradient_horizontal) == BackgroundGradientType.Horizontal
assert BackgroundGradientType.from_string(background_gradient_vertical) == BackgroundGradientType.Vertical
assert BackgroundGradientType.from_string(background_gradient_circular) == BackgroundGradientType.Circular
assert BackgroundGradientType.from_string(background_gradient_left_top) == BackgroundGradientType.LeftTop
assert BackgroundGradientType.from_string(background_gradient_left_bottom) == BackgroundGradientType.LeftBottom
def test_video_to_string(self): def test_transition_type_to_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the to_string method of :class:`TransitionType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The TransitionType member
background_type = BackgroundType.Video transition_type_fade = TransitionType.Fade
transition_type_slide = TransitionType.Slide
transition_type_convex = TransitionType.Convex
transition_type_concave = TransitionType.Concave
transition_type_zoom = TransitionType.Zoom
# WHEN: Calling BackgroundType.to_string # WHEN: Calling TransitionType.to_string
# THEN: The string equivalent should have been returned # THEN: The string equivalents should be returned
assert BackgroundType.to_string(background_type) == 'video' assert TransitionType.to_string(transition_type_fade) == 'fade'
assert TransitionType.to_string(transition_type_slide) == 'slide'
assert TransitionType.to_string(transition_type_convex) == 'convex'
assert TransitionType.to_string(transition_type_concave) == 'concave'
assert TransitionType.to_string(transition_type_zoom) == 'zoom'
def test_stream_to_string(self): def test_transition_type_from_string(self):
""" """
Test the to_string method of :class:`BackgroundType` Test the from_string method of :class:`TransitionType`
""" """
# GIVEN: A BackgroundType member # GIVEN: The TransitionType strings
background_type = BackgroundType.Stream transition_type_fade = 'fade'
transition_type_slide = 'slide'
transition_type_convex = 'convex'
transition_type_concave = 'concave'
transition_type_zoom = 'zoom'
# WHEN: Calling BackgroundType.to_string # WHEN: Calling TransitionType.from_string
# THEN: The string equivalent should have been returned # THEN: The enum equivalents should be returned
assert BackgroundType.to_string(background_type) == 'stream' assert TransitionType.from_string(transition_type_fade) == TransitionType.Fade
assert TransitionType.from_string(transition_type_slide) == TransitionType.Slide
assert TransitionType.from_string(transition_type_convex) == TransitionType.Convex
assert TransitionType.from_string(transition_type_concave) == TransitionType.Concave
assert TransitionType.from_string(transition_type_zoom) == TransitionType.Zoom
def test_transition_speed_to_string(self):
"""
Test the to_string method of :class:`TransitionSpeed`
"""
# GIVEN: The TransitionSpeed member
transition_speed_normal = TransitionSpeed.Normal
transition_speed_fast = TransitionSpeed.Fast
transition_speed_slow = TransitionSpeed.Slow
# WHEN: Calling TransitionSpeed.to_string
# THEN: The string equivalents should be returned
assert TransitionSpeed.to_string(transition_speed_normal) == 'normal'
assert TransitionSpeed.to_string(transition_speed_fast) == 'fast'
assert TransitionSpeed.to_string(transition_speed_slow) == 'slow'
def test_transition_speed_from_string(self):
"""
Test the from_string method of :class:`TransitionSpeed`
"""
# GIVEN: The TransitionSpeed strings
transition_speed_normal = 'normal'
transition_speed_fast = 'fast'
transition_speed_slow = 'slow'
# WHEN: Calling TransitionSpeed.from_string
# THEN: The enum equivalents should be returned
assert TransitionSpeed.from_string(transition_speed_normal) == TransitionSpeed.Normal
assert TransitionSpeed.from_string(transition_speed_fast) == TransitionSpeed.Fast
assert TransitionSpeed.from_string(transition_speed_slow) == TransitionSpeed.Slow
class TestTheme(TestCase): class TestTheme(TestCase):
@ -255,4 +337,4 @@ class TestTheme(TestCase):
assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0'
assert theme.font_footer_bold is False, 'font_footer_bold should be False' assert theme.font_footer_bold is False, 'font_footer_bold should be False'
assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"'
assert 51 == len(theme.__dict__), 'The theme should have 51 attributes' assert 53 == len(theme.__dict__), 'The theme should have 53 attributes'

View File

@ -21,18 +21,20 @@
""" """
Package to test the openlp.core.ui.mainwindow package. Package to test the openlp.core.ui.mainwindow package.
""" """
from unittest import TestCase from unittest import TestCase, skipIf
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from PyQt5 import QtGui from PyQt5 import QtGui
from openlp.core.state import State from openlp.core.state import State
from openlp.core.common import is_macosx
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.plugin import PluginStatus
from openlp.core.ui.mainwindow import MainWindow from openlp.core.ui.mainwindow import MainWindow
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@skipIf(is_macosx(), 'Skip on macOS until we can figure out what the problem is or the tests are refactored')
class TestMainWindow(TestCase, TestMixin): class TestMainWindow(TestCase, TestMixin):
def setUp(self): def setUp(self):

View File

@ -184,7 +184,7 @@ describe("Transitions", function () {
Display.setTheme(theme); Display.setTheme(theme);
expect(Display.setTransition).toHaveBeenCalledWith("slide", "fast"); expect(Display.setTransition).toHaveBeenCalledWith("slide-horizontal", "fast");
}); });
it("should have not enabled transitions when init() with no transitions and setTheme is run", function () { it("should have not enabled transitions when init() with no transitions and setTheme is run", function () {
@ -201,6 +201,22 @@ describe("Transitions", function () {
expect(Display.setTransition).toHaveBeenCalledWith("none", "default"); expect(Display.setTransition).toHaveBeenCalledWith("none", "default");
}); });
it("should have enabled transitions in the correct direction", function () {
spyOn(Display, "setTransition");
Display._doTransitions = true;
var theme = {
"display_slide_transition": true,
"display_slide_transition_type": TransitionType.Convex,
"display_slide_transition_speed": TransitionSpeed.Slow,
"display_slide_transition_direction": TransitionDirection.Vertical,
"display_slide_transition_reverse": true,
}
Display.setTheme(theme);
expect(Display.setTransition).toHaveBeenCalledWith("convex-vertical-reverse", "slow");
});
}); });
describe("Display.alert", function () { describe("Display.alert", function () {

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.pages package.
"""

View File

@ -0,0 +1,386 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.pages.alignment package.
"""
from unittest import TestCase
from unittest.mock import MagicMock
import pytest
from openlp.core.lib.theme import HorizontalType, VerticalType, TransitionType, TransitionSpeed, TransitionDirection
from openlp.core.pages.alignment import AlignmentTransitionsPage
from tests.helpers.testmixin import TestMixin
class TestAlignmentTransitionsPage(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
def test_init_(self):
"""
Test the initialisation of AlignmentTransitionsPage
"""
# GIVEN: The AlignmentTransitionsPage class
# WHEN: Initialising AlignmentTransitionsPage
# THEN: We should have an instance of the widget with no errors
AlignmentTransitionsPage()
def test_on_transition_enabled_changed(self):
"""
Test the _on_transition_enabled_changed() slot
"""
# GIVEN: And instance of AlignmentTransitionsPage and some mock widgets
page = AlignmentTransitionsPage()
# WHEN: _on_transition_enabled_changed
page._on_transition_enabled_changed(True)
# THEN: The correct widgets should be visible
assert page.transition_effect_label.isEnabled()
assert page.transition_effect_combo_box.isEnabled()
assert page.transition_speed_label.isEnabled()
assert page.transition_speed_combo_box.isEnabled()
assert page.transition_direction_combo_box.isEnabled()
assert page.transition_reverse_check_box.isEnabled()
def test_get_horizontal_align(self):
"""
Test the horizontal_align getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1
page = AlignmentTransitionsPage()
page.horizontal_combo_box.setCurrentIndex(1)
# WHEN: The property is accessed
result = page.horizontal_align
# THEN: The result should be correct
assert result == HorizontalType.Right
def test_set_horizontal_align_int(self):
"""
Test the horizontal_align setter with an int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.horizontal_align = HorizontalType.Center
# THEN: The combobox should be correct
assert page.horizontal_combo_box.currentIndex() == 2
def test_set_horizontal_align_str(self):
"""
Test the horizontal_align setter with a str
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.horizontal_align = HorizontalType.to_string(HorizontalType.Justify)
# THEN: The combobox should be correct
assert page.horizontal_combo_box.currentIndex() == 3
def test_set_horizontal_align_exception(self):
"""
Test the horizontal_align setter with something other than a str or int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='horizontal_align must either be a string or an int'):
page.horizontal_align = []
def test_get_vertical_align(self):
"""
Test the vertical_align getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1
page = AlignmentTransitionsPage()
page.vertical_combo_box.setCurrentIndex(1)
# WHEN: The property is accessed
result = page.vertical_align
# THEN: The result should be correct
assert result == VerticalType.Middle
def test_set_vertical_align_int(self):
"""
Test the vertical_align setter with an int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.vertical_align = VerticalType.Bottom
# THEN: The combobox should be correct
assert page.vertical_combo_box.currentIndex() == 2
def test_set_vertical_align_str(self):
"""
Test the vertical_align setter with a str
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.vertical_align = VerticalType.to_string(VerticalType.Top)
# THEN: The combobox should be correct
assert page.vertical_combo_box.currentIndex() == 0
def test_set_vertical_align_exception(self):
"""
Test the vertical_align setter with something other than a str or int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='vertical_align must either be a string or an int'):
page.vertical_align = []
def test_get_is_transition_enabled(self):
"""
Test the is_transition_enabled getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the transitions enabled
page = AlignmentTransitionsPage()
page.transitions_enabled_check_box.setChecked(False)
# WHEN: The property is accessed
result = page.is_transition_enabled
# THEN: The result should be correct
assert result is False
def test_set_is_transition_enabled(self):
"""
Test the is_transition_enabled setter
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
page._on_transition_enabled_changed = MagicMock()
# WHEN: The property is set
page.is_transition_enabled = True
# THEN: The result should be correct
assert page.transitions_enabled_check_box.isChecked() is True
page._on_transition_enabled_changed.assert_called_once_with(True)
def test_get_transition_type(self):
"""
Test the transition_type getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1
page = AlignmentTransitionsPage()
page.transition_effect_combo_box.setCurrentIndex(1)
# WHEN: The property is accessed
result = page.transition_type
# THEN: The result should be correct
assert result == TransitionType.Slide
def test_set_transition_type_int(self):
"""
Test the transition_type setter with an int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_type = TransitionType.Concave
# THEN: The combobox should be correct
assert page.transition_effect_combo_box.currentIndex() == 3
def test_set_transition_type_str(self):
"""
Test the transition_type setter with a str
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_type = TransitionType.to_string(TransitionType.Convex)
# THEN: The combobox should be correct
assert page.transition_effect_combo_box.currentIndex() == 2
def test_set_transition_type_exception(self):
"""
Test the transition_type setter with something other than a str or int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='transition_type must either be a string or an int'):
page.transition_type = []
def test_get_transition_speed(self):
"""
Test the transition_speed getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 0
page = AlignmentTransitionsPage()
page.transition_speed_combo_box.setCurrentIndex(0)
# WHEN: The property is accessed
result = page.transition_speed
# THEN: The result should be correct
assert result == TransitionSpeed.Normal
def test_set_transition_speed_int(self):
"""
Test the transition_speed setter with an int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_speed = TransitionSpeed.Fast
# THEN: The combobox should be correct
assert page.transition_speed_combo_box.currentIndex() == 1
def test_set_transition_speed_str(self):
"""
Test the transition_speed setter with a str
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_speed = TransitionSpeed.to_string(TransitionSpeed.Slow)
# THEN: The combobox should be correct
assert page.transition_speed_combo_box.currentIndex() == 2
def test_set_transition_speed_exception(self):
"""
Test the transition_speed setter with something other than a str or int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='transition_speed must either be a string or an int'):
page.transition_speed = []
def test_get_transition_direction(self):
"""
Test the transition_direction getter
"""
# GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 0
page = AlignmentTransitionsPage()
page.transition_direction_combo_box.setCurrentIndex(0)
# WHEN: The property is accessed
result = page.transition_direction
# THEN: The result should be correct
assert result == TransitionDirection.Horizontal
def test_set_transition_direction_int(self):
"""
Test the transition_direction setter with an int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_direction = TransitionDirection.Horizontal
# THEN: The combobox should be correct
assert page.transition_direction_combo_box.currentIndex() == 0
def test_set_transition_direction_str(self):
"""
Test the transition_direction setter with a str
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.transition_direction = TransitionDirection.to_string(TransitionDirection.Vertical)
# THEN: The combobox should be correct
assert page.transition_direction_combo_box.currentIndex() == 1
def test_set_transition_direction_exception(self):
"""
Test the transition_direction setter with something other than a str or int
"""
# GIVEN: A AlignmentTransitionsPage instance
page = AlignmentTransitionsPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='transition_direction must either be a string or an int'):
page.transition_direction = []
def test_on_transition_reverse_getter(self):
"""
Test the is_transition_reverse_enabled getter
"""
# GIVEN: And instance of AlignmentTransitionsPage and transition_reverse checked
page = AlignmentTransitionsPage()
page.transition_reverse_check_box.setChecked(True)
# WHEN: The property is accessed
result = page.is_transition_reverse_enabled
# THEN: The result should be correct
assert result is True
def test_on_transition_reverse_setter(self):
"""
Test the is_transition_reverse_enabled setter
"""
# GIVEN: And instance of AlignmentTransitionsPage and transition_reverse checked
page = AlignmentTransitionsPage()
# WHEN: The property is set
page.is_transition_reverse_enabled = True
# THEN: The checkbox should be correct
assert page.transition_reverse_check_box.isChecked() is True

View File

@ -0,0 +1,318 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.pages.alignment package.
"""
from unittest import TestCase
from openlp.core.pages.areaposition import AreaPositionPage
from tests.helpers.testmixin import TestMixin
class TestAreaPositionPage(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
def test_init_(self):
"""
Test the initialisation of AreaPositionPage
"""
# GIVEN: The AreaPositionPage class
# WHEN: Initialising AreaPositionPage
# THEN: We should have an instance of the widget with no errors
AreaPositionPage()
def test_get_use_main_default_location(self):
"""
Test the use_main_default_location getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to index 1
page = AreaPositionPage()
page.main_position_check_box.setChecked(False)
# WHEN: The property is accessed
result = page.use_main_default_location
# THEN: The result should be correct
assert result is False
def test_set_use_main_default_location(self):
"""
Test the use_main_default_location setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.use_main_default_location = True
# THEN: The combobox should be correct
assert page.main_position_check_box.isChecked() is True
def test_get_main_x(self):
"""
Test the main_x getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to index 1
page = AreaPositionPage()
page.main_x_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.main_x
# THEN: The result should be correct
assert result == 10
def test_set_main_x(self):
"""
Test the main_x setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.main_x = 20
# THEN: The combobox should be correct
assert page.main_x_spin_box.value() == 20
def test_get_main_y(self):
"""
Test the main_y getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indey 1
page = AreaPositionPage()
page.main_y_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.main_y
# THEN: The result should be correct
assert result == 10
def test_set_main_y(self):
"""
Test the main_y setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.main_y = 20
# THEN: The combobox should be correct
assert page.main_y_spin_box.value() == 20
def test_get_main_width(self):
"""
Test the main_width getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indewidth 1
page = AreaPositionPage()
page.main_width_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.main_width
# THEN: The result should be correct
assert result == 10
def test_set_main_width(self):
"""
Test the main_width setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.main_width = 20
# THEN: The combobox should be correct
assert page.main_width_spin_box.value() == 20
def test_get_main_height(self):
"""
Test the main_height getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indeheight 1
page = AreaPositionPage()
page.main_height_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.main_height
# THEN: The result should be correct
assert result == 10
def test_set_main_height(self):
"""
Test the main_height setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.main_height = 20
# THEN: The combobox should be correct
assert page.main_height_spin_box.value() == 20
def test_get_footer_x(self):
"""
Test the footer_x getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to index 1
page = AreaPositionPage()
page.footer_x_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.footer_x
# THEN: The result should be correct
assert result == 10
def test_set_footer_x(self):
"""
Test the footer_x setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.footer_x = 20
# THEN: The combobox should be correct
assert page.footer_x_spin_box.value() == 20
def test_get_footer_y(self):
"""
Test the footer_y getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indey 1
page = AreaPositionPage()
page.footer_y_spin_box.setValue(10)
# WHEN: The property is accessed
result = page.footer_y
# THEN: The result should be correct
assert result == 10
def test_set_footer_y(self):
"""
Test the footer_y setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.footer_y = 20
# THEN: The combobox should be correct
assert page.footer_y_spin_box.value() == 20
def test_get_footer_width(self):
"""
Test the footer_width getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indewidth 1
page = AreaPositionPage()
page.footer_width_spin_box.setValue(1900)
# WHEN: The property is accessed
result = page.footer_width
# THEN: The result should be correct
assert result == 1900
def test_set_footer_width(self):
"""
Test the footer_width setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.footer_width = 1900
# THEN: The combobox should be correct
assert page.footer_width_spin_box.value() == 1900
def test_get_footer_height(self):
"""
Test the footer_height getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to indeheight 1
page = AreaPositionPage()
page.footer_height_spin_box.setValue(1080)
# WHEN: The property is accessed
result = page.footer_height
# THEN: The result should be correct
assert result == 1080
def test_set_footer_height(self):
"""
Test the footer_height setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.footer_height = 1080
# THEN: The combobox should be correct
assert page.footer_height_spin_box.value() == 1080
def test_get_use_footer_default_location(self):
"""
Test the use_footer_default_location getter
"""
# GIVEN: A AreaPositionPage instance with the combobox set to index 1
page = AreaPositionPage()
page.footer_position_check_box.setChecked(False)
# WHEN: The property is accessed
result = page.use_footer_default_location
# THEN: The result should be correct
assert result is False
def test_set_use_footer_default_location(self):
"""
Test the use_footer_default_location setter with an int
"""
# GIVEN: A AreaPositionPage instance
page = AreaPositionPage()
# WHEN: The property is set
page.use_footer_default_location = True
# THEN: The combobox should be correct
assert page.footer_position_check_box.isChecked() is True

View File

@ -0,0 +1,363 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.pages.background package.
"""
from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock
import pytest
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.pages.background import BackgroundPage
from tests.helpers.testmixin import TestMixin
class TestBackgroundPage(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
def test_init_(self):
"""
Test the initialisation of BackgroundPage
"""
# GIVEN: The BackgroundPage class
# WHEN: Initialising BackgroundPage
# THEN: We should have an instance of the widget with no errors
BackgroundPage()
def test_on_background_type_index_changed(self):
"""
Test the _on_background_type_index_changed() slot
"""
# GIVEN: And instance of BackgroundPage and some mock widgets
page = BackgroundPage()
page.color_widgets = [MagicMock()]
page.gradient_widgets = [MagicMock()]
# WHEN: _on_background_type_index_changed
page._on_background_type_index_changed(1)
# THEN: The correct widgets should be visible
page.color_widgets[0].hide.assert_called_once()
page.gradient_widgets[0].hide.assert_called_once()
page.gradient_widgets[0].show.assert_called_once()
def test_get_background_type(self):
"""
Test the background_type getter
"""
# GIVEN: A BackgroundPage instance with the combobox set to index 1
page = BackgroundPage()
page.background_combo_box.setCurrentIndex(1)
# WHEN: The property is accessed
result = page.background_type
# THEN: The result should be correct
assert result == 'gradient'
def test_set_background_type_int(self):
"""
Test the background_type setter with an int
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.background_type = BackgroundType.Image
# THEN: The combobox should be correct
assert page.background_combo_box.currentIndex() == 2
def test_set_background_type_str(self):
"""
Test the background_type setter with a str
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.background_type = BackgroundType.to_string(BackgroundType.Gradient)
# THEN: The combobox should be correct
assert page.background_combo_box.currentIndex() == 1
def test_set_background_type_exception(self):
"""
Test the background_type setter with something other than a str or int
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='background_type must either be a string or an int'):
page.background_type = []
def test_get_color(self):
"""
Test the color getter
"""
# GIVEN: A BackgroundPage instance with the color button set to #f0f
page = BackgroundPage()
page.color_button.color = '#f0f'
# WHEN: The property is accessed
result = page.color
# THEN: The result should be correct
assert result == '#f0f'
def test_set_color(self):
"""
Test the color setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.color = '#0f0'
# THEN: The result should be correct
assert page.color_button.color == '#0f0'
def test_get_gradient_type(self):
"""
Test the gradient_type getter
"""
# GIVEN: A BackgroundPage instance with the combobox set to index 1
page = BackgroundPage()
page.gradient_combo_box.setCurrentIndex(1)
# WHEN: The property is accessed
result = page.gradient_type
# THEN: The result should be correct
assert result == 'vertical'
def test_set_gradient_type_int(self):
"""
Test the gradient_type setter with an int
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.gradient_type = BackgroundGradientType.Horizontal
# THEN: The combobox should be correct
assert page.gradient_combo_box.currentIndex() == 0
def test_set_gradient_type_str(self):
"""
Test the gradient_type setter with a str
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.gradient_type = BackgroundGradientType.to_string(BackgroundGradientType.Circular)
# THEN: The combobox should be correct
assert page.gradient_combo_box.currentIndex() == 2
def test_set_gradient_type_exception(self):
"""
Test the gradient_type setter with something other than a str or int
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
# THEN: An exception is raised
with pytest.raises(TypeError, match='gradient_type must either be a string or an int'):
page.gradient_type = []
def test_get_gradient_start(self):
"""
Test the gradient_start getter
"""
# GIVEN: A BackgroundPage instance with the gradient_start button set to #f0f
page = BackgroundPage()
page.gradient_start_button.color = '#f0f'
# WHEN: The property is accessed
result = page.gradient_start
# THEN: The result should be correct
assert result == '#f0f'
def test_set_gradient_start(self):
"""
Test the gradient_start setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.gradient_start = '#0f0'
# THEN: The result should be correct
assert page.gradient_start_button.color == '#0f0'
def test_get_gradient_end(self):
"""
Test the gradient_end getter
"""
# GIVEN: A BackgroundPage instance with the gradient_end button set to #f0f
page = BackgroundPage()
page.gradient_end_button.color = '#f0f'
# WHEN: The property is accessed
result = page.gradient_end
# THEN: The result should be correct
assert result == '#f0f'
def test_set_gradient_end(self):
"""
Test the gradient_end setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.gradient_end = '#0f0'
# THEN: The result should be correct
assert page.gradient_end_button.color == '#0f0'
def test_get_image_color(self):
"""
Test the image_color getter
"""
# GIVEN: A BackgroundPage instance with the image_color button set to #f0f
page = BackgroundPage()
page.image_color_button.color = '#f0f'
# WHEN: The property is accessed
result = page.image_color
# THEN: The result should be correct
assert result == '#f0f'
def test_set_image_color(self):
"""
Test the image_color setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.image_color = '#0f0'
# THEN: The result should be correct
assert page.image_color_button.color == '#0f0'
def test_get_image_path(self):
"""
Test the image_path getter
"""
# GIVEN: A BackgroundPage instance with the image_path edit set to a path
page = BackgroundPage()
page.image_path_edit.path = Path('.')
# WHEN: The property is accessed
result = page.image_path
# THEN: The result should be correct
assert result == Path('.')
def test_set_image_path(self):
"""
Test the image_path setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.image_path = Path('openlp')
# THEN: The result should be correct
assert page.image_path_edit.path == Path('openlp')
def test_get_video_color(self):
"""
Test the video_color getter
"""
# GIVEN: A BackgroundPage instance with the video_color button set to #f0f
page = BackgroundPage()
page.video_color_button.color = '#f0f'
# WHEN: The property is accessed
result = page.video_color
# THEN: The result should be correct
assert result == '#f0f'
def test_set_video_color(self):
"""
Test the video_color setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.video_color = '#0f0'
# THEN: The result should be correct
assert page.video_color_button.color == '#0f0'
def test_get_video_path(self):
"""
Test the video_path getter
"""
# GIVEN: A BackgroundPage instance with the video_path edit set to a path
page = BackgroundPage()
page.video_path_edit.path = Path('.')
# WHEN: The property is accessed
result = page.video_path
# THEN: The result should be correct
assert result == Path('.')
def test_set_video_path(self):
"""
Test the video_path setter
"""
# GIVEN: A BackgroundPage instance
page = BackgroundPage()
# WHEN: The property is set
page.video_path = Path('openlp')
# THEN: The result should be correct
assert page.video_path_edit.path == Path('openlp')

View File

@ -0,0 +1,594 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.widgets.fontselect package.
"""
from unittest import TestCase
from unittest.mock import MagicMock, patch
import pytest
from openlp.core.pages.fontselect import FontSelectPage
from tests.helpers.testmixin import TestMixin
class TestFontSelectPage(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
def test_init_(self):
"""
Test the initialisation of FontSelectPage
"""
# GIVEN: The FontSelectPage class
# WHEN: Initialising FontSelectPage
# THEN: We should have an instance of the widget with no errors
FontSelectPage()
def test_font_name_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "font_name_changed" signal
instance = FontSelectPage()
instance.font_name_changed = MagicMock()
# WHEN: The font name changes
instance._on_font_name_changed('Sans serif')
# THEN: The signal should be emitted with the correct value
instance.font_name_changed.emit.assert_called_once_with('Sans serif')
def test_font_name_changed_int(self):
# GIVEN: An instance of FontSelectPage with a mocked out "font_name_changed" signal
instance = FontSelectPage()
instance.font_name_changed = MagicMock()
# WHEN: The font name changes
instance._on_font_name_changed(5)
# THEN: The signal should be emitted with the correct value
assert instance.font_name_changed.emit.call_count == 0
def test_font_color_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "font_color_changed" signal
instance = FontSelectPage()
instance.font_color_changed = MagicMock()
# WHEN: The font color changes
instance._on_font_color_changed('#fff')
# THEN: The signal should be emitted with the correct value
instance.font_color_changed.emit.assert_called_once_with('#fff')
def test_is_bold_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "is_bold_changed" signal
instance = FontSelectPage()
instance.is_bold_changed = MagicMock()
# WHEN: The font name changes
instance._on_style_bold_toggled(True)
# THEN: The signal should be emitted with the correct value
instance.is_bold_changed.emit.assert_called_once_with(True)
def test_is_italic_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "style_italic_changed" signal
instance = FontSelectPage()
instance.is_italic_changed = MagicMock()
# WHEN: The font name changes
instance._on_style_italic_toggled(False)
# THEN: The signal should be emitted with the correct value
instance.is_italic_changed.emit.assert_called_once_with(False)
def test_font_size_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "font_size_changed" signal
instance = FontSelectPage()
instance.font_size_changed = MagicMock()
# WHEN: The font size changes
instance._on_font_size_changed(14)
# THEN: The signal should be emitted with the correct value
instance.font_size_changed.emit.assert_called_once_with(14)
def test_line_spacing_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "line_spacing_changed" signal
instance = FontSelectPage()
instance.line_spacing_changed = MagicMock()
# WHEN: The font name changes
instance._on_line_spacing_changed(1)
# THEN: The signal should be emitted with the correct value
instance.line_spacing_changed.emit.assert_called_once_with(1)
def test_is_outline_enabled_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "outline_enabled_changed" signal
instance = FontSelectPage()
instance.is_outline_enabled_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_toggled(True)
# THEN: The signal should be emitted with the correct value
instance.is_outline_enabled_changed.emit.assert_called_once_with(True)
def test_outline_color_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "outline_color_changed" signal
instance = FontSelectPage()
instance.outline_color_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_color_changed('#000')
# THEN: The signal should be emitted with the correct value
instance.outline_color_changed.emit.assert_called_once_with('#000')
def test_outline_size_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "outline_size_changed" signal
instance = FontSelectPage()
instance.outline_size_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_size_changed(2)
# THEN: The signal should be emitted with the correct value
instance.outline_size_changed.emit.assert_called_once_with(2)
def test_is_shadow_enabled_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "is_shadow_enabled_changed" signal
instance = FontSelectPage()
instance.is_shadow_enabled_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_toggled(False)
# THEN: The signal should be emitted with the correct value
instance.is_shadow_enabled_changed.emit.assert_called_once_with(False)
def test_shadow_color_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "shadow_color_changed" signal
instance = FontSelectPage()
instance.shadow_color_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_color_changed('#000')
# THEN: The signal should be emitted with the correct value
instance.shadow_color_changed.emit.assert_called_once_with('#000')
def test_shadow_size_changed(self):
# GIVEN: An instance of FontSelectPage with a mocked out "shadow_size_changed" signal
instance = FontSelectPage()
instance.shadow_size_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_size_changed(5)
# THEN: The signal should be emitted with the correct value
instance.shadow_size_changed.emit.assert_called_once_with(5)
def test_enable_features(self):
"""
Test that the `enable_features` method correctly enables widgets based on features
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test': [mock_label, mock_control]}
# WHEN: The "test" feature is enabled
instance.enable_features('test')
# THEN: "show()" is called on all the widgets
mock_label.show.assert_called_once()
mock_control.show.assert_called_once()
def test_enable_missing_features(self):
"""
Test that the `enable_features` method correctly raises an error on a non-existent feature
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test1': [mock_label, mock_control]}
# WHEN: The "test" feature is enabled
with pytest.raises(KeyError, match='No such feature'):
instance.enable_features('test2')
def test_disable_features(self):
"""
Test that the `disable_features` method correctly disables widgets based on features
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test': [mock_label, mock_control]}
# WHEN: The "test" feature is disabled
instance.disable_features('test')
# THEN: "show()" is called on all the widgets
mock_label.hide.assert_called_once()
mock_control.hide.assert_called_once()
def test_disable_missing_features(self):
"""
Test that the `disable_features` method correctly raises an error on a non-existent feature
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test1': [mock_label, mock_control]}
# WHEN: The "test" feature is disabled
with pytest.raises(KeyError, match='No such feature'):
instance.disable_features('test2')
def test_get_font_name_property(self):
"""
Test the `font_name` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.font_name_combobox.currentFont = MagicMock(
return_value=MagicMock(**{'family.return_value': 'Sans serif'}))
# WHEN: The `font_name` propert is accessed
result = instance.font_name
# THEN: The value should be correct
assert result == 'Sans serif'
def test_set_font_name_property(self):
"""
Test setting the `font_name` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.font_name_combobox.setCurrentFont = MagicMock()
# WHEN: The `font_name` property is set
with patch('openlp.core.pages.fontselect.QtGui.QFont') as MockFont:
mocked_font = MagicMock()
MockFont.return_value = mocked_font
instance.font_name = 'Serif'
# THEN: The correct value should be set
MockFont.assert_called_once_with('Serif')
instance.font_name_combobox.setCurrentFont.assert_called_once_with(mocked_font)
def test_get_font_color_property(self):
"""
Test the `font_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.font_color_button.color = '#000'
# WHEN: The `font_color` propert is accessed
result = instance.font_color
# THEN: The value should be correct
assert result == '#000'
def test_set_font_color_property(self):
"""
Test setting the `font_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
# WHEN: The `font_color` property is set
instance.font_color = '#fff'
# THEN: The correct value should be set
assert instance.font_color_button.color == '#fff'
def test_get_is_bold_property(self):
"""
Test the `is_bold` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.style_bold_button.isChecked = MagicMock(return_value=False)
# WHEN: The `is_bold` propert is accessed
result = instance.is_bold
# THEN: The value should be correct
assert result is False
def test_set_is_bold_property(self):
"""
Test setting the `is_bold` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.style_bold_button.setChecked = MagicMock()
# WHEN: The `is_bold` property is set
instance.is_bold = True
# THEN: The correct value should be set
instance.style_bold_button.setChecked.assert_called_once_with(True)
def test_get_is_italic_property(self):
"""
Test the `is_italic` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.style_italic_button.isChecked = MagicMock(return_value=True)
# WHEN: The `is_italic` propert is accessed
result = instance.is_italic
# THEN: The value should be correct
assert result is True
def test_set_is_italic_property(self):
"""
Test setting the `is_italic` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.style_italic_button.setChecked = MagicMock()
# WHEN: The `is_italic` property is set
instance.is_italic = False
# THEN: The correct value should be set
instance.style_italic_button.setChecked.assert_called_once_with(False)
def test_get_font_size_property(self):
"""
Test the `font_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.font_size_spinbox.value = MagicMock(return_value=16)
# WHEN: The `font_size` propert is accessed
result = instance.font_size
# THEN: The value should be correct
assert result == 16
def test_set_font_size_property(self):
"""
Test setting the `font_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.font_size_spinbox.setValue = MagicMock()
# WHEN: The `font_size` property is set
instance.font_size = 18
# THEN: The correct value should be set
instance.font_size_spinbox.setValue.assert_called_once_with(18)
def test_get_line_spacing_property(self):
"""
Test the `line_spacing` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.line_spacing_spinbox.value = MagicMock(return_value=1)
# WHEN: The `line_spacing` propert is accessed
result = instance.line_spacing
# THEN: The value should be correct
assert result == 1
def test_set_line_spacing_property(self):
"""
Test setting the `line_spacing` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.line_spacing_spinbox.setValue = MagicMock()
# WHEN: The `line_spacing` property is set
instance.line_spacing = 2
# THEN: The correct value should be set
instance.line_spacing_spinbox.setValue.assert_called_once_with(2)
def test_get_is_outline_enabled_property(self):
"""
Test the `is_outline_enabled` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.outline_groupbox.isChecked = MagicMock(return_value=True)
# WHEN: The `is_outline_enabled` propert is accessed
result = instance.is_outline_enabled
# THEN: The value should be correct
assert result is True
def test_set_is_outline_enabled_property(self):
"""
Test setting the `is_outline_enabled` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.outline_groupbox.setChecked = MagicMock()
# WHEN: The `is_outline_enabled` property is set
instance.is_outline_enabled = False
# THEN: The correct value should be set
instance.outline_groupbox.setChecked.assert_called_once_with(False)
def test_get_outline_color_property(self):
"""
Test the `outline_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.outline_color_button.color = '#fff'
# WHEN: The `outline_color` propert is accessed
result = instance.outline_color
# THEN: The value should be correct
assert result == '#fff'
def test_set_outline_color_property(self):
"""
Test setting the `outline_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
# WHEN: The `outline_color` property is set
instance.outline_color = '#000'
# THEN: The correct value should be set
assert instance.outline_color_button.color == '#000'
def test_get_outline_size_property(self):
"""
Test the `outline_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.outline_size_spinbox.value = MagicMock(return_value=2)
# WHEN: The `outline_size` propert is accessed
result = instance.outline_size
# THEN: The value should be correct
assert result == 2
def test_set_outline_size_property(self):
"""
Test setting the `outline_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.outline_size_spinbox.setValue = MagicMock()
# WHEN: The `outline_size` property is set
instance.outline_size = 1
# THEN: The correct value should be set
instance.outline_size_spinbox.setValue.assert_called_once_with(1)
def test_get_is_shadow_enabled_property(self):
"""
Test the `is_shadow_enabled` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.shadow_groupbox.isChecked = MagicMock(return_value=False)
# WHEN: The `is_shadow_enabled` propert is accessed
result = instance.is_shadow_enabled
# THEN: The value should be correct
assert result is False
def test_set_is_shadow_enabled_property(self):
"""
Test setting the `is_shadow_enabled` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.shadow_groupbox.setChecked = MagicMock()
# WHEN: The `is_shadow_enabled` property is set
instance.is_shadow_enabled = True
# THEN: The correct value should be set
instance.shadow_groupbox.setChecked.assert_called_once_with(True)
def test_get_shadow_color_property(self):
"""
Test the `shadow_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.shadow_color_button.color = '#000'
# WHEN: The `shadow_color` propert is accessed
result = instance.shadow_color
# THEN: The value should be correct
assert result == '#000'
def test_set_shadow_color_property(self):
"""
Test setting the `shadow_color` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
# WHEN: The `shadow_color` property is set
instance.shadow_color = '#fff'
# THEN: The correct value should be set
instance.shadow_color_button.color == '#fff'
def test_get_shadow_size_property(self):
"""
Test the `shadow_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.shadow_size_spinbox.value = MagicMock(return_value=5)
# WHEN: The `shadow_size` propert is accessed
result = instance.shadow_size
# THEN: The value should be correct
assert result == 5
def test_set_shadow_size_property(self):
"""
Test setting the `shadow_size` property
"""
# GIVEN: An instance of FontSelectPage with some mocks
instance = FontSelectPage()
instance.shadow_size_spinbox.setValue = MagicMock()
# WHEN: The `shadow_size` property is set
instance.shadow_size = 10
# THEN: The correct value should be set
instance.shadow_size_spinbox.setValue.assert_called_once_with(10)

View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.pages package.
"""
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
import pytest
from openlp.core.pages import GridLayoutPage
from tests.helpers.testmixin import TestMixin
class TestGridLayoutPage(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
@patch('openlp.core.pages.GridLayoutPage.setup_ui')
@patch('openlp.core.pages.GridLayoutPage.retranslate_ui')
def test_resize_event(self, mocked_retranslate_ui, mocked_setup_ui):
"""
Test that the `resizeEvent()` method called the `resize_columns()` method.
"""
# GIVEN: An instance of GridLayoutPage with a mocked out "resize_columns" method
instance = GridLayoutPage()
instance.resize_columns = MagicMock()
# WHEN: resizeEvent is called
instance.resizeEvent(None)
# THEN: resize_widgets should have been called
instance.resize_columns.assert_called_once()
def test_unimplemented_setup_ui(self):
"""
Test that setup_ui() throws a NotImplementedError
"""
with pytest.raises(NotImplementedError, match='Descendant pages need to implement setup_ui'):
GridLayoutPage()
@patch('openlp.core.pages.GridLayoutPage.setup_ui')
def test_unimplemented_retranslate_ui(self, mocked_setup_ui):
"""
Test that retranslate_ui() throws a NotImplementedError
"""
with pytest.raises(NotImplementedError, match='Descendant pages need to implement retranslate_ui'):
GridLayoutPage()
@patch('openlp.core.pages.GridLayoutPage.setup_ui')
@patch('openlp.core.pages.GridLayoutPage.retranslate_ui')
def test_resize_columns(self, mocked_retranslate_ui, mocked_setup_ui):
"""
Test the `resize_columns()` method with an implemented page
"""
# GIVEN: An instance of GridLayoutPage and various mocked out methods
instance = GridLayoutPage()
instance.layout.contentsRect = MagicMock(return_value=MagicMock(**{'width.return_value': 100}))
instance.layout.horizontalSpacing = MagicMock(return_value=6)
instance.layout.columnCount = MagicMock(return_value=4)
instance.layout.setColumnMinimumWidth = MagicMock()
# WHEN: `resize_columns()` is called
instance.resize_columns()
# THEN: The column widths should be set to 16
instance.layout.contentsRect.assert_called_once()
instance.layout.horizontalSpacing.assert_called_once()
instance.layout.columnCount.assert_called_once()
assert instance._column_width == 20
assert instance.layout.setColumnMinimumWidth.call_args_list == [call(0, 20), call(1, 20),
call(2, 20), call(3, 20)]

View File

@ -19,19 +19,22 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
########################################################################## ##########################################################################
""" """
Interface tests to test the ThemeWizard class and related methods. Test the ThemeForm class and related methods.
""" """
from pathlib import Path
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundType
from openlp.core.ui.themeform import ThemeForm from openlp.core.ui.themeform import ThemeForm
from openlp.core.ui.themelayoutform import ThemeLayoutForm
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
class TestThemeManager(TestCase, TestMixin): class TestThemeForm(TestCase, TestMixin):
""" """
Test the functions in the ThemeManager module Test the functions in the ThemeForm Class
""" """
def setUp(self): def setUp(self):
""" """
@ -41,8 +44,8 @@ class TestThemeManager(TestCase, TestMixin):
mocked_renderer = MagicMock() mocked_renderer = MagicMock()
Registry().register('renderer', mocked_renderer) Registry().register('renderer', mocked_renderer)
@patch('openlp.core.display.window.QtWidgets.QVBoxLayout') @patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_create_theme_wizard(self, mocked_qvboxlayout): def test_create_theme_wizard(self, mocked_setup):
""" """
Test creating a ThemeForm instance Test creating a ThemeForm instance
""" """
@ -50,3 +53,358 @@ class TestThemeManager(TestCase, TestMixin):
# WHEN: An object is created # WHEN: An object is created
# THEN: There should be no problems # THEN: There should be no problems
ThemeForm(None) ThemeForm(None)
def test_setup(self):
"""
Test the _setup method
"""
# GIVEN: A ThemeForm instance
with patch('openlp.core.ui.themeform.ThemeForm._setup'):
theme_form = ThemeForm(None)
theme_form.setup_ui = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
# WHEN: _setup() is called
theme_form._setup()
# THEN: The right calls should have been made
theme_form.setup_ui.assert_called_once_with(theme_form)
assert theme_form.can_update_theme is True
assert theme_form.temp_background_filename is None
assert isinstance(theme_form.theme_layout_form, ThemeLayoutForm)
theme_form.main_area_page.font_name_changed.connect.assert_called_once_with(theme_form.calculate_lines)
theme_form.main_area_page.font_size_changed.connect.assert_called_once_with(theme_form.calculate_lines)
theme_form.main_area_page.line_spacing_changed.connect.assert_called_once_with(theme_form.calculate_lines)
theme_form.main_area_page.is_outline_enabled_changed.connect.assert_called_once_with(
theme_form.on_outline_toggled)
theme_form.main_area_page.outline_size_changed.connect.assert_called_once_with(theme_form.calculate_lines)
theme_form.main_area_page.is_shadow_enabled_changed.connect.assert_called_once_with(
theme_form.on_shadow_toggled)
theme_form.main_area_page.shadow_size_changed.connect.assert_called_once_with(theme_form.calculate_lines)
theme_form.footer_area_page.font_name_changed.connect.assert_called_once_with(theme_form.update_theme)
theme_form.footer_area_page.font_size_changed.connect.assert_called_once_with(theme_form.update_theme)
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_set_defaults(self, mocked_setup):
"""
Test that the right methods are called by the set_defaults() method
"""
# GIVEN: A ThemeForm instance with mocked methods
theme_form = ThemeForm(None)
theme_form.restart = MagicMock()
theme_form.set_background_page_values = MagicMock()
theme_form.set_main_area_page_values = MagicMock()
theme_form.set_footer_area_page_values = MagicMock()
theme_form.set_alignment_page_values = MagicMock()
theme_form.set_position_page_values = MagicMock()
theme_form.set_preview_page_values = MagicMock()
# WHEN: set_defaults() is called
theme_form.set_defaults()
# THEN: all the mocks are called
theme_form.restart.assert_called_once()
theme_form.set_background_page_values.assert_called_once()
theme_form.set_main_area_page_values.assert_called_once()
theme_form.set_footer_area_page_values.assert_called_once()
theme_form.set_alignment_page_values.assert_called_once()
theme_form.set_position_page_values.assert_called_once()
theme_form.set_preview_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_calculate_lines(self, mocked_setup):
"""
Test the calculate_lines() method
"""
# GIVEN: A ThemeForm instance with some mocked methods
theme_form = ThemeForm(None)
theme_form.theme = None
theme_form.welcome_page = None
theme_form.currentPage = MagicMock()
theme_form.update_theme = MagicMock()
mocked_theme_manager = MagicMock()
Registry().register('theme_manager', mocked_theme_manager)
# WHEN: calculate_lines() is called
theme_form.calculate_lines()
# THEN: The mocks should have been called correctly
theme_form.currentPage.assert_called_once()
theme_form.update_theme.assert_called_once()
mocked_theme_manager.generate_image.assert_called_once_with(None, True)
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_update_lines_text(self, mocked_setup):
"""
Test the update_lines_text() method
"""
# GIVEN: A ThemeForm instance with some mocked methods
theme_form = ThemeForm(None)
theme_form.main_line_count_label = MagicMock()
# WHEN: calculate_lines() is called
theme_form.update_lines_text(10)
# THEN: The mocks should have been called correctly
theme_form.main_line_count_label.setText.assert_called_once_with('(approximately 10 lines per slide)')
@patch('openlp.core.ui.themeform.QtWidgets.QWizard.resizeEvent')
@patch('openlp.core.ui.themeform.QtGui.QResizeEvent')
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_resize_event(self, mocked_setup, MockResizeEvent, mocked_resizeEvent):
"""
Test that the resizeEvent method handles resizing correctly
"""
# GIVEN: A ThemeForm instance with a number of mocked methods
mocked_event = MagicMock()
MockResizeEvent.return_value = mocked_event
theme_form = ThemeForm(None)
theme_form.size = MagicMock(return_value=1920)
theme_form.preview_area_layout = MagicMock()
theme_form.preview_box = MagicMock(**{'width.return_value': 300})
mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 1080})
Registry().remove('renderer')
Registry().register('renderer', mocked_renderer)
# WHEN: resizeEvent() is called
theme_form.resizeEvent()
# THEN: The correct calls should have been made
MockResizeEvent.assert_called_once_with(1920, 1920)
mocked_resizeEvent.assert_called_once_with(theme_form, mocked_event)
assert mocked_renderer.width.call_count == 2
mocked_renderer.height.assert_called_once()
theme_form.preview_area_layout.set_aspect_ratio.assert_called_once_with(16 / 9)
theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920))
@patch('openlp.core.ui.themeform.QtWidgets.QWizard.resizeEvent')
@patch('openlp.core.ui.themeform.QtGui.QResizeEvent')
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_resize_event_dbze(self, mocked_setup, MockResizeEvent, mocked_resizeEvent):
"""
Test that the resizeEvent method handles a divide by zero exception correctly
"""
# GIVEN: A ThemeForm instance with a number of mocked methods
mocked_event = MagicMock()
MockResizeEvent.return_value = mocked_event
theme_form = ThemeForm(None)
theme_form.size = MagicMock(return_value=1920)
theme_form.preview_area_layout = MagicMock()
theme_form.preview_box = MagicMock(**{'width.return_value': 300})
mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 0})
Registry().remove('renderer')
Registry().register('renderer', mocked_renderer)
# WHEN: resizeEvent() is called
theme_form.resizeEvent()
# THEN: The correct calls should have been made
MockResizeEvent.assert_called_once_with(1920, 1920)
mocked_resizeEvent.assert_called_once_with(theme_form, mocked_event)
assert mocked_renderer.width.call_count == 2
mocked_renderer.height.assert_called_once()
theme_form.preview_area_layout.set_aspect_ratio.assert_called_once_with(1)
theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920))
@patch('openlp.core.ui.themeform.QtWidgets.QMessageBox.critical')
@patch('openlp.core.ui.themeform.is_not_image_file')
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_validate_current_page_with_image(self, mocked_setup, mocked_is_not_image_file, mocked_critical):
"""
Test the validateCurrentPage() method
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock(background_type=BackgroundType.to_string(BackgroundType.Image),
image_path=Path('picture.jpg'))
theme_form.page = MagicMock(return_value=theme_form.background_page)
mocked_is_not_image_file.return_value = True
# WHEN: validateCurrentPage() is called
result = theme_form.validateCurrentPage()
# THEN: The right methods were called, and the result is False
mocked_critical.assert_called_once_with(theme_form, 'Background Image Empty',
'You have not selected a background image. '
'Please select one before continuing.')
assert result is False
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_validate_current_page(self, mocked_setup):
"""
Test the validateCurrentPage() method
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.page = MagicMock()
# WHEN: validateCurrentPage() is called
result = theme_form.validateCurrentPage()
# THEN: The right methods were called, and the result is False
assert result is True
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_on_current_id_changed_preview(self, mocked_setup):
"""
Test the on_current_id_changed() method
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.theme = 'my fake theme'
theme_form.area_position_page = MagicMock()
theme_form.preview_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.preview_page)
theme_form.update_theme = MagicMock()
theme_form.preview_box = MagicMock(**{'width.return_value': 300})
theme_form.preview_area_layout = MagicMock()
theme_form.resizeEvent = MagicMock()
mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 0})
Registry().remove('renderer')
Registry().register('renderer', mocked_renderer)
# WHEN: on_current_id_changed() is called
theme_form.on_current_id_changed(1)
# THEN: The right options should have been set
theme_form.update_theme.assert_called_once()
theme_form.preview_box.set_theme.assert_called_once_with('my fake theme')
theme_form.preview_box.clear_slides.assert_called_once()
theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920))
theme_form.preview_area_layout.set_aspect_ratio(16 / 9)
theme_form.resizeEvent.assert_called_once()
theme_form.preview_box.show.assert_called_once()
theme_form.preview_box.generate_preview.assert_called_once_with('my fake theme', False, False)
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_on_outline_toggled(self, mocked_setup):
"""
Test the on_outline_toggled() method
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.can_update_theme = True
theme_form.theme = MagicMock()
theme_form.calculate_lines = MagicMock()
# WHEN: on_outline_toggled is called
theme_form.on_outline_toggled(True)
# THEN: Everything is working right
assert theme_form.theme.font_main_outline is True
theme_form.calculate_lines.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_on_shadow_toggled(self, mocked_setup):
"""
Test the on_shadow_toggled() method
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.can_update_theme = True
theme_form.theme = MagicMock()
theme_form.calculate_lines = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.on_shadow_toggled(True)
# THEN: Everything is working right
assert theme_form.theme.font_main_shadow is True
theme_form.calculate_lines.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_initialise_page_background(self, mocked_setup):
"""
Test the initializePage() method with the background page
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.background_page)
theme_form.set_background_page_values = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.initializePage(0)
# THEN: Everything is working right
theme_form.set_background_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_initialise_page_main_area(self, mocked_setup):
"""
Test the initializePage() method with the main_area page
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.main_area_page)
theme_form.set_main_area_page_values = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.initializePage(0)
# THEN: Everything is working right
theme_form.set_main_area_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_initialise_page_footer_area(self, mocked_setup):
"""
Test the initializePage() method with the footer_area page
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.footer_area_page)
theme_form.set_footer_area_page_values = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.initializePage(0)
# THEN: Everything is working right
theme_form.set_footer_area_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_initialise_page_alignment(self, mocked_setup):
"""
Test the initializePage() method with the alignment page
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.alignment_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.alignment_page)
theme_form.set_alignment_page_values = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.initializePage(0)
# THEN: Everything is working right
theme_form.set_alignment_page_values.assert_called_once()
@patch('openlp.core.ui.themeform.ThemeForm._setup')
def test_initialise_page_area_position(self, mocked_setup):
"""
Test the initializePage() method with the area_position page
"""
# GIVEN: An instance of ThemeForm with some mocks
theme_form = ThemeForm(None)
theme_form.background_page = MagicMock()
theme_form.main_area_page = MagicMock()
theme_form.footer_area_page = MagicMock()
theme_form.alignment_page = MagicMock()
theme_form.area_position_page = MagicMock()
theme_form.page = MagicMock(return_value=theme_form.area_position_page)
theme_form.set_position_page_values = MagicMock()
# WHEN: on_shadow_toggled is called
theme_form.initializePage(0)
# THEN: Everything is working right
theme_form.set_position_page_values.assert_called_once()

View File

@ -24,12 +24,11 @@ Package to test the openlp.core.widgets.widgets package.
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch
import pytest
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common.settings import ProxyMode from openlp.core.common.settings import ProxyMode
from openlp.core.display.screens import Screen from openlp.core.display.screens import Screen
from openlp.core.widgets.widgets import ProxyWidget, ProxyDialog, ScreenButton, ScreenSelectionWidget, FontSelectWidget from openlp.core.widgets.widgets import ProxyWidget, ProxyDialog, ScreenButton, ScreenSelectionWidget
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -526,604 +525,3 @@ class TestScreenSelectionWidget(TestCase, TestMixin):
'checkbox for that screen.', parent=instance, question=False) 'checkbox for that screen.', parent=instance, question=False)
assert instance.use_screen_check_box.isChecked() is True assert instance.use_screen_check_box.isChecked() is True
assert instance.display_group_box.isChecked() is True assert instance.display_group_box.isChecked() is True
class TestFontSelectWidget(TestCase, TestMixin):
def setUp(self):
"""Test setup"""
self.setup_application()
self.build_settings()
def tearDown(self):
"""Tear down tests"""
del self.app
def test_init_(self):
"""
Test the initialisation of FontSelectWidget
"""
# GIVEN: The FontSelectWidget class
# WHEN: Initialising FontSelectWidget
# THEN: We should have an instance of the widget with no errors
FontSelectWidget()
def test_resize_event(self):
"""
Test that the `resizeEvent()` method called the `resize_widgets()` method.
"""
# GIVEN: An instance of FontSelectWidget with a mocked out "resize_widgets" method
instance = FontSelectWidget()
instance.resize_widgets = MagicMock()
# WHEN: resizeEvent is called
instance.resizeEvent(None)
# THEN: resize_widgets should have been called
instance.resize_widgets.assert_called_once()
def test_font_name_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "font_name_changed" signal
instance = FontSelectWidget()
instance.font_name_changed = MagicMock()
# WHEN: The font name changes
instance._on_font_name_changed('Sans serif')
# THEN: The signal should be emitted with the correct value
instance.font_name_changed.emit.assert_called_once_with('Sans serif')
def test_font_name_changed_int(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "font_name_changed" signal
instance = FontSelectWidget()
instance.font_name_changed = MagicMock()
# WHEN: The font name changes
instance._on_font_name_changed(5)
# THEN: The signal should be emitted with the correct value
assert instance.font_name_changed.emit.call_count == 0
def test_font_color_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "font_color_changed" signal
instance = FontSelectWidget()
instance.font_color_changed = MagicMock()
# WHEN: The font color changes
instance._on_font_color_changed('#fff')
# THEN: The signal should be emitted with the correct value
instance.font_color_changed.emit.assert_called_once_with('#fff')
def test_is_bold_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "is_bold_changed" signal
instance = FontSelectWidget()
instance.is_bold_changed = MagicMock()
# WHEN: The font name changes
instance._on_style_bold_toggled(True)
# THEN: The signal should be emitted with the correct value
instance.is_bold_changed.emit.assert_called_once_with(True)
def test_is_italic_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "style_italic_changed" signal
instance = FontSelectWidget()
instance.is_italic_changed = MagicMock()
# WHEN: The font name changes
instance._on_style_italic_toggled(False)
# THEN: The signal should be emitted with the correct value
instance.is_italic_changed.emit.assert_called_once_with(False)
def test_font_size_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "font_size_changed" signal
instance = FontSelectWidget()
instance.font_size_changed = MagicMock()
# WHEN: The font size changes
instance._on_font_size_changed(14)
# THEN: The signal should be emitted with the correct value
instance.font_size_changed.emit.assert_called_once_with(14)
def test_line_spacing_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "line_spacing_changed" signal
instance = FontSelectWidget()
instance.line_spacing_changed = MagicMock()
# WHEN: The font name changes
instance._on_line_spacing_changed(1)
# THEN: The signal should be emitted with the correct value
instance.line_spacing_changed.emit.assert_called_once_with(1)
def test_is_outline_enabled_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "outline_enabled_changed" signal
instance = FontSelectWidget()
instance.is_outline_enabled_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_toggled(True)
# THEN: The signal should be emitted with the correct value
instance.is_outline_enabled_changed.emit.assert_called_once_with(True)
def test_outline_color_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "outline_color_changed" signal
instance = FontSelectWidget()
instance.outline_color_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_color_changed('#000')
# THEN: The signal should be emitted with the correct value
instance.outline_color_changed.emit.assert_called_once_with('#000')
def test_outline_size_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "outline_size_changed" signal
instance = FontSelectWidget()
instance.outline_size_changed = MagicMock()
# WHEN: The font name changes
instance._on_outline_size_changed(2)
# THEN: The signal should be emitted with the correct value
instance.outline_size_changed.emit.assert_called_once_with(2)
def test_is_shadow_enabled_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "is_shadow_enabled_changed" signal
instance = FontSelectWidget()
instance.is_shadow_enabled_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_toggled(False)
# THEN: The signal should be emitted with the correct value
instance.is_shadow_enabled_changed.emit.assert_called_once_with(False)
def test_shadow_color_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "shadow_color_changed" signal
instance = FontSelectWidget()
instance.shadow_color_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_color_changed('#000')
# THEN: The signal should be emitted with the correct value
instance.shadow_color_changed.emit.assert_called_once_with('#000')
def test_shadow_size_changed(self):
# GIVEN: An instance of FontSelectWidget with a mocked out "shadow_size_changed" signal
instance = FontSelectWidget()
instance.shadow_size_changed = MagicMock()
# WHEN: The font name changes
instance._on_shadow_size_changed(5)
# THEN: The signal should be emitted with the correct value
instance.shadow_size_changed.emit.assert_called_once_with(5)
def test_resize_widgets(self):
"""
Test the `resize_widgets()` method
"""
# GIVEN: An instance of FontSelectWidget and various mocked out methods
instance = FontSelectWidget()
instance.geometry = MagicMock(return_value=MagicMock(**{'width.return_value': 100}))
instance.layout.contentsMargins = MagicMock(return_value=MagicMock(**{'left.return_value': 8,
'right.return_value': 8}))
instance.layout.horizontalSpacing = MagicMock(return_value=6)
instance.layout.setColumnMinimumWidth = MagicMock()
# WHEN: `resize_widgets()` is called
instance.resize_widgets()
# THEN: The column widths should be set to 16
instance.geometry.assert_called_once()
instance.layout.contentsMargins.assert_called_once()
instance.layout.horizontalSpacing.assert_called_once()
assert instance._column_width == 16
assert instance.layout.setColumnMinimumWidth.call_args_list == [call(0, 16), call(1, 16),
call(2, 16), call(3, 16)]
def test_enable_features(self):
"""
Test that the `enable_features` method correctly enables widgets based on features
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test': [mock_label, mock_control]}
# WHEN: The "test" feature is enabled
instance.enable_features('test')
# THEN: "show()" is called on all the widgets
mock_label.show.assert_called_once()
mock_control.show.assert_called_once()
def test_enable_missing_features(self):
"""
Test that the `enable_features` method correctly raises an error on a non-existent feature
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test1': [mock_label, mock_control]}
# WHEN: The "test" feature is enabled
with pytest.raises(KeyError, match='No such feature'):
instance.enable_features('test2')
def test_disable_features(self):
"""
Test that the `disable_features` method correctly disables widgets based on features
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test': [mock_label, mock_control]}
# WHEN: The "test" feature is disabled
instance.disable_features('test')
# THEN: "show()" is called on all the widgets
mock_label.hide.assert_called_once()
mock_control.hide.assert_called_once()
def test_disable_missing_features(self):
"""
Test that the `disable_features` method correctly raises an error on a non-existent feature
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
mock_label = MagicMock()
mock_control = MagicMock()
instance.feature_widgets = {'test1': [mock_label, mock_control]}
# WHEN: The "test" feature is disabled
with pytest.raises(KeyError, match='No such feature'):
instance.disable_features('test2')
def test_get_font_name_property(self):
"""
Test the `font_name` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.font_name_combobox.currentFont = MagicMock(
return_value=MagicMock(**{'family.return_value': 'Sans serif'}))
# WHEN: The `font_name` propert is accessed
result = instance.font_name
# THEN: The value should be correct
assert result == 'Sans serif'
def test_set_font_name_property(self):
"""
Test setting the `font_name` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.font_name_combobox.setCurrentFont = MagicMock()
# WHEN: The `font_name` property is set
with patch('openlp.core.widgets.widgets.QtGui.QFont') as MockFont:
mocked_font = MagicMock()
MockFont.return_value = mocked_font
instance.font_name = 'Serif'
# THEN: The correct value should be set
MockFont.assert_called_once_with('Serif')
instance.font_name_combobox.setCurrentFont.assert_called_once_with(mocked_font)
def test_get_font_color_property(self):
"""
Test the `font_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.font_color_button.color = '#000'
# WHEN: The `font_color` propert is accessed
result = instance.font_color
# THEN: The value should be correct
assert result == '#000'
def test_set_font_color_property(self):
"""
Test setting the `font_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
# WHEN: The `font_color` property is set
instance.font_color = '#fff'
# THEN: The correct value should be set
assert instance.font_color_button.color == '#fff'
def test_get_is_bold_property(self):
"""
Test the `is_bold` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.style_bold_button.isChecked = MagicMock(return_value=False)
# WHEN: The `is_bold` propert is accessed
result = instance.is_bold
# THEN: The value should be correct
assert result is False
def test_set_is_bold_property(self):
"""
Test setting the `is_bold` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.style_bold_button.setChecked = MagicMock()
# WHEN: The `is_bold` property is set
instance.is_bold = True
# THEN: The correct value should be set
instance.style_bold_button.setChecked.assert_called_once_with(True)
def test_get_is_italic_property(self):
"""
Test the `is_italic` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.style_italic_button.isChecked = MagicMock(return_value=True)
# WHEN: The `is_italic` propert is accessed
result = instance.is_italic
# THEN: The value should be correct
assert result is True
def test_set_is_italic_property(self):
"""
Test setting the `is_italic` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.style_italic_button.setChecked = MagicMock()
# WHEN: The `is_italic` property is set
instance.is_italic = False
# THEN: The correct value should be set
instance.style_italic_button.setChecked.assert_called_once_with(False)
def test_get_font_size_property(self):
"""
Test the `font_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.font_size_spinbox.value = MagicMock(return_value=16)
# WHEN: The `font_size` propert is accessed
result = instance.font_size
# THEN: The value should be correct
assert result == 16
def test_set_font_size_property(self):
"""
Test setting the `font_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.font_size_spinbox.setValue = MagicMock()
# WHEN: The `font_size` property is set
instance.font_size = 18
# THEN: The correct value should be set
instance.font_size_spinbox.setValue.assert_called_once_with(18)
def test_get_line_spacing_property(self):
"""
Test the `line_spacing` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.line_spacing_spinbox.value = MagicMock(return_value=1)
# WHEN: The `line_spacing` propert is accessed
result = instance.line_spacing
# THEN: The value should be correct
assert result == 1
def test_set_line_spacing_property(self):
"""
Test setting the `line_spacing` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.line_spacing_spinbox.setValue = MagicMock()
# WHEN: The `line_spacing` property is set
instance.line_spacing = 2
# THEN: The correct value should be set
instance.line_spacing_spinbox.setValue.assert_called_once_with(2)
def test_get_is_outline_enabled_property(self):
"""
Test the `is_outline_enabled` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.outline_groupbox.isChecked = MagicMock(return_value=True)
# WHEN: The `is_outline_enabled` propert is accessed
result = instance.is_outline_enabled
# THEN: The value should be correct
assert result is True
def test_set_is_outline_enabled_property(self):
"""
Test setting the `is_outline_enabled` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.outline_groupbox.setChecked = MagicMock()
# WHEN: The `is_outline_enabled` property is set
instance.is_outline_enabled = False
# THEN: The correct value should be set
instance.outline_groupbox.setChecked.assert_called_once_with(False)
def test_get_outline_color_property(self):
"""
Test the `outline_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.outline_color_button.color = '#fff'
# WHEN: The `outline_color` propert is accessed
result = instance.outline_color
# THEN: The value should be correct
assert result == '#fff'
def test_set_outline_color_property(self):
"""
Test setting the `outline_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
# WHEN: The `outline_color` property is set
instance.outline_color = '#000'
# THEN: The correct value should be set
assert instance.outline_color_button.color == '#000'
def test_get_outline_size_property(self):
"""
Test the `outline_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.outline_size_spinbox.value = MagicMock(return_value=2)
# WHEN: The `outline_size` propert is accessed
result = instance.outline_size
# THEN: The value should be correct
assert result == 2
def test_set_outline_size_property(self):
"""
Test setting the `outline_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.outline_size_spinbox.setValue = MagicMock()
# WHEN: The `outline_size` property is set
instance.outline_size = 1
# THEN: The correct value should be set
instance.outline_size_spinbox.setValue.assert_called_once_with(1)
def test_get_is_shadow_enabled_property(self):
"""
Test the `is_shadow_enabled` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.shadow_groupbox.isChecked = MagicMock(return_value=False)
# WHEN: The `is_shadow_enabled` propert is accessed
result = instance.is_shadow_enabled
# THEN: The value should be correct
assert result is False
def test_set_is_shadow_enabled_property(self):
"""
Test setting the `is_shadow_enabled` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.shadow_groupbox.setChecked = MagicMock()
# WHEN: The `is_shadow_enabled` property is set
instance.is_shadow_enabled = True
# THEN: The correct value should be set
instance.shadow_groupbox.setChecked.assert_called_once_with(True)
def test_get_shadow_color_property(self):
"""
Test the `shadow_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.shadow_color_button.color = '#000'
# WHEN: The `shadow_color` propert is accessed
result = instance.shadow_color
# THEN: The value should be correct
assert result == '#000'
def test_set_shadow_color_property(self):
"""
Test setting the `shadow_color` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
# WHEN: The `shadow_color` property is set
instance.shadow_color = '#fff'
# THEN: The correct value should be set
instance.shadow_color_button.color == '#fff'
def test_get_shadow_size_property(self):
"""
Test the `shadow_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.shadow_size_spinbox.value = MagicMock(return_value=5)
# WHEN: The `shadow_size` propert is accessed
result = instance.shadow_size
# THEN: The value should be correct
assert result == 5
def test_set_shadow_size_property(self):
"""
Test setting the `shadow_size` property
"""
# GIVEN: An instance of FontSelectWidget with some mocks
instance = FontSelectWidget()
instance.shadow_size_spinbox.setValue = MagicMock()
# WHEN: The `shadow_size` property is set
instance.shadow_size = 10
# THEN: The correct value should be set
instance.shadow_size_spinbox.setValue.assert_called_once_with(10)