This commit is contained in:
Raoul Snyman 2013-01-20 21:33:26 +02:00
commit 8c916254e4
44 changed files with 497 additions and 120 deletions

View File

@ -1 +1 @@
2.0
2.1.0-bzr2141

View File

@ -34,7 +34,6 @@ import logging
import os
from urllib import quote_plus as urlquote
from PyQt4 import QtCore
from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError
from sqlalchemy.orm import scoped_session, sessionmaker, mapper

View File

@ -31,8 +31,6 @@ Provide HTML Tag management and Formatting Tag access class
"""
import cPickle
from PyQt4 import QtCore
from openlp.core.lib import translate, Settings
class FormattingTags(object):

View File

@ -37,7 +37,9 @@ import logging
import os
import uuid
from openlp.core.lib import build_icon, clean_tags, expand_tags, translate, ImageSource
from PyQt4 import QtGui
from openlp.core.lib import build_icon, clean_tags, expand_tags, translate, ImageSource, Settings
log = logging.getLogger(__name__)
@ -182,6 +184,7 @@ class ServiceItem(object):
self.theme_overwritten = False
self.temporary_edit = False
self.will_auto_start = False
self.has_original_files = True
self._new_item()
def _new_item(self):
@ -190,6 +193,7 @@ class ServiceItem(object):
service items to see if they are the same.
"""
self._uuid = unicode(uuid.uuid1())
self.validate_item()
def add_capability(self, capability):
"""
@ -395,6 +399,7 @@ class ServiceItem(object):
self.end_time = header.get(u'end_time', 0)
self.media_length = header.get(u'media_length', 0)
self.will_auto_start = header.get(u'will_auto_start', False)
self.has_original_files = True
if u'background_audio' in header:
self.background_audio = []
for filename in header[u'background_audio']:
@ -405,16 +410,20 @@ class ServiceItem(object):
for slide in serviceitem[u'serviceitem'][u'data']:
self._raw_frames.append(slide)
elif self.service_item_type == ServiceItemType.Image:
settingsSection = serviceitem[u'serviceitem'][u'header'][u'name']
background = QtGui.QColor(Settings().value(settingsSection + u'/background color', u'#000000'))
if path:
self.has_original_files = False
for text_image in serviceitem[u'serviceitem'][u'data']:
filename = os.path.join(path, text_image)
self.add_from_image(filename, text_image)
self.add_from_image(filename, text_image, background)
else:
for text_image in serviceitem[u'serviceitem'][u'data']:
self.add_from_image(text_image[u'path'], text_image[u'title'])
self.add_from_image(text_image[u'path'], text_image[u'title'], background)
elif self.service_item_type == ServiceItemType.Command:
for text_image in serviceitem[u'serviceitem'][u'data']:
if path:
self.has_original_files = False
self.add_from_command(path, text_image[u'title'], text_image[u'image'])
else:
self.add_from_command(text_image[u'path'], text_image[u'title'], text_image[u'image'])
@ -605,8 +614,25 @@ class ServiceItem(object):
if self.get_frame_path(frame=frame) in invalid_paths:
self.remove_frame(frame)
def validate(self):
def missing_frames(self):
"""
Validates this service item
Returns if there are any frames in the service item
"""
return bool(self._raw_frames)
return not bool(self._raw_frames)
def validate_item(self, suffix_list=None):
"""
Validates a service item to make sure it is valid
"""
self.is_valid = True
for frame in self._raw_frames:
if self.is_image() and not os.path.exists((frame[u'path'])):
self.is_valid = False
elif self.is_command():
file = os.path.join(frame[u'path'],frame[u'title'])
if not os.path.exists(file):
self.is_valid = False
if suffix_list and not self.is_text():
type = frame[u'title'].split(u'.')[-1]
if type.lower() not in suffix_list:
self.is_valid = False

View File

@ -26,5 +26,9 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.theme` module contains all the themeing functions used by
OpenLP when displaying a song or a scripture.
"""
from openlp.core.theme.theme import Theme

View File

