Make it possible to go to next or previous service item when stepping through a presentation. Disables impress and powerpoint presentation console.

bzr-revno: 2875
Fixes: https://launchpad.net/bugs/1165855, https://launchpad.net/bugs/1798651
This commit is contained in:
second@tgc.dk 2019-06-05 21:25:35 +02:00 committed by Tomas Groth
commit f2779edcd4
7 changed files with 284 additions and 58 deletions

View File

@ -146,7 +146,7 @@ class Registry(object):
try: try:
log.debug('Running function {} for {}'.format(function, event)) log.debug('Running function {} for {}'.format(function, event))
result = function(*args, **kwargs) result = function(*args, **kwargs)
if result: if result is not None:
results.append(result) results.append(result)
except TypeError: except TypeError:
# Who has called me can help in debugging # Who has called me can help in debugging

View File

@ -984,8 +984,10 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
prev_item_last_slide = None prev_item_last_slide = None
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list) service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
while service_iterator.value(): while service_iterator.value():
# Found the selected/current service item
if service_iterator.value() == selected: if service_iterator.value() == selected:
if last_slide and prev_item_last_slide: if last_slide and prev_item_last_slide:
# Go to the last slide of the previous service item
pos = prev_item.data(0, QtCore.Qt.UserRole) pos = prev_item.data(0, QtCore.Qt.UserRole)
check_expanded = self.service_items[pos - 1]['expanded'] check_expanded = self.service_items[pos - 1]['expanded']
self.service_manager_list.setCurrentItem(prev_item_last_slide) self.service_manager_list.setCurrentItem(prev_item_last_slide)
@ -994,13 +996,17 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
self.make_live() self.make_live()
self.service_manager_list.setCurrentItem(prev_item) self.service_manager_list.setCurrentItem(prev_item)
elif prev_item: elif prev_item:
# Go to the first slide of the previous service item
self.service_manager_list.setCurrentItem(prev_item) self.service_manager_list.setCurrentItem(prev_item)
self.make_live() self.make_live()
return return
# Found the previous service item root
if service_iterator.value().parent() is None: if service_iterator.value().parent() is None:
prev_item = service_iterator.value() prev_item = service_iterator.value()
# Found the last slide of the previous item
if service_iterator.value().parent() is prev_item: if service_iterator.value().parent() is prev_item:
prev_item_last_slide = service_iterator.value() prev_item_last_slide = service_iterator.value()
# Go to next item in the tree
service_iterator += 1 service_iterator += 1
def on_set_item(self, message): def on_set_item(self, message):

View File

@ -1261,9 +1261,18 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if not self.service_item: if not self.service_item:
return return
if self.service_item.is_command(): if self.service_item.is_command():
Registry().execute('{text}_next'.format(text=self.service_item.name.lower()), past_end = Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live]) [self.service_item, self.is_live])
if self.is_live: # Check if we have gone past the end of the last slide
if self.is_live and past_end and past_end[0]:
if wrap is None:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([0])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.service_next()
elif wrap:
self.on_slide_selected_index([0])
elif self.is_live:
self.update_preview() self.update_preview()
else: else:
row = self.preview_widget.current_slide_number() + 1 row = self.preview_widget.current_slide_number() + 1
@ -1290,9 +1299,16 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if not self.service_item: if not self.service_item:
return return
if self.service_item.is_command(): if self.service_item.is_command():
Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()), before_start = Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live]) [self.service_item, self.is_live])
if self.is_live: # Check id we have tried to go before that start slide
if self.is_live and before_start and before_start[0]:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([self.preview_widget.slide_count() - 1])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
self._process_queue()
elif self.is_live:
self.update_preview() self.update_preview()
else: else:
row = self.preview_widget.current_slide_number() - 1 row = self.preview_widget.current_slide_number() - 1

View File

