This commit is contained in:
Tim Bentley 2014-10-12 13:52:33 +01:00
commit f8fc418925
32 changed files with 1190 additions and 280 deletions

View File

@ -145,11 +145,13 @@ def build_icon(icon):
return button_icon
def image_to_byte(image):
def image_to_byte(image, base_64=True):
"""
Resize an image to fit on the current screen for the web and returns it as a byte stream.
:param image: The image to converted.
:param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
To preserve original intention, this defaults to True
"""
log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray()
@ -158,6 +160,8 @@ def image_to_byte(image):
buffie.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG")
log.debug('image_to_byte - end')
if not base_64:
return byte_array
# convert to base64 encoding so does not get missed!
return bytes(byte_array.toBase64()).decode('utf-8')

View File

@ -106,7 +106,7 @@ class Image(object):
"""
secondary_priority = 0
def __init__(self, path, source, background):
def __init__(self, path, source, background, width=-1, height=-1):
"""
Create an image for the :class:`ImageManager`'s cache.
@ -115,7 +115,8 @@ class Image(object):
:class:`~openlp.core.lib.ImageSource` class.
:param background: A ``QtGui.QColor`` object specifying the colour to be used to fill the gabs if the image's
ratio does not match with the display ratio.
:param width: The width of the image, defaults to -1 meaning that the screen width will be used.
:param height: The height of the image, defaults to -1 meaning that the screen height will be used.
"""
self.path = path
self.image = None
@ -124,6 +125,8 @@ class Image(object):
self.source = source
self.background = background
self.timestamp = 0
self.width = width
self.height = height
# FIXME: We assume that the path exist. The caller has to take care that it exists!
if os.path.exists(path):
self.timestamp = os.stat(path).st_mtime
@ -210,13 +213,13 @@ class ImageManager(QtCore.QObject):
image.background = background
self._reset_image(image)
def update_image_border(self, path, source, background):
def update_image_border(self, path, source, background, width=-1, height=-1):
"""
Border has changed so update the image affected.
"""
log.debug('update_image_border')
# Mark the image as dirty for a rebuild by setting the image and byte stream to None.
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.source == source:
image.background = background
self._reset_image(image)
@ -237,12 +240,12 @@ class ImageManager(QtCore.QObject):
if not self.image_thread.isRunning():
self.image_thread.start()
def get_image(self, path, source):
def get_image(self, path, source, width=-1, height=-1):
"""
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
"""
log.debug('getImage %s' % path)
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.image is None:
self._conversion_queue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick
@ -257,12 +260,12 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.modify_priority(image, Priority.Low)
return image.image
def get_image_bytes(self, path, source):
def get_image_bytes(self, path, source, width=-1, height=-1):
"""
Returns the byte string for an image. If not present wait for the background thread to process it.
"""
log.debug('get_image_bytes %s' % path)
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.image_bytes is None:
self._conversion_queue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@ -272,14 +275,14 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1)
return image.image_bytes
def add_image(self, path, source, background):
def add_image(self, path, source, background, width=-1, height=-1):
"""
Add image to cache if it is not already there.
"""
log.debug('add_image %s' % path)
if not (path, source) in self._cache:
image = Image(path, source, background)
self._cache[(path, source)] = image
if not (path, source, width, height) in self._cache:
image = Image(path, source, background, width, height)
self._cache[(path, source, width, height)] = image
self._conversion_queue.put((image.priority, image.secondary_priority, image))
# Check if the there are any images with the same path and check if the timestamp has changed.
for image in list(self._cache.values()):
@ -308,7 +311,10 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[2]
# Generate the QImage for the image.
if image.image is None:
image.image = resize_image(image.path, self.width, self.height, image.background)
# Let's see if the image was requested with specific dimensions
width = self.width if image.width == -1 else image.width
height = self.height if image.height == -1 else image.height
image.image = resize_image(image.path, width, height, image.background)
# Set the priority to Lowest and stop here as we need to process more important images first.
if image.priority == Priority.Normal:
self._conversion_queue.modify_priority(image, Priority.Lowest)

View File

@ -39,8 +39,8 @@ import uuid
from PyQt4 import QtGui
from openlp.core.common import RegistryProperties, Settings, translate
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
log = logging.getLogger(__name__)
@ -112,7 +112,17 @@ class ItemCapabilities(object):
The capability to edit the title of the item
``IsOptical``
.Determines is the service_item is based on an optical device
Determines is the service_item is based on an optical device
``HasDisplayTitle``
The item contains 'displaytitle' on every frame which should be
preferred over 'title' when displaying the item
``HasNotes``
The item contains 'notes'
``HasThumbnails``
The item has related thumbnails available
"""
CanPreview = 1
@ -133,6 +143,9 @@ class ItemCapabilities(object):
CanAutoStartForLive = 16
CanEditTitle = 17
IsOptical = 18
HasDisplayTitle = 19
HasNotes = 20
HasThumbnails = 21
class ServiceItem(RegistryProperties):
@ -272,18 +285,22 @@ class ServiceItem(RegistryProperties):
self.raw_footer = []
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
def add_from_image(self, path, title, background=None):
def add_from_image(self, path, title, background=None, thumbnail=None):
"""
Add an image slide to the service item.
:param path: The directory in which the image file is located.
:param title: A title for the slide in the service item.
:param background:
:param thumbnail: Optional alternative thumbnail, used for remote thumbnails.
"""
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({'title': title, 'path': path})
if not thumbnail:
self._raw_frames.append({'title': title, 'path': path})
else:
self._raw_frames.append({'title': title, 'path': path, 'image': thumbnail})
self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
@ -301,16 +318,22 @@ class ServiceItem(RegistryProperties):
self._raw_frames.append({'title': title, 'raw_slide': raw_slide, 'verseTag': verse_tag})
self._new_item()
def add_from_command(self, path, file_name, image):
def add_from_command(self, path, file_name, image, display_title=None, notes=None):
"""
Add a slide from a command.
:param path: The title of the slide in the service item.
:param file_name: The title of the slide in the service item.
:param image: The command of/for the slide.
:param display_title: Title to show in gui/webinterface, optional.
:param notes: Notes to show in the webinteface, optional.
"""
self.service_item_type = ServiceItemType.Command
self._raw_frames.append({'title': file_name, 'image': image, 'path': path})
# If the item should have a display title but this frame doesn't have one, we make one up
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
'display_title': display_title, 'notes': notes})
self._new_item()
def get_service_repr(self, lite_save):
@ -354,7 +377,8 @@ class ServiceItem(RegistryProperties):
service_data = [slide['title'] for slide in self._raw_frames]
elif self.service_item_type == ServiceItemType.Command:
for slide in self._raw_frames:
service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path']})
service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'],
'display_title': slide['display_title'], 'notes': slide['notes']})
return {'header': service_header, 'data': service_data}
def set_from_service(self, service_item, path=None):
@ -425,7 +449,8 @@ class ServiceItem(RegistryProperties):
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
elif path:
self.has_original_files = False
self.add_from_command(path, text_image['title'], text_image['image'])
self.add_from_command(path, text_image['title'], text_image['image'],
text_image.get('display_title', ''), text_image.get('notes', ''))
else:
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
self._new_item()

View File

