openlp/openlp/plugins/presentations/lib/pptviewcontroller.py

312 lines
12 KiB
Python

# -*- 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 os
import logging
import zipfile
import re
from xml.etree import ElementTree
from openlp.core.common import is_win
if is_win():
from ctypes import cdll
from ctypes.wintypes import RECT
from openlp.core.common import AppLocation
from openlp.core.lib import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
class PptviewController(PresentationController):
"""
Class to control interactions with PowerPoint Viewer Presentations. It creates the runtime Environment , Loads the
and Closes the Presentation. As well as triggering the correct activities based on the users input
"""
log.info('PPTViewController loaded')
def __init__(self, plugin):
"""
Initialise the class
"""
log.debug('Initialising')
self.process = None
super(PptviewController, self).__init__(plugin, 'Powerpoint Viewer', PptviewDocument)
self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm']
def check_available(self):
"""
PPT Viewer is able to run on this machine.
"""
log.debug('check_available')
if not is_win():
return False
return self.check_installed()
if is_win():
def check_installed(self):
"""
Check the viewer is installed.
"""
log.debug('Check installed')
try:
self.start_process()
return self.process.CheckInstalled()
except WindowsError:
return False
def start_process(self):
"""
Loads the PPTVIEWLIB library.
"""
if self.process:
return
log.debug('start PPTView')
dll_path = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
self.process = cdll.LoadLibrary(dll_path)
if log.isEnabledFor(logging.DEBUG):
self.process.SetDebug(1)
def kill(self):
"""
Called at system exit to clean up any running presentations
"""
log.debug('Kill pptviewer')
while self.docs:
self.docs[0].close_presentation()
class PptviewDocument(PresentationDocument):
"""
Class which holds information and controls a single presentation.
"""
def __init__(self, controller, presentation):
"""
Constructor, store information about the file and initialise.
"""
log.debug('Init Presentation PowerPoint')
super(PptviewDocument, self).__init__(controller, presentation)
self.presentation = None
self.ppt_id = None
self.blanked = False
self.hidden = False
def load_presentation(self):
"""
Called when a presentation is added to the SlideController. It builds the environment, starts communication with
the background PptView task started earlier.
"""
log.debug('LoadPresentation')
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
byte_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(byte_file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
return True
else:
return False
def create_thumbnails(self):
"""
PPTviewLib creates large BMP's, but we want small PNG's for consistency. Convert them here.
"""
log.debug('create_thumbnails')
if self.check_thumbnails():
return
log.debug('create_thumbnails proceeding')
for idx in range(self.get_slide_count()):
path = '{folder}\\slide{index}.bmp'.format(folder=self.get_temp_folder(), index=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(r'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(r'notesSlides/notesSlide(.+)\.xml', zip_info.filename)
if match:
index = int(match.group(1)) - 1
node_type = 'body'
list_to_add = notes
# if it is one of our files, index shouldn't be -1
if index >= 0:
with zip_file.open(zip_info) as zipped_file:
tree = ElementTree.parse(zipped_file)
text = ''
nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t",
namespaces=namespaces)
# if we found any content
if nodes and len(nodes) > 0:
for node in nodes:
if len(text) > 0:
text += '\n'
text += node.text
# Let's remove the \n from the titles and
# just add one at the end
if node_type == 'ctrTitle':
text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n'
list_to_add[index] = text
# now let's write the files
self.save_titles_and_notes(titles, notes)
def close_presentation(self):
"""
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
shut down.
"""
log.debug('ClosePresentation')
if self.controller.process:
self.controller.process.ClosePPT(self.ppt_id)
self.ppt_id = -1
self.controller.remove_doc(self)
def is_loaded(self):
"""
Returns true if a presentation is loaded.
"""
if self.ppt_id < 0:
return False
if self.get_slide_count() < 0:
return False
return True
def is_active(self):
"""
Returns true if a presentation is currently active.
"""
return self.is_loaded() and not self.hidden
def blank_screen(self):
"""
Blanks the screen.
"""
self.controller.process.Blank(self.ppt_id)
self.blanked = True
def unblank_screen(self):
"""
Unblanks (restores) the presentation.
"""
self.controller.process.Unblank(self.ppt_id)
self.blanked = False
def is_blank(self):
"""
Returns true if screen is blank.
"""
log.debug('is blank OpenOffice')
return self.blanked
def stop_presentation(self):
"""
Stops the current presentation and hides the output.
"""
self.hidden = True
self.controller.process.Stop(self.ppt_id)
def start_presentation(self):
"""
Starts a presentation from the beginning.
"""
if self.hidden:
self.hidden = False
self.controller.process.Resume(self.ppt_id)
else:
self.controller.process.RestartShow(self.ppt_id)
def get_slide_number(self):
"""
Returns the current slide number.
"""
return self.controller.process.GetCurrentSlide(self.ppt_id)
def get_slide_count(self):
"""
Returns total number of slides.
"""
return self.controller.process.GetSlideCount(self.ppt_id)
def goto_slide(self, slide_no):
"""
Moves to a specific slide in the presentation.
:param slide_no: The slide the text is required for, starting at 1
"""
self.controller.process.GotoSlide(self.ppt_id, slide_no)
def next_step(self):
"""
Triggers the next effect of slide on the running presentation.
"""
self.controller.process.NextStep(self.ppt_id)
def previous_step(self):
"""
Triggers the previous slide on the running presentation.
"""
self.controller.process.PrevStep(self.ppt_id)