@ -29,9 +29,7 @@
"""
The :mod:`ui` module provides the core user interface for OpenLP
"""
from PyQt4 import QtGui
from openlp.core.lib import translate
class HideMode(object):
"""

View File

@ -29,7 +29,7 @@
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, build_icon
from openlp.core.lib import translate
from openlp.core.lib.ui import create_button, create_button_box
class Ui_ExceptionDialog(object):

View File

@ -29,19 +29,49 @@
import logging
import os
import sys
import datetime
from PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, Receiver, translate, Settings
from openlp.core.lib.ui import UiStrings, critical_error_message_box
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, \
get_media_players, set_media_players
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players
from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.utils import AppLocation
from openlp.core.ui import DisplayControllerType
log = logging.getLogger(__name__)
class MediaSlider(QtGui.QSlider):
"""
Allows the mouse events of a slider to be overridden and extra functionality added
"""
def __init__(self, direction, manager, controller, parent=None):
QtGui.QSlider.__init__(self, direction)
self.manager = manager
self.controller = controller
def mouseMoveEvent(self, event):
"""
Override event to allow hover time to be displayed.
"""
timevalue = QtGui.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width())
self.setToolTip(u'%s' % datetime.timedelta(seconds=int(timevalue/1000)))
QtGui.QSlider.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
"""
Mouse Press event no new functionality
"""
QtGui.QSlider.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
"""
Set the slider position when the mouse is clicked and released on the slider.
"""
self.setValue(QtGui.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width()))
QtGui.QSlider.mouseReleaseEvent(self, event)
class MediaController(object):
"""
The implementation of the Media Controller. The Media Controller adds an own
@ -69,8 +99,8 @@ class MediaController(object):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'playbackPlay'), self.media_play_msg)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'playbackPause'), self.media_pause_msg)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'playbackStop'), self.media_stop_msg)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'seekSlider'), self.media_seek)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'volumeSlider'), self.media_volume)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'seekSlider'), self.media_seek_msg)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'volumeSlider'), self.media_volume_msg)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_hide'), self.media_hide)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_blank'), self.media_blank)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_unblank'), self.media_unblank)
@ -241,9 +271,10 @@ class MediaController(object):
icon=u':/slides/media_playback_stop.png',
tooltip=translate('OpenLP.SlideController', 'Stop playing media.'), triggers=controller.sendToPlugins)
# Build the seekSlider.
controller.seekSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
controller.seekSlider = MediaSlider(QtCore.Qt.Horizontal, self, controller)
controller.seekSlider.setMaximum(1000)
controller.seekSlider.setTracking(False)
controller.seekSlider.setTracking(True)
controller.seekSlider.setMouseTracking(True)
controller.seekSlider.setToolTip(translate('OpenLP.SlideController', 'Video position.'))
controller.seekSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
controller.seekSlider.setObjectName(u'seekSlider')
@ -344,12 +375,8 @@ class MediaController(object):
# stop running videos
self.media_reset(controller)
controller.media_info = MediaInfo()
if videoBehindText:
controller.media_info.volume = 0
controller.media_info.is_background = True
else:
controller.media_info.volume = controller.volumeSlider.value()
controller.media_info.is_background = False
controller.media_info.volume = controller.volumeSlider.value()
controller.media_info.is_background = videoBehindText
controller.media_info.file_info = QtCore.QFileInfo(serviceItem.get_frame_path())
display = self._define_display(controller)
if controller.isLive:
@ -361,7 +388,7 @@ class MediaController(object):
controller.media_info.start_time = 0
controller.media_info.end_time = 0
else:
controller.media_info.start_time = display.serviceItem.start_time
controller.media_info.start_time = serviceItem.start_time
controller.media_info.end_time = serviceItem.end_time
elif controller.previewDisplay:
isValid = self._check_file_type(controller, display, serviceItem)
@ -483,9 +510,17 @@ class MediaController(object):
The controller to be played
"""
log.debug(u'media_play')
controller.seekSlider.blockSignals(True)
controller.volumeSlider.blockSignals(True)
display = self._define_display(controller)
if not self.currentMediaPlayer[controller.controllerType].play(display):
controller.seekSlider.blockSignals(False)
controller.volumeSlider.blockSignals(False)
return False
if controller.media_info.is_background:
self.media_volume(controller, 0)
else:
self.media_volume(controller, controller.media_info.volume)
if status:
display.frame.evaluateJavaScript(u'show_blank("desktop");')
self.currentMediaPlayer[controller.controllerType].set_visible(display, True)
@ -503,6 +538,8 @@ class MediaController(object):
# Start Timer for ui updates
if not self.timer.isActive():
self.timer.start()
controller.seekSlider.blockSignals(False)
controller.volumeSlider.blockSignals(False)
return True
def media_pause_msg(self, msg):
@ -557,7 +594,7 @@ class MediaController(object):
controller.mediabar.actions[u'playbackStop'].setVisible(False)
controller.mediabar.actions[u'playbackPause'].setVisible(False)
def media_volume(self, msg):
def media_volume_msg(self, msg):
"""
Changes the volume of a running video
@ -566,11 +603,21 @@ class MediaController(object):
"""
controller = msg[0]
vol = msg[1][0]
log.debug(u'media_volume %d' % vol)
display = self._define_display(controller)
self.currentMediaPlayer[controller.controllerType].volume(display, vol)
self.media_volume(controller, vol)
def media_seek(self, msg):
def media_volume(self, controller, volume):
"""
Changes the volume of a running video
``msg``
First element is the controller which should be used
"""
log.debug(u'media_volume %d' % volume)
display = self._define_display(controller)
self.currentMediaPlayer[controller.controllerType].volume(display, volume)
controller.volumeSlider.setValue(volume)
def media_seek_msg(self, msg):
"""
Responds to the request to change the seek Slider of a loaded video
@ -581,6 +628,17 @@ class MediaController(object):
log.debug(u'media_seek')
controller = msg[0]
seekVal = msg[1][0]
self.media_seek(controller, seekVal)
def media_seek(self, controller, seekVal):
"""
Responds to the request to change the seek Slider of a loaded video
``msg``
First element is the controller which should be used
Second element is a list with the seek Value as first element
"""
log.debug(u'media_seek')
display = self._define_display(controller)
self.currentMediaPlayer[controller.controllerType].seek(display, seekVal)
@ -610,7 +668,8 @@ class MediaController(object):
return
controller = self.mainWindow.liveController
display = self._define_display(controller)
if self.currentMediaPlayer[controller.controllerType].state == MediaState.Playing:
if controller.controllerType in self.currentMediaPlayer and \
self.currentMediaPlayer[controller.controllerType].state == MediaState.Playing:
self.currentMediaPlayer[controller.controllerType].pause(display)
self.currentMediaPlayer[controller.controllerType].set_visible(display, False)

