← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~marmyshev/openlp/presentation into lp:openlp

 

Dmitriy Marmyshev has proposed merging lp:~marmyshev/openlp/presentation into lp:openlp.

Requested reviews:
  matysek (mzibricky)
  Raoul Snyman (raoul-snyman)
  Andreas Preikschat (googol)
  Dmitriy Marmyshev (marmyshev)
  Tim Bentley (trb143)
Related bugs:
  Bug #836574 in OpenLP: "PowerPoint Presentations with Office for Mac"
  https://bugs.launchpad.net/openlp/+bug/836574

For more details, see:
https://code.launchpad.net/~marmyshev/openlp/presentation/+merge/185973

Add support to load presentations with PowerPoint and Keynote on Mac OS X.
-- 
https://code.launchpad.net/~marmyshev/openlp/presentation/+merge/185973
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file '.bzrignore'
--- .bzrignore	2013-09-14 02:42:12 +0000
+++ .bzrignore	2013-09-17 06:09:33 +0000
@@ -2,6 +2,7 @@
 *.*~
 \#*\#
 *.eric4project
+*.eric5project
 *.ropeproject
 *.e4*
 .eric4project

=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py	2013-08-31 18:17:38 +0000
+++ openlp/core/lib/pluginmanager.py	2013-09-17 06:09:33 +0000
@@ -94,11 +94,6 @@
         present_plugin_dir = os.path.join(self.base_path, 'presentations')
         log.debug('finding plugins in %s at depth %d', str(self.base_path), start_depth)
         for root, dirs, files in os.walk(self.base_path):
-            if sys.platform == 'darwin' and root.startswith(present_plugin_dir):
-                # TODO Presentation plugin is not yet working on Mac OS X.
-                # For now just ignore it. The following code will ignore files from the presentation plugin directory
-                # and thereby never import the plugin.
-                continue
             for name in files:
                 if name.endswith('.py') and not name.startswith('__'):
                     path = os.path.abspath(os.path.join(root, name))

=== modified file 'openlp/core/ui/firsttimeform.py'
--- openlp/core/ui/firsttimeform.py	2013-08-31 18:17:38 +0000
+++ openlp/core/ui/firsttimeform.py	2013-09-17 06:09:33 +0000
@@ -413,10 +413,7 @@
         self._increment_progress_bar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...'))
         self._set_plugin_status(self.songs_check_box, 'songs/status')
         self._set_plugin_status(self.bible_check_box, 'bibles/status')
-        # TODO Presentation plugin is not yet working on Mac OS X.
-        # For now just ignore it.
-        if sys.platform != 'darwin':
-            self._set_plugin_status(self.presentation_check_box, 'presentations/status')
+        self._set_plugin_status(self.presentation_check_box, 'presentations/status')
         self._set_plugin_status(self.image_check_box, 'images/status')
         self._set_plugin_status(self.media_check_box, 'media/status')
         self._set_plugin_status(self.remote_check_box, 'remotes/status')

=== modified file 'openlp/core/ui/firsttimewizard.py'
--- openlp/core/ui/firsttimewizard.py	2013-08-31 18:17:38 +0000
+++ openlp/core/ui/firsttimewizard.py	2013-09-17 06:09:33 +0000
@@ -93,13 +93,10 @@
         self.image_check_box.setChecked(True)
         self.image_check_box.setObjectName('image_check_box')
         self.plugin_layout.addWidget(self.image_check_box)
-        # TODO Presentation plugin is not yet working on Mac OS X.
-        # For now just ignore it.
-        if sys.platform != 'darwin':
-            self.presentation_check_box = QtGui.QCheckBox(self.plugin_page)
-            self.presentation_check_box.setChecked(True)
-            self.presentation_check_box.setObjectName('presentation_check_box')
-            self.plugin_layout.addWidget(self.presentation_check_box)
+        self.presentation_check_box = QtGui.QCheckBox(self.plugin_page)
+        self.presentation_check_box.setChecked(True)
+        self.presentation_check_box.setObjectName('presentation_check_box')
+        self.plugin_layout.addWidget(self.presentation_check_box)
         self.media_check_box = QtGui.QCheckBox(self.plugin_page)
         self.media_check_box.setChecked(True)
         self.media_check_box.setObjectName('media_check_box')