@ -94,8 +94,8 @@ class ListPreviewWidget(QtGui.QTableWidget, RegistryProperties):
Displays the given slide.
"""
self.service_item = service_item
self.clear()
self.setRowCount(0)
self.clear()
self.setColumnWidth(0, width)
row = 0
text = []

View File

@ -1281,7 +1281,11 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
# Add the children to their parent tree_widget_item.
for count, frame in enumerate(service_item_from_item.get_frames()):
child = QtGui.QTreeWidgetItem(tree_widget_item)
text = frame['title'].replace('\n', ' ')
# prefer to use a display_title
if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle):
text = frame['display_title'].replace('\n', ' ')
else:
text = frame['title'].replace('\n', ' ')
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, count)
if service_item == item_count:

View File

@ -873,6 +873,7 @@ class SlideController(DisplayController, RegistryProperties):
if self.service_item.is_command():
Registry().execute('%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
self.update_preview()
self.selected_row = index
else:
self.preview_widget.change_slide(index)
self.slide_selected()
@ -1042,8 +1043,8 @@ class SlideController(DisplayController, RegistryProperties):
self.display.image(to_display)
# reset the store used to display first image
self.service_item.bg_image_bytes = None
self.update_preview()
self.selected_row = row
self.update_preview()
self.preview_widget.change_slide(row)
self.display.setFocus()
@ -1055,6 +1056,7 @@ class SlideController(DisplayController, RegistryProperties):
"""
self.preview_widget.change_slide(row)
self.update_preview()
self.selected_row = row
def update_preview(self):
"""

View File

@ -85,7 +85,7 @@ class BibleFormat(object):
BibleFormat.CSV,
BibleFormat.OpenSong,
BibleFormat.WebDownload,
BibleFormar.Zefania,
BibleFormat.Zefania,
]

View File

@ -551,6 +551,7 @@ class ImageMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.HasThumbnails)
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
@ -589,7 +590,7 @@ class ImageMediaItem(MediaManagerItem):
# Continue with the existing images.
for filename in images_file_names:
name = os.path.split(filename)[1]
service_item.add_from_image(filename, name, background)
service_item.add_from_image(filename, name, background, os.path.join(self.service_path, name))
return True
def check_group_exists(self, new_group):

View File

@ -65,7 +65,7 @@ from PyQt4 import QtCore
from openlp.core.lib import ScreenList
from openlp.core.utils import delete_file, get_uno_command, get_uno_instance
from .presentationcontroller import PresentationController, PresentationDocument
from .presentationcontroller import PresentationController, PresentationDocument, TextType
log = logging.getLogger(__name__)
@ -257,6 +257,7 @@ class ImpressDocument(PresentationDocument):
self.presentation.Display = ScreenList().current['number'] + 1
self.control = None
self.create_thumbnails()
self.create_titles_and_notes()
return True
def create_thumbnails(self):
@ -450,22 +451,44 @@ class ImpressDocument(PresentationDocument):
:param slide_no: The slide the notes are required for, starting at 1
"""
return self.__get_text_from_page(slide_no, True)
return self.__get_text_from_page(slide_no, TextType.Notes)
def __get_text_from_page(self, slide_no, notes=False):
def __get_text_from_page(self, slide_no, text_type=TextType.SlideText):
"""
Return any text extracted from the presentation page.
:param slide_no: The slide the notes are required for, starting at 1
:param notes: A boolean. If set the method searches the notes of the slide.
:param text_type: A TextType. Enumeration of the types of supported text.
"""
text = ''
pages = self.document.getDrawPages()
page = pages.getByIndex(slide_no - 1)
if notes:
page = page.getNotesPage()
for index in range(page.getCount()):
shape = page.getByIndex(index)
if shape.supportsService("com.sun.star.drawing.Text"):
text += shape.getString() + '\n'
if TextType.Title <= text_type <= TextType.Notes:
pages = self.document.getDrawPages()
if 0 < slide_no <= pages.getCount():
page = pages.getByIndex(slide_no - 1)
if text_type == TextType.Notes:
page = page.getNotesPage()
for index in range(page.getCount()):
shape = page.getByIndex(index)
shape_type = shape.getShapeType()
if shape.supportsService("com.sun.star.drawing.Text"):
# if they requested title, make sure it is the title
if text_type != TextType.Title or shape_type == "com.sun.star.presentation.TitleTextShape":
text += shape.getString() + '\n'
return text
def create_titles_and_notes(self):
"""
Writes the list of titles (one per slide) to 'titles.txt' and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = []
notes = []
pages = self.document.getDrawPages()
for slide_no in range(1, pages.getCount() + 1):
titles.append(self.__get_text_from_page(slide_no, TextType.Title).replace('\n', ' ') + '\n')
note = self.__get_text_from_page(slide_no, TextType.Notes)
if len(note) == 0:
note = ' '
notes.append(note)
self.save_titles_and_notes(titles, notes)

View File

@ -288,13 +288,14 @@ class PresentationMediaItem(MediaManagerItem):
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
while os.path.isfile(image):
service_item.add_from_image(image, name)
service_item.add_from_image(image, name, thumbnail=thumbnail)
i += 1
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
else:
@ -323,8 +324,21 @@ class PresentationMediaItem(MediaManagerItem):
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
# Get titles and notes
titles, notes = doc.get_titles_and_notes()
service_item.add_capability(ItemCapabilities.HasDisplayTitle)
if notes.count('') != len(notes):
service_item.add_capability(ItemCapabilities.HasNotes)
service_item.add_capability(ItemCapabilities.HasThumbnails)
while img:
service_item.add_from_command(path, name, img)
# Use title and note if available
title = ''
if titles and len(titles) >= i:
title = titles[i - 1]
note = ''
if notes and len(notes) >= i:
note = notes[i - 1]
service_item.add_from_command(path, name, img, title, note)
i += 1
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()

View File

@ -27,7 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This modul is for controlling powerpiont. PPT API documentation:
This module is for controlling powerpoint. PPT API documentation:
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
"""
import os
@ -37,16 +37,17 @@ from openlp.core.common import is_win
if is_win():
from win32com.client import Dispatch
import win32com
import winreg
import win32ui
import pywintypes
from openlp.core.lib import ScreenList
from openlp.core.common import Registry
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
from openlp.core.common import trace_error_handler
from .presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
@ -136,6 +137,7 @@ class PowerpointDocument(PresentationDocument):
self.controller.process.Presentations.Open(self.file_path, False, False, True)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
self.create_thumbnails()
self.create_titles_and_notes()
# Powerpoint 2013 pops up when loading a file, so we minimize it again
if self.presentation.Application.Version == u'15.0':
try:
@ -392,6 +394,28 @@ class PowerpointDocument(PresentationDocument):
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
def create_titles_and_notes(self):
"""
Writes the list of titles (one per slide)
to 'titles.txt'
and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = []
notes = []
for slide in self.presentation.Slides:
try:
text = slide.Shapes.Title.TextFrame.TextRange.Text
except Exception as e:
log.exception(e)
text = ''
titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n')
note = _get_text_from_shapes(slide.NotesPage.Shapes)
if len(note) == 0:
note = ' '
notes.append(note)
self.save_titles_and_notes(titles, notes)
def show_error_msg(self):
"""
Stop presentation and display an error message.
@ -410,8 +434,8 @@ def _get_text_from_shapes(shapes):
:param shapes: A set of shapes to search for text.
"""
text = ''
for index in range(shapes.Count):
shape = shapes(index + 1)
if shape.HasTextFrame:
text += shape.TextFrame.TextRange.Text + '\n'
for shape in shapes:
if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
if shape.HasTextFrame and shape.TextFrame.HasText:
text += shape.TextFrame.TextRange.Text + '\n'
return text

View File

@ -29,6 +29,11 @@
import logging
import os
import logging
import zipfile
import re
from xml.etree import ElementTree
from openlp.core.common import is_win
@ -127,14 +132,14 @@ class PptviewDocument(PresentationDocument):
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
file_path = os.path.normpath(self.file_path)
self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
file_path = file_path.encode('utf-16-le') + b'\0'
self.file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_path = preview_path.encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder):
os.makedirs(temp_folder)
self.ppt_id = self.controller.process.OpenPPT(file_path, None, rect, preview_path)
self.ppt_id = self.controller.process.OpenPPT(self.file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
@ -154,6 +159,68 @@ class PptviewDocument(PresentationDocument):
path = '%s\\slide%s.bmp' % (self.get_temp_folder(), str(idx + 1))
self.convert_thumbnail(path, idx + 1)
def create_titles_and_notes(self):
"""
Extracts the titles and notes from the zipped file
and writes the list of titles (one per slide)
to 'titles.txt'
and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = None
notes = None
filename = os.path.normpath(self.file_path)
# let's make sure we have a valid zipped presentation
if os.path.exists(filename) and zipfile.is_zipfile(filename):
namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
# open the file
with zipfile.ZipFile(filename) as zip_file:
# find the presentation.xml to get the slide count
with zip_file.open('ppt/presentation.xml') as pres:
tree = ElementTree.parse(pres)
nodes = tree.getroot().findall(".//p:sldIdLst/p:sldId", namespaces=namespaces)
# initialize the lists
titles = ['' for i in range(len(nodes))]
notes = ['' for i in range(len(nodes))]
# loop thru the file list to find slides and notes
for zip_info in zip_file.infolist():
node_type = ''
index = -1
list_to_add = None
# check if it is a slide
match = re.search("slides/slide(.+)\.xml", zip_info.filename)
if match:
index = int(match.group(1))-1
node_type = 'ctrTitle'
list_to_add = titles
# or a note
match = re.search("notesSlides/notesSlide(.+)\.xml", zip_info.filename)
if match:
index = int(match.group(1))-1
node_type = 'body'
list_to_add = notes
# if it is one of our files, index shouldn't be -1
if index >= 0:
with zip_file.open(zip_info) as zipped_file:
tree = ElementTree.parse(zipped_file)
text = ''
nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t",
namespaces=namespaces)
# if we found any content
if nodes and len(nodes) > 0:
for node in nodes:
if len(text) > 0:
text += '\n'
text += node.text
# Let's remove the \n from the titles and
# just add one at the end
if node_type == 'ctrTitle':
text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n'
list_to_add[index] = text
# now let's write the files
self.save_titles_and_notes(titles, notes)
def close_presentation(self):
"""
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being

Binary file not shown.

View File

@ -293,6 +293,49 @@ class PresentationDocument(object):
"""
return ''
def get_titles_and_notes(self):
"""
Reads the titles from the titles file and
the notes files and returns the content in two lists
"""
titles = []
notes = []
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
if os.path.exists(titles_file):
try:
with open(titles_file) as fi:
titles = fi.read().splitlines()
except:
log.exception('Failed to open/read existing titles file')
titles = []
for slide_no, title in enumerate(titles, 1):
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
note = ''
if os.path.exists(notes_file):
try:
with open(notes_file) as fn:
note = fn.read()
except:
log.exception('Failed to open/read notes file')
note = ''
notes.append(note)
return titles, notes
def save_titles_and_notes(self, titles, notes):
"""
Performs the actual persisting of titles to the titles.txt
and notes to the slideNote%.txt
"""
if titles:
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
with open(titles_file, mode='w') as fo:
fo.writelines(titles)
if notes:
for slide_no, note in enumerate(notes, 1):
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
with open(notes_file, mode='w') as fn:
fn.write(note)
class PresentationController(object):
"""
@ -427,3 +470,12 @@ class PresentationController(object):
def close_presentation(self):
pass
class TextType(object):
"""
Type Enumeration for Types of Text to request
"""
Title = 0
SlideText = 1
Notes = 2

View File

@ -87,16 +87,30 @@ window.OpenLP = {
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
var text = data.results.slides[idx]["tag"];
if (text != "") text = text + ": ";
text = text + data.results.slides[idx]["text"];
var indexInt = parseInt(idx,10);
var slide = data.results.slides[idx];
var text = slide["tag"];
if (text != "") {
text = text + ": ";
}
if (slide["title"]) {
text += slide["title"]
} else {
text += slide["text"];
}
if (slide["notes"]) {
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>");
}
text = text.replace(/\n/g, '<br />');
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text));
if (data.results.slides[idx]["selected"]) {
if (slide["img"]) {
text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
}
var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
if (slide["selected"]) {
li.attr("data-theme", "e");
}
li.children("a").click(OpenLP.setSlide);
li.find("*").attr("value", indexInt );
ul.append(li);
}
OpenLP.currentItem = data.results.item;

View File

@ -102,7 +102,21 @@ window.OpenLP = {
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
var text = slide["text"];
var text = "";
// use title if available
if (slide["title"]) {
text = slide["title"];
} else {
text = slide["text"];
}
// use thumbnail if available
if (slide["img"]) {
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
}
// use notes if available
if (slide["notes"]) {
text += '<br />' + slide["notes"];
}
text = text.replace(/\n/g, "<br />");
$("#currentslide").html(text);
text = "";
@ -110,7 +124,11 @@ window.OpenLP = {
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "<p class=\"nextslide\">";
text = text + OpenLP.currentSlides[idx]["text"];
if (OpenLP.currentSlides[idx]["title"]) {
text = text + OpenLP.currentSlides[idx]["title"];
} else {
text = text + OpenLP.currentSlides[idx]["text"];
}
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "</p>";
else

View File

@ -125,7 +125,7 @@ from mako.template import Template
from PyQt4 import QtCore
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, translate
from openlp.core.lib import PluginStatus, StringContent, image_to_byte
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
log = logging.getLogger(__name__)
FILE_TYPES = {
@ -159,6 +159,7 @@ class HttpRouter(RegistryProperties):
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
@ -328,7 +329,8 @@ class HttpRouter(RegistryProperties):
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
'slides': translate('RemotePlugin.Mobile', 'Slides'),
'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
def serve_file(self, file_name=None):
@ -380,6 +382,42 @@ class HttpRouter(RegistryProperties):
content_type = FILE_TYPES.get(ext, 'text/plain')
return ext, content_type
def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
"""
Serve an image file. If not found return 404.
"""
log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
supported_controllers = ['presentations', 'images']
# -1 means use the default dimension in ImageManager
width = -1
height = -1
if dimensions:
match = re.search('(\d+)x(\d+)', dimensions)
if match:
# let's make sure that the dimensions are within reason
width = sorted([10, int(match.group(1)), 1000])[1]
height = sorted([10, int(match.group(2)), 1000])[1]
content = ''
content_type = None
if controller_name and file_name:
if controller_name in supported_controllers:
full_path = urllib.parse.unquote(file_name)
if '..' not in full_path: # no hacking please
full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
'thumbnails/' + full_path))
if os.path.exists(full_path):
path, just_file_name = os.path.split(full_path)
self.image_manager.add_image(full_path, just_file_name, None, width, height)
ext, content_type = self.get_content_type(full_path)
image = self.image_manager.get_image(full_path, just_file_name, width, height)
content = image_to_byte(image, False)
if len(content) == 0:
return self.do_not_found()
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
@ -458,6 +496,7 @@ class HttpRouter(RegistryProperties):
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
# Handle text (songs, custom, bibles)
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
@ -465,11 +504,37 @@ class HttpRouter(RegistryProperties):
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
# Handle images, unless a custom thumbnail is given or if thumbnails is disabled
elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
item['tag'] = str(index + 1)
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path)
# Create thumbnail if it doesn't exists
if not os.path.exists(full_thumbnail_path):
create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
else:
# Handle presentation etc.
item['tag'] = str(index + 1)
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
item['title'] = str(frame['display_title'])
if current_item.is_capable(ItemCapabilities.HasNotes):
item['notes'] = str(frame['notes'])
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
Settings().value('remotes/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = AppLocation.get_data_path()
print(frame)
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
if current_item.notes:
item['notes'] = item.get('notes', '') + '\n' + current_item.notes
print(item)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:

View File

@ -144,6 +144,7 @@ class OpenLPServer(RegistryProperties):
try:
self.httpd = server_class((address, port), CustomHandler)
log.debug("Server started for class %s %s %d" % (server_class, address, port))
break
except OSError:
log.debug("failed to start http server thread state %d %s" %
(loop, self.http_thread.isRunning()))
@ -151,6 +152,8 @@ class OpenLPServer(RegistryProperties):
time.sleep(0.1)
except:
log.error('Failed to start server ')
loop += 1
time.sleep(0.1)
def stop_server(self):
"""