View File

@ -31,7 +31,7 @@ import logging
import mimetypes
from datetime import datetime
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from PyQt4.phonon import Phonon
from openlp.core.lib import Receiver, translate, Settings
@ -215,8 +215,9 @@ class PhononPlayer(MediaPlayer):
self.stop(display)
self.set_visible(display, False)
if not controller.seekSlider.isSliderDown():
controller.seekSlider.setSliderPosition(
display.mediaObject.currentTime())
controller.seekSlider.blockSignals(True)
controller.seekSlider.setSliderPosition(display.mediaObject.currentTime())
controller.seekSlider.blockSignals(False)
def get_media_display_css(self):
"""

View File

@ -26,3 +26,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.ui.media.vendor` module contains any scripts or libraries
from 3rd party vendors which are required to make certain media modules work.
"""

View File

@ -33,7 +33,7 @@ import logging
import os
import sys
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from openlp.core.lib import Receiver, translate, Settings
from openlp.core.ui.media import MediaState
@ -109,6 +109,7 @@ class VlcPlayer(MediaPlayer):
def setup(self, display):
display.vlcWidget = QtGui.QFrame(display)
display.vlcWidget.setFrameStyle(QtGui.QFrame.NoFrame)
# creating a basic vlc instance
command_line_options = u'--no-video-title-show'
if not display.hasAudio:
@ -188,6 +189,7 @@ class VlcPlayer(MediaPlayer):
display.vlcMediaPlayer.play()
if not self.media_state_wait(display, vlc.State.Playing):
return False
self.volume(display, controller.media_info.volume)
if start_time > 0:
self.seek(display, controller.media_info.start_time * 1000)
controller.media_info.length = int(display.vlcMediaPlayer.get_media().get_duration() / 1000)
@ -234,7 +236,9 @@ class VlcPlayer(MediaPlayer):
self.stop(display)
self.set_visible(display, False)
if not controller.seekSlider.isSliderDown():
controller.seekSlider.blockSignals(True)
controller.seekSlider.setSliderPosition(display.vlcMediaPlayer.get_time())
controller.seekSlider.blockSignals(False)
def get_info(self):
return(translate('Media.player', 'VLC is an external player which '

View File

@ -423,7 +423,9 @@ class WebkitPlayer(MediaPlayer):
controller.media_info.length = length
controller.seekSlider.setMaximum(length)
if not controller.seekSlider.isSliderDown():
controller.seekSlider.blockSignals(True)
controller.seekSlider.setSliderPosition(currentTime)
controller.seekSlider.blockSignals(False)
def get_info(self):
return(translate('Media.player', 'Webkit is a media player which runs '

View File

@ -69,7 +69,7 @@ class ScreenList(object):
screen_list.screen_list = []
screen_list.display_count = 0
screen_list.screen_count_changed()
screen_list._load_screen_settings()
screen_list.load_screen_settings()
QtCore.QObject.connect(desktop, QtCore.SIGNAL(u'resized(int)'), screen_list.screen_resolution_changed)
QtCore.QObject.connect(desktop, QtCore.SIGNAL(u'screenCountChanged(int)'), screen_list.screen_count_changed)
return screen_list
@ -237,7 +237,7 @@ class ScreenList(object):
y >= size.y() and y <= (size.y() + size.height()):
return screen[u'number']
def _load_screen_settings(self):
def load_screen_settings(self):
"""
Loads the screen size and the monitor number from the settings.
"""

View File

@ -114,6 +114,7 @@ class ServiceManager(QtGui.QWidget):
# is a new service and has not been saved
self._modified = False
self._fileName = u''
self.service_has_all_original_files = True
self.serviceNoteForm = ServiceNoteForm(self.mainwindow)
self.serviceItemEditForm = ServiceItemEditForm(self.mainwindow)
self.startTimeForm = StartTimeForm(self.mainwindow)
@ -458,7 +459,7 @@ class ServiceManager(QtGui.QWidget):
for item in list(self.serviceItems):
self.mainwindow.incrementProgressBar()
item[u'service_item'].remove_invalid_frames(missing_list)
if not item[u'service_item'].validate():
if item[u'service_item'].missing_frames():
self.serviceItems.remove(item)
else:
service_item = item[u'service_item'].get_service_repr(self._saveLite)
@ -620,7 +621,7 @@ class ServiceManager(QtGui.QWidget):
path = os.path.join(directory, default_filename)
# SaveAs from osz to oszl is not valid as the files will be deleted
# on exit which is not sensible or usable in the long term.
if self._fileName.endswith(u'oszl') or not self._fileName:
if self._fileName.endswith(u'oszl') or self.service_has_all_original_files:
fileName = QtGui.QFileDialog.getSaveFileName(self.mainwindow, UiStrings().SaveService, path,
translate('OpenLP.ServiceManager',
'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
@ -693,7 +694,7 @@ class ServiceManager(QtGui.QWidget):
serviceItem.set_from_service(item)
else:
serviceItem.set_from_service(item, self.servicePath)
self.validateItem(serviceItem)
serviceItem.validate_item(self.suffixes)
self.load_item_uuid = 0
if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate):
Receiver.send_message(u'%s_service_load' % serviceItem.name.lower(), serviceItem)
@ -1032,9 +1033,12 @@ class ServiceManager(QtGui.QWidget):
"""
# Correct order of items in array
count = 1
self.service_has_all_original_files = True
for item in self.serviceItems:
item[u'order'] = count
count += 1
if not item[u'service_item'].has_original_files:
self.service_has_all_original_files = False
# Repaint the screen
self.serviceManagerList.clear()
for itemcount, item in enumerate(self.serviceItems):
@ -1093,18 +1097,6 @@ class ServiceManager(QtGui.QWidget):
self.serviceManagerList.setCurrentItem(treewidgetitem)
treewidgetitem.setExpanded(item[u'expanded'])
def validateItem(self, serviceItem):
"""
Validates the service item and if the suffix matches an accepted
one it allows the item to be displayed.
"""
#@todo check file items exist
if serviceItem.is_command():
type = serviceItem._raw_frames[0][u'title'].split(u'.')[-1]
if type.lower() not in self.suffixes:
serviceItem.is_valid = False
#@todo check file items exist
def cleanUp(self):
"""
Empties the servicePath of temporary files on system exit.
@ -1144,7 +1136,7 @@ class ServiceManager(QtGui.QWidget):
Receiver.send_message(u'cursor_busy')
log.debug(u'regenerateServiceItems')
# force reset of renderer as theme data has changed
self.mainwindow.renderer.themedata = None
self.service_has_all_original_files = True
if self.serviceItems:
for item in self.serviceItems:
item[u'selected'] = False

View File

@ -31,7 +31,7 @@ The :mod:`settingsform` provides a user interface for the OpenLP settings
"""
import logging
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from openlp.core.lib import Receiver, build_icon, PluginStatus
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
@ -141,4 +141,4 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
"""
if self.resetSuffixes:
self.mainWindow.serviceManagerContents.resetSupportedSuffixes()
self.resetSuffixes = False
self.resetSuffixes = False

View File

@ -933,8 +933,7 @@ class SlideController(DisplayController):
else:
if not self.serviceItem.is_command():
Receiver.send_message(u'live_display_show')
Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive])
Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive])
else:
if hide_mode:
Receiver.send_message(u'live_display_hide', hide_mode)
@ -949,13 +948,11 @@ class SlideController(DisplayController):
if self.serviceItem is not None:
if hide:
Receiver.send_message(u'live_display_hide', HideMode.Screen)
Receiver.send_message(u'%s_hide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive])
Receiver.send_message(u'%s_hide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive])
else:
if not self.serviceItem.is_command():
Receiver.send_message(u'live_display_show')
Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive])
Receiver.send_message(u'%s_unblank' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive])
else:
if hide:
Receiver.send_message(u'live_display_hide', HideMode.Screen)
@ -995,6 +992,7 @@ class SlideController(DisplayController):
self.selectedRow = row
self.__checkUpdateSelectedSlide(row)
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix, row)
self.display.setFocus()
def onSlideChange(self, row):
"""

View File

@ -29,7 +29,7 @@
"""
The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
"""
from datetime import datetime
from datetime import datetime, timedelta
from distutils.version import LooseVersion
import logging
import locale
@ -277,20 +277,31 @@ def check_latest_version(current_version):
``current_version``
The current version of OpenLP.
**Rules around versions and version files:**
* If a version number has a build (i.e. -bzr1234), then it is a nightly.
* If a version number's minor version is an odd number, it is a development release.
* If a version number's minor version is an even number, it is a stable release.
"""
version_string = current_version[u'full']
# set to prod in the distribution config file.
settings = Settings()
settings.beginGroup(u'general')
last_test = settings.value(u'last version test', datetime.now().date())
# This defaults to yesterday in order to force the update check to run when you've never run it before.
last_test = settings.value(u'last version test', datetime.now().date() - timedelta(days=1))
this_test = datetime.now().date()
settings.setValue(u'last version test', this_test)
settings.endGroup()
if last_test != this_test:
if current_version[u'build']:
req = urllib2.Request(u'http://www.openlp.org/files/dev_version.txt')
req = urllib2.Request(u'http://www.openlp.org/files/nightly_version.txt')
else:
req = urllib2.Request(u'http://www.openlp.org/files/version.txt')
version_parts = current_version[u'version'].split(u'.')
if int(version_parts[1]) % 2 != 0:
req = urllib2.Request(u'http://www.openlp.org/files/dev_version.txt')
else:
req = urllib2.Request(u'http://www.openlp.org/files/version.txt')
req.add_header(u'User-Agent', u'OpenLP/%s' % current_version[u'full'])
remote_version = None
try:

