forked from openlp/openlp
head
This commit is contained in:
commit
f8fc418925
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 = []
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -85,7 +85,7 @@ class BibleFormat(object):
|
||||
BibleFormat.CSV,
|
||||
BibleFormat.OpenSong,
|
||||
BibleFormat.WebDownload,
|
||||
BibleFormar.Zefania,
|
||||
BibleFormat.Zefania,
|
||||
]
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
BIN
openlp/plugins/presentations/lib/pptviewlib/test.pptx
Normal file
BIN
openlp/plugins/presentations/lib/pptviewlib/test.pptx
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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'))
|
||||
|
@ -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')
|
||||
|
BIN
tests/resources/presentations/test.ppt
Normal file
BIN
tests/resources/presentations/test.ppt
Normal file
Binary file not shown.
BIN
tests/resources/presentations/test.pptx
Normal file
BIN
tests/resources/presentations/test.pptx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user