diff --git a/appveyor.yml b/appveyor.yml
index ed622df04..d54bd9ff6 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -34,7 +34,7 @@ install:
# Install Windows only dependencies
- cmd: python -m pip install pyodbc pypiwin32
# Mac only dependencies
- - sh: python -m pip install pyobjc-core pyobjc-framework-Cocoa
+ - sh: python -m pip install pyobjc-core pyobjc-framework-Cocoa py-applescript
build: off
diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py
index b167ed3ab..ce84416cf 100644
--- a/openlp/core/common/settings.py
+++ b/openlp/core/common/settings.py
@@ -286,6 +286,8 @@ class Settings(QtCore.QSettings):
'presentations/Impress': QtCore.Qt.Checked,
'presentations/Powerpoint': QtCore.Qt.Checked,
'presentations/Pdf': QtCore.Qt.Checked,
+ 'presentations/Keynote': QtCore.Qt.Checked,
+ 'presentations/PowerPointMac': QtCore.Qt.Checked,
'presentations/presentations files': [],
'presentations/thumbnail_scheme': '',
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
diff --git a/openlp/plugins/presentations/lib/applescriptbasecontroller.py b/openlp/plugins/presentations/lib/applescriptbasecontroller.py
new file mode 100644
index 000000000..3b2ec3fa7
--- /dev/null
+++ b/openlp/plugins/presentations/lib/applescriptbasecontroller.py
@@ -0,0 +1,310 @@
+# -*- coding: utf-8 -*-
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2020 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, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# 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, see . #
+##########################################################################
+"""
+This module is a base to be used for Mac OS X presentation modules using applescript
+to control presentation application, such as keynote and powerpoint for mac
+"""
+import logging
+import applescript
+
+from openlp.core.common import is_macosx
+from openlp.core.display.screens import ScreenList
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
+
+
+log = logging.getLogger(__name__)
+
+DEFAULT_APPLESCRIPT = """
+on check_available()
+ return false
+end check_available
+"""
+
+
+class AppleScriptBaseController(PresentationController):
+ """
+ Class to control interactions with a Presentation program like Keynote or Powerpoint.
+ It uses py-applescript to compile and execute applescript to Load and Close the Presentation.
+ As well as triggering the correct activities based on the users input.
+ """
+ log.info('ApplescriptController loaded')
+
+ base_class = True
+
+ def __init__(self, plugin, name, doc):
+ """
+ Initialise the class
+ """
+ super(AppleScriptBaseController, self).__init__(plugin, name, doc)
+ # Script expected to be overwritten by subclasses
+ self.applescript = applescript.AppleScript(DEFAULT_APPLESCRIPT)
+
+ def check_available(self):
+ """
+ Program is able to run on this machine.
+ """
+ log.debug('check_available')
+ if not is_macosx():
+ return False
+ try:
+ return self.applescript.call('check_available')
+ except Exception:
+ log.exception('script execution failed')
+ return False
+
+ def start_process(self):
+ """
+ Loads the presentation application
+ """
+ log.debug('start_process')
+ try:
+ self.applescript.call('start_application')
+ except Exception:
+ log.exception('script execution failed')
+
+ def kill(self):
+ """
+ Called at system exit to clean up any running presentations.
+ """
+ log.debug('Kill presentation program')
+ while self.docs:
+ self.docs[0].close_presentation()
+ try:
+ self.applescript.call('stop_application')
+ except Exception:
+ log.exception('script execution failed')
+
+
+class AppleScriptBaseDocument(PresentationDocument):
+ """
+ Class which holds information and controls a single presentation.
+ """
+
+ def __init__(self, controller, document_path):
+ """
+ Constructor, store information about the file and initialise.
+
+ :param controller:
+ :param pathlib.Path document_path: Path to the document to load
+ :rtype: None
+ """
+ log.debug('Init Presentation Applescript')
+ super().__init__(controller, document_path)
+ self.presentation_loaded = False
+ self.is_blanked = False
+ self.slide_count = 0
+
+ def load_presentation(self):
+ """
+ Called when a presentation is added to the SlideController. Opens the presentation file.
+ """
+ log.debug('load_presentation')
+ # check if there already exists thumbnails etc. If so skip loading the presentation.
+ files = self.get_thumbnail_folder().glob('*')
+ if len(list(files)) > 0:
+ self.presentation_loaded = True
+ return self.presentation_loaded
+ try:
+ self.controller.applescript.call('create_thumbs', str(self.file_path), str(self.get_thumbnail_folder()))
+ self.presentation_loaded = True
+ except Exception:
+ log.exception('script execution failed')
+ self.presentation_loaded = False
+ return self.presentation_loaded
+
+ def create_thumbnails(self):
+ """
+ Create the thumbnail images for the current presentation.
+ """
+ log.debug('create_thumbnails')
+ # No need to do anything, already created thumbs in load_presentation
+
+ def close_presentation(self):
+ """
+ Close presentation and clean up objects. This is triggered by a new object being added to SlideController or
+ OpenLP being shut down.
+ """
+ log.debug('close_presentation')
+ if self.presentation_loaded:
+ try:
+ self.controller.applescript.call('close_presentation')
+ except Exception:
+ log.exception('script execution failed')
+ self.presentation_loaded = False
+ self.controller.remove_doc(self)
+
+ def is_loaded(self):
+ """
+ Returns ``True`` if a presentation is loaded.
+ """
+ self.presentation_loaded = False
+ log.debug('is_loaded')
+ try:
+ self.presentation_loaded = self.controller.applescript.call('is_loaded', str(self.file_path))
+ except Exception:
+ log.exception('script execution failed')
+ return self.presentation_loaded
+
+ def is_active(self):
+ """
+ Returns ``True`` if a presentation is currently active.
+ """
+ log.debug('is_active')
+ if not self.is_loaded():
+ return False
+ try:
+ return self.controller.applescript.call('is_active')
+ except Exception:
+ log.exception('script execution failed')
+ return False
+
+ def unblank_screen(self):
+ """
+ Unblanks (restores) the presentation.
+ """
+ log.debug('unblank_screen')
+ try:
+ self.controller.applescript.call('unblank')
+ except Exception:
+ log.exception('script execution failed')
+ self.is_blanked = False
+
+ def blank_screen(self):
+ """
+ Blanks the screen.
+ """
+ log.debug('blank_screen')
+ try:
+ self.controller.applescript.call('blank')
+ except Exception:
+ log.exception('script execution failed')
+
+ def is_blank(self):
+ """
+ Returns ``True`` if screen is blank.
+ """
+ log.debug('is_blank')
+ return self.is_blanked
+
+ def stop_presentation(self):
+ """
+ Stops the current presentation and hides the output. Used when blanking to desktop.
+ """
+ try:
+ self.controller.applescript.call('stop_presentation')
+ except Exception:
+ log.exception('script execution failed')
+
+ def start_presentation(self):
+ """
+ Starts a presentation from the beginning.
+ """
+ log.debug('start_presentation')
+ try:
+ fullscreen = (ScreenList().current.custom_geometry is None)
+ size = ScreenList().current.display_geometry
+ self.controller.applescript.call('start_presentation', str(self.file_path), size.top(), size.left(),
+ size.width(), size.height(), fullscreen)
+ except Exception:
+ log.exception('script execution failed')
+
+ def get_slide_number(self):
+ """
+ Returns the current slide number.
+ """
+ log.debug('get_slide_number')
+ ret = 0
+ try:
+ ret = int(self.controller.applescript.call('get_current_slide'))
+ except Exception:
+ log.exception('script execution failed')
+ return ret
+
+ def get_slide_count(self):
+ """
+ Returns total number of slides.
+ """
+ log.debug('get_slide_count')
+ return self.slide_count
+
+ 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
+ """
+ log.debug('goto_slide')
+ try:
+ self.controller.applescript.call('goto_slide', slide_no)
+ except Exception:
+ log.exception('script execution failed')
+
+ def next_step(self):
+ """
+ Triggers the next effect of slide on the running presentation.
+ """
+ log.debug('next_step')
+ past_end = False
+ try:
+ self.controller.applescript.call('next_slide')
+ # TODO: detect past end
+ except Exception:
+ log.exception('script execution failed')
+ return past_end
+
+ def previous_step(self):
+ """
+ Triggers the previous slide on the running presentation.
+ """
+ log.debug('previous_step')
+ before_start = False
+ try:
+ self.controller.applescript.call('previous_slide')
+ # TODO: detect before start
+ except Exception:
+ log.exception('script execution failed')
+ return before_start
+
+ 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 ''
+
+ 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 ''
+
+ 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
+ """
+ # Already done in create thumbs
+ pass
diff --git a/openlp/plugins/presentations/lib/keynotecontroller.py b/openlp/plugins/presentations/lib/keynotecontroller.py
new file mode 100644
index 000000000..76b086396
--- /dev/null
+++ b/openlp/plugins/presentations/lib/keynotecontroller.py
@@ -0,0 +1,307 @@
+# -*- coding: utf-8 -*-
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2020 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, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# 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, see . #
+##########################################################################
+"""
+This module is for controlling keynote.
+"""
+import logging
+import shutil
+import applescript
+
+from openlp.plugins.presentations.lib.applescriptbasecontroller import AppleScriptBaseController,\
+ AppleScriptBaseDocument
+
+
+log = logging.getLogger(__name__)
+
+KEYNOTE_APPLESCRIPT = """
+on check_available()
+ try
+ set appID to "com.apple.iWork.Keynote"
+ if exists application id appID then
+ return true
+ end if
+ return false
+ on error
+ return false
+ end try
+end check_available
+
+on get_version()
+ return (version of application "Keynote")
+end get_version
+
+on start_application()
+ tell application "Keynote"
+ launch
+ end tell
+end start_application
+
+on close_presentation()
+ try
+ tell application "Keynote"
+ -- close presentation
+ close front document
+ end tell
+ end try
+end close_presentation
+
+on create_thumbs(documentName, outputPath)
+ set outputPathHfs to POSIX file outputPath
+ tell application "Keynote"
+ if playing is true then tell the front document to stop
+ -- Open document
+ open documentName
+ -- hide keynote
+ tell application "System Events"
+ set visible of application process "Keynote" to false
+ end tell
+ -- Export slides to png
+ export front document as slide images to file outputPathHfs with properties ¬
+ {image format:PNG, skipped slides:false}
+ -- Get the Presentation Notes and slide title
+ set the slideTitles to ""
+ tell the front document
+ repeat with i from 1 to the count of slides
+ tell slide i
+ set noteFilename to outputPath & "/slideNotes" & (slide number as string) & ".txt"
+ if skipped is false then
+ -- Save the notes to file
+ set noteText to presenter notes of it
+ set noteFile to open for access noteFilename with write permission
+ write noteText to noteFile
+ close access noteFile
+ -- if title is available, save it
+ if title showing is true then
+ set the slideTitle to object text of default title item
+ set the slideTitles to slideTitles & slideTitle & return
+ else
+ set the slideTitles to "slide " & (slide number as string) & return
+ end if
+ end if
+ end tell
+ end repeat
+ end tell
+ -- Save the titles to file
+ set titlesFilename to outputPath & "/titles.txt"
+ set titlesFile to open for access titlesFilename with write permission
+ write slideTitles to titlesFile
+ close access titlesFile
+ -- close presentation
+ close front document
+ end tell
+end create_thumbs
+--create_thumbs("/Users/tgc/Downloads/keynote-pres.key", "/Users/tgc/Downloads/tmp/")
+
+on start_presentation(documentName, new_top, new_left_pos, new_width, new_height, fullscreen)
+ tell application "Keynote"
+ if playing is true then tell the front document to stop
+ -- Open document
+ open documentName
+ --start front document from the first slide of front document
+ --activate
+ end tell
+ -- There seems to be no applescript command to start presentation in window mode
+ -- so we that by faking mouse clicks (requires accessability rights)
+ tell application "System Events"
+ tell process "Keynote"
+ click (menu bar item 10 of menu bar 1)
+ click menu item 2 of menu 1 of menu bar item 10 of menu bar 1
+ end tell
+ end tell
+ tell application "Keynote"
+ -- set the size and postion on presentation window
+ if fullscreen is true then
+ -- move window to right screen
+ set bounds of front window to {new_top, new_left_pos, new_width, new_height}
+ -- put it in fullscreen mode
+ tell application "System Events" to tell process "Keynote"
+ set value of attribute "AXFullScreen" of front window to true
+ end tell
+ else
+ set bounds of front window to {new_top, new_left_pos, new_width, new_height}
+ end if
+ activate
+ end tell
+end start_presentation
+--start_presentation("Users:solva_a:Downloads:tomas:applescript:keynote-pres.key", 10, 10, 640, 480, false)
+
+on stop_presentation()
+ tell application "Keynote"
+ --activate
+ -- stop presenting
+ if playing is true then tell the front document to stop
+ -- close presentation
+ close front document
+ -- hide keynote
+ tell application "System Events"
+ set visible of application process "Keynote" to false
+ end tell
+ end tell
+end stop_presentation
+
+on is_loaded(documentPath)
+ set documentOldStylePath to POSIX file documentPath as alias
+ tell application "Keynote"
+ return (file of front document as string is (documentOldStylePath as string))
+ end tell
+end is_loaded
+
+on is_active()
+ tell application "Keynote"
+ -- print(?) true/false playing state
+ return playing
+ end tell
+end is_active
+
+
+on next_slide()
+ tell application "Keynote"
+ --activate
+ if playing is false then start front document from the first slide of front document
+ show next
+ -- Print(?) slide number
+ slide number
+ end tell
+end next_slide
+
+on previous_slide()
+ tell application "Keynote"
+ if playing is false then start front document from the first slide of front document
+ show previous
+ -- Print(?) slide number
+ slide number
+ end tell
+end previous_slide
+
+on goto_slide(slideNumber)
+ tell application "Keynote"
+ if playing is false then
+ start front document from slide slideNumber of front document
+ else
+ set the current slide to the first slide whose slide number is slideNumber
+ end if
+ end tell
+end goto_slide
+
+on get_current_slide()
+ tell application "Keynote"
+ -- Print(?) slide number
+ slide number
+ end tell
+end get_current_slide
+
+on blank()
+ tell application "Keynote"
+ tell application "System Events" to keystroke "b"
+ end tell
+end blank
+
+on unblank()
+ tell application "Keynote"
+ tell application "System Events" to keystroke "b"
+ end tell
+end blank
+"""
+
+
+class KeynoteController(AppleScriptBaseController):
+ """
+ Class to control interactions with Keynote.
+ As well as triggering the correct activities based on the users input.
+ """
+ log.info('KeynoteController loaded')
+ base_class = False
+
+ def __init__(self, plugin):
+ """
+ Initialise the class
+ """
+ log.debug('Initialising')
+ super(KeynoteController, self).__init__(plugin, 'Keynote', KeynoteDocument)
+ # Compiled script expected to be set by subclasses
+ try:
+ self.applescript = applescript.AppleScript(KEYNOTE_APPLESCRIPT)
+ except applescript.ScriptError:
+ log.exception('Compilation of Keynote applescript failed')
+ self.supports = ['key']
+ self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm']
+
+ def check_available(self):
+ """
+ Program is able to run on this machine.
+ """
+ ret = super().check_available()
+ if ret:
+ # Powerpoint is available! now check if the version is 10.1 or above
+ try:
+ version = self.applescript.call('get_version')
+ except Exception:
+ log.exception('script execution failed')
+ return False
+ try:
+ versions = version.split('.')
+ major_version = int(versions[0])
+ if major_version == 10:
+ minor_version = int(versions[1])
+ if minor_version >= 1:
+ return True
+ elif major_version > 10:
+ return True
+ except ValueError:
+ pass
+ return False
+
+
+class KeynoteDocument(AppleScriptBaseDocument):
+ """
+ Class which holds information and controls a single presentation.
+ """
+
+ def __init__(self, controller, document_path):
+ """
+ Constructor, store information about the file and initialise.
+
+ :param controller:
+ :param pathlib.Path document_path: Path to the document to load
+ :rtype: None
+ """
+ log.debug('Init Presentation KeynoteDocument')
+ super().__init__(controller, document_path)
+ self.presentation_loaded = False
+ self.is_blank = False
+
+ def load_presentation(self):
+ ret = super().load_presentation()
+ if ret:
+ # Keynote will name the thumbnails in the format: .001.png, we need to convert to slide1.png
+ old_images = list(self.get_thumbnail_folder().glob('*.png'))
+ # if the convertion was already done, don't do it again
+ if self.get_thumbnail_folder() / 'slide1.png' in old_images:
+ return True
+ self.slide_count = len(old_images)
+ for old_image in old_images:
+ # get the image name in format 001.png
+ number_name = old_image.name.split('.', 1)[1]
+ # get the image name in format 1.png
+ number_name = number_name.lstrip('0')
+ new_name = 'slide' + number_name
+ shutil.move(old_image, self.get_thumbnail_folder() / new_name)
+ return ret
diff --git a/openlp/plugins/presentations/lib/powerpointmaccontroller.py b/openlp/plugins/presentations/lib/powerpointmaccontroller.py
new file mode 100644
index 000000000..1662a7f32
--- /dev/null
+++ b/openlp/plugins/presentations/lib/powerpointmaccontroller.py
@@ -0,0 +1,347 @@
+# -*- coding: utf-8 -*-
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2020 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, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# 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, see . #
+##########################################################################
+"""
+This module is for controlling keynote.
+"""
+import logging
+import applescript
+
+try:
+ import fitz
+ PYMUPDF_AVAILABLE = True
+except ImportError:
+ PYMUPDF_AVAILABLE = False
+
+from openlp.plugins.presentations.lib.applescriptbasecontroller import AppleScriptBaseController,\
+ AppleScriptBaseDocument
+
+
+log = logging.getLogger(__name__)
+
+
+POWERPOINT_MAC_APPLESCRIPT = """
+on check_available()
+ set appID to "com.microsoft.PowerPoint"
+ set doesExist to false
+ try
+ tell application "Finder" to get application file id appID
+ set doesExist to true
+ end try
+ return doesExist
+end check_available
+
+on get_version()
+ return (version of application "Microsoft PowerPoint")
+end get_version
+
+on start_application()
+ tell application "Microsoft PowerPoint"
+ launch
+ -- Wait for powerpoint to start
+ tell application "System Events"
+ repeat while true
+ set PowerPointUp to (count of (every process whose name is "Microsoft PowerPoint")) > 0
+ if PowerPointUp then
+ exit repeat
+ else
+ delay 0.2
+ end if
+ end repeat
+ end tell
+ end tell
+end start_application
+
+-- helper function to write text to files
+on write_file(fileName, theData)
+ set fileDescriptor to open for access the fileName with write permission
+ write theData to fileDescriptor as «class utf8»
+ close access fileDescriptor
+end write_file
+
+-- helper function to create empty file
+on create_empty_file(f)
+ write_file(f, "")
+ return (POSIX path of f) as POSIX file
+end create_empty_file
+
+on create_thumbs(documentName, outputPath)
+ tell application "Microsoft PowerPoint"
+ -- Open document
+ open documentName
+ -- wait for document to load
+ repeat until document window 1 exists
+ delay 0.2
+ end repeat
+ -- Export slides to jpg
+ --save active presentation in outputPath as save as PNG
+ -- Export slides to pdf
+ save active presentation in my create_empty_file(outputPath & "/tmp.pdf") as save as PDF
+ -- Get the Presentation Notes and slide title
+ set the slideTitles to ""
+ set titleTypes to {placeholder type title placeholder, placeholder type center title placeholder, ¬
+ placeholder type vertical title placeholder}
+ repeat with tSlide in (get slides of active presentation)
+ -- Only handle non-hidden slides
+ if hidden of slide show transition of tSlide is false then
+ --text = slide.Shapes.Title.TextFrame.TextRange.Text
+ -- Get the title of the current slide
+ repeat with currentShape in (get shapes of tSlide)
+ tell currentShape to if has text frame then tell its text frame to if has text then
+ set aType to placeholder type of currentShape
+ if aType is not missing value and aType is in titleTypes then
+ set slideTitles to slideTitles & (content of its text range) & return
+ exit repeat
+ end if
+ end if
+ end repeat
+ set the noteText to ""
+ repeat with t_shape in (get shapes of notes page of tSlide)
+ tell t_shape to if has text frame then tell its text frame to if has text then
+ -- get the note of this slide
+ set the noteText to (content of its text range)
+ exit repeat
+ end if
+ end repeat
+ -- Save note to file
+ set noteFilename to outputPath & "/slideNotes" & (slide index of tSlide as string) & ".txt"
+ my write_file(noteFilename, noteText)
+ end if
+ end repeat
+ -- Save the titles to file
+ set titlesFilename to outputPath & "/titles.txt"
+ my write_file(titlesFilename, slideTitles)
+ close active presentation
+ end tell
+end create_thumbs
+
+on close_presentation()
+ tell application "Microsoft PowerPoint"
+ -- close presentation
+ close active presentation
+ -- Hide powerpoint
+ tell application "System Events"
+ set visible of application process "Microsoft PowerPoint" to false
+ end tell
+ end tell
+end close_presentation
+
+on start_presentation(documentName, new_top, new_left_pos, new_width, new_height, fullscreen)
+ tell application "Microsoft PowerPoint"
+ -- Open document
+ open documentName
+ -- wait for document to load
+ repeat until document window 1 exists
+ delay 0.2
+ end repeat
+ -- disable presenter console
+ set showpresenterview of slide show settings of active presentation to 0
+ -- set presentation to show presentation in window
+ set show type of slide show settings of active presentation to slide show type window
+ -- start presentation
+ run slide show slide show settings of active presentation
+ -- set the size and postion on presentation window
+ set top of slide show window of active presentation to new_top
+ set left position of slide show window of active presentation to new_left_pos
+ if fullscreen is true then
+ -- put it in fullscreen mode
+ tell application "System Events" to tell process "Microsoft PowerPoint"
+ set value of attribute "AXFullScreen" of front window to true
+ end tell
+ else
+ set height of slide show window of active presentation to new_height
+ set width of slide show window of active presentation to new_width
+ end if
+ -- move to the front
+ activate
+ end tell
+end start_presentation
+
+on stop_presentation()
+ tell application "Microsoft PowerPoint"
+ -- stop presenting - self.presentation.SlideShowWindow.View.Exit()
+ exit slide show slide show view of slide show window 1
+ -- close presentation
+ close active presentation
+ -- Hide powerpoint
+ tell application "System Events"
+ set visible of application process "Microsoft PowerPoint" to false
+ end tell
+ end tell
+end stop_presentation
+
+on is_loaded(documentPath)
+ tell application "Microsoft PowerPoint"
+ return (full name of active presentation is (documentPath as string))
+ end tell
+end is_loaded
+
+on is_active()
+ tell application "Microsoft PowerPoint"
+ -- when the presentation window is out of focus it is paused, so check for both paused and running
+ return (slide state of slide show view of slide show window of active presentation is ¬
+ slide show state running) ¬
+ or (slide state of slide show view of slide show window of active presentation is ¬
+ slide show state paused)
+ end tell
+end is_active
+
+on next_slide()
+ tell application "Microsoft PowerPoint"
+ --activate
+ -- self.presentation.SlideShowWindow.View.Next()
+ go to next slide slide show view of slide show window 1
+ -- Print(?) slide number
+ -- slide number of slide of slideshow view of slide show window of active presentation
+ end tell
+end next_slide
+
+on previous_slide()
+ tell application "Microsoft PowerPoint"
+ --activate
+ go to previous slide slide show view of slide show window 1
+ -- Print(?) slide number
+ -- self.presentation.SlideShowWindow.View.Slide.SlideNumber
+ -- slide number of slide of view of slide show window of active presentation
+ end tell
+end previous_slide
+
+on goto_slide(slideNumber)
+ tell application "Microsoft PowerPoint"
+ activate
+ -- self.presentation.SlideShowWindow.View.GotoSlide(slideNumber)
+ --go to slide slideNumber of slide show window of active presentation
+ -- there seems to be no applescipt-way of going to a specific slide, so send keystroke
+ tell application "System Events"
+ keystroke (slideNumber as string)
+ key code 36
+ end tell
+ end tell
+end goto_slide
+
+on get_current_slide()
+ tell application "Microsoft PowerPoint"
+ --activate
+ -- Print(?) slide number
+ return slide number of slide of slide show view of slide show window of active presentation
+ end tell
+end get_current_slide
+
+on blank()
+ tell application "Microsoft PowerPoint"
+ --activate
+ -- self.presentation.SlideShowWindow.View.State = 3
+ --set state of slide of view of slide show window of active presentation to 3
+ set slide state of slide show view of slide show window 1 to slide show state black screen
+ end tell
+end blank
+
+on unblank()
+ tell application "Microsoft PowerPoint"
+ --activate
+ -- self.presentation.SlideShowWindow.View.State = 1
+ set slide state of slide show view of slide show window 1 to slide show state running
+ end tell
+end unblank
+"""
+
+
+class PowerPointMacController(AppleScriptBaseController):
+ """
+ Class to control interactions with a Powerpoint for Mac.
+ As well as triggering the correct activities based on the users input.
+ """
+ log.info('PowerPointMacController loaded')
+ base_class = False
+
+ def __init__(self, plugin):
+ """
+ Initialise the class
+ """
+ log.debug('Initialising')
+ super(PowerPointMacController, self).__init__(plugin, 'PowerPointMac', PowerPointMacDocument)
+ # Compiled script expected to be set by subclasses
+ try:
+ self.applescript = applescript.AppleScript(POWERPOINT_MAC_APPLESCRIPT)
+ except applescript.ScriptError:
+ log.exception('Compilation of Powerpoint applescript failed')
+ self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm']
+ self.also_supports = ['odp']
+
+ def check_available(self):
+ """
+ Program is able to run on this machine.
+ """
+ ret = super().check_available()
+ if ret:
+ # Powerpoint is available! now check if the version is 15 (Powerpoint 2016) or above
+ try:
+ version = self.applescript.call('get_version')
+ except Exception:
+ log.exception('script execution failed')
+ return False
+ try:
+ major_version = int(version.split('.')[0])
+ except ValueError:
+ return False
+ if major_version >= 15:
+ return True
+
+
+class PowerPointMacDocument(AppleScriptBaseDocument):
+ """
+ Class which holds information and controls a single presentation.
+ """
+
+ def __init__(self, controller, document_path):
+ """
+ Constructor, store information about the file and initialise.
+
+ :param controller:
+ :param pathlib.Path document_path: Path to the document to load
+ :rtype: None
+ """
+ log.debug('Init Presentation Applescript')
+ super().__init__(controller, document_path)
+ self.presentation_loaded = False
+ self.is_blank = False
+
+ def load_presentation(self):
+ ret = super().load_presentation()
+ if ret:
+ # we make powerpoint export to PDF and then convert the pdf to thumbnails
+ if PYMUPDF_AVAILABLE:
+ pdf_file = self.get_thumbnail_folder() / 'tmp.pdf'
+ # if the pdf file does not exists anymore it is assume the convertion has been done
+ if not pdf_file.exists():
+ return True
+ log.debug('converting pdf to png thumbnails using PyMuPDF')
+ pdf = fitz.open(str(pdf_file))
+ for i, page in enumerate(pdf, start=1):
+ src_size = page.bound().round()
+ # keep aspect ratio
+ scale = min(640 / src_size.width, 480 / src_size.height)
+ m = fitz.Matrix(scale, scale)
+ page.getPixmap(m, alpha=False).writeImage(str(self.get_thumbnail_folder() /
+ 'slide{num}.png'.format(num=i)))
+ pdf.close()
+ # delete pdf
+ pdf_file.unlink()
+ return ret
diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py
index bbfe5cd62..c4b50824f 100644
--- a/openlp/plugins/presentations/presentationplugin.py
+++ b/openlp/plugins/presentations/presentationplugin.py
@@ -120,6 +120,10 @@ class PresentationPlugin(Plugin):
extension_loader(glob_pattern, ['presentationcontroller.py'])
controller_classes = PresentationController.__subclasses__()
for controller_class in controller_classes:
+ # Don't use classes marked as base
+ if hasattr(controller_class, 'base_class') and controller_class.base_class:
+ controller_classes.extend(controller_class.__subclasses__())
+ continue
controller = controller_class(self)
self.register_controllers(controller)
return bool(self.controllers)