View File

@ -26,5 +26,31 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Forms in OpenLP are made up of two classes. One class holds all the graphical
elements, like buttons and lists, and the other class holds all the functional
code, like slots and loading and saving.
The first class, commonly known as the **Dialog** class, is typically named
``Ui_<name>Dialog``. It is a slightly modified version of the class that the
``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
converting most strings from "" to u'' and using OpenLP's ``translate()``
function for translating strings.
The second class, commonly known as the **Form** class, is typically named
``<name>Form``. This class is the one which is instantiated and used. It uses
dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class
mentioned above, like so::
class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
This allows OpenLP to use ``self.object`` for all the GUI elements while keeping
them separate from the functionality, so that it is easier to recreate the GUI
from the .ui files later if necessary.
"""
from alertform import AlertForm

View File

@ -26,6 +26,10 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.plugins.alerts.lib.alertsmanager` module contains the part of
the plugin which manages storing and displaying of alerts.
"""
import logging

View File

@ -29,7 +29,7 @@
import logging
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from openlp.core.lib import Plugin, StringContent, build_icon, translate, Settings
from openlp.core.lib.ui import create_action, UiStrings

View File

@ -34,7 +34,7 @@ import logging
import re
from openlp.core.lib import translate, Settings
from openlp.plugins.bibles.lib.db import BiblesResourcesDB
log = logging.getLogger(__name__)