View File

@ -62,6 +62,9 @@ class RemoteTab(SettingsTab):
self.twelve_hour_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
self.server_settings_layout.addRow(self.twelve_hour_check_box)
self.thumbnails_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.thumbnails_check_box.setObjectName('thumbnails_check_box')
self.server_settings_layout.addRow(self.thumbnails_check_box)
self.left_layout.addWidget(self.server_settings_group_box)
self.http_settings_group_box = QtGui.QGroupBox(self.left_column)
self.http_settings_group_box.setObjectName('http_settings_group_box')
@ -163,6 +166,7 @@ class RemoteTab(SettingsTab):
self.left_layout.addStretch()
self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls)
self.https_port_spin_box.valueChanged.connect(self.set_urls)
@ -176,6 +180,8 @@ class RemoteTab(SettingsTab):
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="https://play.google.com/store/'
@ -240,6 +246,8 @@ class RemoteTab(SettingsTab):
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour)
self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
self.thumbnails_check_box.setChecked(self.thumbnails)
local_data = AppLocation.get_directory(AppLocation.DataDir)
if not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.crt')) or \
not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.key')):
@ -271,6 +279,7 @@ class RemoteTab(SettingsTab):
Settings().setValue(self.settings_section + '/https enabled', self.https_settings_group_box.isChecked())
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
Settings().setValue(self.settings_section + '/password', self.password.text())
@ -285,6 +294,15 @@ class RemoteTab(SettingsTab):
if check_state == QtCore.Qt.Checked:
self.twelve_hour = True
def on_thumbnails_check_box_changed(self, check_state):
"""
Toggle the thumbnail check box.
"""
self.thumbnails = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
self.thumbnails = True
def https_changed(self):
"""
Invert the HTTP group box based on Https group settings

View File

@ -44,7 +44,8 @@ __default_settings__ = {
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
'remotes/ip address': '0.0.0.0',
'remotes/thumbnails': True
}

View File

@ -162,9 +162,9 @@ class TestAppLocation(TestCase):
patch('openlp.core.common.applocation.os.path.abspath') as mocked_abspath, \
patch('openlp.core.common.applocation.os.path.split') as mocked_split, \
patch('openlp.core.common.applocation.sys') as mocked_sys:
mocked_abspath.return_value = 'plugins/dir'
mocked_abspath.return_value = os.path.join('plugins', 'dir')
mocked_split.return_value = ['openlp']
mocked_get_frozen_path.return_value = 'plugins/dir'
mocked_get_frozen_path.return_value = os.path.join('plugins', 'dir')
mocked_sys.frozen = 1
mocked_sys.argv = ['openlp']
@ -172,7 +172,7 @@ class TestAppLocation(TestCase):
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN: The correct directory should be returned
self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')
self.assertEqual(os.path.join('plugins', 'dir'), directory, 'Directory should be "plugins/dir"')
def get_frozen_path_in_unfrozen_app_test(self):
"""

