openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #34012
[Merge] lp:~tomasgroth/openlp/presentation-beyond-last into lp:openlp
Tomas Groth has proposed merging lp:~tomasgroth/openlp/presentation-beyond-last into lp:openlp.
Commit message:
Make it possible to go to next or previous service item when stepping through a presentation.
Requested reviews:
OpenLP Core (openlp-core)
Related bugs:
Bug #1165855 in OpenLP: "Presentations do not advance correctly despite settings"
https://bugs.launchpad.net/openlp/+bug/1165855
Bug #1798651 in OpenLP: "Impress Presentation Console should be disabled by OpenLP"
https://bugs.launchpad.net/openlp/+bug/1798651
For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/presentation-beyond-last/+merge/367670
--
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/presentation-beyond-last into lp:openlp.
=== modified file 'openlp/core/common/registry.py'
--- openlp/core/common/registry.py 2019-04-13 13:00:22 +0000
+++ openlp/core/common/registry.py 2019-05-21 06:35:17 +0000
@@ -146,7 +146,7 @@
try:
log.debug('Running function {} for {}'.format(function, event))
result = function(*args, **kwargs)
- if result:
+ if result is not None:
results.append(result)
except TypeError:
# Who has called me can help in debugging
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2019-05-07 16:56:21 +0000
+++ openlp/core/ui/slidecontroller.py 2019-05-21 06:35:17 +0000
@@ -1261,9 +1261,18 @@
if not self.service_item:
return
if self.service_item.is_command():
- Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
- [self.service_item, self.is_live])
- if self.is_live:
+ past_end = Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
+ [self.service_item, 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()
else:
row = self.preview_widget.current_slide_number() + 1
@@ -1290,9 +1299,17 @@
if not self.service_item:
return
if self.service_item.is_command():
- Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
- [self.service_item, self.is_live])
- if self.is_live:
+ before_start = Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
+ [self.service_item, 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]:
+ print('detected before start!')
+ 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()
else:
row = self.preview_widget.current_slide_number() - 1
=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
--- openlp/plugins/presentations/lib/impresscontroller.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/presentations/lib/impresscontroller.py 2019-05-21 06:35:17 +0000
@@ -36,7 +36,7 @@
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.display.screens import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
@@ -47,15 +47,30 @@
from win32com.client import Dispatch
import pywintypes
uno_available = False
+ 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):
pass
else:
try:
import uno
+ import unohelper
from com.sun.star.beans import PropertyValue
from com.sun.star.task import ErrorCodeIOException
+ from com.sun.star.presentation import XSlideShowListener
+
+ class SlideShowListenerImport(unohelper.Base, XSlideShowListener):
+ pass
uno_available = True
except ImportError:
@@ -82,6 +97,8 @@
self.process = None
self.desktop = None
self.manager = None
+ self.conf_provider = None
+ self.presenter_screen_disabled_by_openlp = False
def check_available(self):
"""
@@ -90,8 +107,7 @@
log.debug('check_available')
if is_win():
return self.get_com_servicemanager() is not None
- else:
- return uno_available
+ return uno_available
def start_process(self):
"""
@@ -131,6 +147,7 @@
self.manager = uno_instance.ServiceManager
log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop')
desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance)
+ self.toggle_presentation_screen(False)
return desktop
except Exception:
log.warning('Failed to get UNO desktop')
@@ -148,6 +165,7 @@
desktop = self.manager.createInstance('com.sun.star.frame.Desktop')
except (AttributeError, pywintypes.com_error):
log.warning('Failure to find desktop - Impress may have closed')
+ self.toggle_presentation_screen(False)
return desktop if desktop else None
def get_com_servicemanager(self):
@@ -166,6 +184,8 @@
Called at system exit to clean up any running presentations.
"""
log.debug('Kill OpenOffice')
+ if self.presenter_screen_disabled_by_openlp:
+ self._toggle_presentation_screen(True)
while self.docs:
self.docs[0].close_presentation()
desktop = None
@@ -195,6 +215,51 @@
except Exception:
log.warning('Failed to terminate OpenOffice')
+ def toggle_presentation_screen(self, target_value):
+ """
+ Enable or disable the Presentation Screen/Console
+ """
+ # 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 != target_value:
+ impress_conf_props.setHierarchicalPropertyValue('Misc/Start/EnablePresenterScreen', target_value)
+ impress_conf_props.commitChanges()
+ # if target_value 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 target_value == False:
+ self.presenter_screen_disabled_by_openlp = True
+ except Exception as e:
+ log.exception(e)
+ trace_error_handler(log)
+ return
+
+ 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.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):
"""
@@ -213,6 +278,8 @@
self.document = None
self.presentation = None
self.control = None
+ self.slide_ended = False
+ self.slide_ended_reverse = False
def load_presentation(self):
"""
@@ -233,13 +300,16 @@
return False
self.desktop = desktop
properties = []
- properties.append(self.create_property('Hidden', True))
+ properties.append(self.controller.create_property('Hidden', True))
properties = tuple(properties)
try:
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
except Exception:
log.warning('Failed to load presentation {url}'.format(url=url))
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.Display = ScreenList().current.number + 1
self.control = None
@@ -257,7 +327,7 @@
temp_folder_path = self.get_temp_folder()
thumb_dir_url = temp_folder_path.as_uri()
properties = []
- properties.append(self.create_property('FilterName', 'impress_png_Export'))
+ properties.append(self.controller.create_property('FilterName', 'impress_png_Export'))
properties = tuple(properties)
doc = self.document
pages = doc.getDrawPages()
@@ -279,19 +349,6 @@
except Exception:
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):
"""
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
@@ -356,8 +413,7 @@
log.debug('is blank OpenOffice')
if self.control and self.control.isRunning():
return self.control.isPaused()
- else:
- return False
+ return False
def stop_presentation(self):
"""
@@ -384,6 +440,8 @@
sleep_count += 1
self.control = self.presentation.getController()
window.setVisible(False)
+ listener = SlideShowListener(self)
+ self.control.getSlideShow().addSlideShowListener(listener)
else:
self.control.activate()
self.goto_slide(1)
@@ -415,17 +473,33 @@
"""
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()
self.control.gotoNextEffect()
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():
self.control.gotoPreviousEffect()
+ past_end = True
+ return past_end
def previous_step(self):
"""
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()
+ return False
def get_slide_text(self, slide_no):
"""
@@ -479,7 +553,102 @@
titles.append(self.__get_text_from_page(slide_no, TextType.Title).replace('\r\n', ' ')
.replace('\n', ' ').strip())
note = self.__get_text_from_page(slide_no, TextType.Notes)
- if len(note) == 0:
+ if not note:
note = ' '
notes.append(note)
self.save_titles_and_notes(titles, notes)
+
+ if is_win():
+ property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
+
+class SlideShowListener(SlideShowListenerImport):
+ """
+ Listener interface to receive global slide show events.
+ """
+
+ def __init__(self, document):
+ """
+
+ :param control: SlideShowController
+ """
+ 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.
+ """
+ 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')
=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py 2019-05-02 17:11:55 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py 2019-05-21 06:35:17 +0000
@@ -169,24 +169,21 @@
"""
log.debug('Live = {live}, next'.format(live=self.is_live))
if not self.doc:
- return
+ return False
if not self.is_live:
- return
+ return False
if self.hide_mode:
if not self.doc.is_active():
- return
+ return False
if self.doc.slidenumber < self.doc.get_slide_count():
self.doc.slidenumber += 1
self.poll()
- return
+ return False
if not self.activate():
- return
- # The "End of slideshow" screen is after the last slide. Note, we can't just stop on the last slide, since it
- # may contain animations that need to be stepped through.
- if self.doc.slidenumber > self.doc.get_slide_count():
- return
- self.doc.next_step()
+ return False
+ ret = self.doc.next_step()
self.poll()
+ return ret
def previous(self):
"""
@@ -194,20 +191,22 @@
"""
log.debug('Live = {live}, previous'.format(live=self.is_live))
if not self.doc:
- return
+ return False
if not self.is_live:
- return
+ return False
if self.hide_mode:
if not self.doc.is_active():
- return
+ return False
if self.doc.slidenumber > 1:
self.doc.slidenumber -= 1
self.poll()
- return
+ return False
if not self.activate():
- return
- self.doc.previous_step()
+ return False
+ ret = self.doc.previous_step()
self.poll()
+ print('previous returning: %d' % ret)
+ return ret
def shutdown(self):
"""
@@ -418,11 +417,12 @@
"""
is_live = message[1]
if is_live:
- self.live_handler.next()
+ ret = self.live_handler.next()
if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
+ return ret
else:
- self.preview_handler.next()
+ return self.preview_handler.next()
def previous(self, message):
"""
@@ -432,11 +432,12 @@
"""
is_live = message[1]
if is_live:
- self.live_handler.previous()
+ ret = self.live_handler.previous()
if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
+ return ret
else:
- self.preview_handler.previous()
+ return self.preview_handler.previous()
def shutdown(self, message):
"""
=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
--- openlp/plugins/presentations/lib/powerpointcontroller.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2019-05-21 06:35:17 +0000
@@ -318,6 +318,9 @@
size = ScreenList().current.display_geometry
ppt_window = None
try:
+ # Disable the presentation console
+ self.presentation.SlideShowSettings.ShowPresenterView = 0
+ # Start the presentation
ppt_window = self.presentation.SlideShowSettings.Run()
except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in start_presentation')
@@ -437,6 +440,11 @@
Triggers the next effect of slide on the running presentation.
"""
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:
self.presentation.SlideShowWindow.Activate()
self.presentation.SlideShowWindow.View.Next()
@@ -444,28 +452,35 @@
log.exception('Caught exception while in next_step')
trace_error_handler(log)
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():
log.debug('past end, stepping back to previous')
self.previous_step()
+ past_end = True
# Stop powerpoint from flashing in the taskbar
if self.presentation_hwnd:
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
if len(ScreenList()) > 1:
Registry().get('main_window').activateWindow()
+ return past_end
def previous_step(self):
"""
Triggers the previous slide on the running presentation.
"""
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:
self.presentation.SlideShowWindow.View.Previous()
except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in previous_step')
trace_error_handler(log)
self.show_error_msg()
+ return False
def get_slide_text(self, slide_no):
"""
=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py 2019-05-21 06:35:17 +0000
@@ -246,15 +246,17 @@
def next_step(self):
"""
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.
+ Returns True if we stepped beyond the slides of the presentation
"""
- pass
+ return False
def previous_step(self):
"""
Triggers the previous slide on the running presentation
+ Returns True if we stepped beyond the slides of the presentation
"""
- pass
+ return False
def convert_thumbnail(self, image_path, index):
"""
Follow ups