openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #22134
[Merge] lp:~marmyshev/openlp/presentation into lp:openlp
Dmitriy Marmyshev has proposed merging lp:~marmyshev/openlp/presentation into lp:openlp.
Requested reviews:
Dmitriy Marmyshev (marmyshev)
matysek (mzibricky)
Raoul Snyman (raoul-snyman)
Andreas Preikschat (googol)
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/192798
Add support to load presentations with PowerPoint and Keynote on Mac OS X.
--
https://code.launchpad.net/~marmyshev/openlp/presentation/+merge/192798
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file '.bzrignore'
--- .bzrignore 2013-09-14 02:42:12 +0000
+++ .bzrignore 2013-10-26 22:59:24 +0000
@@ -2,6 +2,7 @@
*.*~
\#*\#
*.eric4project
+*.eric5project
*.ropeproject
*.e4*
.eric4project
=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py 2013-10-13 13:51:13 +0000
+++ openlp/core/lib/pluginmanager.py 2013-10-26 22:59:24 +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-10-13 21:07:28 +0000
+++ openlp/core/ui/firsttimeform.py 2013-10-26 22:59:24 +0000
@@ -414,10 +414,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-10-13 21:07:28 +0000
+++ openlp/core/ui/firsttimewizard.py 2013-10-26 22:59:24 +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-10-26 22:59:24 +0000
@@ -0,0 +1,360 @@
+# -*- 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 appscript
+
+from openlp.core.common import Settings
+from openlp.core.lib import 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-10-26 22:59:24 +0000
@@ -0,0 +1,397 @@
+# -*- 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(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():
+ 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 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-10-13 20:36:42 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py 2013-10-26 22:59:24 +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
@@ -71,6 +73,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()
@@ -87,6 +94,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():
@@ -103,6 +112,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):
"""
@@ -128,6 +138,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-10-13 21:07:28 +0000
+++ openlp/plugins/presentations/presentationplugin.py 2013-10-26 22:59:24 +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': []
}
@@ -125,6 +131,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-10-03 19:56:12 +0000
+++ scripts/check_dependencies.py 2013-10-26 22:59:24 +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',
@@ -177,6 +185,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():
@@ -228,6 +242,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()
=== added file 'tests/functional/openlp_plugins/presentations/test_keynotemaccontroller.py'
--- tests/functional/openlp_plugins/presentations/test_keynotemaccontroller.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/presentations/test_keynotemaccontroller.py 2013-10-26 22:59:24 +0000
@@ -0,0 +1,93 @@
+"""
+This module contains tests for the KeynoteController class of the Presentations plugin.
+"""
+
+from unittest import TestCase
+
+from tests.functional import MagicMock
+import os
+from openlp.plugins.presentations.lib.keynotemaccontroller import KeynoteController, KeynoteDocument
+
+RESOURCES_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
+
+# TODO: write tests for classes
+# - KeynoteController
+# - KeynoteDocument
+
+class TestKeynoteController(TestCase):
+ """
+ Test the KeynoteController class
+ """
+ def setUp(self):
+ """
+ Set up the KeynoteController
+ """
+ presentation_controller = MagicMock()
+ presentation_controller.settings_section = 'presentations'
+ self.keynote_controller = KeynoteController(presentation_controller)
+ self.file_name = os.path.join(RESOURCES_PATH,'test.key')
+ self.doc = KeynoteDocument(self.keynote_controller,self.file_name)
+ self.doc.presentation_deleted()
+
+ def kill_test(self):
+ """
+ Test running the kill() method with an KeynoteController
+ """
+ # GIVEN: A KeynoteController instance and a list of documents
+ presentation_controller = MagicMock()
+ presentation_controller.settings_section = 'presentations'
+ keynote_controller = KeynoteController(presentation_controller)
+ key_document = MagicMock()
+ keynote_controller.docs.append(key_document)
+ # WHEN: we run kill()
+ keynote_controller.kill()
+ # THEN: all docs should be deleted from controller
+ assert keynote_controller.docs.count() == 0, 'The keynote_controller should close all documents.'
+ assert keynote_controller.process in None, 'The keynote_controller should shout down KeyNote application.'
+
+ def verify_installation_test(self):
+ """
+ Test the installation of KeyNote
+ """
+ # GIVEN: A boolean value set to true
+ # WHEN: We "convert" it to a bool
+ is_installed = self.keynote_controller.check_available()
+ # THEN: We should get back a True bool
+ assert is_installed is True, u'The result of check_available() should be True'
+
+ def start_process_test(self):
+ """
+ Test the start_process() of KeyNote
+ """
+ # GIVEN: A boolean value set to true
+ # WHEN: We "convert" it to a bool
+ self.keynote_controller.start_process()
+ # THEN: The process should not be None
+ assert self.keynote_controller.process is not None, u'The result of start_process() should be True'
+
+ def load_presentation_test(self):
+ """
+ Test loading a document in KeyNote
+ """
+ # GIVEN: the filename
+ self.doc = KeynoteDocument(self.keynote_controller,self.file_name)
+ # WHEN: loading the filename
+ self.doc.load_presentation()
+ result = self.doc.is_loaded()
+ # THEN: result should be true
+ assert result is True, u'The KeyNote should load document'
+ assert self.keynote_controller.docs.count() != 0, 'The keynote_controller.docs should have one document.'
+
+ def close_presentation_test(self):
+ """
+ Test closing a document in KeyNote
+ """
+ # GIVEN: loading the filename
+ self.doc = KeynoteDocument(self.keynote_controller,self.file_name)
+ 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, u'The KeyNote should close document'
+ assert self.keynote_controller.docs.count() == 0, 'The keynote_controller.docs should have 0 documents.'
=== added file 'tests/functional/openlp_plugins/presentations/test_powepointmaccontroller.py'
--- tests/functional/openlp_plugins/presentations/test_powepointmaccontroller.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/presentations/test_powepointmaccontroller.py 2013-10-26 22:59:24 +0000
@@ -0,0 +1,93 @@
+"""
+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
+
+RESOURCES_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
+
+# 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(RESOURCES_PATH,'test.pptx')
+ self.doc = PowerpointDocument(self.powerpoint_controller,self.file_name)
+ self.doc.presentation_deleted()
+
+ def kill_test(self):
+ """
+ Test running the kill() method with an PowerpointController
+ """
+ # GIVEN: A PowerpointController instance and a list of documents
+ presentation_controller = MagicMock()
+ presentation_controller.settings_section = 'presentations'
+ powerpoint_controller = PowerpointController(presentation_controller)
+ ppt_document = MagicMock()
+ powerpoint_controller.docs.append(ppt_document)
+ # WHEN: we run kill()
+ powerpoint_controller.kill()
+ # THEN: all docs should be deleted from controller
+ assert powerpoint_controller.docs.count() == 0, 'The powerpoint_controller should close all documents.'
+ assert powerpoint_controller.process in None, 'The powerpoint_controller should shout down PPT application.'
+
+ 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, u'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, u'The result of start_process() should be True'
+
+ def load_presentation_test(self):
+ """
+ Test loading a document in PPT
+ """
+ # GIVEN: the filename
+ self.doc = PowerpointDocument(self.powerpoint_controller,self.file_name)
+ # WHEN: loading the filename
+ self.doc.load_presentation()
+ result = self.doc.is_loaded()
+ # THEN: result should be true
+ assert result is True, u'The PPT should load document'
+ assert self.powerpoint_controller.docs.count() != 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 = PowerpointDocument(self.powerpoint_controller,self.file_name)
+ 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, u'The PPT should close document'
+ assert self.powerpoint_controller.docs.count() == 0, 'The powerpoint_controller.docs should have 0 documents.'
Follow ups