View File

@ -69,16 +69,17 @@ class TestImageManager(TestCase, TestMixin):
Test the Image Manager setup basic functionality
"""
# GIVEN: the an image add to the image manager
self.image_manager.add_image(TEST_PATH, 'church.jpg', None)
full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
self.image_manager.add_image(full_path, 'church.jpg', None)
# WHEN the image is retrieved
image = self.image_manager.get_image(TEST_PATH, 'church.jpg')
image = self.image_manager.get_image(full_path, 'church.jpg')
# THEN returned record is a type of image
self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
# WHEN: The image bytes are requested.
byte_array = self.image_manager.get_image_bytes(TEST_PATH, 'church.jpg')
byte_array = self.image_manager.get_image_bytes(full_path, 'church.jpg')
# THEN: Type should be a str.
self.assertEqual(isinstance(byte_array, str), True, 'The returned object should be a str')
@ -89,6 +90,38 @@ class TestImageManager(TestCase, TestMixin):
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing image')
def different_dimension_image_test(self):
"""
Test the Image Manager with dimensions
"""
# GIVEN: add an image with specific dimensions
full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
# WHEN: the image is retrieved
image = self.image_manager.get_image(full_path, 'church.jpg', 80, 80)
# THEN: The return should be of type image
self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
# WHEN: adding the same image with different dimensions
self.image_manager.add_image(full_path, 'church.jpg', None, 100, 100)
# THEN: the cache should contain two pictures
self.assertEqual(len(self.image_manager._cache), 2,
'Image manager should consider two dimensions of the same picture as different')
# WHEN: adding the same image with first dimensions
self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
# THEN: the cache should still contain only two pictures
self.assertEqual(len(self.image_manager._cache), 2, 'Same dimensions should not be added again')
# WHEN: calling with correct image, but wrong dimensions
with self.assertRaises(KeyError) as context:
self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing dimension')
def process_cache_test(self):
"""
Test the process_cache method
@ -151,7 +184,7 @@ class TestImageManager(TestCase, TestMixin):
:param image: The name of the image. E. g. ``image1``
"""
return self.image_manager._cache[(TEST_PATH, image)].priority
return self.image_manager._cache[(TEST_PATH, image, -1, -1)].priority
def mocked_resize_image(self, *args):
"""

View File

