Refactor slide controller so slide list is a separate class

bzr-revno: 2250
This commit is contained in:
Patrick Zimmermann 2013-06-15 20:28:08 +01:00 committed by Tim Bentley
commit f3e77b39d7
9 changed files with 367 additions and 133 deletions

View File

@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`listpreviewwidget` is a widget that lists the slides in the slide controller.
It is based on a QTableWidget but represents its contents in list form.
"""
from PyQt4 import QtCore, QtGui
from openlp.core.lib import ImageSource, Registry, ServiceItem
class ListPreviewWidget(QtGui.QTableWidget):
def __init__(self, parent, screen_ratio):
"""
Initializes the widget to default state.
An empty ServiceItem is used per default.
One needs to call replace_service_manager_item() to make this widget display something.
"""
super(QtGui.QTableWidget, self).__init__(parent)
# Set up the widget.
self.setColumnCount(1)
self.horizontalHeader().setVisible(False)
self.setColumnWidth(0, parent.width())
self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setAlternatingRowColors(True)
# Initialize variables.
self.service_item = ServiceItem()
self.screen_ratio = screen_ratio
def resizeEvent(self, QResizeEvent):
"""
Overloaded method from QTableWidget. Will recalculate the layout.
"""
self.__recalculate_layout()
def __recalculate_layout(self):
"""
Recalculates the layout of the table widget. It will set height and width
of the table cells. QTableWidget does not adapt the cells to the widget size on its own.
"""
self.setColumnWidth(0, self.viewport().width())
if self.service_item:
# Sort out songs, bibles, etc.
if self.service_item.is_text():
self.resizeRowsToContents()
else:
# Sort out image heights.
for framenumber in range(len(self.service_item.get_frames())):
height = self.viewport().width() / self.screen_ratio
self.setRowHeight(framenumber, height)
def screen_size_changed(self, screen_ratio):
"""
To be called whenever the live screen size changes.
Because this makes a layout recalculation necessary.
"""
self.screen_ratio = screen_ratio
self.__recalculate_layout()
def replace_service_item(self, service_item, width, slideNumber):
"""
Replaces the current preview items with the ones in service_item.
Displays the given slide.
"""
self.service_item = service_item
self.clear()
self.setRowCount(0)
self.setColumnWidth(0, width)
row = 0
text = []
for framenumber, frame in enumerate(self.service_item.get_frames()):
self.setRowCount(self.slide_count() + 1)
item = QtGui.QTableWidgetItem()
slideHeight = 0
if self.service_item.is_text():
if frame[u'verseTag']:
# These tags are already translated.
verse_def = frame[u'verseTag']
verse_def = u'%s%s' % (verse_def[0], verse_def[1:])
two_line_def = u'%s\n%s' % (verse_def[0], verse_def[1:])
row = two_line_def
else:
row += 1
item.setText(frame[u'text'])
else:
label = QtGui.QLabel()
label.setMargin(4)
if self.service_item.is_media():
label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
else:
label.setScaledContents(True)
if self.service_item.is_command():
label.setPixmap(QtGui.QPixmap(frame[u'image']))
else:
image = self.image_manager.get_image(frame[u'path'], ImageSource.ImagePlugin)
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.setCellWidget(framenumber, 0, label)
slideHeight = width / self.screen_ratio
row += 1
text.append(unicode(row))
self.setItem(framenumber, 0, item)
if slideHeight:
self.setRowHeight(framenumber, slideHeight)
self.setVerticalHeaderLabels(text)
if self.service_item.is_text():
self.resizeRowsToContents()
self.setColumnWidth(0, self.viewport().width())
self.setFocus()
self.change_slide(slideNumber)
def change_slide(self, slide):
"""
Switches to the given row.
"""
if slide >= self.slide_count():
slide = self.slide_count() - 1
# Scroll to next item if possible.
if slide + 1 < self.slide_count():
self.scrollToItem(self.item(slide + 1, 0))
self.selectRow(slide)
def current_slide_number(self):
"""
Returns the position of the currently active item. Will return -1 if the widget is empty.
"""
return super(ListPreviewWidget, self).currentRow()
def slide_count(self):
"""
Returns the number of slides this widget holds.
"""
return super(ListPreviewWidget, self).rowCount()
def _get_image_manager(self):
"""
Adds the image manager to the class dynamically.
"""
if not hasattr(self, u'_image_manager'):
self._image_manager = Registry().get(u'image_manager')
return self._image_manager
image_manager = property(_get_image_manager)

View File

@ -1369,7 +1369,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
self.preview_controller.addServiceManagerItem(self.service_items[item][u'service_item'], 0)
next_item = self.service_manager_list.topLevelItem(item)
self.service_manager_list.setCurrentItem(next_item)
self.live_controller.preview_list_widget.setFocus()
self.live_controller.preview_widget.setFocus()
else:
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
translate('OpenLP.ServiceManager',

View File

@ -41,6 +41,7 @@ from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageS
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
from openlp.core.lib.ui import create_action
from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
log = logging.getLogger(__name__)
@ -159,18 +160,8 @@ class SlideController(DisplayController):
self.controller_layout.setSpacing(0)
self.controller_layout.setMargin(0)
# Controller list view
self.preview_list_widget = QtGui.QTableWidget(self.controller)
self.preview_list_widget.setColumnCount(1)
self.preview_list_widget.horizontalHeader().setVisible(False)
self.preview_list_widget.setColumnWidth(0, self.controller.width())
self.preview_list_widget.is_live = self.is_live
self.preview_list_widget.setObjectName(u'preview_list_widget')
self.preview_list_widget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.preview_list_widget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.preview_list_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.preview_list_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.preview_list_widget.setAlternatingRowColors(True)
self.controller_layout.addWidget(self.preview_list_widget)
self.preview_widget = ListPreviewWidget(self, self.ratio)
self.controller_layout.addWidget(self.preview_widget)
# Build the full toolbar
self.toolbar = OpenLPToolbar(self)
size_toolbar_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
@ -352,7 +343,7 @@ class SlideController(DisplayController):
{u'key': u'O', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Other"')}
]
shortcuts.extend([{u'key': unicode(number)} for number in range(10)])
self.preview_list_widget.addActions([create_action(self,
self.controller.addActions([create_action(self,
u'shortcutAction_%s' % s[u'key'], text=s.get(u'text'),
can_shortcuts=True,
context=QtCore.Qt.WidgetWithChildrenShortcut,
@ -360,7 +351,7 @@ class SlideController(DisplayController):
triggers=self._slideShortcutActivated) for s in shortcuts])
self.shortcutTimer.timeout.connect(self._slideShortcutActivated)
# Signals
self.preview_list_widget.clicked.connect(self.onSlideSelected)
self.preview_widget.clicked.connect(self.onSlideSelected)
if self.is_live:
# Need to use event as called across threads and UI is updated
QtCore.QObject.connect(self, QtCore.SIGNAL(u'slidecontroller_toggle_display'), self.toggle_display)
@ -368,13 +359,13 @@ class SlideController(DisplayController):
self.toolbar.set_widget_visible(self.loop_list, False)
self.toolbar.set_widget_visible(self.wide_menu, False)
else:
self.preview_list_widget.doubleClicked.connect(self.onGoLiveClick)
self.preview_widget.doubleClicked.connect(self.onGoLiveClick)
self.toolbar.set_widget_visible([u'editSong'], False)
if self.is_live:
self.setLiveHotkeys(self)
self.__addActionsToWidget(self.preview_list_widget)
self.__addActionsToWidget(self.controller)
else:
self.preview_list_widget.addActions([self.nextItem, self.previous_item])
self.controller.addActions([self.nextItem, self.previous_item])
Registry().register_function(u'slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
Registry().register_function(u'slidecontroller_%s_change' % self.type_prefix, self.on_slide_change)
Registry().register_function(u'slidecontroller_%s_blank' % self.type_prefix, self.on_slide_blank)
@ -433,7 +424,7 @@ class SlideController(DisplayController):
if len(matches) == 1:
self.shortcutTimer.stop()
self.current_shortcut = u''
self.__checkUpdateSelectedSlide(self.slideList[matches[0]])
self.preview_widget.change_slide(self.slideList[matches[0]])
self.slideSelected()
elif sender_name != u'shortcutTimer':
# Start the time as we did not have any match.
@ -443,7 +434,7 @@ class SlideController(DisplayController):
if self.current_shortcut in keys:
# We had more than one match for example "V1" and "V10", but
# "V1" was the slide we wanted to go.
self.__checkUpdateSelectedSlide(self.slideList[self.current_shortcut])
self.preview_widget.change_slide(self.slideList[self.current_shortcut])
self.slideSelected()
# Reset the shortcut.
self.current_shortcut = u''
@ -538,6 +529,7 @@ class SlideController(DisplayController):
self.ratio = 1
self.media_controller.setup_display(self.display, False)
self.preview_size_changed()
self.preview_widget.screen_size_changed(self.ratio)
self.preview_display.setup()
service_item = ServiceItem()
self.preview_display.web_view.setHtml(build_html(service_item, self.preview_display.screen, None, self.is_live,
@ -575,17 +567,6 @@ class SlideController(DisplayController):
self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
self.preview_display.screen = {
u'size': self.preview_display.geometry()}
# Make sure that the frames have the correct size.
self.preview_list_widget.setColumnWidth(0, self.preview_list_widget.viewport().size().width())
if self.service_item:
# Sort out songs, bibles, etc.
if self.service_item.is_text():
self.preview_list_widget.resizeRowsToContents()
else:
# Sort out image heights.
width = self.main_window.controlSplitter.sizes()[self.split]
for framenumber in range(len(self.service_item.get_frames())):
self.preview_list_widget.setRowHeight(framenumber, width / self.ratio)
self.onControllerSizeChanged(self.controller.width())
def onControllerSizeChanged(self, width):
@ -710,7 +691,7 @@ class SlideController(DisplayController):
Replacement item following a remote edit
"""
if item == self.service_item:
self._process_item(item, self.preview_list_widget.currentRow())
self._process_item(item, self.preview_widget.current_slide_number())
def addServiceManagerItem(self, item, slideno):
"""
@ -726,7 +707,7 @@ class SlideController(DisplayController):
slidenum = 0
# If service item is the same as the current one, only change slide
if slideno >= 0 and item == self.service_item:
self.__checkUpdateSelectedSlide(slidenum)
self.preview_widget.change_slide(slidenum)
self.slideSelected()
else:
self._process_item(item, slidenum)
@ -753,10 +734,6 @@ class SlideController(DisplayController):
self._resetBlank()
Registry().execute(u'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
self.slideList = {}
width = self.main_window.controlSplitter.sizes()[self.split]
self.preview_list_widget.clear()
self.preview_list_widget.setRowCount(0)
self.preview_list_widget.setColumnWidth(0, width)
if self.is_live:
self.song_menu.menu().clear()
self.display.audio_player.reset()
@ -781,9 +758,8 @@ class SlideController(DisplayController):
self.setAudioItemsVisibility(True)
row = 0
text = []
width = self.main_window.controlSplitter.sizes()[self.split]
for framenumber, frame in enumerate(self.service_item.get_frames()):
self.preview_list_widget.setRowCount(self.preview_list_widget.rowCount() + 1)
item = QtGui.QTableWidgetItem()
slideHeight = 0
if self.service_item.is_text():
if frame[u'verseTag']:
@ -799,36 +775,15 @@ class SlideController(DisplayController):
else:
row += 1
self.slideList[unicode(row)] = row - 1
item.setText(frame[u'text'])
else:
label = QtGui.QLabel()
label.setMargin(4)
if service_item.is_media():
label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
else:
label.setScaledContents(True)
if self.service_item.is_command():
label.setPixmap(QtGui.QPixmap(frame[u'image']))
else:
# If current slide set background to image
if framenumber == slideno:
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(frame[u'path'],
ImageSource.ImagePlugin)
image = self.image_manager.get_image(frame[u'path'], ImageSource.ImagePlugin)
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.preview_list_widget.setCellWidget(framenumber, 0, label)
slideHeight = width * (1 / self.ratio)
row += 1
self.slideList[unicode(row)] = row - 1
text.append(unicode(row))
self.preview_list_widget.setItem(framenumber, 0, item)
if slideHeight:
self.preview_list_widget.setRowHeight(framenumber, slideHeight)
self.preview_list_widget.setVerticalHeaderLabels(text)
if self.service_item.is_text():
self.preview_list_widget.resizeRowsToContents()
self.preview_list_widget.setColumnWidth(0, self.preview_list_widget.viewport().size().width())
self.__updatePreviewSelection(slideno)
# If current slide set background to image
if not self.service_item.is_command() and framenumber == slideno:
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(frame[u'path'],
ImageSource.ImagePlugin)
self.preview_widget.replace_service_item(self.service_item, width, slideno)
self.enable_tool_bar(service_item)
# Pass to display for viewing.
# Postpone image build, we need to do this later to avoid the theme
@ -838,7 +793,6 @@ class SlideController(DisplayController):
if service_item.is_media():
self.onMediaStart(service_item)
self.slideSelected(True)
self.preview_list_widget.setFocus()
if old_item:
# Close the old item after the new one is opened
# This avoids the service theme/desktop flashing on screen
@ -850,16 +804,6 @@ class SlideController(DisplayController):
self.onMediaClose()
Registry().execute(u'slidecontroller_%s_started' % self.type_prefix, [service_item])
def __updatePreviewSelection(self, slideno):
"""
Utility method to update the selected slide in the list.
"""
if slideno > self.preview_list_widget.rowCount():
self.preview_list_widget.selectRow(
self.preview_list_widget.rowCount() - 1)
else:
self.__checkUpdateSelectedSlide(slideno)
# Screen event methods
def on_slide_selected_index(self, message):
"""
@ -872,7 +816,7 @@ class SlideController(DisplayController):
Registry().execute(u'%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
self.updatePreview()
else:
self.__checkUpdateSelectedSlide(index)
self.preview_widget.change_slide(index)
self.slideSelected()
def mainDisplaySetBackground(self):
@ -1015,9 +959,9 @@ class SlideController(DisplayController):
Generate the preview when you click on a slide.
if this is the Live Controller also display on the screen
"""
row = self.preview_list_widget.currentRow()
row = self.preview_widget.current_slide_number()
self.selected_row = 0
if -1 < row < self.preview_list_widget.rowCount():
if -1 < row < self.preview_widget.slide_count():
if self.service_item.is_command():
if self.is_live and not start:
Registry().execute(u'%s_slide' % self.service_item.name.lower(),
@ -1035,7 +979,7 @@ class SlideController(DisplayController):
self.service_item.bg_image_bytes = None
self.updatePreview()
self.selected_row = row
self.__checkUpdateSelectedSlide(row)
self.preview_widget.change_slide(row)
Registry().execute(u'slidecontroller_%s_changed' % self.type_prefix, row)
self.display.setFocus()
@ -1043,7 +987,7 @@ class SlideController(DisplayController):
"""
The slide has been changed. Update the slidecontroller accordingly
"""
self.__checkUpdateSelectedSlide(row)
self.preview_widget.change_slide(row)
self.updatePreview()
Registry().execute(u'slidecontroller_%s_changed' % self.type_prefix, row)
@ -1089,8 +1033,8 @@ class SlideController(DisplayController):
if self.service_item.is_command() and self.is_live:
self.updatePreview()
else:
row = self.preview_list_widget.currentRow() + 1
if row == self.preview_list_widget.rowCount():
row = self.preview_widget.current_slide_number() + 1
if row == self.preview_widget.slide_count():
if wrap is None:
if self.slide_limits == SlideLimits.Wrap:
row = 0
@ -1098,12 +1042,12 @@ class SlideController(DisplayController):
self.serviceNext()
return
else:
row = self.preview_list_widget.rowCount() - 1
row = self.preview_widget.slide_count() - 1
elif wrap:
row = 0
else:
row = self.preview_list_widget.rowCount() - 1
self.__checkUpdateSelectedSlide(row)
row = self.preview_widget.slide_count() - 1
self.preview_widget.change_slide(row)
self.slideSelected()
def on_slide_selected_previous(self):
@ -1116,27 +1060,19 @@ class SlideController(DisplayController):
if self.service_item.is_command() and self.is_live:
self.updatePreview()
else:
row = self.preview_list_widget.currentRow() - 1
row = self.preview_widget.current_slide_number() - 1
if row == -1:
if self.slide_limits == SlideLimits.Wrap:
row = self.preview_list_widget.rowCount() - 1
row = self.preview_widget.slide_count() - 1
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
self._process_queue()
return
else:
row = 0
self.__checkUpdateSelectedSlide(row)
self.preview_widget.change_slide(row)
self.slideSelected()
def __checkUpdateSelectedSlide(self, row):
"""
Check if this slide has been updated
"""
if row + 1 < self.preview_list_widget.rowCount():
self.preview_list_widget.scrollToItem(self.preview_list_widget.item(row + 1, 0))
self.preview_list_widget.selectRow(row)
def onToggleLoop(self):
"""
Toggles the loop state.
@ -1151,7 +1087,7 @@ class SlideController(DisplayController):
"""
Start the timer loop running and store the timer id
"""
if self.preview_list_widget.rowCount() > 1:
if self.preview_widget.slide_count() > 1:
self.timer_id = self.startTimer(int(self.delay_spin_box.value()) * 1000)
def on_stop_loop(self):
@ -1261,8 +1197,8 @@ class SlideController(DisplayController):
"""
If preview copy slide item to live controller from Preview Controller
"""
row = self.preview_list_widget.currentRow()
if -1 < row < self.preview_list_widget.rowCount():
row = self.preview_widget.current_slide_number()
if -1 < row < self.preview_widget.slide_count():
if self.service_item.from_service:
self.service_manager.preview_live(self.service_item.unique_identifier, row)
else:

0
tests/__init__.py Normal file
View File

View File

@ -2,11 +2,12 @@
Package to test the openlp.core.lib package.
"""
import os
import cPickle
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
from tests.utils.osdinteraction import read_service_from_file
from tests.utils.constants import TEST_RESOURCES_PATH
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
@ -18,8 +19,6 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n'\
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestServiceItem(TestCase):
@ -78,7 +77,7 @@ class TestServiceItem(TestCase):
service_item.name = u'test'
# WHEN: adding image to a service item
test_image = os.path.join(TEST_PATH, u'church.jpg')
test_image = os.path.join(TEST_RESOURCES_PATH, u'church.jpg')
service_item.add_from_image(test_image, u'Image Title')
# THEN: We should get back a valid service item
@ -133,8 +132,8 @@ class TestServiceItem(TestCase):
service_item.name = u'test'
# WHEN: adding image to a service item
test_file = os.path.join(TEST_PATH, u'church.jpg')
service_item.add_from_command(TEST_PATH, u'church.jpg', test_file)
test_file = os.path.join(TEST_RESOURCES_PATH, u'church.jpg')
service_item.add_from_command(TEST_RESOURCES_PATH, 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'
@ -151,7 +150,7 @@ class TestServiceItem(TestCase):
assert len(service) == 2, u'The saved service should have two parts'
assert service[u'header'][u'name'] == u'test', u'A test plugin should be returned'
assert service[u'data'][0][u'title'] == u'church.jpg', u'The first title name should be "church,jpg"'
assert service[u'data'][0][u'path'] == TEST_PATH, u'The path should match the input path'
assert service[u'data'][0][u'path'] == TEST_RESOURCES_PATH, u'The path should match the input path'
assert service[u'data'][0][u'image'] == test_file, u'The image should match the full path to image'
# WHEN validating a service item
@ -170,13 +169,12 @@ class TestServiceItem(TestCase):
"""
Test the Service Item - adding a custom slide from a saved service
"""
# GIVEN: A new service item and a mocked add icon function
# GIVEN: A new service item
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service
line = self.convert_file_service_item(u'serviceitem_custom_1.osd')
service_item.set_from_service(line)
service = read_service_from_file(u'serviceitem_custom_1.osd')
service_item.set_from_service(service[0])
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
@ -195,18 +193,17 @@ class TestServiceItem(TestCase):
"""
Test the Service Item - adding an image from a saved service
"""
# GIVEN: A new service item and a mocked add icon function
# GIVEN: A new service item
image_name = u'image_1.jpg'
test_file = os.path.join(TEST_PATH, image_name)
test_file = os.path.join(TEST_RESOURCES_PATH, image_name)
frame_array = {u'path': test_file, u'title': image_name}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_1.osd')
service = read_service_from_file(u'serviceitem_image_1.osd')
with patch('os.path.exists'):
service_item.set_from_service(line, TEST_PATH)
service_item.set_from_service(service[0], TEST_RESOURCES_PATH)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
@ -229,7 +226,7 @@ class TestServiceItem(TestCase):
"""
Test the Service Item - adding an image from a saved local service
"""
# GIVEN: A new service item and a mocked add icon function
# GIVEN: A new service item
image_name1 = u'image_1.jpg'
image_name2 = u'image_2.jpg'
test_file1 = os.path.join(u'/home/openlp', image_name1)
@ -238,12 +235,11 @@ class TestServiceItem(TestCase):
frame_array2 = {u'path': test_file2, u'title': image_name2}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_2.osd')
service = read_service_from_file(u'serviceitem_image_2.osd')
with patch('os.path.exists'):
service_item.set_from_service(line)
service_item.set_from_service(service[0])
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
@ -286,16 +282,3 @@ class TestServiceItem(TestCase):
assert service_item.title is None, u'The title should be set to a value'
assert service_item.is_capable(ItemCapabilities.HasDetailedTitleDisplay) is False, \
u'The Capability should have been removed'
def convert_file_service_item(self, name):
service_file = os.path.join(TEST_PATH, name)
try:
open_file = open(service_file, u'r')
items = cPickle.load(open_file)
first_line = items[0]
except IOError:
first_line = u''
finally:
open_file.close()
return first_line

View File

@ -0,0 +1,88 @@
"""
Package to test the openlp.core.ui.listpreviewwidget.
"""
from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtGui
from openlp.core.lib import Registry, ServiceItem
from openlp.core.ui import listpreviewwidget
from tests.utils.osdinteraction import read_service_from_file
class TestListPreviewWidget(TestCase):
def setUp(self):
"""
Create the UI.
"""
Registry.create()
self.app = QtGui.QApplication([])
self.main_window = QtGui.QMainWindow()
self.image = QtGui.QImage(1, 1, QtGui.QImage.Format_RGB32)
self.image_manager = MagicMock()
self.image_manager.get_image.return_value = self.image
Registry().register(u'image_manager', self.image_manager)
self.preview_widget = listpreviewwidget.ListPreviewWidget(self.main_window, 2)
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault.
"""
del self.preview_widget
del self.main_window
del self.app
def initial_slide_count_test(self):
"""
Test the inital slide count.
"""
# GIVEN: A new ListPreviewWidget instance.
# WHEN: No SlideItem has been added yet.
# THEN: The count of items should be zero.
self.assertEqual(self.preview_widget.slide_count(), 0,
u'The slide list should be empty.')
def initial_slide_number_test(self):
"""
Test the inital slide number.
"""
# GIVEN: A new ListPreviewWidget instance.
# WHEN: No SlideItem has been added yet.
# THEN: The number of the current item should be -1.
self.assertEqual(self.preview_widget.current_slide_number(), -1,
u'The slide number should be -1.')
def replace_service_item_test(self):
"""
Test item counts and current number with a service item.
"""
# GIVEN: A ServiceItem with two frames.
service_item = ServiceItem(None)
service = read_service_from_file(u'serviceitem_image_2.osd')
with patch('os.path.exists'):
service_item.set_from_service(service[0])
# WHEN: Added to the preview widget.
self.preview_widget.replace_service_item(service_item, 1, 1)
# THEN: The slide count and number should fit.
self.assertEqual(self.preview_widget.slide_count(), 2,
u'The slide count should be 2.')
self.assertEqual(self.preview_widget.current_slide_number(), 1,
u'The current slide number should be 1.')
def change_slide_test(self):
"""
Test the change_slide method.
"""
# GIVEN: A ServiceItem with two frames content.
service_item = ServiceItem(None)
service = read_service_from_file(u'serviceitem_image_2.osd')
with patch('os.path.exists'):
service_item.set_from_service(service[0])
# WHEN: Added to the preview widget and switched to the second frame.
self.preview_widget.replace_service_item(service_item, 1, 0)
self.preview_widget.change_slide(1)
# THEN: The current_slide_number should reflect the change.
self.assertEqual(self.preview_widget.current_slide_number(), 1,
u'The current slide number should be 1.')

0
tests/utils/__init__.py Normal file
View File

5
tests/utils/constants.py Normal file
View File

@ -0,0 +1,5 @@
import os
OPENLP_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..'))
TEST_RESOURCES_PATH = os.path.join(OPENLP_PATH, u'tests', u'resources')

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`osdinteraction` provides miscellaneous functions for interacting with
OSD files.
"""
import os
import cPickle
from tests.utils.constants import TEST_RESOURCES_PATH
def read_service_from_file(file_name):
"""
Reads an OSD file and returns the first service item found therein.
@param file_name: File name of an OSD file residing in the tests/resources folder.
@return: The service contained in the file.
"""
service_file = os.path.join(TEST_RESOURCES_PATH, file_name)
with open(service_file, u'r') as open_file:
service = cPickle.load(open_file)
return service