View File

@ -30,8 +30,6 @@
import logging
import os
from PyQt4 import QtCore
from openlp.core.lib import Receiver, SettingsManager, translate, Settings
from openlp.core.utils import AppLocation, delete_file
from openlp.plugins.bibles.lib import parse_reference, get_reference_separator, LanguageSelection

View File

@ -26,6 +26,10 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.plugins.custom.customplugin` module contains the Plugin class
for the Custom Slides plugin.
"""
import logging

View File

@ -26,6 +26,10 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.plugins.custom.lib.customtab` module contains the settings tab
for the Custom Slides plugin, which is inserted into the configuration dialog.
"""
from PyQt4 import QtCore, QtGui
@ -66,6 +70,9 @@ class CustomTab(SettingsTab):
'Import missing custom slides from service files'))
def onDisplayFooterCheckBoxChanged(self, check_state):
"""
Toggle the setting for displaying the footer.
"""
self.displayFooter = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:

View File

@ -50,6 +50,7 @@ from lxml import etree, objectify
log = logging.getLogger(__name__)
#TODO: These classes need to be refactored into a single class.
class CustomXMLBuilder(object):
"""
This class builds the XML used to describe songs.
@ -84,11 +85,11 @@ class CustomXMLBuilder(object):
self.lyrics.setAttribute(u'language', u'en')
self.song.appendChild(self.lyrics)
def add_verse_to_lyrics(self, type, number, content):
def add_verse_to_lyrics(self, verse_type, number, content):
"""
Add a verse to the ``<lyrics>`` tag.
``type``
``verse_type``
A string denoting the type of verse. Possible values are "Chorus",
"Verse", "Bridge", and "Custom".
@ -99,7 +100,7 @@ class CustomXMLBuilder(object):
The actual text of the verse to be stored.
"""
verse = self.custom_xml.createElement(u'verse')
verse.setAttribute(u'type', type)
verse.setAttribute(u'type', verse_type)
verse.setAttribute(u'label', number)
self.lyrics.appendChild(verse)
# add data as a CDATA section to protect the XML from special chars

View File

