This commit is contained in:
Tim Bentley 2013-01-21 19:45:23 +00:00
commit dbf9860c8e
42 changed files with 756 additions and 112 deletions

View File

@ -23,3 +23,5 @@ resources/windows/warnOpenLP.txt
openlp.cfg
.idea
openlp.pro
.kdev4
tests.kdev4

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__)
@ -181,7 +183,11 @@ class ServiceItem(object):
self.background_audio = []
self.theme_overwritten = False
self.temporary_edit = False
self.auto_play_slides_once = False
self.auto_play_slides_loop = False
self.timed_slide_interval = 0
self.will_auto_start = False
self.has_original_files = True
self._new_item()
def _new_item(self):
@ -190,6 +196,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):
"""
@ -340,6 +347,9 @@ class ServiceItem(object):
u'search': self.search_string,
u'data': self.data_string,
u'xml_version': self.xml_version,
u'auto_play_slides_once': self.auto_play_slides_once,
u'auto_play_slides_loop': self.auto_play_slides_loop,
u'timed_slide_interval': self.timed_slide_interval,
u'start_time': self.start_time,
u'end_time': self.end_time,
u'media_length': self.media_length,
@ -394,7 +404,11 @@ class ServiceItem(object):
self.start_time = header.get(u'start_time', 0)
self.end_time = header.get(u'end_time', 0)
self.media_length = header.get(u'media_length', 0)
self.auto_play_slides_once = header.get(u'auto_play_slides_once', False)
self.auto_play_slides_loop = header.get(u'auto_play_slides_loop', False)
self.timed_slide_interval = header.get(u'timed_slide_interval', 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,20 +419,23 @@ 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'])
self._new_item()
def get_display_title(self):
@ -605,8 +622,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)
@ -256,6 +257,20 @@ class ServiceManager(QtGui.QWidget):
text=translate('OpenLP.ServiceManager', 'Create New &Custom Slide'),
icon=u':/general/general_edit.png', triggers=self.create_custom)
self.menu.addSeparator()
# Add AutoPlay menu actions
self.autoPlaySlidesGroup = QtGui.QMenu(translate('OpenLP.ServiceManager', '&Auto play slides'))
self.menu.addMenu(self.autoPlaySlidesGroup)
self.autoPlaySlidesLoop = create_widget_action(self.autoPlaySlidesGroup,
text=translate('OpenLP.ServiceManager', 'Auto play slides &Loop'),
checked=False, triggers=self.toggleAutoPlaySlidesLoop)
self.autoPlaySlidesOnce = create_widget_action(self.autoPlaySlidesGroup,
text=translate('OpenLP.ServiceManager', 'Auto play slides &Once'),
checked=False, triggers=self.toggleAutoPlaySlidesOnce)
self.autoPlaySlidesGroup.addSeparator()
self.timedSlideInterval = create_widget_action(self.autoPlaySlidesGroup,
text=translate('OpenLP.ServiceManager', '&Delay between slides'),
checked=False, triggers=self.onTimedSlideInterval)
self.menu.addSeparator()
self.previewAction = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'),
icon=u':/general/general_preview.png', triggers=self.makePreview)
# Add already existing make live action to the menu.
@ -458,7 +473,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 +635,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 +708,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)
@ -765,6 +780,22 @@ class ServiceManager(QtGui.QWidget):
self.maintainAction.setVisible(True)
if item.parent() is None:
self.notesAction.setVisible(True)
if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanLoop) and \
len(serviceItem[u'service_item'].get_frames()) > 1:
self.autoPlaySlidesGroup.menuAction().setVisible(True)
self.autoPlaySlidesOnce.setChecked(serviceItem[u'service_item'].auto_play_slides_once)
self.autoPlaySlidesLoop.setChecked(serviceItem[u'service_item'].auto_play_slides_loop)
self.timedSlideInterval.setChecked(serviceItem[u'service_item'].timed_slide_interval > 0)
if serviceItem[u'service_item'].timed_slide_interval > 0:
delay_suffix = u' '
delay_suffix += unicode(serviceItem[u'service_item'].timed_slide_interval)
delay_suffix += u' s'
else:
delay_suffix = u' ...'
self.timedSlideInterval.setText(translate('OpenLP.ServiceManager', '&Delay between slides') + delay_suffix)
# TODO for future: make group explains itself more visually
else:
self.autoPlaySlidesGroup.menuAction().setVisible(False)
if serviceItem[u'service_item'].is_capable(ItemCapabilities.HasVariableStartTime):
self.timeAction.setVisible(True)
if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanAutoStartForLive):
@ -810,6 +841,59 @@ class ServiceManager(QtGui.QWidget):
if self.startTimeForm.exec_():
self.repaintServiceList(item, -1)
def toggleAutoPlaySlidesOnce(self):
"""
Toggle Auto play slide once.
Inverts auto play once option for the item
"""
item = self.findServiceItem()[0]
service_item = self.serviceItems[item][u'service_item']
service_item.auto_play_slides_once = not service_item.auto_play_slides_once
if service_item.auto_play_slides_once:
service_item.auto_play_slides_loop = False
self.autoPlaySlidesLoop.setChecked(False)
if service_item.auto_play_slides_once and service_item.timed_slide_interval == 0:
service_item.timed_slide_interval = Settings().value(u'loop delay', 5)
self.setModified()
def toggleAutoPlaySlidesLoop(self):
"""
Toggle Auto play slide loop.
"""
item = self.findServiceItem()[0]
service_item = self.serviceItems[item][u'service_item']
service_item.auto_play_slides_loop = not service_item.auto_play_slides_loop
if service_item.auto_play_slides_loop:
service_item.auto_play_slides_once = False
self.autoPlaySlidesOnce.setChecked(False)
if service_item.auto_play_slides_loop and service_item.timed_slide_interval == 0:
service_item.timed_slide_interval = Settings().value(u'loop delay', 5)
self.setModified()
def onTimedSlideInterval(self):
"""
on set times slide interval.
Shows input dialog for enter interval in seconds for delay
"""
item = self.findServiceItem()[0]
service_item = self.serviceItems[item][u'service_item']
if service_item.timed_slide_interval == 0:
timed_slide_interval = Settings().value(u'loop delay', 5)
else:
timed_slide_interval = service_item.timed_slide_interval
timed_slide_interval, ok = QtGui.QInputDialog.getInteger(self, translate('OpenLP.ServiceManager',
'Input delay'), translate('OpenLP.ServiceManager', 'Delay between slides in seconds.'),
timed_slide_interval, 0, 180, 1)
if ok:
service_item.timed_slide_interval = timed_slide_interval
if service_item.timed_slide_interval <> 0 and not service_item.auto_play_slides_loop\
and not service_item.auto_play_slides_once:
service_item.auto_play_slides_loop = True
elif service_item.timed_slide_interval == 0:
service_item.auto_play_slides_loop = False
service_item.auto_play_slides_once = False
self.setModified()
def onAutoStart(self):
"""
Toggles to Auto Start Setting.
@ -1032,9 +1116,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 +1180,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 +1219,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
@ -1299,6 +1374,8 @@ class ServiceManager(QtGui.QWidget):
if self.serviceItems and item < len(self.serviceItems) and \
self.serviceItems[item][u'service_item'].is_capable(ItemCapabilities.CanPreview):
self.mainwindow.previewController.addServiceManagerItem(self.serviceItems[item][u'service_item'], 0)
next_item = self.serviceManagerList.topLevelItem(item)
self.serviceManagerList.setCurrentItem(next_item)
self.mainwindow.liveController.previewListWidget.setFocus()
else:
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),

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