@ -32,13 +32,11 @@ Package to test the openlp.core.lib package.
import os
from unittest import TestCase
from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item
from openlp.core.common import Registry
from openlp.core.lib import ItemCapabilities, ServiceItem
from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@ -120,13 +118,17 @@ class TestServiceItem(TestCase):
# WHEN: adding an image from a saved Service and mocked exists
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\
patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb,\
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
mocked_get_section_data_path:
mocked_exists.return_value = True
mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertEqual(test_file, service_item.get_rendered_frame(0),
self.assertEqual(os.path.normpath(test_file), os.path.normpath(service_item.get_rendered_frame(0)),
'The first frame should match the path to the image')
self.assertEqual(frame_array, service_item.get_frames()[0],
'The return should match frame array1')
@ -153,8 +155,8 @@ class TestServiceItem(TestCase):
# GIVEN: A new service item and a mocked add icon function
image_name1 = 'image_1.jpg'
image_name2 = 'image_2.jpg'
test_file1 = os.path.join('/home/openlp', image_name1)
test_file2 = os.path.join('/home/openlp', image_name2)
test_file1 = os.path.normpath(os.path.join('/home/openlp', image_name1))
test_file2 = os.path.normpath(os.path.join('/home/openlp', image_name2))
frame_array1 = {'path': test_file1, 'title': image_name1}
frame_array2 = {'path': test_file2, 'title': image_name2}
@ -168,8 +170,12 @@ class TestServiceItem(TestCase):
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists, \
patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb, \
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
mocked_get_section_data_path:
mocked_exists.return_value = True
mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item2.set_from_service(line2)
service_item.set_from_service(line)
@ -207,6 +213,44 @@ class TestServiceItem(TestCase):
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')
def add_from_command_for_a_presentation_test(self):
"""
Test the Service Item - adding a presentation
"""
# GIVEN: A service item, a mocked icon and presentation data
service_item = ServiceItem(None)
presentation_name = 'test.pptx'
image = MagicMock()
display_title = 'DisplayTitle'
notes = 'Note1\nNote2\n'
frame = {'title': presentation_name, 'image': image, 'path': TEST_PATH,
'display_title': display_title, 'notes': notes}
# WHEN: adding presentation to service_item
service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
# THEN: verify that it is setup as a Command and that the frame data matches
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
def add_from_comamnd_without_display_title_and_notes_test(self):
"""
Test the Service Item - add from command, but not presentation
"""
# GIVEN: A new service item, a mocked icon and image data
service_item = ServiceItem(None)
image_name = 'test.img'
image = MagicMock()
frame = {'title': image_name, 'image': image, 'path': TEST_PATH,
'display_title': None, 'notes': None}
# WHEN: adding image to service_item
service_item.add_from_command(TEST_PATH, image_name, image)
# THEN: verify that it is setup as a Command and that the frame data matches
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
def service_item_load_optical_media_from_service_test(self):
"""
Test the Service Item - load an optical media item

View File

@ -57,17 +57,18 @@ class TestThemeManager(TestCase):
# GIVEN: A new ThemeManager instance.
theme_manager = ThemeManager()
theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes')
zipfile.ZipFile.__init__ = MagicMock()
zipfile.ZipFile.__init__.return_value = None
zipfile.ZipFile.write = MagicMock()
with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \
patch('zipfile.ZipFile.write') as mocked_zipfile_write:
mocked_zipfile_init.return_value = None
# WHEN: The theme is exported
theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
# WHEN: The theme is exported
theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
# THEN: The zipfile should be created at the given path
zipfile.ZipFile.__init__.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
zipfile.ZipFile.write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', 'Default', 'Default.xml'),
os.path.join('Default', 'Default.xml'))
# THEN: The zipfile should be created at the given path
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
mocked_zipfile_write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes',
'Default', 'Default.xml'),
os.path.join('Default', 'Default.xml'))
def initial_theme_manager_test(self):
"""

View File

@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 #
###############################################################################
"""
Functional tests to test the Impress class and related methods.
"""
from unittest import TestCase
import os
import shutil
from tempfile import mkdtemp
from tests.functional import patch, MagicMock
from tests.utils.constants import TEST_RESOURCES_PATH
from tests.helpers.testmixin import TestMixin
from openlp.plugins.presentations.lib.impresscontroller import \
ImpressController, ImpressDocument, TextType
class TestImpressController(TestCase, TestMixin):
"""
Test the ImpressController Class
"""
def setUp(self):
"""
Set up the patches and mocks need for all tests.
"""
self.get_application()
self.build_settings()
self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder
def tearDown(self):
"""
Stop the patches
"""
self.destroy_settings()
shutil.rmtree(self.temp_folder)
def constructor_test(self):
"""
Test the Constructor from the ImpressController
"""
# GIVEN: No presentation controller
controller = None
# WHEN: The presentation controller object is created
controller = ImpressController(plugin=self.mock_plugin)
# THEN: The name of the presentation controller should be correct
self.assertEqual('Impress', controller.name,
'The name of the presentation controller should be correct')
class TestImpressDocument(TestCase):
"""
Test the ImpressDocument Class
"""
def setUp(self):
mocked_plugin = MagicMock()
mocked_plugin.settings_section = 'presentations'
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
self.ppc = ImpressController(mocked_plugin)
self.doc = ImpressDocument(self.ppc, self.file_name)
def create_titles_and_notes_test(self):
"""
Test ImpressDocument.create_titles_and_notes
"""
# GIVEN: mocked PresentationController.save_titles_and_notes with
# 0 pages and the LibreOffice Document
self.doc.save_titles_and_notes = MagicMock()
self.doc.document = MagicMock()
self.doc.document.getDrawPages.return_value = MagicMock()
self.doc.document.getDrawPages().getCount.return_value = 0
# WHEN reading the titles and notes
self.doc.create_titles_and_notes()
# THEN save_titles_and_notes should have been called with empty arrays
self.doc.save_titles_and_notes.assert_called_once_with([], [])
# GIVEN: reset mock and set it to 2 pages
self.doc.save_titles_and_notes.reset_mock()
self.doc.document.getDrawPages().getCount.return_value = 2
# WHEN: a new call to create_titles_and_notes
self.doc.create_titles_and_notes()
# THEN: save_titles_and_notes should have been called once with
# two arrays of two elements
self.doc.save_titles_and_notes.assert_called_once_with(['\n', '\n'], [' ', ' '])
def get_text_from_page_out_of_bound_test(self):
"""
Test ImpressDocument.__get_text_from_page with out-of-bounds index
"""
# GIVEN: mocked LibreOffice Document with one slide,
# two notes and three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: __get_text_from_page is called with an index of 0x00
result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Notes)
# THEN: the result should be an empty string
self.assertEqual(result, '', 'Result should be an empty string')
# WHEN: regardless of the type of text, index 0x00 is out of bounds
result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Title)
# THEN: result should be an empty string
self.assertEqual(result, '', 'Result should be an empty string')
# WHEN: when called with 2, it should also be out of bounds
result = self.doc._ImpressDocument__get_text_from_page(2, TextType.SlideText)
# THEN: result should be an empty string ... and, getByIndex should
# have never been called
self.assertEqual(result, '', 'Result should be an empty string')
self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
'There should be no call to getByIndex')
def get_text_from_page_wrong_type_test(self):
"""
Test ImpressDocument.__get_text_from_page with wrong TextType
"""
# GIVEN: mocked LibreOffice Document with one slide, two notes and
# three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: called with TextType 3
result = self.doc._ImpressDocument__get_text_from_page(1, 3)
# THEN: result should be an empty string
self.assertEqual(result, '', 'Result should be and empty string')
self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
'There should be no call to getByIndex')
def get_text_from_page_valid_params_test(self):
"""
Test ImpressDocument.__get_text_from_page with valid parameters
"""
# GIVEN: mocked LibreOffice Document with one slide,
# two notes and three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: __get_text_from_page is called to get the Notes
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Notes)
# THEN: result should be 'Note\nNote\n'
self.assertEqual(result, 'Note\nNote\n', 'Result should be \'Note\\n\' times the count of notes in the page')
# WHEN: get the Title
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Title)
# THEN: result should be 'Title\n'
self.assertEqual(result, 'Title\n', 'Result should be exactly \'Title\\n\'')
# WHEN: get all text
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.SlideText)
# THEN: result should be 'Title\nString\nString\n'
self.assertEqual(result, 'Title\nString\nString\n', 'Result should be exactly \'Title\\nString\\nString\\n\'')
def _mock_a_LibreOffice_document(self, page_count, note_count, text_count):
"""
Helper function, creates a mock libreoffice document.
:param page_count: Number of pages in the document
:param note_count: Number of note pages in the document
:param text_count: Number of text pages in the document
"""
pages = MagicMock()
page = MagicMock()
pages.getByIndex.return_value = page
notes_page = MagicMock()
notes_page.getCount.return_value = note_count
shape = MagicMock()
shape.supportsService.return_value = True
shape.getString.return_value = 'Note'
notes_page.getByIndex.return_value = shape
page.getNotesPage.return_value = notes_page
page.getCount.return_value = text_count
page.getByIndex.side_effect = self._get_page_shape_side_effect
pages.getCount.return_value = page_count
document = MagicMock()
document.getDrawPages.return_value = pages
document.getByIndex.return_value = page
return document
def _get_page_shape_side_effect(*args):
"""
Helper function.
"""
page_shape = MagicMock()
page_shape.supportsService.return_value = True
if args[1] == 0:
page_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
page_shape.getString.return_value = 'Title'
else:
page_shape.getString.return_value = 'String'
return page_shape

