← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/powerpointmac into lp:openlp

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/powerpointmac into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/powerpointmac/+merge/220530

This is pretty much a straight copy of Dmitriy's mac presentations branch, but only the PowerPoint bit (thanks to Jonathan Springer for separating them).

I'm not too sure what needs to be done, hence this merge proposal. It works with PPT on Mac.
-- 
https://code.launchpad.net/~raoul-snyman/openlp/powerpointmac/+merge/220530
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/powerpointmac into lp:openlp.
=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py	2014-04-12 20:19:22 +0000
+++ openlp/core/lib/pluginmanager.py	2014-05-21 21:23:25 +0000
@@ -82,11 +82,6 @@
         present_plugin_dir = os.path.join(self.base_path, 'presentations')
         self.log_debug('finding plugins in %s at depth %d' % (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	2014-05-02 06:34:57 +0000
+++ openlp/core/ui/firsttimeform.py	2014-05-21 21:23:25 +0000
@@ -412,10 +412,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	2014-05-03 15:01:43 +0000
+++ openlp/core/ui/firsttimewizard.py	2014-05-21 21:23:25 +0000
@@ -95,13 +95,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')
@@ -222,10 +219,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'))

=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py	2014-03-08 21:23:47 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py	2014-05-21 21:23:25 +0000
@@ -98,7 +98,7 @@
             return True
         if not self.doc.is_loaded():
             if not self.doc.load_presentation():
-                log.warn('Failed to activate %s' % self.doc.filepath)
+                log.warn('Failed to activate %s' % self.doc.file_path)
                 return False
         if self.is_live:
             self.doc.start_presentation()
@@ -109,7 +109,7 @@
         if self.doc.is_active():
             return True
         else:
-            log.warn('Failed to activate %s' % self.doc.filepath)
+            log.warn('Failed to activate %s' % self.doc.file_path)
             return False
 
     def slide(self, slide):

=== 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	2014-05-21 21:23:25 +0000
@@ -0,0 +1,403 @@
+# -*- 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 logging
+import shutil
+
+import appscript
+
+from openlp.core.common import Settings
+from openlp.core.lib import 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('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:
+            self.process = appscript.app(id='com.microsoft.powerpoint')
+            if not self.process.isrunning():
+                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.file_path)
+        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.file_path == 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()
+        temp_dir = self.get_temp_folder()
+        self.presentation.save(in_=temp_dir, as_=appscript.k.save_as_PNG)
+        slide_no = 0
+        self.move_thumbnails(thumbnail_folder, slide_no, temp_dir)
+
+    def move_thumbnails(self, thumbnail_folder, slide_no, temp_dir):
+        """
+        Move the thumbnail images from subdirectories to right path.
+
+        :param thumbnail_folder:
+        :param slide_no:
+        :param temp_dir:
+        """
+        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 += 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.renames(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.file_path == 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():
+            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('core/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.
+
+        :param slideno:
+        """
+        log.debug('goto_slide')
+        #FIXME: this works. but needs to fix this 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.
+
+        :param 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.
+
+        :param 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	2014-04-12 20:19:22 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py	2014-05-21 21:23:25 +0000
@@ -27,6 +27,8 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 
+import sys
+
 from PyQt4 import QtGui
 
 from openlp.core.common import Settings, UiStrings, translate
@@ -39,6 +41,7 @@
     """
     PresentationsTab is the Presentations settings tab in the settings dialog.
     """
+
     def __init__(self, parent, title, visible_title, controllers, icon_path):
         """
         Constructor
@@ -74,6 +77,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)
         # Pdf options
         self.pdf_group_box = QtGui.QGroupBox(self.left_column)
@@ -117,6 +125,8 @@
         self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
         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'))
         self.pdf_program_check_box.setText(
             translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
 
@@ -135,6 +145,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'))
         # load pdf-program settings
         enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
         self.pdf_program_check_box.setChecked(enable_pdf_program)
@@ -168,6 +179,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
         # Save pdf-settings
         pdf_program = self.pdf_program_path.text()
         enable_pdf_program = self.pdf_program_check_box.checkState()

=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py	2014-04-12 20:19:22 +0000
+++ openlp/plugins/presentations/presentationplugin.py	2014-05-21 21:23:25 +0000
@@ -31,6 +31,7 @@
 formats.
 """
 import os
+import sys
 import logging
 
 from PyQt4 import QtCore
@@ -42,14 +43,18 @@
 
 log = logging.getLogger(__name__)
 
-
 __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
                         'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
                         'presentations/pdf_program': '',
                         'presentations/Impress': QtCore.Qt.Checked,
                         'presentations/Powerpoint': QtCore.Qt.Checked,
                         'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
+                        'presentations/Keynote': QtCore.Qt.Checked,
                         'presentations/Pdf': QtCore.Qt.Checked,
+                        'presentations/show presenter view': QtCore.Qt.Checked,
+                        'PresentationModeUseSecondary': '',
+                        'PresentationModeEnableFeedbackDisplay': False,
+                        'PresentationModePlayWellWithOthers': False,
                         'presentations/presentations files': []
                         }
 
@@ -127,6 +132,10 @@
         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('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]
@@ -146,10 +155,10 @@
         Return information about this plugin.
         """
         about_text = translate('PresentationPlugin', '<strong>Presentation '
-                               'Plugin</strong><br />The presentation plugin provides the '
-                               'ability to show presentations using a number of different '
-                               'programs. The choice of available presentation programs is '
-                               'available to the user in a drop down box.')
+                                                     'Plugin</strong><br />The presentation plugin provides the '
+                                                     'ability to show presentations using a number of different '
+                                                     'programs. The choice of available presentation programs is '
+                                                     'available to the user in a drop down box.')
         return about_text
 
     def set_plugin_text_strings(self):

=== modified file 'scripts/check_dependencies.py'
--- scripts/check_dependencies.py	2014-03-31 17:57:15 +0000
+++ scripts/check_dependencies.py	2014-05-21 21:23:25 +0000
@@ -47,6 +47,7 @@
     nose = None
 
 IS_WIN = sys.platform.startswith('win')
+IS_OSX = sys.platform.startswith('darwin')
 
 
 VERS = {
@@ -57,6 +58,8 @@
     # pyenchant 1.6 required on Windows
     'enchant': '1.6' if IS_WIN else '1.3'
 }
+if IS_OSX:
+    VERS.update({'appscript': '1.0.0'})
 
 # pywin32
 WIN32_MODULES = [
@@ -67,6 +70,11 @@
     'icu',
 ]
 
+# OSX
+OSX_MODULES = [
+    'appscript',
+    'mactypes', 
+]
 MODULES = [
     'PyQt4',
     'PyQt4.QtCore',
@@ -178,6 +186,12 @@
         check_vers(enchant.__version__, VERS['enchant'], 'enchant')
     except ImportError:
         print_vers_fail(VERS['enchant'], 'enchant')
+    if IS_OSX:
+        try:
+            import appscript
+            check_vers(appscript.__version__, VERS['appscript'], 'appscript')
+        except ImportError:
+            print_vers_fail(VERS['appscript'], 'appscript')
 
 
 def print_enchant_backends_and_languages():
@@ -229,6 +243,10 @@
         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()
     print_qt_image_formats()
     print_enchant_backends_and_languages()

=== modified file 'scripts/jenkins_script.py' (properties changed: -x to +x)
=== added file 'tests/functional/openlp_plugins/presentations/test_powerpointmaccontroller.py'
--- tests/functional/openlp_plugins/presentations/test_powerpointmaccontroller.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/presentations/test_powerpointmaccontroller.py	2014-05-21 21:23:25 +0000
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+This module contains tests for the PowerpointController class of the Presentations plugin.
+"""
+
+from unittest import TestCase
+
+from tests.functional import MagicMock
+import os
+from openlp.plugins.presentations.lib.powerpointmaccontroller import PowerpointController, PowerpointDocument
+from tests.utils.constants import TEST_RESOURCES_PATH
+
+# TODO: write tests for classes
+# - PowerpointController
+# - PowerpointDocument
+
+
+class TestPowerpointController(TestCase):
+    """
+    Test the PowerpointController class
+    """
+    def setUp(self):
+        """
+        Set up the PowerpointController
+        """
+        presentation_controller = MagicMock()
+        presentation_controller.settings_section = 'presentations'
+        self.powerpoint_controller = PowerpointController(presentation_controller)
+        self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
+        self.doc = PowerpointDocument(self.powerpoint_controller, self.file_name)
+        self.doc.presentation_deleted()
+
+    def tearDown(self):
+        self.doc.controller.kill()
+
+    def verify_installation_test(self):
+        """
+        Test the installation of PPT
+        """
+        # GIVEN: A boolean value set to true
+        # WHEN: We "convert" it to a bool
+        is_installed = self.powerpoint_controller.check_available()
+        # THEN: We should get back a True bool
+        assert is_installed is True, 'The result of check_available() should be True'
+
+    def start_process_test(self):
+        """
+        Test the start_process() of PPT
+        """
+        # GIVEN: A boolean value set to true
+        # WHEN: We "convert" it to a bool
+        self.powerpoint_controller.start_process()
+        # THEN: The process should not be None
+        assert self.powerpoint_controller.process is not None, 'The result of start_process() should be True'
+
+    def load_presentation_test(self):
+        """
+        Test loading a document in PPT
+        """
+        # GIVEN: the filename
+        # WHEN: loading the filename
+        self.doc.load_presentation()
+        result = self.doc.is_loaded()
+        # THEN: result should be true
+        assert result is True, 'The PPT should load document'
+        assert len(self.doc.controller.docs) != 0, 'The powerpoint_controller.docs should have one document.'
+
+    def close_presentation_test(self):
+        """
+        Test closing a document in PPT
+        """
+        # GIVEN: loading the filename
+        self.doc.load_presentation()
+        # WHEN: closing the filename
+        self.doc.close_presentation()
+        result = self.doc.is_loaded()
+        # THEN: result should be true
+        assert result is False, 'The PPT should close document'
+        assert len(self.doc.controller.docs) == 0, 'The controller should have 0 documents.'
+
+    def kill_test(self):
+        """
+        Test running the kill() method with an PowerpointController
+        """
+        # GIVEN: A PowerpointController instance and a list of documents
+        ppt_document = MagicMock()
+        self.powerpoint_controller.start_process()
+        self.powerpoint_controller.docs.append(ppt_document)
+        # WHEN: we run kill()
+        self.powerpoint_controller.kill()
+        # THEN: all docs should be deleted from controller
+        assert len(self.powerpoint_controller.docs) == 0, 'The powerpoint_controller should close all documents.'
+        assert self.powerpoint_controller.process in None, \
+            'The powerpoint_controller should shout down PPT application.'

=== modified file 'tests/interfaces/openlp_core_lib/test_pluginmanager.py'
--- tests/interfaces/openlp_core_lib/test_pluginmanager.py	2014-04-02 19:35:09 +0000
+++ tests/interfaces/openlp_core_lib/test_pluginmanager.py	2014-05-21 21:23:25 +0000
@@ -88,7 +88,7 @@
         plugin_names = [plugin.name for plugin in plugin_manager.plugins]
         assert 'songs' in plugin_names, 'There should be a "songs" plugin.'
         assert 'bibles' in plugin_names, 'There should be a "bibles" plugin.'
-        assert 'presentations' not in plugin_names, 'There should NOT be a "presentations" plugin.'
+        assert 'presentations' in plugin_names, 'There should be a "presentations" plugin.'
         assert 'images' in plugin_names, 'There should be a "images" plugin.'
         assert 'media' in plugin_names, 'There should be a "media" plugin.'
         assert 'custom' in plugin_names, 'There should be a "custom" plugin.'

=== added file 'tests/resources/presentations/test.pptx'
Binary files tests/resources/presentations/test.pptx	1970-01-01 00:00:00 +0000 and tests/resources/presentations/test.pptx	2014-05-21 21:23:25 +0000 differ

Follow ups