@ -692,6 +692,14 @@ class SlideController(DisplayController):
self.slideSelected()
else:
self._processItem(item, slidenum)
if self.isLive and item.auto_play_slides_loop and item.timed_slide_interval > 0:
self.playSlidesLoop.setChecked(item.auto_play_slides_loop)
self.delaySpinBox.setValue(int(item.timed_slide_interval))
self.onPlaySlidesLoop()
elif self.isLive and item.auto_play_slides_once and item.timed_slide_interval > 0:
self.playSlidesOnce.setChecked(item.auto_play_slides_once)
self.delaySpinBox.setValue(int(item.timed_slide_interval))
self.onPlaySlidesOnce()
def _processItem(self, serviceItem, slideno):
"""
@ -881,6 +889,7 @@ class SlideController(DisplayController):
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
self.blankPlugin()
self.updatePreview()
self.onToggleLoop()
def onThemeDisplay(self, checked=None):
"""
@ -899,6 +908,7 @@ class SlideController(DisplayController):
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
self.blankPlugin()
self.updatePreview()
self.onToggleLoop()
def onHideDisplay(self, checked=None):
"""
@ -917,6 +927,7 @@ class SlideController(DisplayController):
Settings().remove(self.parent().generalSettingsSection + u'/screen blank')
self.hidePlugin(checked)
self.updatePreview()
self.onToggleLoop()
def blankPlugin(self):
"""
@ -933,8 +944,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 +959,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)
@ -1095,7 +1103,8 @@ class SlideController(DisplayController):
"""
Toggles the loop state.
"""
if self.playSlidesLoop.isChecked() or self.playSlidesOnce.isChecked():
hide_mode = self.hideMode()
if hide_mode is None and (self.playSlidesLoop.isChecked() or self.playSlidesOnce.isChecked()):
self.onStartLoop()
else:
self.onStopLoop()
@ -1129,11 +1138,11 @@ class SlideController(DisplayController):
self.playSlidesLoop.setText(UiStrings().StopPlaySlidesInLoop)
self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd)
self.playSlidesMenu.setDefaultAction(self.playSlidesLoop)
self.playSlidesOnce.setChecked(False)
else:
self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop)
self.playSlidesMenu.setDefaultAction(self.playSlidesLoop)
self.playSlidesOnce.setChecked(False)
self.onToggleLoop()
def onPlaySlidesOnce(self, checked=None):
@ -1150,11 +1159,11 @@ class SlideController(DisplayController):
self.playSlidesOnce.setText(UiStrings().StopPlaySlidesToEnd)
self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop)
self.playSlidesMenu.setDefaultAction(self.playSlidesOnce)
self.playSlidesLoop.setChecked(False)
else:
self.playSlidesOnce.setIcon(build_icon(u':/media/media_time'))
self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd)
self.playSlidesMenu.setDefaultAction(self.playSlidesOnce)
self.playSlidesLoop.setChecked(False)
self.onToggleLoop()
def setAudioItemsVisibility(self, visible):

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