@@ -219,10 +216,7 @@
         self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Custom Slides'))
         self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Bible'))
         self.image_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Images'))
-        # TODO Presentation plugin is not yet working on Mac OS X.
-        # For now just ignore it.
-        if sys.platform != 'darwin':
-            self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Presentations'))
+        self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Presentations'))
         self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)'))
         self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Allow remote access'))
         self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Monitor Song Usage'))

=== added file 'openlp/plugins/presentations/lib/keynotemaccontroller.py'
--- openlp/plugins/presentations/lib/keynotemaccontroller.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/presentations/lib/keynotemaccontroller.py	2013-09-17 06:09:33 +0000
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2012 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Eric Ludin, 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                                             #
+# --------------------------------------------------------------------------- #
+# 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 sys
+import logging
+#import mactypes
+
+import appscript
+
+from openlp.core.lib import Settings,  ScreenList
+from .presentationcontroller import PresentationController, PresentationDocument
+
+
+log = logging.getLogger(__name__)
+
+
+class KeynoteController(PresentationController):
+    """
+    Class to control interactions with KeyNote 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('KeynoteController loaded')
+
+    def __init__(self, plugin):
+        """
+        Initialise the class
+        """
+        log.debug('Initialising')
+        super(KeynoteController,  self).__init__(plugin, 'Keynote', KeynoteDocument)
+        self.supports = ['key']
+        self.process = None
+
+    def check_available(self):
+        """
+        KeyNote is able to run on this machine
+        """
+        log.debug('check_available')
+        try:
+            self.process = appscript.app('Keynote')
+        except appscript.ApplicationNotFoundError:
+            return False
+        self.kill()
+        return True
+    
+    def start_process(self):
+        """
+        Loads KeyNote process
+        """
+        log.debug('start_process')
+        if not self.process or not self.process.isrunning():            
+            self.process = appscript.app('Keynote')
+            self.process.relaunchmode = 'limited' #'always'
+            self.process.launch()
+            self.apply_app_settings()
+        
+    def kill(self):
+        """
+        Called at system exit to clean up any running presentations
+        """
+        log.debug('Kill Keynote')
+        while self.docs and self.process.isrunning():
+            self.docs[0].close_presentation()
+        if self.process is None or not self.process.isrunning():
+            return    
+        try:
+            total = self.process.slideshows()
+            if self.process and total != appscript.k.missing_value and len(total) > 0:
+                return
+            self.process.quit(saving = appscript.k.ask)
+        except appscript.CantLaunchApplicationError:
+            log.debug('Kill Keynote failed')
+        self.process = None
+        
+    def apply_app_settings(self):
+        """
+        Apply settings for Keynote
+        PresentationModeEnableFeedbackDisplay = True if in settings of OpenLP show presenter view = True
+        PresentationModeUseSecondary = 1 if OpenLP monitor for output = Screen 2        
+        """
+        openlp_settings = Settings('openlp.org','OpenLP')        
+        keynote_settings = Settings('apple', 'iWork.Keynote')
+        use_secondary = int(keynote_settings.value('PresentationModeUseSecondary'))
+        monitor = openlp_settings.value( 'core/monitor')
+        override_position = openlp_settings.value( 'core/override position')
+        if not override_position and use_secondary != monitor:            
+            keynote_settings.setValue('PresentationModeUseSecondary', monitor)
+        elif override_position and use_secondary != 0:
+            keynote_settings.setValue('PresentationModeUseSecondary', '0')            
+        show_presenter_view = openlp_settings.value(self.plugin.settings_section + '/show presenter view')
+        keynote_feedback_display = keynote_settings.value('PresentationModeEnableFeedbackDisplay')
+        if show_presenter_view != keynote_feedback_display:
+            keynote_settings.setValue('PresentationModeEnableFeedbackDisplay', show_presenter_view)
+        play_well_with_others = keynote_settings.value('PresentationModePlayWellWithOthers')
+        if not play_well_with_others:
+            keynote_settings.setValue('PresentationModePlayWellWithOthers', True)
+        
+
+class KeynoteDocument(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 Keynote')
+        super(KeynoteDocument, self).__init__(controller, presentation)
+        self.presentation = None
+
+    def load_presentation(self):
+        """
+        Called when a presentation is added to the SlideController.
+        Opens the Keynote file using the process created earlier.
+        """
+        log.debug('load_presentation')        
+        if not self.controller.process or not self.controller.process.isrunning():
+            self.controller.start_process()
+        try:
+            self.controller.process.open(self.filepath)
+        except appscript.CommandError:
+            log.debug('Keynote open failed')
+            return False
+        slideshows = self.controller.process.slideshows()
+        for slideshow in slideshows:
+            path = slideshow.path()
+            if self.filepath == path:
+                self.presentation = slideshow
+                self.create_thumbnails()
+                return True
+        self.presentation = None
+        return False
+
+    def create_thumbnails(self):
+        """
+        Create the thumbnail images for the current presentation.
+        """
+        log.debug('create_thumbnails')
+        if self.check_thumbnails():
+            return
+        thumbnail_folder = self.get_thumbnail_folder()
+        if not os.path.exists(thumbnail_folder): 
+            os.makedirs(thumbnail_folder)
+        try:
+            self.controller.process.open(self.filepath)
+        except appscript.CommandError:
+            log.debug('KeyNote open failed')
+            return
+        temp_dir = self.controller.thumbnail_folder
+        keystroke = os.path.join(temp_dir, 'temp' + self.controller.thumbnail_prefix)
+        self.controller.process.activate()
+        appscript.app('System Events').processes['Keynote'].menu_bars[1].menu_bar_items[3].menus.menu_items[11].click()
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].tool_bars.buttons[4].click()
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].radio_groups[1].radio_buttons[1].click()
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].pop_up_buttons[1].click()
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].pop_up_buttons[1]\
+            .menus.menu_items[2].click()
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].buttons[2].click()
+        appscript.app('System Events').processes['Keynote'].keystroke(keystroke)
+        appscript.app('System Events').processes['Keynote'].key_code(36)
+        appscript.app('System Events').processes['Keynote'].windows[1].sheets[1].buttons[1].click()
+        self.controller.plugin._main_window.activateWindow()
+        slide_no = 0
+        for filename in os.listdir(temp_dir):
+            full_filename = os.path.join(temp_dir, filename)
+            if not os.path.isfile(full_filename) or not filename.endswith('.png') or filename == 'icon.png':
+                continue
+            slide_no = slide_no + 1
+            if not filename.startswith(self.controller.thumbnail_prefix):                    
+                path = os.path.join(thumbnail_folder,
+                    self.controller.thumbnail_prefix + str(slide_no) + '.png')
+                try:
+                    os.rename(full_filename, path)
+                except:
+                    open(path,'w').write(open(full_filename,'r').read())
+                    os.unlink(full_filename)
+
+    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:
+            try:
+                self.presentation.close()
+            except appscript.CommandError:
+                log.debug('Could not close the presentation')
+        self.presentation = None
+        self.controller.remove_doc(self)
+
+    def is_loaded(self):
+        """
+        Returns ``True`` if a presentation is loaded.
+        """
+        log.debug('is_loaded')        
+        try:
+            if not self.controller.process.isrunning():
+                return False
+            windows = self.controller.process.windows()
+            if len(windows) == 0:
+                return False
+            slideshows = self.controller.process.slideshows()
+            if len(slideshows) == 0:
+                return False
+        except:
+            return False
+        for slideshow in slideshows:
+            path = slideshow.path()
+            if self.filepath == path:
+                return True
+        return False
+
+    def is_active(self):
+        """
+        Returns ``True`` if a presentation is currently active.
+        """
+        log.debug('is_active')
+        if not self.is_loaded():
+            return False        
+        try:
+            if not self.presentation.playing():
+                return False
+        except appscript.CommandError:
+            return False
+        return True
+
+    def unblank_screen(self):
+        """
+        Unblanks (restores) the presentation.
+        """
+        log.debug('unblank_screen')
+        if self.is_blank():
+            self.presentation.start_presentation()
+
+    def blank_screen(self):
+        """
+        Blanks the screen.
+        """
+        log.debug('blank_screen')
+        self.presentation.stop_slideshow()
+
+    def is_blank(self):
+        """
+        Returns ``True`` if screen is blank.
+        """
+        log.debug('is_blank')
+        if self.is_active():
+            return not self.presentation.playing()
+        else:
+            return False
+
+    def stop_presentation(self):
+        """
+        Stops the current presentation and hides the output.
+        """
+        log.debug('stop_presentation')        
+        self.presentation.stop_slideshow()
+
+    
+    def start_presentation(self):
+        """
+        Starts a presentation from the beginning.
+        """
+        log.debug('start_presentation')
+        if not self.is_active():
+            self.controller.apply_app_settings()
+            try:
+                self.presentation.start()
+            except appscript.CommandError:
+                return
+            rect = ScreenList().current['size']
+            top = rect.y()    
+            height = rect.height() 
+            left_position = rect.x()
+            width = rect.width()
+            self.controller.process.windows[1].bounds.set([left_position, top, left_position + width, top + height])
+
+    def get_slide_number(self):
+        """
+        Returns the current slide number.
+        """
+        log.debug('get_slide_number')        
+        return self.presentation.current_slide().slide_number()
+
+    def get_slide_count(self):
+        """
+        Returns total number of slides.
+        """
+        log.debug('get_slide_count')
+        slides = self.presentation.slides()
+        return len(slides)
+
+    def goto_slide(self, slideno):
+        """
+        Moves to a specific slide in the presentation.
+        """
+        log.debug('goto_slide') 
+        slide = self.presentation.slides()[slideno-1]
+        self.presentation.show(slide)
+
+    def next_step(self):
+        """
+        Triggers the next effect of slide on the running presentation.
+        """
+        log.debug('next_step')       
+        self.presentation.show_next()
+        if self.get_slide_number() > self.get_slide_count():
+            self.previous_step()
+
+    def previous_step(self):
+        """
+        Triggers the previous slide on the running presentation.
+        """
+        log.debug('previous_step')        
+        self.presentation.show_previous()
+
+    def get_slide_text(self, slide_no):
+        """
+        Returns the text on the slide.
+
+        ``slide_no``
+            The slide the text is required for, starting at 1.
+        """
+        return self.presentation.slides[slide_no].body
+
+    def get_slide_notes(self, slide_no):
+        """
+        Returns the text on the slide.
+
+        ``slide_no``
+            The slide the notes are required for, starting at 1.
+        """
+        return self.presentation.slides[slide_no].notes

=== added file 'openlp/plugins/presentations/lib/powerpointmaccontroller.py'
--- openlp/plugins/presentations/lib/powerpointmaccontroller.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/presentations/lib/powerpointmaccontroller.py	2013-09-17 06:09:33 +0000
@@ -0,0 +1,399 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2012 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Eric Ludin, 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                                             #
+# --------------------------------------------------------------------------- #
+# 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 sys
+import logging
+import shutil
+#import mactypes
+
+import appscript
+
+from openlp.core.lib import Settings,  ScreenList
+from .presentationcontroller import PresentationController, PresentationDocument
+
+
+log = logging.getLogger(__name__)
+
+
+class PowerpointController(PresentationController):
+    """
+    Class to control interactions with PowerPoint 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(u'PowerpointController loaded')
+
+    def __init__(self, plugin):
+        """
+        Initialise the class
+        """
+        log.debug('Initialising')
+        super(PowerpointController,  self).__init__(plugin, 'Powerpoint', PowerpointDocument)
+        self.supports = ['ppt', 'pps', 'pptx', 'ppsx']
+        self.process = None
+
+    def check_available(self):
+        """
+        PowerPoint is able to run on this machine
+        """
+        log.debug('check_available')
+        try:
+            self.process = appscript.app(id='com.microsoft.powerpoint')
+        except appscript.ApplicationNotFoundError:
+            return False
+        self.kill()
+        return True
+    
+    def start_process(self):
+        """
+        Loads PowerPoint process
+        """
+        log.debug('start_process')
+        if not self.process  or not self.process.isrunning():            
+            self.process = appscript.app(id='com.microsoft.powerpoint')
+            self.process.relaunchmode = 'limited' #'always'
+            self.process.launch()
+            self.apply_app_settings()
+        
+    def kill(self):
+        """
+        Called at system exit to clean up any running presentations
+        """
+        log.debug('Kill Powerpoint')
+        while self.docs and self.process.isrunning():
+            self.docs[0].close_presentation()
+        if self.process is None or not self.process.isrunning():
+            return
+        try:
+            total = self.process.presentations()
+            if self.process and total != appscript.k.missing_value and len(total) > 0:
+                return
+            self.process.quit(saving = appscript.k.ask)
+        except appscript.CantLaunchApplicationError:
+            log.debug('Kill Keynote failed')
+        self.process = None
+        
+    def apply_app_settings(self):
+        """
+        Apply settings for PowerPoint
+        14\Options\Options\Save graphics screen heigth = 240
+        14\Options\Options\Save graphics screen width = 320
+        14\Options\Options\Save only current slide graphics = 0
+           
+        """
+        #TODO: add settings for different versions of MS PPT
+        pass
+
+
+class PowerpointDocument(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(PowerpointDocument, self).__init__(controller, presentation)
+        self.presentation = None
+
+    def load_presentation(self):
+        """
+        Called when a presentation is added to the SlideController.
+        Opens the PowerPoint file using the process created earlier.
+        """
+        log.debug('load_presentation')        
+        if not self.controller.process or not self.controller.process.isrunning():
+            self.controller.start_process()
+        try:
+            self.controller.process.open(self.filepath)
+        except appscript.CommandError:
+            log.debug('PPT open failed')
+            return False 
+        presentations = self.controller.process.presentations()
+        for presentation in presentations:
+            full_name = presentation.full_name()
+            full_name = full_name.replace('Macintosh HD', '')
+            full_name = full_name.replace(':', '/')
+            if self.filepath == full_name:
+                self.presentation = presentation
+                self.create_thumbnails()
+                return True
+        return False
+
+    def create_thumbnails(self):
+        """
+        Create the thumbnail images for the current presentation.
+        """
+        log.debug('create_thumbnails')
+        if self.check_thumbnails():
+            return
+        thumbnail_folder = self.get_thumbnail_folder()
+        self.presentation.save(in_=thumbnail_folder, as_=appscript.k.save_as_PNG)
+        slide_no = 0
+        self.move_thumbnails(thumbnail_folder, slide_no, thumbnail_folder)
+    
+    def move_thumbnails(self, thumbnail_folder, slide_no, temp_dir):
+        """
+        Move the thumbnail images from subdirectories to right path.
+        """
+        if not os.path.isdir(temp_dir):
+            return
+        for filename in os.listdir(temp_dir):
+            full_filename = os.path.join(temp_dir, filename)
+            if os.path.isdir(full_filename):
+                self.move_thumbnails(thumbnail_folder, slide_no, full_filename)
+            elif os.path.isfile(full_filename) and filename.endswith('.png') and not filename == 'icon.png':
+                slide_no = slide_no + 1
+                new_name = os.path.join(thumbnail_folder, self.controller.thumbnail_prefix + str(slide_no) + '.png')
+                if full_filename == new_name \
+                    or full_filename.startswith(os.path.join(thumbnail_folder, self.controller.thumbnail_prefix)):
+                    continue
+                try:
+                    os.rename(full_filename, new_name)
+                except:
+                    open(new_name,'w').write(open(full_filename,'r').read())
+                    os.unlink(full_filename)
+        if temp_dir != thumbnail_folder:
+            try:
+                shutil.rmtree(temp_dir)
+            except:
+                pass
+
+    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:
+            try:
+                self.presentation.close()
+            except appscript.CommandError:
+                log.debug('Could not close the presentation')
+        self.presentation = None
+        self.controller.remove_doc(self)
+
+    def is_loaded(self):
+        """
+        Returns ``True`` if a presentation is loaded.
+        """
+        log.debug('is_loaded')        
+        try:
+            if not self.controller.process.isrunning():
+                return False
+            if len(self.controller.process.document_windows()) == 0:
+                return False
+            presentations = self.controller.process.presentations()
+            if len(presentations) == 0:
+                return False         
+        except:
+            return False
+        for presentation in presentations:
+            full_name = presentation.full_name()
+            full_name = full_name.replace('Macintosh HD', '')
+            full_name = full_name.replace(':', '/')
+            if self.filepath == full_name:
+                return True
+        return False
+
+    def is_active(self):
+        """
+        Returns ``True`` if a presentation is currently active.
+        """
+        log.debug('is_active')
+        if not self.is_loaded():
+            return False        
+        try:
+            slide_show_window = self.presentation.slide_show_window
+            if slide_show_window is None:
+                return False
+            slide_state = self.presentation.slide_show_window.slideshow_view.slide_state()
+            if slide_state is None or slide_state == appscript.k.missing_value:
+                return False
+        except:
+            return False
+        return True
+
+    def unblank_screen(self):
+        """
+        Unblanks (restores) the presentation.
+        """
+        log.debug('unblank_screen')        
+        if self.is_blank():
+            settings = self.presentation.slide_show_settings
+            self.presentation.slide_show_window.slideshow_view.slide_state.set(appscript.k.slide_show_state_running)
+            self.presentation.slide_show_window.slideshow_view.go_to_next_slide()
+            self.presentation.slide_show_window.slideshow_view.go_to_previous_slide()
+
+    def blank_screen(self):
+        """
+        Blanks the screen.
+        """
+        log.debug('blank_screen')
+        self.presentation.slide_show_window.slideshow_view.slide_state.set(appscript.k.slide_show_state_black_screen)
+
+    def is_blank(self):
+        """
+        Returns ``True`` if screen is blank.
+        """
+        log.debug('is_blank')
+        if self.is_active() and self.presentation.slide_show_window.slideshow_view\
+            .slide_state() is appscript.k.slide_show_state_black_screen:
+            return True
+        else:
+            return False
+
+    def stop_presentation(self):
+        """
+        Stops the current presentation and hides the output.
+        """
+        log.debug('stop_presentation')        
+        self.presentation.slide_show_window.slideshow_view.exit_slide_show()
+
+    
+    def start_presentation(self):
+        """
+        Starts a presentation from the beginning.
+        """
+        log.debug('start_presentation')
+        rect = ScreenList().current['size']          
+        ppt_settings = self.presentation.slide_show_settings
+        openlp_settings = Settings("openlp.org","OpenLP")
+        show_presenter_view = openlp_settings.value(self.controller.plugin.settings_section + '/show presenter view')
+        override_position = openlp_settings.value( 'general/override position')
+        if show_presenter_view:
+            ppt_settings.show_type.set(appscript.k.slide_show_type_presenter)
+        elif override_position:
+            ppt_settings.show_type.set(appscript.k.slide_show_type_window)
+        else:
+            ppt_settings.show_type.set(appscript.k.slide_show_type_kiosk)
+            #ppt_settings.show_type.set(appscript.k.slide_show_type_speaker) 
+        ppt_window = ppt_settings.run_slide_show()
+        #TODO: set slideshow state = running
+#        if ppt_window.slideshow_view.slide_state() != appscript.k.slide_show_state_running:
+#            ppt_window.slideshow_view.slide_state.set(appscript.k.slide_show_state_running)
+        if not ppt_window or show_presenter_view:
+            return
+        if override_position:
+            top = float(rect.y())
+            height = float(rect.height())
+            left_position = float(rect.x())
+            width = float(rect.width())
+            ppt_window.top.set(top)
+            ppt_window.height.set(height)
+            ppt_window.left_position.set(left_position)
+            ppt_window.width.set(width)
+        else:
+            #TODO: set output monitor for fullscreen mode
+            pass       
+
+    def get_slide_number(self):
+        """
+        Returns the current slide number.
+        """
+        log.debug('get_slide_number')        
+        return self.presentation.slide_show_window.slideshow_view.current_show_position()
+
+    def get_slide_count(self):
+        """
+        Returns total number of slides.
+        """
+        log.debug('get_slide_count')
+        return len(self.presentation.slides())
+
+    def goto_slide(self, slideno):
+        """
+        Moves to a specific slide in the presentation.
+        """
+        log.debug('goto_slide') 
+        #FIXME: this works. but needs to fix this dumb code
+        while self.get_slide_number() != slideno:
+            if self.get_slide_number() < slideno:
+                self.presentation.slide_show_window.slideshow_view.go_to_next_slide()
+            else:
+                self.presentation.slide_show_window.slideshow_view.go_to_previous_slide()
+        self.controller.process.activate()
+
+    def next_step(self):
+        """
+        Triggers the next effect of slide on the running presentation.
+        """
+        log.debug('next_step')
+        #TODO: check if slideshow stoped then restart it from current position
+        if self.is_active():
+            self.presentation.slide_show_window.slideshow_view.go_to_next_slide()
+            self.controller.process.activate()
+            if self.get_slide_number() > self.get_slide_count():
+                self.previous_step()
+
+    def previous_step(self):
+        """
+        Triggers the previous slide on the running presentation.
+        """
+        log.debug('previous_step')
+        #TODO: check if slideshow stoped then restart it from current position
+        if self.is_active():
+            self.presentation.slide_show_window.slideshow_view.go_to_previous_slide()
+            self.controller.process.activate()
+
+    def get_slide_text(self, slide_no):
+        """
+        Returns the text on the slide.
+
+        ``slide_no``
+            The slide the text is required for, starting at 1.
+        """
+        return _get_text_from_shapes(self.presentation.slides[slide_no-1].shapes)
+
+    def get_slide_notes(self, slide_no):
+        """
+        Returns the text on the slide.
+
+        ``slide_no``
+            The slide the notes are required for, starting at 1.
+        """
+        return _get_text_from_shapes(
+            self.presentation.slides[slide_no].notes_page.shapes())
+
+def _get_text_from_shapes(shapes):
+    """
+    Returns any text extracted from the shapes on a presentation slide.
+
+    ``shapes``
+        A set of shapes to search for text.
+    """
+    text = ''
+    for idx in range(len(shapes)):
+        shape = shapes[idx + 1]
+        if shape.has_text_frame():
+            text += shape.text_frame.text_range.content() + '\n'
+    return text

=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py	2013-08-31 18:17:38 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py	2013-09-17 06:09:33 +0000
@@ -30,6 +30,7 @@
 from PyQt4 import QtGui
 
 from openlp.core.lib import Settings, SettingsTab, UiStrings, translate
+import sys
 
 
 class PresentationTab(SettingsTab):
@@ -70,6 +71,11 @@
         self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box)
         self.override_app_check_box.setObjectName('override_app_check_box')
         self.advanced_layout.addWidget(self.override_app_check_box)
