openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #24049
Re: [Merge] lp:~tomasgroth/openlp/better-remote into lp:openlp
Review: Needs Fixing
Don't take all the comments personally but I had issues before you picked this!
Diff comments:
> === modified file 'openlp/core/lib/__init__.py'
> --- openlp/core/lib/__init__.py 2014-05-01 17:49:43 +0000
> +++ openlp/core/lib/__init__.py 2014-07-15 13:42:09 +0000
> @@ -145,11 +145,13 @@
> 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 @@
> 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')
>
>
> === modified file 'openlp/core/lib/imagemanager.py'
> --- openlp/core/lib/imagemanager.py 2014-03-20 19:10:31 +0000
> +++ openlp/core/lib/imagemanager.py 2014-07-15 13:42:09 +0000
> @@ -36,6 +36,7 @@
> import os
> import time
> import queue
> +import re
>
> from PyQt4 import QtCore
>
> @@ -106,7 +107,7 @@
> """
> secondary_priority = 0
>
> - def __init__(self, path, source, background):
> + def __init__(self, path, source, background, dimensions=''):
dimensions Why a single string. This should be individual fields.
This needs a big fix.
> """
> Create an image for the :class:`ImageManager`'s cache.
>
> @@ -124,6 +125,15 @@
> self.source = source
> self.background = background
> self.timestamp = 0
> + match = re.search('(\d+)x(\d+)', dimensions)
> + if match:
> + # let's make sure that the dimensions are within reason
> + self.width = sorted([10, int(match.group(1)), 1000])[1]
> + self.height = sorted([10, int(match.group(2)), 1000])[1]
> + else:
> + # -1 means use the default dimension in ImageManager
> + self.width = -1
> + self.height = -1
> # 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 +220,13 @@
> image.background = background
> self._reset_image(image)
>
> - def update_image_border(self, path, source, background):
> + def update_image_border(self, path, source, background, dimensions=''):
> """
> 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, dimensions)]
> if image.source == source:
> image.background = background
> self._reset_image(image)
> @@ -237,12 +247,12 @@
> if not self.image_thread.isRunning():
> self.image_thread.start()
>
> - def get_image(self, path, source):
> + def get_image(self, path, source, dimensions=''):
> """
> 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, dimensions)]
> 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 +267,12 @@
> 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, dimensions=''):
> """
> 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, dimensions)]
> 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 +282,14 @@
> time.sleep(0.1)
> return image.image_bytes
>
> - def add_image(self, path, source, background):
> + def add_image(self, path, source, background, dimensions=''):
> """
> 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, dimensions) in self._cache:
> + image = Image(path, source, background, dimensions)
> + self._cache[(path, source, dimensions)] = 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 +318,10 @@
> 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)
>
> === modified file 'openlp/core/lib/serviceitem.py'
> --- openlp/core/lib/serviceitem.py 2014-04-14 18:28:04 +0000
> +++ openlp/core/lib/serviceitem.py 2014-07-15 13:42:09 +0000
> @@ -111,6 +111,16 @@
> ``CanEditTitle``
> The capability to edit the title of the item
>
> + ``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
> CanEdit = 2
> @@ -129,6 +139,9 @@
> HasBackgroundAudio = 15
> CanAutoStartForLive = 16
> CanEditTitle = 17
> + HasDisplayTitle = 18
> + HasNotes = 19
> + HasThumbnails = 20
>
>
> class ServiceItem(RegistryProperties):
> @@ -297,16 +310,19 @@
> 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})
> + 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):
> @@ -350,7 +366,8 @@
> 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']})
What happens to old services which pre-date this change.
> return {'header': service_header, 'data': service_data}
>
> def set_from_service(self, service_item, path=None):
> @@ -418,7 +435,8 @@
> self.title = text_image['title']
> if 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()
>
> === modified file 'openlp/core/ui/listpreviewwidget.py'
> --- openlp/core/ui/listpreviewwidget.py 2014-05-19 19:19:05 +0000
> +++ openlp/core/ui/listpreviewwidget.py 2014-07-15 13:42:09 +0000
> @@ -94,8 +94,8 @@
> Displays the given slide.
> """
> self.service_item = service_item
> + self.setRowCount(0)
> self.clear()
> - self.setRowCount(0)
> self.setColumnWidth(0, width)
> row = 0
> text = []
>
> === modified file 'openlp/core/ui/servicemanager.py'
> --- openlp/core/ui/servicemanager.py 2014-07-07 11:17:24 +0000
> +++ openlp/core/ui/servicemanager.py 2014-07-15 13:42:09 +0000
> @@ -1281,7 +1281,14 @@
> # 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', ' ')
> + # oops, it is missing, let's make one up
This should be hidden in the service item not in core code.
> + if len(text.strip()) == 0:
> + text = '[slide ' + str(count+1) + ']'
> + else:
> + text = frame['title'].replace('\n', ' ')
> child.setText(0, text[:40])
> child.setData(0, QtCore.Qt.UserRole, count)
> if service_item == item_count:
>
> === modified file 'openlp/core/ui/slidecontroller.py'
> --- openlp/core/ui/slidecontroller.py 2014-07-12 23:47:53 +0000
> +++ openlp/core/ui/slidecontroller.py 2014-07-15 13:42:09 +0000
> @@ -867,12 +867,17 @@
>
> :param message: remote message to be processed.
> """
> - index = int(message[0])
> + index = 0
> + if len(message) == 0 or message[0] == 'undefined':
How do we get undefined?
> + return
> + else:
> + index = int(message[0])
> if not self.service_item:
> return
> 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 +1047,8 @@
> 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 +1060,7 @@
> """
> self.preview_widget.change_slide(row)
> self.update_preview()
> + self.selected_row = row
>
> def update_preview(self):
> """
>
> === modified file 'openlp/plugins/images/lib/mediaitem.py'
> --- openlp/plugins/images/lib/mediaitem.py 2014-04-24 02:57:02 +0000
> +++ openlp/plugins/images/lib/mediaitem.py 2014-07-15 13:42:09 +0000
> @@ -551,6 +551,7 @@
> 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 = []
>
> === modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
> --- openlp/plugins/presentations/lib/impresscontroller.py 2014-04-12 20:19:22 +0000
> +++ openlp/plugins/presentations/lib/impresscontroller.py 2014-07-15 13:42:09 +0000
> @@ -63,7 +63,7 @@
>
> 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__)
> @@ -255,6 +255,7 @@
> self.presentation.Display = ScreenList().current['number'] + 1
> self.control = None
> self.create_thumbnails()
> + self.create_titles_and_notes()
> return True
>
> def create_thumbnails(self):
> @@ -448,22 +449,47 @@
>
> :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 = ''
> + 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()
> - 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'
> - return text
> + 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)
> + return
>
> === modified file 'openlp/plugins/presentations/lib/mediaitem.py'
> --- openlp/plugins/presentations/lib/mediaitem.py 2014-06-05 16:25:37 +0000
> +++ openlp/plugins/presentations/lib/mediaitem.py 2014-07-15 13:42:09 +0000
> @@ -295,6 +295,7 @@
> i += 1
> image_file = 'mainslide%03d.png' % i
> image = os.path.join(doc.get_temp_folder(), image_file)
> + service_item.add_capability(ItemCapabilities.HasThumbnails)
> doc.close_presentation()
> return True
> else:
> @@ -323,11 +324,25 @@
> i = 1
> img = doc.get_thumbnail_path(i, True)
> if img:
> + # Get titles and notes
> + titles, notes = doc.get_titles_and_notes()
> while img:
> - service_item.add_from_command(path, name, img)
> + # Use title and note if available
> + title = None
Should this be None or ''
> + if titles and len(titles) >= i:
> + title = titles[i - 1]
> + note = None
> + 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()
> + if titles.count('') != len(titles):
> + service_item.add_capability(ItemCapabilities.HasDisplayTitle)
> + if notes.count('') != len(notes):
> + service_item.add_capability(ItemCapabilities.HasNotes)
> + service_item.add_capability(ItemCapabilities.HasThumbnails)
> return True
> else:
> # File is no longer present
>
> === modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
> --- openlp/plugins/presentations/lib/powerpointcontroller.py 2014-07-03 11:21:12 +0000
> +++ openlp/plugins/presentations/lib/powerpointcontroller.py 2014-07-15 13:42:09 +0000
> @@ -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
> @@ -35,16 +35,17 @@
>
> if os.name == 'nt':
> 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__)
>
>
> @@ -134,6 +135,7 @@
> 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:
> @@ -390,6 +392,29 @@
> """
> 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)
> + return
return?
> +
> def show_error_msg(self):
> """
> Stop presentation and display an error message.
> @@ -408,8 +433,8 @@
> :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
>
> === modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
> --- openlp/plugins/presentations/lib/pptviewcontroller.py 2014-03-29 19:56:20 +0000
> +++ openlp/plugins/presentations/lib/pptviewcontroller.py 2014-07-15 13:42:09 +0000
> @@ -29,6 +29,11 @@
>
> import logging
> import os
> +import logging
> +import zipfile
> +import re
> +from xml.etree import ElementTree
> +
>
> if os.name == 'nt':
> from ctypes import cdll
> @@ -125,14 +130,14 @@
> 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()
> @@ -152,6 +157,69 @@
> 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)
> + return
> +
> def close_presentation(self):
> """
> Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
>
> === added file 'openlp/plugins/presentations/lib/pptviewlib/test.pptx'
> Binary files openlp/plugins/presentations/lib/pptviewlib/test.pptx 1970-01-01 00:00:00 +0000 and openlp/plugins/presentations/lib/pptviewlib/test.pptx 2014-07-15 13:42:09 +0000 differ
> === modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
> --- openlp/plugins/presentations/lib/presentationcontroller.py 2014-05-02 06:42:17 +0000
> +++ openlp/plugins/presentations/lib/presentationcontroller.py 2014-07-15 13:42:09 +0000
> @@ -293,6 +293,49 @@
> """
> 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 @@
>
> def close_presentation(self):
> pass
> +
> +
> +class TextType(object):
> + """
> + Type Enumeration for Types of Text to request
> + """
> + Title = 0
> + SlideText = 1
> + Notes = 2
>
> === modified file 'openlp/plugins/remotes/html/index.html'
> --- openlp/plugins/remotes/html/index.html 2013-12-24 08:56:50 +0000
> +++ openlp/plugins/remotes/html/index.html 2014-07-15 13:42:09 +0000
> @@ -120,6 +120,21 @@
> <a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">${prev}</a>
> <a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
> </div>
> + <div data-role="controlgroup" data-type="horizontal" style="float:left">
> + <a href="#settings" id="controller-settings" data-role="button" data-icon="gear" data-rel="dialog">${settings}</a>
> + </div>
> + </div>
> +</div>
> +<div data-role="page" id="settings">
> + <div data-role="header" data-position="inline" data-theme="b">
> + <h1>${settings}</h1>
> + </div>
> + <div data-role="content">
> + Display thumbnails:
> + <div data-role="controlgroup" data-type="horizontal">
> + <a href="#" id="display-thumbnails" data-role="button">Yes</a>
> + <a href="#" id="dont-display-thumbnails" data-role="button">No</a>
> + </div>
> </div>
> </div>
> <div data-role="page" id="alerts">
>
> === modified file 'openlp/plugins/remotes/html/openlp.js'
> --- openlp/plugins/remotes/html/openlp.js 2013-12-24 08:56:50 +0000
> +++ openlp/plugins/remotes/html/openlp.js 2014-07-15 13:42:09 +0000
> @@ -87,16 +87,26 @@
> 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"];
> + var indexInt = parseInt(idx,10);
> + var slide = data.results.slides[idx];
> + var text = slide["tag"];
> if (text != "") text = text + ": ";
> - text = text + data.results.slides[idx]["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 />');
> + if (slide["img"] && OpenLP.showThumbnails)
> + text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails80x80/") + "'>";
> var li = $("<li data-icon=\"false\">").append(
> - $("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text));
> - if (data.results.slides[idx]["selected"]) {
> + $("<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;
> @@ -241,6 +251,17 @@
> }
> );
> },
> + displayThumbnails: function (event) {
> + event.preventDefault();
> + var target = $(event.target);
> + OpenLP.showThumbnails = target.text() == "No" ? false : true;
> + var dt = new Date();
> + dt.setTime(dt.getTime() + 365 * 24 * 60 * 60 * 1000);
> + document.cookie = "displayThumbs=" + OpenLP.showThumbnails + "; expires=" +
> + dt.toGMTString() + "; path=/";
> + OpenLP.loadController();
> + $("#settings").dialog("close");
> + },
> search: function (event) {
> event.preventDefault();
> var query = OpenLP.escapeString($("#search-text").val())
> @@ -320,12 +341,24 @@
> },
> escapeString: function (string) {
> return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")
> - }
> + },
> + showThumbnails: false
> }
> // Initial jQueryMobile options
> $(document).bind("mobileinit", function(){
> $.mobile.defaultDialogTransition = "none";
> $.mobile.defaultPageTransition = "none";
> + var cookies = document.cookie;
> + if( cookies )
> + {
> + var allcookies = cookies.split(";")
> + for(ii = 0; ii < allcookies.length; ii++)
> + {
> + var parts = allcookies[ii].split("=");
> + if(parts.length == 2 && parts[0] == "displayThumbs")
> + OpenLP.showThumbnails = (parts[1]=='true');
> + }
> + }
> });
> // Service Manager
> $("#service-manager").live("pagebeforeshow", OpenLP.loadService);
> @@ -345,6 +378,8 @@
> $("#controller-theme").live("click", OpenLP.themeDisplay);
> $("#controller-desktop").live("click", OpenLP.desktopDisplay);
> $("#controller-show").live("click", OpenLP.showDisplay);
> +$("#display-thumbnails").live("click", OpenLP.displayThumbnails);
> +$("#dont-display-thumbnails").live("click", OpenLP.displayThumbnails);
> // Alerts
> $("#alert-submit").live("click", OpenLP.showAlert);
> // Search
>
> === modified file 'openlp/plugins/remotes/html/stage.js'
> --- openlp/plugins/remotes/html/stage.js 2013-12-24 08:56:50 +0000
> +++ openlp/plugins/remotes/html/stage.js 2014-07-15 13:42:09 +0000
> @@ -102,7 +102,21 @@
> $("#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 @@
> 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
>
> === modified file 'openlp/plugins/remotes/lib/httprouter.py'
> --- openlp/plugins/remotes/lib/httprouter.py 2014-04-19 05:09:54 +0000
> +++ openlp/plugins/remotes/lib/httprouter.py 2014-07-15 13:42:09 +0000
> @@ -125,7 +125,7 @@
> 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
>
> log = logging.getLogger(__name__)
> FILE_TYPES = {
> @@ -159,6 +159,7 @@
> ('^/(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 @@
> '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,35 @@
> 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']
> + if not dimensions:
> + dimensions = ''
> + 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, dimensions)
Not guaranteed to work as the processing is on a thread and may not be ready to be used when needed in 2 lines.
> + ext, content_type = self.get_content_type(full_path)
> + image = self.image_manager.get_image(full_path, just_file_name, dimensions)
> + 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.
> @@ -465,11 +496,28 @@
> item['tag'] = str(index + 1)
> item['text'] = str(frame['text'])
> item['html'] = str(frame['html'])
> + elif current_item.is_image():
> + item['tag'] = str(index + 1)
> + thumbnail_path = os.path.sep + os.path.join('images', 'thumbnails', frame['title'])
> + item['img'] = urllib.request.pathname2url(thumbnail_path)
> + item['text'] = str(frame['title'])
> + item['html'] = str(frame['title'])
> else:
> 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):
> + # If the file is under our app directory tree send the portion after the match
> + data_path = AppLocation.get_data_path()
> + 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
> data.append(item)
> json_data = {'results': {'slides': data}}
> if current_item:
>
> === modified file 'tests/functional/openlp_core_common/test_applocation.py'
> --- tests/functional/openlp_core_common/test_applocation.py 2014-04-02 18:51:21 +0000
> +++ tests/functional/openlp_core_common/test_applocation.py 2014-07-15 13:42:09 +0000
> @@ -162,9 +162,9 @@
> 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 @@
> 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):
> """
>
> === modified file 'tests/functional/openlp_core_lib/test_image_manager.py'
> --- tests/functional/openlp_core_lib/test_image_manager.py 2014-06-04 04:54:44 +0000
> +++ tests/functional/openlp_core_lib/test_image_manager.py 2014-07-15 13:42:09 +0000
> @@ -69,16 +69,17 @@
> 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 @@
> 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, '80x80')
> +
> + # WHEN: the image is retrieved
> + image = self.image_manager.get_image(full_path, 'church.jpg', '80x80')
> +
> + # 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, '100x100')
> +
> + # 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, '80x80')
> +
> + # 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', '120x120')
> + 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 @@
>
> :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, '')].priority
>
> def mocked_resize_image(self, *args):
> """
>
> === modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
> --- tests/functional/openlp_core_lib/test_serviceitem.py 2014-03-13 20:59:10 +0000
> +++ tests/functional/openlp_core_lib/test_serviceitem.py 2014-07-15 13:42:09 +0000
> @@ -32,13 +32,11 @@
> 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'\
> @@ -126,7 +124,7 @@
>
> # 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 +151,8 @@
> # 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}
>
> @@ -206,3 +204,41 @@
> 'This service item should be able to be run in a can be made to Loop')
> 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')
>
> === added file 'tests/functional/openlp_plugins/presentations/test_impresscontroller.py'
> --- tests/functional/openlp_plugins/presentations/test_impresscontroller.py 1970-01-01 00:00:00 +0000
> +++ tests/functional/openlp_plugins/presentations/test_impresscontroller.py 2014-07-15 13:42:09 +0000
> @@ -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 TestImpressDocumnt(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
>
> === modified file 'tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py'
> --- tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2014-07-03 11:21:12 +0000
> +++ tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2014-07-15 13:42:09 +0000
> @@ -38,8 +38,10 @@
>
> 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
>
>
> class TestPowerpointController(TestCase, TestMixin):
> @@ -79,7 +81,7 @@
> 'The name of the presentation controller should be correct')
>
>
> -class TestPowerpointDocument(TestCase):
> +class TestPowerpointDocument(TestCase, TestMixin):
> """
> Test the PowerpointDocument Class
> """
> @@ -88,6 +90,11 @@
> """
> 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 +107,8 @@
> 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,6 +117,8 @@
> 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):
> """
> @@ -129,3 +140,95 @@
> '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 os.name == 'nt' 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 os.name == 'nt' 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 os.name == 'nt' 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')
>
> === modified file 'tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py'
> --- tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2014-04-20 20:19:21 +0000
> +++ tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2014-07-15 13:42:09 +0000
> @@ -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, #
> @@ -39,6 +39,7 @@
>
> 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
>
> @@ -130,7 +131,7 @@
> """
> 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,41 +142,40 @@
> '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'
> @@ -191,17 +191,78 @@
> 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()
> -
> - # 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)
> + 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 os.name == 'nt':
> + result = instance.load_presentation()
> +
> + # 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')
>
> === modified file 'tests/functional/openlp_plugins/presentations/test_presentationcontroller.py'
> --- tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2014-03-13 20:59:10 +0000
> +++ tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2014-07-15 13:42:09 +0000
> @@ -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')
>
> === modified file 'tests/functional/openlp_plugins/remotes/test_router.py'
> --- tests/functional/openlp_plugins/remotes/test_router.py 2014-05-07 20:38:34 +0000
> +++ tests/functional/openlp_plugins/remotes/test_router.py 2014-07-15 13:42:09 +0000
> @@ -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 @@
> 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')
>
> === added file 'tests/resources/presentations/test.ppt'
> Binary files tests/resources/presentations/test.ppt 1970-01-01 00:00:00 +0000 and tests/resources/presentations/test.ppt 2014-07-15 13:42:09 +0000 differ
> === added file 'tests/resources/presentations/test.pptx'
> Binary files tests/resources/presentations/test.pptx 1970-01-01 00:00:00 +0000 and tests/resources/presentations/test.pptx 2014-07-15 13:42:09 +0000 differ
--
https://code.launchpad.net/~tomasgroth/openlp/better-remote/+merge/226795
Your team OpenLP Core is subscribed to branch lp:openlp.
References