@ -36,31 +36,49 @@ import time
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win, trace_error_handler
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.display.screens import ScreenList from openlp.core.display.screens import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \ from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
TextType TextType
# Load the XSlideShowListener class so we can inherit from it
if is_win(): if is_win():
from win32com.client import Dispatch from win32com.client import Dispatch
import pywintypes import pywintypes
uno_available = False uno_available = False
# Declare an empty exception to match the exception imported from UNO try:
service_manager = Dispatch('com.sun.star.ServiceManager')
service_manager._FlagAsMethod('Bridge_GetStruct')
XSlideShowListenerObj = service_manager.Bridge_GetStruct('com.sun.star.presentation.XSlideShowListener')
class SlideShowListenerImport(XSlideShowListenerObj.__class__):
pass
except (AttributeError, pywintypes.com_error):
class SlideShowListenerImport():
pass
# Declare an empty exception to match the exception imported from UNO
class ErrorCodeIOException(Exception): class ErrorCodeIOException(Exception):
pass pass
else: else:
try: try:
import uno import uno
import unohelper
from com.sun.star.beans import PropertyValue from com.sun.star.beans import PropertyValue
from com.sun.star.task import ErrorCodeIOException from com.sun.star.task import ErrorCodeIOException
from com.sun.star.presentation import XSlideShowListener
class SlideShowListenerImport(unohelper.Base, XSlideShowListener):
pass
uno_available = True uno_available = True
except ImportError: except ImportError:
uno_available = False uno_available = False
class SlideShowListenerImport():
pass
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -82,6 +100,8 @@ class ImpressController(PresentationController):
self.process = None self.process = None
self.desktop = None self.desktop = None
self.manager = None self.manager = None
self.conf_provider = None
self.presenter_screen_disabled_by_openlp = False
def check_available(self): def check_available(self):
""" """
@ -90,8 +110,7 @@ class ImpressController(PresentationController):
log.debug('check_available') log.debug('check_available')
if is_win(): if is_win():
return self.get_com_servicemanager() is not None return self.get_com_servicemanager() is not None
else: return uno_available
return uno_available
def start_process(self): def start_process(self):
""" """
@ -131,6 +150,7 @@ class ImpressController(PresentationController):
self.manager = uno_instance.ServiceManager self.manager = uno_instance.ServiceManager
log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop') log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop')
desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance) desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance)
self.toggle_presentation_screen(False)
return desktop return desktop
except Exception: except Exception:
log.warning('Failed to get UNO desktop') log.warning('Failed to get UNO desktop')
@ -148,6 +168,7 @@ class ImpressController(PresentationController):
desktop = self.manager.createInstance('com.sun.star.frame.Desktop') desktop = self.manager.createInstance('com.sun.star.frame.Desktop')
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.warning('Failure to find desktop - Impress may have closed') log.warning('Failure to find desktop - Impress may have closed')
self.toggle_presentation_screen(False)
return desktop if desktop else None return desktop if desktop else None
def get_com_servicemanager(self): def get_com_servicemanager(self):
@ -166,6 +187,8 @@ class ImpressController(PresentationController):
Called at system exit to clean up any running presentations. Called at system exit to clean up any running presentations.
""" """
log.debug('Kill OpenOffice') log.debug('Kill OpenOffice')
if self.presenter_screen_disabled_by_openlp:
self.toggle_presentation_screen(True)
while self.docs: while self.docs:
self.docs[0].close_presentation() self.docs[0].close_presentation()
desktop = None desktop = None
@ -195,6 +218,60 @@ class ImpressController(PresentationController):
except Exception: except Exception:
log.warning('Failed to terminate OpenOffice') log.warning('Failed to terminate OpenOffice')
def toggle_presentation_screen(self, set_visible):
"""
Enable or disable the Presentation Screen/Console
:param bool set_visible: Should the presentation screen/console be set to be visible.
:rtype: None
"""
# Create Instance of ConfigurationProvider
if not self.conf_provider:
if is_win():
self.conf_provider = self.manager.createInstance('com.sun.star.configuration.ConfigurationProvider')
else:
self.conf_provider = self.manager.createInstanceWithContext(
'com.sun.star.configuration.ConfigurationProvider', uno.getComponentContext())
# Setup lookup properties to get Impress settings
properties = []
properties.append(self.create_property('nodepath', 'org.openoffice.Office.Impress'))
properties = tuple(properties)
try:
# Get an updateable configuration view
impress_conf_props = self.conf_provider.createInstanceWithArguments(
'com.sun.star.configuration.ConfigurationUpdateAccess', properties)
# Get the specific setting for presentation screen
presenter_screen_enabled = impress_conf_props.getHierarchicalPropertyValue(
'Misc/Start/EnablePresenterScreen')
# If the presentation screen is enabled we disable it
if presenter_screen_enabled != set_visible:
impress_conf_props.setHierarchicalPropertyValue('Misc/Start/EnablePresenterScreen', set_visible)
impress_conf_props.commitChanges()
# if set_visible is False this is an attempt to disable the Presenter Screen
# so we make a note that it has been disabled, so it can be enabled again on close.
if set_visible is False:
self.presenter_screen_disabled_by_openlp = True
except Exception as e:
log.exception(e)
trace_error_handler(log)
def create_property(self, name, value):
"""
Create an OOo style property object which are passed into some Uno methods.
:param str name: The name of the property
:param str value: The value of the property
:rtype: com.sun.star.beans.PropertyValue
"""
log.debug('create property OpenOffice')
if is_win():
property_object = self.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
else:
property_object = PropertyValue()
property_object.Name = name
property_object.Value = value
return property_object
class ImpressDocument(PresentationDocument): class ImpressDocument(PresentationDocument):
""" """
@ -213,6 +290,8 @@ class ImpressDocument(PresentationDocument):
self.document = None self.document = None
self.presentation = None self.presentation = None
self.control = None self.control = None
self.slide_ended = False
self.slide_ended_reverse = False
def load_presentation(self): def load_presentation(self):
""" """
@ -233,13 +312,16 @@ class ImpressDocument(PresentationDocument):
return False return False
self.desktop = desktop self.desktop = desktop
properties = [] properties = []
properties.append(self.create_property('Hidden', True)) properties.append(self.controller.create_property('Hidden', True))
properties = tuple(properties) properties = tuple(properties)
try: try:
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties) self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
except Exception: except Exception:
log.warning('Failed to load presentation {url}'.format(url=url)) log.warning('Failed to load presentation {url}'.format(url=url))
return False return False
if self.document is None:
log.warning('Presentation {url} could not be loaded'.format(url=url))
return False
self.presentation = self.document.getPresentation() self.presentation = self.document.getPresentation()
self.presentation.Display = ScreenList().current.number + 1 self.presentation.Display = ScreenList().current.number + 1
self.control = None self.control = None
@ -257,7 +339,7 @@ class ImpressDocument(PresentationDocument):
temp_folder_path = self.get_temp_folder() temp_folder_path = self.get_temp_folder()
thumb_dir_url = temp_folder_path.as_uri() thumb_dir_url = temp_folder_path.as_uri()
properties = [] properties = []
properties.append(self.create_property('FilterName', 'impress_png_Export')) properties.append(self.controller.create_property('FilterName', 'impress_png_Export'))
properties = tuple(properties) properties = tuple(properties)
doc = self.document doc = self.document
pages = doc.getDrawPages() pages = doc.getDrawPages()
@ -279,19 +361,6 @@ class ImpressDocument(PresentationDocument):
except Exception: except Exception:
log.exception('{path} - Unable to store openoffice preview'.format(path=path)) log.exception('{path} - Unable to store openoffice preview'.format(path=path))
def create_property(self, name, value):
"""
Create an OOo style property object which are passed into some Uno methods.
"""
log.debug('create property OpenOffice')
if is_win():
property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
else:
property_object = PropertyValue()
property_object.Name = name
property_object.Value = value
return property_object
def close_presentation(self): def close_presentation(self):
""" """
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
@ -356,8 +425,7 @@ class ImpressDocument(PresentationDocument):
log.debug('is blank OpenOffice') log.debug('is blank OpenOffice')
if self.control and self.control.isRunning(): if self.control and self.control.isRunning():
return self.control.isPaused() return self.control.isPaused()
else: return False
return False
def stop_presentation(self): def stop_presentation(self):
""" """
@ -384,6 +452,8 @@ class ImpressDocument(PresentationDocument):
sleep_count += 1 sleep_count += 1
self.control = self.presentation.getController() self.control = self.presentation.getController()
window.setVisible(False) window.setVisible(False)
listener = SlideShowListener(self)
self.control.getSlideShow().addSlideShowListener(listener)
else: else:
self.control.activate() self.control.activate()
self.goto_slide(1) self.goto_slide(1)
@ -415,17 +485,33 @@ class ImpressDocument(PresentationDocument):
""" """
Triggers the next effect of slide on the running presentation. Triggers the next effect of slide on the running presentation.
""" """
# if we are at the presentations end don't go further, just return True
if self.slide_ended and self.get_slide_count() == self.get_slide_number():
return True
self.slide_ended = False
self.slide_ended_reverse = False
past_end = False
is_paused = self.control.isPaused() is_paused = self.control.isPaused()
self.control.gotoNextEffect() self.control.gotoNextEffect()
time.sleep(0.1) time.sleep(0.1)
# If for some reason the presentation end was not detected above, this will catch it.
# The presentation is set to paused when going past the end.
if not is_paused and self.control.isPaused(): if not is_paused and self.control.isPaused():
self.control.gotoPreviousEffect() self.control.gotoPreviousEffect()
past_end = True
return past_end
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation. Triggers the previous slide on the running presentation.
""" """
# if we are at the presentations start don't go further back, just return True
if self.slide_ended_reverse and self.get_slide_number() == 1:
return True
self.slide_ended = False
self.slide_ended_reverse = False
self.control.gotoPreviousEffect() self.control.gotoPreviousEffect()
return False
def get_slide_text(self, slide_no): def get_slide_text(self, slide_no):
""" """
@ -483,3 +569,100 @@ class ImpressDocument(PresentationDocument):
note = ' ' note = ' '
notes.append(note) notes.append(note)
self.save_titles_and_notes(titles, notes) self.save_titles_and_notes(titles, notes)
class SlideShowListener(SlideShowListenerImport):
"""
Listener interface to receive global slide show events.
"""
def __init__(self, document):
"""
Constructor
:param document: The ImpressDocument being presented
"""
self.document = document
def paused(self):
"""
Notify that the slide show is paused
"""
log.debug('LibreOffice SlideShowListener event: paused')
def resumed(self):
"""
Notify that the slide show is resumed from a paused state
"""
log.debug('LibreOffice SlideShowListener event: resumed')
def slideTransitionStarted(self):
"""
Notify that a new slide starts to become visible.
"""
log.debug('LibreOffice SlideShowListener event: slideTransitionStarted')
def slideTransitionEnded(self):
"""
Notify that the slide transtion of the current slide ended.
"""
log.debug('LibreOffice SlideShowListener event: slideTransitionEnded')
def slideAnimationsEnded(self):
"""
Notify that the last animation from the main sequence of the current slide has ended.
"""
log.debug('LibreOffice SlideShowListener event: slideAnimationsEnded')
if not Registry().get('main_window').isActiveWindow():
log.debug('main window is not in focus - should update slidecontroller')
Registry().execute('slidecontroller_live_change', self.document.control.getCurrentSlideIndex() + 1)
def slideEnded(self, reverse):
"""
Notify that the current slide has ended, e.g. the user has clicked on the slide. Calling displaySlide()
twice will not issue this event.
:param bool reverse: Whether or not the direction of the "slide movement" is reversed/backwards.
:rtype: None
"""
log.debug('LibreOffice SlideShowListener event: slideEnded %d' % reverse)
if reverse:
self.document.slide_ended = False
self.document.slide_ended_reverse = True
else:
self.document.slide_ended = True
self.document.slide_ended_reverse = False
def hyperLinkClicked(self, hyperLink):
"""
Notifies that a hyperlink has been clicked.
"""
log.debug('LibreOffice SlideShowListener event: hyperLinkClicked %s' % hyperLink)
def disposing(self, source):
"""
gets called when the broadcaster is about to be disposed.
:param source:
"""
log.debug('LibreOffice SlideShowListener event: disposing')
def beginEvent(self, node):
"""
This event is raised when the element local timeline begins to play.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: beginEvent')
def endEvent(self, node):
"""
This event is raised at the active end of the element.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: endEvent')
def repeat(self, node):
"""
This event is raised when the element local timeline repeats.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: repeat')

View File

@ -169,24 +169,21 @@ class Controller(object):
""" """
log.debug('Live = {live}, next'.format(live=self.is_live)) log.debug('Live = {live}, next'.format(live=self.is_live))
if not self.doc: if not self.doc:
return return False
if not self.is_live: if not self.is_live:
return return False
if self.hide_mode: if self.hide_mode:
if not self.doc.is_active(): if not self.doc.is_active():
return return False
if self.doc.slidenumber < self.doc.get_slide_count(): if self.doc.slidenumber < self.doc.get_slide_count():
self.doc.slidenumber += 1 self.doc.slidenumber += 1
self.poll() self.poll()
return return False
if not self.activate(): if not self.activate():
return return False
# The "End of slideshow" screen is after the last slide. Note, we can't just stop on the last slide, since it ret = self.doc.next_step()
# may contain animations that need to be stepped through.
if self.doc.slidenumber > self.doc.get_slide_count():
return
self.doc.next_step()
self.poll() self.poll()
return ret
def previous(self): def previous(self):
""" """
@ -194,20 +191,21 @@ class Controller(object):
""" """
log.debug('Live = {live}, previous'.format(live=self.is_live)) log.debug('Live = {live}, previous'.format(live=self.is_live))
if not self.doc: if not self.doc:
return return False
if not self.is_live: if not self.is_live:
return return False
if self.hide_mode: if self.hide_mode:
if not self.doc.is_active(): if not self.doc.is_active():
return return False
if self.doc.slidenumber > 1: if self.doc.slidenumber > 1:
self.doc.slidenumber -= 1 self.doc.slidenumber -= 1
self.poll() self.poll()
return return False
if not self.activate(): if not self.activate():
return return False
self.doc.previous_step() ret = self.doc.previous_step()
self.poll() self.poll()
return ret
def shutdown(self): def shutdown(self):
""" """
@ -418,11 +416,12 @@ class MessageListener(object):
""" """
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.next() ret = self.live_handler.next()
if Settings().value('core/click live slide to unblank'): if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank') Registry().execute('slidecontroller_live_unblank')
return ret
else: else:
self.preview_handler.next() return self.preview_handler.next()
def previous(self, message): def previous(self, message):
""" """
@ -432,11 +431,12 @@ class MessageListener(object):
""" """
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.previous() ret = self.live_handler.previous()
if Settings().value('core/click live slide to unblank'): if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank') Registry().execute('slidecontroller_live_unblank')
return ret
else: else:
self.preview_handler.previous() return self.preview_handler.previous()
def shutdown(self, message): def shutdown(self, message):
""" """

View File

@ -145,8 +145,8 @@ class PowerpointDocument(PresentationDocument):
try: try:
if not self.controller.process: if not self.controller.process:
self.controller.start_process() self.controller.start_process()
self.controller.process.Presentations.Open(str(self.file_path), False, False, False) self.presentation = self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count) log.debug('Loaded presentation %s' % self.presentation.FullName)
self.create_thumbnails() self.create_thumbnails()
self.create_titles_and_notes() self.create_titles_and_notes()
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
@ -170,14 +170,17 @@ class PowerpointDocument(PresentationDocument):
However, for the moment, we want a physical file since it makes life easier elsewhere. However, for the moment, we want a physical file since it makes life easier elsewhere.
""" """
log.debug('create_thumbnails') log.debug('create_thumbnails')
generate_thumbs = True
if self.check_thumbnails(): if self.check_thumbnails():
return # No need for thumbnails but we still need the index
generate_thumbs = False
key = 1 key = 1
for num in range(self.presentation.Slides.Count): for num in range(self.presentation.Slides.Count):
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden: if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
self.index_map[key] = num + 1 self.index_map[key] = num + 1
self.presentation.Slides(num + 1).Export( if generate_thumbs:
str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240) self.presentation.Slides(num + 1).Export(
str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
key += 1 key += 1
self.slide_count = key - 1 self.slide_count = key - 1
@ -318,6 +321,9 @@ class PowerpointDocument(PresentationDocument):
size = ScreenList().current.display_geometry size = ScreenList().current.display_geometry
ppt_window = None ppt_window = None
try: try:
# Disable the presentation console
self.presentation.SlideShowSettings.ShowPresenterView = 0
# Start the presentation
ppt_window = self.presentation.SlideShowSettings.Run() ppt_window = self.presentation.SlideShowSettings.Run()
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in start_presentation') log.exception('Caught exception while in start_presentation')
@ -437,6 +443,12 @@ class PowerpointDocument(PresentationDocument):
Triggers the next effect of slide on the running presentation. Triggers the next effect of slide on the running presentation.
""" """
log.debug('next_step') log.debug('next_step')
# if we are at the presentations end don't go further, just return True
if self.presentation.SlideShowWindow.View.GetClickCount() == \
self.presentation.SlideShowWindow.View.GetClickIndex() \
and self.get_slide_number() == self.get_slide_count():
return True
past_end = False
try: try:
self.presentation.SlideShowWindow.Activate() self.presentation.SlideShowWindow.Activate()
self.presentation.SlideShowWindow.View.Next() self.presentation.SlideShowWindow.View.Next()
@ -444,28 +456,35 @@ class PowerpointDocument(PresentationDocument):
log.exception('Caught exception while in next_step') log.exception('Caught exception while in next_step')
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return return past_end
# If for some reason the presentation end was not detected above, this will catch it.
if self.get_slide_number() > self.get_slide_count(): if self.get_slide_number() > self.get_slide_count():
log.debug('past end, stepping back to previous') log.debug('past end, stepping back to previous')
self.previous_step() self.previous_step()
past_end = True
# Stop powerpoint from flashing in the taskbar # Stop powerpoint from flashing in the taskbar
if self.presentation_hwnd: if self.presentation_hwnd:
win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0) win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
if len(ScreenList()) > 1: if len(ScreenList()) > 1:
Registry().get('main_window').activateWindow() Registry().get('main_window').activateWindow()
return past_end
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation. Triggers the previous slide on the running presentation.
""" """
log.debug('previous_step') log.debug('previous_step')
# if we are at the presentations start we can't go further back, just return True
if self.presentation.SlideShowWindow.View.GetClickIndex() == 0 and self.get_slide_number() == 1:
return True
try: try:
self.presentation.SlideShowWindow.View.Previous() self.presentation.SlideShowWindow.View.Previous()
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in previous_step') log.exception('Caught exception while in previous_step')
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return False
def get_slide_text(self, slide_no): def get_slide_text(self, slide_no):
""" """

View File

@ -248,15 +248,17 @@ class PresentationDocument(object):
def next_step(self): def next_step(self):
""" """
Triggers the next effect of slide on the running presentation. This might be the next animation on the current Triggers the next effect of slide on the running presentation. This might be the next animation on the current
slide, or the next slide slide, or the next slide.
:rtype bool: True if we stepped beyond the slides of the presentation
""" """
pass return False
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation Triggers the previous slide on the running presentation
:rtype bool: True if we stepped beyond the slides of the presentation
""" """
pass return False
def convert_thumbnail(self, image_path, index): def convert_thumbnail(self, image_path, index):
""" """