+        self.show_presenter_view_check_box = QtGui.QCheckBox(self.advanced_group_box)
+        self.show_presenter_view_check_box.setObjectName(u'show_presenter_view_check_box')        
+        self.advanced_layout.addWidget(self.show_presenter_view_check_box)
+        if not sys.platform.startswith('darwin'):
+            self.show_presenter_view_check_box.setVisible(False)
         self.left_layout.addWidget(self.advanced_group_box)
         self.left_layout.addStretch()
         self.right_layout.addStretch()
@@ -86,6 +92,8 @@
         self.advanced_group_box.setTitle(UiStrings().Advanced)
         self.override_app_check_box.setText(
             translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
+        self.show_presenter_view_check_box.setText(translate('PresentationPlugin.PresentationTab', 
+            'Show Presenter View'))
 
     def set_controller_text(self, checkbox, controller):
         if checkbox.isEnabled():
@@ -102,6 +110,7 @@
             checkbox = self.presenter_check_boxes[controller.name]
             checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
         self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
+        self.show_presenter_view_check_box.setChecked(Settings().value(self.settings_section + '/show presenter view'))
 
     def save(self):
         """
@@ -127,6 +136,10 @@
         if Settings().value(setting_key) != self.override_app_check_box.checkState():
             Settings().setValue(setting_key, self.override_app_check_box.checkState())
             changed = True
+        setting_key = self.settings_section + '/show presenter view'
+        if Settings().value(setting_key) != self.show_presenter_view_check_box.checkState():
+            Settings().setValue(setting_key, self.show_presenter_view_check_box.isChecked())
+            changed = True
         if changed:
             self.settings_form.register_post_process('mediaitem_suffix_reset')
             self.settings_form.register_post_process('mediaitem_presentation_rebuild')

=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py	2013-08-31 18:17:38 +0000
+++ openlp/plugins/presentations/presentationplugin.py	2013-09-17 06:09:33 +0000
@@ -31,6 +31,7 @@
 formats.
 """
 import os
+import sys
 import logging
 
 from PyQt4 import QtCore
@@ -48,6 +49,11 @@
         'presentations/Impress': QtCore.Qt.Checked,
         'presentations/Powerpoint': QtCore.Qt.Checked,
         'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
+        'presentations/Keynote': QtCore.Qt.Checked,
+        'presentations/show presenter view': QtCore.Qt.Checked,
+        'PresentationModeUseSecondary': '',
+        'PresentationModeEnableFeedbackDisplay': False,
+        'PresentationModePlayWellWithOthers': False, 
         'presentations/presentations files': []
 }
 
