Source code for openlp.plugins.presentations.lib.presentationcontroller

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4

###############################################################################
# OpenLP - Open Source Lyrics Projection                                      #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers                                   #
# --------------------------------------------------------------------------- #
# 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                          #
###############################################################################

import logging
import os
import shutil

from PyQt5 import QtCore

from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash
from openlp.core.lib import create_thumb, validate_thumb

log = logging.getLogger(__name__)


[docs]class PresentationDocument(object): """ Base class for presentation documents to inherit from. Loads and closes the presentation as well as triggering the correct activities based on the users input **Hook Functions** ``load_presentation()`` Load a presentation file ``close_presentation()`` Close presentation and clean up objects ``presentation_loaded()`` Returns True if presentation is currently loaded ``is_active()`` Returns True if a presentation is currently running ``blank_screen()`` Blanks the screen, making it black. ``unblank_screen()`` Unblanks the screen, restoring the output ``is_blank`` Returns true if screen is blank ``stop_presentation()`` Stops the presentation, removing it from the output display ``start_presentation()`` Starts the presentation from the beginning ``get_slide_number()`` Returns the current slide number, from 1 ``get_slide_count()`` Returns total number of slides ``goto_slide(slide_no)`` Jumps directly to the requested slide. ``next_step()`` Triggers the next effect of slide on the running presentation ``previous_step()`` Triggers the previous slide on the running presentation ``get_thumbnail_path(slide_no, check_exists)`` Returns a path to an image containing a preview for the requested slide """ def __init__(self, controller, name): """ Constructor for the PresentationController class """ self.controller = controller self._setup(name) def _setup(self, name): """ Run some initial setup. This method is separate from __init__ in order to mock it out in tests. """ self.slide_number = 0 self.file_path = name check_directory_exists(self.get_thumbnail_folder())
[docs] def load_presentation(self): """ Called when a presentation is added to the SlideController. Loads the presentation and starts it. Returns False if the file could not be opened """ return False
[docs] def presentation_deleted(self): """ Cleans up/deletes any controller specific files created for a file, e.g. thumbnails """ try: if os.path.exists(self.get_thumbnail_folder()): shutil.rmtree(self.get_thumbnail_folder()) if os.path.exists(self.get_temp_folder()): shutil.rmtree(self.get_temp_folder()) except OSError: log.exception('Failed to delete presentation controller files')
[docs] def get_file_name(self): """ Return just the filename of the presentation, without the directory """ return os.path.split(self.file_path)[1]
[docs] def get_thumbnail_folder(self): """ The location where thumbnail images will be stored """ # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(self.file_path.encode('utf-8')) else: folder = self.get_file_name() return os.path.join(self.controller.thumbnail_folder, folder)
[docs] def get_temp_folder(self): """ The location where thumbnail images will be stored """ # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(self.file_path.encode('utf-8')) else: folder = folder = self.get_file_name() return os.path.join(self.controller.temp_folder, folder)
[docs] def check_thumbnails(self): """ Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file. """ last_image = self.get_thumbnail_path(self.get_slide_count(), True) if not (last_image and os.path.isfile(last_image)): return False return validate_thumb(self.file_path, last_image)
[docs] def close_presentation(self): """ Close presentation and clean up objects. Triggered by new object being added to SlideController """ self.controller.close_presentation()
[docs] def is_active(self): """ Returns True if a presentation is currently running """ return False
[docs] def is_loaded(self): """ Returns true if a presentation is loaded """ return False
[docs] def blank_screen(self): """ Blanks the screen, making it black. """ pass
[docs] def unblank_screen(self): """ Unblanks (restores) the presentation """ pass
[docs] def is_blank(self): """ Returns true if screen is blank """ return False
[docs] def stop_presentation(self): """ Stops the presentation, removing it from the output display """ pass
[docs] def start_presentation(self): """ Starts the presentation from the beginning """ pass
[docs] def get_slide_number(self): """ Returns the current slide number, from 1 """ return 0
[docs] def get_slide_count(self): """ Returns total number of slides """ return 0
[docs] def goto_slide(self, slide_no): """ Jumps directly to the requested slide. :param slide_no: The slide to jump to, starting at 1 """ pass
[docs] def next_step(self): """ Triggers the next effect of slide on the running presentation. This might be the next animation on the current slide, or the next slide """ pass
[docs] def previous_step(self): """ Triggers the previous slide on the running presentation """ pass
[docs] def convert_thumbnail(self, file, idx): """ Convert the slide image the application made to a scaled 360px height .png image. """ if self.check_thumbnails(): return if os.path.isfile(file): thumb_path = self.get_thumbnail_path(idx, False) create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
[docs] def get_thumbnail_path(self, slide_no, check_exists): """ Returns an image path containing a preview for the requested slide :param slide_no: The slide an image is required for, starting at 1 :param check_exists: """ path = os.path.join(self.get_thumbnail_folder(), self.controller.thumbnail_prefix + str(slide_no) + '.png') if os.path.isfile(path) or not check_exists: return path else: return None
[docs] def poll_slidenumber(self, is_live, hide_mode): """ Check the current slide number """ if not self.is_active(): return if not hide_mode: current = self.get_slide_number() if current == self.slide_number: return self.slide_number = current if is_live: prefix = 'live' else: prefix = 'preview' Registry().execute('slidecontroller_{prefix}_change'.format(prefix=prefix), self.slide_number - 1)
[docs] def get_slide_text(self, slide_no): """ Returns the text on the slide :param slide_no: The slide the text is required for, starting at 1 """ return ''
[docs] def get_slide_notes(self, slide_no): """ Returns the text on the slide :param slide_no: The slide the text is required for, starting at 1 """ return ''
[docs] 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, encoding='utf-8') 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{number:d}.txt'.format(number=slide_no)) note = '' if os.path.exists(notes_file): try: with open(notes_file, encoding='utf-8') as fn: note = fn.read() except: log.exception('Failed to open/read notes file') note = '' notes.append(note) return titles, notes
[docs] 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='wt', encoding='utf-8') 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{number:d}.txt'.format(number=slide_no)) with open(notes_file, mode='wt', encoding='utf-8') as fn: fn.write(note)
[docs]class PresentationController(object): """ This class is used to control interactions with presentation applications by creating a runtime environment. This is a base class for presentation controllers to inherit from. To create a new controller, take a copy of this file and name it so it ends with ``controller.py``, i.e. ``foobarcontroller.py``. Make sure it inherits :class:`~openlp.plugins.presentations.lib.presentationcontroller.PresentationController`, and then fill in the blanks. If possible try to make sure it loads on all platforms, usually by using :mod:``os.name`` checks, although ``__init__``, ``check_available`` and ``presentation_deleted`` should always be implemented. See :class:`~openlp.plugins.presentations.lib.impresscontroller.ImpressController`, :class:`~openlp.plugins.presentations.lib.powerpointcontroller.PowerpointController` or :class:`~openlp.plugins.presentations.lib.pptviewcontroller.PptviewController` for examples. **Basic Attributes** ``name`` The name that appears in the options and the media manager. ``enabled`` The controller is enabled. ``available`` The controller is available on this machine. Set by init via call to check_available. ``plugin`` The presentationplugin object. ``supports`` The primary native file types this application supports. ``also_supports`` Other file types the application can import, although not necessarily the first choice due to potential incompatibilities. **Hook Functions** ``kill()`` Called at system exit to clean up any running presentations. ``check_available()`` Returns True if presentation application is installed/can run on this machine. ``presentation_deleted()`` Deletes presentation specific files, e.g. thumbnails. """ log.info('PresentationController loaded') def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument): """ This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins to populate common data. This method *must* be overridden, like so:: class MyPresentationController(PresentationController): def __init__(self, plugin): PresentationController.__init( self, plugin, 'My Presenter App') :param plugin: Defaults to *None*. The presentationplugin object :param name: Name of the application, to appear in the application :param document_class: """ self.supports = [] self.also_supports = [] self.docs = [] self.plugin = plugin self.name = name self.document_class = document_class self.settings_section = self.plugin.settings_section self.available = None self.temp_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), name) self.thumbnail_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') self.thumbnail_prefix = 'slide' check_directory_exists(self.thumbnail_folder) check_directory_exists(self.temp_folder)
[docs] def enabled(self): """ Return whether the controller is currently enabled """ if Settings().value(self.settings_section + '/' + self.name) == QtCore.Qt.Checked: return self.is_available() else: return False
[docs] def is_available(self): if self.available is None: self.available = self.check_available() return self.available
[docs] def check_available(self): """ Presentation app is able to run on this machine. """ return False
[docs] def start_process(self): """ Loads a running version of the presentation application in the background. """ pass
[docs] def kill(self): """ Called at system exit to clean up any running presentations and close the application. """ log.debug('Kill') self.close_presentation()
[docs] def add_document(self, name): """ Called when a new presentation document is opened. """ document = self.document_class(self, name) self.docs.append(document) return document
[docs] def remove_doc(self, doc=None): """ Called to remove an open document from the collection. """ log.debug('remove_doc Presentation') if doc is None: return if doc in self.docs: self.docs.remove(doc)
[docs] def close_presentation(self): pass
[docs]class TextType(object): """ Type Enumeration for Types of Text to request """ Title = 0 SlideText = 1 Notes = 2