View File

@ -30,16 +30,20 @@
Functional tests to test the PowerPointController class and related methods.
"""
import os
if os.name == 'nt':
import pywintypes
import shutil
from unittest import TestCase
from tempfile import mkdtemp
from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin
from tests.utils.constants import TEST_RESOURCES_PATH
from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument
from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument,\
_get_text_from_shapes
from openlp.core.common import is_win
if is_win():
import pywintypes
class TestPowerpointController(TestCase, TestMixin):
@ -79,7 +83,7 @@ class TestPowerpointController(TestCase, TestMixin):
'The name of the presentation controller should be correct')
class TestPowerpointDocument(TestCase):
class TestPowerpointDocument(TestCase, TestMixin):
"""
Test the PowerpointDocument Class
"""
@ -88,6 +92,11 @@ class TestPowerpointDocument(TestCase):
"""
Set up the patches and mocks need for all tests.
"""
self.get_application()
self.build_settings()
self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder
self.powerpoint_document_stop_presentation_patcher = patch(
'openlp.plugins.presentations.lib.powerpointcontroller.PowerpointDocument.stop_presentation')
self.presentation_document_get_temp_folder_patcher = patch(
@ -100,6 +109,8 @@ class TestPowerpointDocument(TestCase):
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
self.real_controller = PowerpointController(self.mock_plugin)
def tearDown(self):
"""
@ -108,12 +119,14 @@ class TestPowerpointDocument(TestCase):
self.powerpoint_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
self.destroy_settings()
shutil.rmtree(self.temp_folder)
def show_error_msg_test(self):
"""
Test the PowerpointDocument.show_error_msg() method gets called on com exception
"""
if os.name == 'nt':
if is_win():
# GIVEN: A PowerpointDocument with mocked controller and presentation
with patch('openlp.plugins.presentations.lib.powerpointcontroller.critical_error_message_box') as \
mocked_critical_error_message_box:
@ -129,3 +142,95 @@ class TestPowerpointDocument(TestCase):
'integration and the presentation will be stopped.'
' Restart the presentation if you wish to '
'present it.')
# add _test to the following if necessary
def verify_loading_document(self):
"""
Test loading a document in PowerPoint
"""
if is_win() and self.real_controller.check_available():
# GIVEN: A PowerpointDocument and a presentation
doc = PowerpointDocument(self.real_controller, self.file_name)
# WHEN: loading the filename
doc.load_presentation()
result = doc.is_loaded()
# THEN: result should be true
self.assertEqual(result, True, 'The result should be True')
else:
self.skipTest('Powerpoint not available, skipping test.')
def create_titles_and_notes_test(self):
"""
Test creating the titles from PowerPoint
"""
if is_win() and self.real_controller.check_available():
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
self.doc = PowerpointDocument(self.real_controller, self.file_name)
self.doc.save_titles_and_notes = MagicMock()
self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
slide = MagicMock()
slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
pres = MagicMock()
pres.Slides = [slide, slide]
self.doc.presentation = pres
# WHEN reading the titles and notes
self.doc.create_titles_and_notes()
# THEN the save should have been called exactly once with 2 titles and 2 notes
self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
else:
self.skipTest('Powerpoint not available, skipping test.')
def create_titles_and_notes_with_no_slides_test(self):
"""
Test creating the titles from PowerPoint when it returns no slides
"""
if is_win() and self.real_controller.check_available():
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
doc = PowerpointDocument(self.real_controller, self.file_name)
doc.save_titles_and_notes = MagicMock()
doc._PowerpointDocument__get_text_from_shapes = MagicMock()
pres = MagicMock()
pres.Slides = []
doc.presentation = pres
# WHEN reading the titles and notes
doc.create_titles_and_notes()
# THEN the save should have been called exactly once with empty titles and notes
doc.save_titles_and_notes.assert_called_once_with([], [])
else:
self.skipTest('Powerpoint not available, skipping test.')
def get_text_from_shapes_test(self):
"""
Test getting text from powerpoint shapes
"""
# GIVEN: mocked shapes
shape = MagicMock()
shape.PlaceholderFormat.Type = 2
shape.HasTextFrame = shape.TextFrame.HasText = True
shape.TextFrame.TextRange.Text = 'slideText'
shapes = [shape, shape]
# WHEN: getting the text
result = _get_text_from_shapes(shapes)
# THEN: it should return the text
self.assertEqual(result, 'slideText\nslideText\n', 'result should match \'slideText\nslideText\n\'')
def get_text_from_shapes_with_no_shapes_test(self):
"""
Test getting text from powerpoint shapes with no shapes
"""
# GIVEN: empty shapes array
shapes = []
# WHEN: getting the text
result = _get_text_from_shapes(shapes)
# THEN: it should not fail but return empty string
self.assertEqual(result, '', 'result should be empty')

View File