@@ -126,6 +132,12 @@
         controller_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'presentations', 'lib')
         for filename in os.listdir(controller_dir):
             if filename.endswith('controller.py') and not filename == 'presentationcontroller.py':
+                if not sys.platform.startswith('darwin') and filename == 'powerpointmaccontroller.py':
+                    continue
+                if not sys.platform.startswith('darwin') and filename == 'keynotemaccontroller.py':
+                    continue
+                if not sys.platform.startswith('win') and filename == 'powerpointcontroller.py':
+                    continue
                 path = os.path.join(controller_dir, filename)
                 if os.path.isfile(path):
                     module_name = 'openlp.plugins.presentations.lib.' + os.path.splitext(filename)[0]

=== modified file 'scripts/check_dependencies.py'
--- scripts/check_dependencies.py	2013-09-07 21:29:31 +0000
+++ scripts/check_dependencies.py	2013-09-17 06:09:33 +0000
@@ -47,6 +47,7 @@
     pass
 
 IS_WIN = sys.platform.startswith('win')
+IS_OSX = sys.platform.startswith('darwin')
 
 VERS = {
     'Python': '3.0',
@@ -66,6 +67,11 @@
     'icu',
 ]
 
+# OSX
+OSX_MODULES = [
+    'appscript',
+    'mactypes', 
+]
 MODULES = [
     'PyQt4',
     'PyQt4.QtCore',
@@ -189,6 +195,11 @@
         print('Checking for Windows specific modules...')
         for m in WIN32_MODULES:
             check_module(m)
+    
+    if IS_OSX:
+        print('Checking for OS X specific modules...')
+        for m in OSX_MODULES:
+            check_module(m)
 
     verify_versions()
     verify_pyqt()


Follow ups