@ -2,10 +2,12 @@
Package to test the openlp.core.lib package.
"""
from unittest import TestCase
from datetime import datetime, timedelta
from mock import MagicMock, patch
from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string
from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string, build_icon, \
image_to_byte, check_item_selected, validate_thumb
class TestLib(TestCase):
@ -197,3 +199,163 @@ class TestLib(TestCase):
"""
assert True, u'Impossible to test due to conflicts when mocking out the "open" function'
def build_icon_with_qicon_test(self):
"""
Test the build_icon() function with a QIcon instance
"""
with patch(u'openlp.core.lib.QtGui') as MockedQtGui:
# GIVEN: A mocked QIcon
MockedQtGui.QIcon = MagicMock
mocked_icon = MockedQtGui.QIcon()
# WHEN: We pass a QIcon instance in
result = build_icon(mocked_icon)
# THEN: The result should be our mocked QIcon
assert result is mocked_icon, u'The result should be the mocked QIcon'
def build_icon_with_resource_test(self):
"""
Test the build_icon() function with a resource URI
"""
with patch(u'openlp.core.lib.QtGui') as MockedQtGui, \
patch(u'openlp.core.lib.QtGui.QPixmap') as MockedQPixmap:
# GIVEN: A mocked QIcon and a mocked QPixmap
MockedQtGui.QIcon = MagicMock
MockedQtGui.QIcon.Normal = 1
MockedQtGui.QIcon.Off = 2
MockedQPixmap.return_value = u'mocked_pixmap'
resource_uri = u':/resource/uri'
# WHEN: We pass a QIcon instance in
result = build_icon(resource_uri)
# THEN: The result should be our mocked QIcon
MockedQPixmap.assert_called_with(resource_uri)
# There really should be more assert statements here but due to type checking and things they all break. The
# best we can do is to assert that we get back a MagicMock object.
assert isinstance(result, MagicMock), u'The result should be a MagicMock, because we mocked it out'
def image_to_byte_test(self):
"""
Test the image_to_byte() function
"""
with patch(u'openlp.core.lib.QtCore') as MockedQtCore:
# GIVEN: A set of mocked-out Qt classes
mocked_byte_array = MagicMock()
MockedQtCore.QByteArray.return_value = mocked_byte_array
mocked_byte_array.toBase64.return_value = u'base64mock'
mocked_buffer = MagicMock()
MockedQtCore.QBuffer.return_value = mocked_buffer
MockedQtCore.QIODevice.WriteOnly = u'writeonly'
mocked_image = MagicMock()
# WHEN: We convert an image to a byte array
result = image_to_byte(mocked_image)
# THEN: We should receive a value of u'base64mock'
MockedQtCore.QByteArray.assert_called_with()
MockedQtCore.QBuffer.assert_called_with(mocked_byte_array)
mocked_buffer.open.assert_called_with(u'writeonly')
mocked_image.save.assert_called_with(mocked_buffer, "PNG")
mocked_byte_array.toBase64.assert_called_with()
assert result == u'base64mock', u'The result should be the return value of the mocked out base64 method'
def check_item_selected_true_test(self):
"""
Test that the check_item_selected() function returns True when there are selected indexes.
"""
# GIVEN: A mocked out QtGui module and a list widget with selected indexes
MockedQtGui = patch(u'openlp.core.lib.QtGui')
mocked_list_widget = MagicMock()
mocked_list_widget.selectedIndexes.return_value = True
message = u'message'
# WHEN: We check if there are selected items
result = check_item_selected(mocked_list_widget, message)
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
assert result, u'The result should be True'
def check_item_selected_false_test(self):
"""
Test that the check_item_selected() function returns False when there are no selected indexes.
"""
# GIVEN: A mocked out QtGui module and a list widget with selected indexes
with patch(u'openlp.core.lib.QtGui') as MockedQtGui, \
patch(u'openlp.core.lib.translate') as mocked_translate:
mocked_translate.return_value = u'mocked translate'
mocked_list_widget = MagicMock()
mocked_list_widget.selectedIndexes.return_value = False
mocked_list_widget.parent.return_value = u'parent'
message = u'message'
# WHEN: We check if there are selected items
result = check_item_selected(mocked_list_widget, message)
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
MockedQtGui.QMessageBox.information.assert_called_with(u'parent', u'mocked translate', 'message')
assert not result, u'The result should be False'
def validate_thumb_file_does_not_exist_test(self):
"""
Test the validate_thumb() function when the thumbnail does not exist
"""
# GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb
with patch(u'openlp.core.lib.os') as mocked_os:
file_path = u'path/to/file'
thumb_path = u'path/to/thumb'
mocked_os.path.exists.return_value = False
# WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be False
mocked_os.path.exists.assert_called_with(thumb_path)
assert result is False, u'The result should be False'
def validate_thumb_file_exists_and_newer_test(self):
"""
Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
"""
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
with patch(u'openlp.core.lib.os') as mocked_os:
file_path = u'path/to/file'
thumb_path = u'path/to/thumb'
file_mocked_stat = MagicMock()
file_mocked_stat.st_mtime = datetime.now()
thumb_mocked_stat = MagicMock()
thumb_mocked_stat.st_mtime = datetime.now() + timedelta(seconds=10)
mocked_os.path.exists.return_value = True
mocked_os.stat.side_effect = [file_mocked_stat, thumb_mocked_stat]
# WHEN: we run the validate_thumb() function
# THEN: we should have called a few functions, and the result should be True
#mocked_os.path.exists.assert_called_with(thumb_path)
def validate_thumb_file_exists_and_older_test(self):
"""
Test the validate_thumb() function when the thumbnail exists but is older than the file
"""
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
with patch(u'openlp.core.lib.os') as mocked_os:
file_path = u'path/to/file'
thumb_path = u'path/to/thumb'
file_mocked_stat = MagicMock()
file_mocked_stat.st_mtime = datetime.now()
thumb_mocked_stat = MagicMock()
thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10)
mocked_os.path.exists.return_value = True
mocked_os.stat.side_effect = [file_mocked_stat, thumb_mocked_stat]
# WHEN: we run the validate_thumb() function
result = validate_thumb(file_path, thumb_path)
# THEN: we should have called a few functions, and the result should be False
mocked_os.path.exists.assert_called_with(thumb_path)
mocked_os.stat.assert_any_call(file_path)
mocked_os.stat.assert_any_call(thumb_path)
assert result is False, u'The result should be False'

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'..', u'..', 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,57 @@
"""
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([])
self.window = QtGui.QMainWindow()
self.form = starttimeform.StartTimeForm(self.window)
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.form
del self.window
del self.app
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_()

BIN
tests/resources/church.jpg Normal file

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'