@ -4,8 +4,8 @@
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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, #
@ -31,16 +31,19 @@ This module contains tests for the pptviewcontroller module of the Presentations
"""
import os
import shutil
if os.name == 'nt':
from ctypes import cdll
from tempfile import mkdtemp
from unittest import TestCase
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
from tests.utils.constants import TEST_RESOURCES_PATH
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
from openlp.core.common import is_win
if is_win():
from ctypes import cdll
class TestPptviewController(TestCase, TestMixin):
@ -98,7 +101,7 @@ class TestPptviewController(TestCase, TestMixin):
available = controller.check_available()
# THEN: On windows it should return True, on other platforms False
if os.name == 'nt':
if is_win():
self.assertTrue(available, 'check_available should return True on windows.')
else:
self.assertFalse(available, 'check_available should return False when not on windows.')
@ -130,7 +133,7 @@ class TestPptviewDocument(TestCase):
"""
Set up the patches and mocks need for all tests.
"""
self.os_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os')
self.os_isdir_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.isdir')
self.pptview_document_create_thumbnails_patcher = patch(
'openlp.plugins.presentations.lib.pptviewcontroller.PptviewDocument.create_thumbnails')
self.pptview_document_stop_presentation_patcher = patch(
@ -141,46 +144,45 @@ class TestPptviewDocument(TestCase):
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup')
self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
self.rect_patcher = MagicMock()
self.mock_os = self.os_patcher.start()
self.mock_os_isdir = self.os_isdir_patcher.start()
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start()
self.mock_pptview_document_stop_presentation = self.pptview_document_stop_presentation_patcher.start()
self.mock_presentation_document_get_temp_folder = self.presentation_document_get_temp_folder_patcher.start()
self.mock_presentation_document_setup = self.presentation_document_setup_patcher.start()
self.mock_rect = self.rect_patcher.start()
self.mock_screen_list = self.screen_list_patcher.start()
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
self.temp_folder = mkdtemp()
self.mock_presentation_document_get_temp_folder.return_value = self.temp_folder
def tearDown(self):
"""
Stop the patches
"""
self.os_patcher.stop()
self.os_isdir_patcher.stop()
self.pptview_document_create_thumbnails_patcher.stop()
self.pptview_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
self.rect_patcher.stop()
self.screen_list_patcher.stop()
shutil.rmtree(self.temp_folder)
def load_presentation_succesfull_test(self):
"""
Test the PptviewDocument.load_presentation() method when the PPT is successfully opened
"""
# GIVEN: A reset mocked_os
self.mock_os.reset()
self.mock_os_isdir.reset()
# WHEN: The temporary directory exists and OpenPPT returns successfully (not -1)
self.mock_os.path.isdir.return_value = True
self.mock_os_isdir.return_value = True
self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
if is_win():
result = instance.load_presentation()
# THEN: PptviewDocument.load_presentation should return True
@ -191,17 +193,78 @@ class TestPptviewDocument(TestCase):
Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
not successfully opened
"""
# GIVEN: A reset mocked_os
self.mock_os.reset()
# GIVEN: A reset mock_os_isdir
self.mock_os_isdir.reset()
# WHEN: The temporary directory does not exist and OpenPPT returns unsuccessfully (-1)
self.mock_os.path.isdir.return_value = False
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()
with patch('openlp.plugins.presentations.lib.pptviewcontroller.os.makedirs') as mock_makedirs:
self.mock_os_isdir.return_value = False
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if is_win():
result = instance.load_presentation()
# THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False
self.mock_os.makedirs.assert_called_once_with('temp folder')
self.assertFalse(result)
# THEN: The temp folder should be created and PptviewDocument.load_presentation should return False
mock_makedirs.assert_called_once_with(self.temp_folder)
self.assertFalse(result)
def create_titles_and_notes_test(self):
"""
Test PowerpointController.create_titles_and_notes
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
doc.save_titles_and_notes = MagicMock()
# WHEN reading the titles and notes
doc.create_titles_and_notes()
# THEN save_titles_and_notes should have been called once with empty arrays
doc.save_titles_and_notes.assert_called_once_with(['Test 1\n', '\n', 'Test 2\n', 'Test 4\n', 'Test 3\n'],
['Notes for slide 1', 'Inserted', 'Notes for slide 2',
'Notes \nfor slide 4', 'Notes for slide 3'])
def create_titles_and_notes_nonexistent_file_test(self):
"""
Test PowerpointController.create_titles_and_notes with nonexistent file
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
with patch('builtins.open') as mocked_open, \
patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
mocked_dir_exists:
mocked_exists.return_value = False
mocked_dir_exists.return_value = False
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = 'Idontexist.pptx'
doc.save_titles_and_notes = MagicMock()
# WHEN: Reading the titles and notes
doc.create_titles_and_notes()
# THEN: File existens should have been checked, and not have been opened.
doc.save_titles_and_notes.assert_called_once_with(None, None)
mocked_exists.assert_any_call('Idontexist.pptx')
self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
def create_titles_and_notes_invalid_file_test(self):
"""
Test PowerpointController.create_titles_and_notes with invalid file
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and an invalid file
with patch('builtins.open') as mocked_open, \
patch('openlp.plugins.presentations.lib.pptviewcontroller.zipfile.is_zipfile') as mocked_is_zf:
mocked_is_zf.return_value = False
mocked_open.filesize = 10
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
doc.save_titles_and_notes = MagicMock()
# WHEN: reading the titles and notes
doc.create_titles_and_notes()
# THEN:
doc.save_titles_and_notes.assert_called_once_with(None, None)
self.assertEqual(mocked_is_zf.call_count, 1, 'is_zipfile should have been called once')

View File

@ -1,158 +1,166 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 #
###############################################################################
"""
This module contains tests for the Presentation Controller.
"""
from unittest import TestCase
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
from tests.functional import MagicMock, patch
class TestPresentationController(TestCase):
"""
Test the PresentationController.
"""
# TODO: Items left to test
# PresentationController
# __init__
# enabled
# is_available
# check_available
# start_process
# kill
# add_document
# remove_doc
# close_presentation
# _get_plugin_manager
def constructor_test(self):
"""
Test the Constructor
"""
# GIVEN: No presentation controller
controller = None
# WHEN: The presentation controller object is created
mock_plugin = MagicMock()
mock_plugin.settings_section = ''
controller = PresentationController(plugin=mock_plugin)
# THEN: The name of the presentation controller should be correct
self.assertEqual('PresentationController', controller.name,
'The name of the presentation controller should be correct')
class TestPresentationDocument(TestCase):
"""
Test the PresentationDocument Class
"""
# TODO: Items left to test
# PresentationDocument
# __init__
# load_presentation
# presentation_deleted
# get_file_name
# get_thumbnail_folder
# get_temp_folder
# check_thumbnails
# close_presentation
# is_active
# is_loaded
# blank_screen
# unblank_screen
# is_blank
# stop_presentation
# start_presentation
# get_slide_number
# get_slide_count
# goto_slide
# next_step
# previous_step
# convert_thumbnail
# get_thumbnail_path
# poll_slidenumber
# get_slide_text
# get_slide_notes
def setUp(self):
"""
Set up the patches and mocks need for all tests.
"""
self.check_directory_exists_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
self.get_thumbnail_folder_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
self._setup_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
self.mock_setup = self._setup_patcher.start()
self.mock_controller = MagicMock()
self.mock_get_thumbnail_folder.return_value = 'returned/path/'
def tearDown(self):
"""
Stop the patches
"""
self.check_directory_exists_patcher.stop()
self.get_thumbnail_folder_patcher.stop()
self._setup_patcher.stop()
def initialise_presentation_document_test(self):
"""
Test the PresentationDocument __init__ method when initialising the PresentationDocument Class
"""
# GIVEN: A reset mock_setup and mocked controller
self.mock_setup.reset()
# WHEN: Creating an instance of PresentationDocument
PresentationDocument(self.mock_controller, 'Name')
# THEN: PresentationDocument.__init__ should have been called with the correct arguments
self.mock_setup.assert_called_once_with('Name')
def presentation_document_setup_test(self):
"""
Test the PresentationDocument _setup method when initialising the PresentationDocument Class
"""
self._setup_patcher.stop()
# GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method
# WHEN: Creating an instance of PresentationDocument
PresentationDocument(self.mock_controller, 'Name')
# THEN: check_directory_exists should have been called with the correct arguments
self.mock_check_directory_exists.assert_called_once_with('returned/path/')
self._setup_patcher.start()
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 #
###############################################################################
"""
Functional tests to test the PresentationController and PresentationDocument
classes and related methods.
"""
from unittest import TestCase
import os
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
from tests.functional import MagicMock, patch, mock_open
FOLDER_TO_PATCH = 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder'
class TestPresentationController(TestCase):
"""
Test the PresentationController.
"""
def setUp(self):
mocked_plugin = MagicMock()
mocked_plugin.settings_section = 'presentations'
self.presentation = PresentationController(mocked_plugin)
self.document = PresentationDocument(self.presentation, '')
def constructor_test(self):
"""
Test the Constructor
"""
# GIVEN: A mocked plugin
# WHEN: The PresentationController is created
# THEN: The name of the presentation controller should be correct
self.assertEqual('PresentationController', self.presentation.name,
'The name of the presentation controller should be correct')
def save_titles_and_notes_test(self):
"""
Test PresentationDocument.save_titles_and_notes method with two valid lists
"""
# GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
mocked_open = mock_open()
with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
titles = ['uno', 'dos']
notes = ['one', 'two']
# WHEN: calling save_titles_and_notes
mocked_get_thumbnail_folder.return_value = 'test'
self.document.save_titles_and_notes(titles, notes)
# THEN: the last call to open should have been for slideNotes2.txt
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='w')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='w')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='w')
self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
mocked_open().write.assert_called_any('one')
mocked_open().write.assert_called_any('two')
def save_titles_and_notes_with_None_test(self):
"""
Test PresentationDocument.save_titles_and_notes method with no data
"""
# GIVEN: None and an empty list and a mocked open and get_thumbnail_folder
with patch('builtins.open') as mocked_open, patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
titles = None
notes = None
# WHEN: calling save_titles_and_notes
mocked_get_thumbnail_folder.return_value = 'test'
self.document.save_titles_and_notes(titles, notes)
# THEN: No file should have been created
self.assertEqual(mocked_open.call_count, 0, 'No file should be created')
def get_titles_and_notes_test(self):
"""
Test PresentationDocument.get_titles_and_notes method
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = True
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two items for the titles and two empty strings for the notes
self.assertIs(type(result_titles), list, 'result_titles should be of type list')
self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
self.assertIs(type(result_notes), list, 'result_notes should be of type list')
self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened')
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'))
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'))
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'))
self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
def get_titles_and_notes_with_file_not_found_test(self):
"""
Test PresentationDocument.get_titles_and_notes method with file not found
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = False
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two empty lists
self.assertIs(type(result_titles), list, 'result_titles should be of type list')
self.assertEqual(len(result_titles), 0, 'there be no titles')
self.assertIs(type(result_notes), list, 'result_notes should be a list')
self.assertEqual(len(result_notes), 0, 'but the list should be empty')
self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
def get_titles_and_notes_with_file_error_test(self):
"""
Test PresentationDocument.get_titles_and_notes method with file errors
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = True
mocked_open.side_effect = IOError()
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two empty lists
self.assertIs(type(result_titles), list, 'result_titles should be a list')