@ -33,11 +33,11 @@ import os
from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, SettingsManager, translate, \
check_item_selected, Receiver, MediaType, ServiceItem, build_html, ServiceItemContext, Settings
check_item_selected, Receiver, MediaType, ServiceItem, ServiceItemContext, Settings, check_directory_exists
from openlp.core.lib.ui import UiStrings, critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.ui import DisplayController, Display, DisplayControllerType
from openlp.core.ui.media import get_media_players, set_media_players
from openlp.core.utils import locale_compare
from openlp.core.utils import AppLocation, locale_compare
log = logging.getLogger(__name__)
@ -130,8 +130,7 @@ class MediaMediaItem(MediaManagerItem):
"""
Called to reset the Live background with the media selected,
"""
self.plugin.liveController.mediaController.media_reset(
self.plugin.liveController)
self.plugin.liveController.mediaController.media_reset(self.plugin.liveController)
self.resetAction.setVisible(False)
def videobackgroundReplaced(self):
@ -145,8 +144,7 @@ class MediaMediaItem(MediaManagerItem):
Called to replace Live background with the media selected.
"""
if check_item_selected(self.listView,
translate('MediaPlugin.MediaItem',
'You must select a media file to replace the background with.')):
translate('MediaPlugin.MediaItem', 'You must select a media file to replace the background with.')):
item = self.listView.currentItem()
filename = item.data(QtCore.Qt.UserRole)
if os.path.exists(filename):
@ -166,8 +164,8 @@ class MediaMediaItem(MediaManagerItem):
translate('MediaPlugin.MediaItem',
'There was a problem replacing your background, the media file "%s" no longer exists.') % filename)
def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False, context=ServiceItemContext.Live):
def generateSlideData(self, service_item, item=None, xmlVersion=False, remote=False,
context=ServiceItemContext.Live):
if item is None:
item = self.listView.currentItem()
if item is None:
@ -201,6 +199,8 @@ class MediaMediaItem(MediaManagerItem):
def initialise(self):
self.listView.clear()
self.listView.setIconSize(QtCore.QSize(88, 50))
self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settingsSection), u'thumbnails')
check_directory_exists(self.servicePath)
self.loadList(SettingsManager.load_list(self.settingsSection, u'media'))
self.populateDisplayTypes()
@ -247,14 +247,13 @@ class MediaMediaItem(MediaManagerItem):
"""
Remove a media item from the list.
"""
if check_item_selected(self.listView, translate('MediaPlugin.MediaItem',
'You must select a media file to delete.')):
if check_item_selected(self.listView,
translate('MediaPlugin.MediaItem', 'You must select a media file to delete.')):
row_list = [item.row() for item in self.listView.selectedIndexes()]
row_list.sort(reverse=True)
for row in row_list:
self.listView.takeItem(row)
SettingsManager.set_list(self.settingsSection,
u'media', self.getFileList())
SettingsManager.set_list(self.settingsSection, u'media', self.getFileList())
def loadList(self, media):
# Sort the media by its filename considering language specific

View File

@ -28,7 +28,6 @@
###############################################################################
import logging
import os
from PyQt4 import QtCore

View File

@ -77,7 +77,7 @@ class PresentationPlugin(Plugin):
if self.controllers[controller].enabled():
try:
self.controllers[controller].start_process()
except:
except Exception:
log.warn(u'Failed to start controller process')
self.controllers[controller].available = False
self.mediaItem.buildFileMaskString()

View File

@ -26,7 +26,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Forms in OpenLP are made up of two classes. One class holds all the graphical
elements, like buttons and lists, and the other class holds all the functional

View File

@ -26,6 +26,10 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.plugins.songs.forms.editsongform` module contains the form
used to edit songs.
"""
import logging
import re
@ -42,7 +46,7 @@ from openlp.plugins.songs.forms import EditVerseForm, MediaFilesForm
from openlp.plugins.songs.lib import SongXML, VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
from editsongdialog import Ui_EditSongDialog
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
log = logging.getLogger(__name__)
@ -56,7 +60,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
"""
Constructor
"""
QtGui.QDialog.__init__(self, parent)
super(EditSongForm, self).__init__(parent)
self.mediaitem = mediaitem
self.song = None
# can this be automated?
@ -113,12 +117,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.whitespace = re.compile(r'\W+', re.UNICODE)
def initialise(self):
"""
Set up the form for when it is displayed.
"""
self.verseEditButton.setEnabled(False)
self.verseDeleteButton.setEnabled(False)
self.authorRemoveButton.setEnabled(False)
self.topicRemoveButton.setEnabled(False)
def loadAuthors(self):
"""
Load the authors from the database into the combobox.
"""
authors = self.manager.get_all_objects(Author,
order_by_ref=Author.display_name)
self.authorsComboBox.clear()
@ -132,14 +142,23 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
set_case_insensitive_completer(self.authors, self.authorsComboBox)
def loadTopics(self):
"""
Load the topics into the combobox.
"""
self.topics = []
self.__loadObjects(Topic, self.topicsComboBox, self.topics)
def loadBooks(self):
"""
Load the song books into the combobox
"""
self.books = []
self.__loadObjects(Book, self.songBookComboBox, self.books)
def __loadObjects(self, cls, combo, cache):
"""
Generically load a set of objects into a cache and a combobox.
"""
objects = self.manager.get_all_objects(cls, order_by_ref=cls.name)
combo.clear()
combo.addItem(u'')
@ -151,6 +170,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
set_case_insensitive_completer(cache, combo)
def loadThemes(self, theme_list):
"""
Load the themes into a combobox.
"""
self.themeComboBox.clear()
self.themeComboBox.addItem(u'')
self.themes = theme_list
@ -158,6 +180,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
set_case_insensitive_completer(self.themes, self.themeComboBox)
def loadMediaFiles(self):
"""
Load the media files into a combobox.
"""
self.audioAddFromMediaButton.setVisible(False)
for plugin in self.parent().pluginManager.plugins:
if plugin.name == u'media' and plugin.status == PluginStatus.Active:
@ -166,6 +191,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
break
def newSong(self):
"""
Blank the edit form out in preparation for a new song.
"""
log.debug(u'New Song')
self.song = None
self.initialise()
@ -313,6 +341,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.repaint()
def onAuthorAddButtonClicked(self):
"""
Add the author to the list of authors associated with this song when the button is clicked.
"""
item = int(self.authorsComboBox.currentIndex())
text = self.authorsComboBox.currentText().strip(u' \r\n\t')
# This if statement is for OS X, which doesn't seem to work well with
@ -361,10 +392,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.authorsListView.addItem(author_item)
def onAuthorsListViewClicked(self):
"""
Run a set of actions when an author in the list is selected (mainly enable the delete button).
"""
if self.authorsListView.count() > 1:
self.authorRemoveButton.setEnabled(True)
def onAuthorRemoveButtonClicked(self):
"""
Remove the author from the list when the delete button is clicked.
"""
self.authorRemoveButton.setEnabled(False)
item = self.authorsListView.currentItem()
row = self.authorsListView.row(item)

View File

@ -28,7 +28,7 @@
###############################################################################
import re
from PyQt4 import QtGui, QtCore
from PyQt4 import QtGui
from openlp.core.lib import translate
from openlp.core.utils import CONTROL_CHARS, locale_direct_compare

View File

@ -36,7 +36,6 @@ import re
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func
from PyQt4 import QtCore
from openlp.core.lib.db import BaseModel, init_db

View File

@ -30,8 +30,6 @@
The :mod:`dreambeamimport` module provides the functionality for importing
DreamBeam songs into the OpenLP database.
"""
import os
import sys
import logging
from lxml import etree, objectify
@ -46,11 +44,11 @@ class DreamBeamImport(SongImport):
"""
The :class:`DreamBeamImport` class provides the ability to import song files from
DreamBeam.
An example of DreamBeam xml mark-up::
<?xml version="1.0"?>
<DreamSong xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<DreamSong xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<WordWrap>false</WordWrap>
<Version>0.80</Version>
@ -84,7 +82,7 @@ class DreamBeamImport(SongImport):
* \*.xml
"""
def doImport(self):
"""
Receive a single file or a list of files to import.

View File

@ -30,8 +30,6 @@
The :mod:`mediashoutimport` module provides the functionality for importing
a MediaShout database into the OpenLP database.
"""
import re
import os
import pyodbc
from openlp.core.lib import translate

View File

@ -31,9 +31,7 @@ The :mod:`songproimport` module provides the functionality for importing SongPro
songs into the OpenLP database.
"""
import re
import os
from openlp.core.lib import translate
from openlp.plugins.songs.lib import strip_rtf
from openlp.plugins.songs.lib.songimport import SongImport

View File

@ -107,13 +107,13 @@ class WowImport(SongImport):
"""
if isinstance(self.importSource, list):
self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.importSource:
for source in self.importSource:
if self.stopImportFlag:
return
self.setDefaults()
song_data = open(file, 'rb')
song_data = open(source, 'rb')
if song_data.read(19) != u'WoW File\nSong Words':
self.logError(file, unicode(translate('SongsPlugin.WordsofWorshipSongImport',
self.logError(source, unicode(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "Wow File\\nSong Words" header.'))))
continue
# Seek to byte which stores number of blocks in the song
@ -121,7 +121,7 @@ class WowImport(SongImport):
no_of_blocks = ord(song_data.read(1))
song_data.seek(66)
if song_data.read(16) != u'CSongDoc::CBlock':
self.logError(file, unicode(translate('SongsPlugin.WordsofWorshipSongImport',
self.logError(source, unicode(translate('SongsPlugin.WordsofWorshipSongImport',
('Invalid Words of Worship song file. Missing "CSongDoc::CBlock" string.'))))
continue
# Seek to the beginning of the first block
@ -150,9 +150,9 @@ class WowImport(SongImport):
copyright_length = ord(song_data.read(1))
if copyright_length:
self.addCopyright(unicode(song_data.read(copyright_length), u'cp1252'))
file_name = os.path.split(file)[1]
file_name = os.path.split(source)[1]
# Get the song title
self.title = file_name.rpartition(u'.')[0]
song_data.close()
if not self.finish():
self.logError(file)
self.logError(source)

View File

@ -26,6 +26,10 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.plugins.songs.songsplugin` module contains the Plugin class
for the Songs plugin.
"""
import logging
import os

View File

@ -38,6 +38,7 @@ modules, simply run this script::
"""
import os
import sys
from distutils.version import LooseVersion
is_win = sys.platform.startswith('win')
@ -89,15 +90,13 @@ OPTIONAL_MODULES = [
w = sys.stdout.write
def check_vers(version, required, text):
if type(version) is str:
version = version.split('.')
version = map(int, version)
if type(required) is str:
required = required.split('.')
required = map(int, required)
w(' %s >= %s ... ' % (text, '.'.join(map(str, required))))
if version >= required:
w('.'.join(map(str, version)) + os.linesep)
if type(version) is not str:
version = '.'.join(map(str, version))
if type(required) is not str:
required = '.'.join(map(str, required))
w(' %s >= %s ... ' % (text, required))
if LooseVersion(version) >= LooseVersion(required):
w(version + os.linesep)
return True
else:
w('FAIL' + os.linesep)

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

View File

@ -3,7 +3,7 @@ Package to test the openlp.core.lib package.
"""
from unittest import TestCase
from mock import MagicMock, patch, call
from mock import MagicMock, patch
from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string, build_icon

View File