View File

@ -48,7 +48,8 @@ __default_settings__ = {
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
'remotes/ip address': '0.0.0.0',
'remotes/thumbnails': True
}
ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))

View File

@ -30,10 +30,12 @@
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
import urllib.request
from unittest import TestCase
from openlp.core.common import Settings, Registry
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from urllib.parse import urlparse
from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin
@ -186,3 +188,86 @@ class TestRouter(TestCase, TestMixin):
self.router.send_response.assert_called_once_with(200)
self.router.send_header.assert_called_once_with('Content-type', 'text/html')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
def serve_thumbnail_without_params_test(self):
"""
Test the serve_thumbnail routine without params
"""
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.serve_thumbnail()
self.router.send_response.assert_called_once_with(404)
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
def serve_thumbnail_with_invalid_params_test(self):
"""
Test the serve_thumbnail routine with invalid params
"""
# GIVEN: Mocked send_header, send_response, end_headers and wfile
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
# WHEN: pass a bad controller
self.router.serve_thumbnail('badcontroller', 'tecnologia 1.pptx/slide1.png')
# THEN: a 404 should be returned
self.assertEqual(len(self.router.send_header.mock_calls), 1, 'One header')
self.assertEqual(len(self.router.send_response.mock_calls), 1, 'One response')
self.assertEqual(len(self.router.wfile.mock_calls), 1, 'Once call to write to the socket')
self.router.send_response.assert_called_once_with(404)
# WHEN: pass a bad filename
self.router.send_response.reset_mock()
self.router.serve_thumbnail('presentations', 'tecnologia 1.pptx/badfilename.png')
# THEN: return a 404
self.router.send_response.assert_called_once_with(404)
# WHEN: a dangerous URL is passed
self.router.send_response.reset_mock()
self.router.serve_thumbnail('presentations', '../tecnologia 1.pptx/slide1.png')
# THEN: return a 404
self.router.send_response.assert_called_once_with(404)
def serve_thumbnail_with_valid_params_test(self):
"""
Test the serve_thumbnail routine with valid params
"""
# GIVEN: Mocked send_header, send_response, end_headers and wfile
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
mocked_image_manager = MagicMock()
Registry.create()
Registry().register('image_manager', mocked_image_manager)
file_name = 'another%20test/slide1.png'
full_path = os.path.normpath(os.path.join('thumbnails', file_name))
width = 120
height = 90
with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
patch('builtins.open', mock_open(read_data='123')), \
patch('openlp.plugins.remotes.lib.httprouter.AppLocation') as mocked_location, \
patch('openlp.plugins.remotes.lib.httprouter.image_to_byte') as mocked_image_to_byte:
mocked_exists.return_value = True
mocked_image_to_byte.return_value = '123'
mocked_location.get_section_data_path.return_value = ''
# WHEN: pass good controller and filename
result = self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
# THEN: a file should be returned
self.assertEqual(self.router.send_header.call_count, 1, 'One header')
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
mocked_exists.assert_called_with(urllib.parse.unquote(full_path))
self.assertEqual(mocked_image_to_byte.call_count, 1, 'Called once')
mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'),
'slide1.png', None, '120x90')
mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'), 'slide1.png', '120x90')

Binary file not shown.

Binary file not shown.