@ -0,0 +1,163 @@
"""
Package to test the openlp.core.lib package.
"""
import os
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib import ServiceItem
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
'The Lord said to {g}Noah{/g}:\n'\
'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n'\
'Get those children out of the muddy, muddy \n'\
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'resources'))
class TestServiceItem(TestCase):
def serviceitem_basic_test(self):
"""
Test the Service Item basic test
"""
# GIVEN: A new service item
# WHEN:A service item is created (without a plugin)
service_item = ServiceItem(None)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert service_item.missing_frames() is True, u'There should not be any frames in the service item'
def serviceitem_add_text_test(self):
"""
Test the Service Item add text test
"""
# GIVEN: A new service item
service_item = ServiceItem(None)
# WHEN: adding text to a service item
service_item.add_from_text(VERSE)
service_item.raw_footer = FOOTER
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert service_item.missing_frames() is False, u'check frames loaded '
# GIVEN: A service item with text
mocked_renderer = MagicMock()
mocked_renderer.format_slide.return_value = [VERSE]
service_item.renderer = mocked_renderer
# WHEN: Render called
assert len(service_item._display_frames) == 0, u'A blank Service Item with no display frames'
service_item.render(True)
# THEN: We should have a page of output.
assert len(service_item._display_frames) == 1, u'A valid rendered Service Item has 1 display frame'
assert service_item.get_rendered_frame(0) == VERSE.split(u'\n')[0], u'A output has rendered correctly.'
def serviceitem_add_image_test(self):
"""
Test the Service Item add image test
"""
# GIVEN: A new service item and a mocked renderer
service_item = ServiceItem(None)
service_item.name = u'test'
mocked_renderer = MagicMock()
service_item.renderer = mocked_renderer
# WHEN: adding image to a service item
test_image = os.path.join(TESTPATH, u'church.jpg')
service_item.add_from_image(test_image, u'Image Title')
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert len(service_item._display_frames) == 0, u'The service item has no display frames'
# THEN: We should have a page of output.
assert len(service_item._raw_frames) == 1, u'A valid rendered Service Item has display frames'
assert service_item.get_rendered_frame(0) == test_image
# WHEN: adding a second image to a service item
service_item.add_from_image(test_image, u'Image1 Title')
# THEN: We should have an increased page of output.
assert len(service_item._raw_frames) == 2, u'A valid rendered Service Item has display frames'
assert service_item.get_rendered_frame(0) == test_image
assert service_item.get_rendered_frame(0) == service_item.get_rendered_frame(1)
# WHEN requesting a saved service item
service = service_item.get_service_repr(True)
# THEN: We should have two parts of the service.
assert len(service) == 2, u'A saved service has two parts'
assert service[u'header'][u'name'] == u'test' , u'A test plugin was returned'
assert service[u'data'][0][u'title'] == u'Image Title' , u'The first title name matches the request'
assert service[u'data'][0][u'path'] == test_image , u'The first image name matches'
assert service[u'data'][0][u'title'] != service[u'data'][1][u'title'], \
u'The individual titles should not match'
assert service[u'data'][0][u'path'] == service[u'data'][1][u'path'], u'The file paths should match'
# WHEN validating a service item
service_item.validate_item([u'jpg'])
# THEN the service item should be valid
assert service_item.is_valid is True, u'The new service item should be valid'
# WHEN: adding a second image to a service item
service_item.add_from_image(u'resources/church1.jpg', u'Image1 Title')
# WHEN validating a service item
service_item.validate_item([u'jpg'])
# THEN the service item should be valid
assert service_item.is_valid is False, u'The service item is not valid due to validation changes'
def serviceitem_add_command_test(self):
"""
Test the Service Item add command test
"""
# GIVEN: A new service item and a mocked renderer
service_item = ServiceItem(None)
service_item.name = u'test'
mocked_renderer = MagicMock()
service_item.renderer = mocked_renderer
# WHEN: adding image to a service item
test_file = os.path.join(TESTPATH, u'church.jpg')
service_item.add_from_command(TESTPATH, u'church.jpg', test_file)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert len(service_item._display_frames) == 0, u'The service item has no display frames '
# THEN: We should have a page of output.
assert len(service_item._raw_frames) == 1, u'A valid rendered Service Item has one raw frame'
assert service_item.get_rendered_frame(0) == test_file, u'The image matches the input'
# WHEN requesting a saved service item
service = service_item.get_service_repr(True)
# THEN: We should have two parts of the service.
assert len(service) == 2, u'A saved service has two parts'
assert service[u'header'][u'name'] == u'test' , u'A test plugin'
assert service[u'data'][0][u'title'] == u'church.jpg' , u'The first title name '
assert service[u'data'][0][u'path'] == TESTPATH , u'The first image name'
assert service[u'data'][0][u'image'] == test_file , u'The first image name'
# WHEN validating a service item
service_item.validate_item([u'jpg'])
# THEN the service item should be valid
assert service_item.is_valid is True, u'The service item is valid'
# WHEN validating a service item with a different suffix
service_item.validate_item([u'png'])
# THEN the service item should not be valid
assert service_item.is_valid is False, u'The service item is not valid'

View File

@ -0,0 +1,48 @@
"""
Package to test the openlp.core.ui package.
"""
import sys
from unittest import TestCase
from mock import MagicMock
from openlp.core.ui import starttimeform
from PyQt4 import QtCore, QtGui, QtTest
class TestStartTimeDialog(TestCase):
def setUp(self):
"""
Create the UI
"""
self.app = QtGui.QApplication(sys.argv)
self.window = QtGui.QMainWindow()
self.form = starttimeform.StartTimeForm(self.window)
def ui_defaults_test(self):
"""
Test StartTimeDialog defaults
"""
self.assertEqual(self.form.hourSpinBox.minimum(), 0)
self.assertEqual(self.form.hourSpinBox.maximum(), 4)
self.assertEqual(self.form.minuteSpinBox.minimum(), 0)
self.assertEqual(self.form.minuteSpinBox.maximum(), 59)
self.assertEqual(self.form.secondSpinBox.minimum(), 0)
self.assertEqual(self.form.secondSpinBox.maximum(), 59)
self.assertEqual(self.form.hourFinishSpinBox.minimum(), 0)
self.assertEqual(self.form.hourFinishSpinBox.maximum(), 4)
self.assertEqual(self.form.minuteFinishSpinBox.minimum(), 0)
self.assertEqual(self.form.minuteFinishSpinBox.maximum(), 59)
self.assertEqual(self.form.secondFinishSpinBox.minimum(), 0)
self.assertEqual(self.form.secondFinishSpinBox.maximum(), 59)
def time_display_test(self):
"""
Test StartTimeDialog display initialisation
"""
#GIVEN: A service item with with time
mocked_serviceitem = MagicMock()
mocked_serviceitem.start_time = 61
mocked_serviceitem.end_time = 3701
self.form.item = mocked_serviceitem
#self.form.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

View File

@ -34,4 +34,4 @@ from openlp.core.ui.mainwindow import MainWindow
def test_start_app(openlpapp):
assert type(openlpapp) == OpenLP
assert type(openlpapp.mainWindow) == MainWindow
assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'
assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.1'