openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #34096
[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.
Disables impress and powerpoint presentation console.
Requested reviews:
Tim Bentley (trb143)
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/368091
--
Your team OpenLP Core is subscribed to branch 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-29 19:24:56 +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/servicemanager.py'
--- openlp/core/ui/servicemanager.py 2019-05-24 18:50:51 +0000
+++ openlp/core/ui/servicemanager.py 2019-05-29 19:24:56 +0000
@@ -976,8 +976,10 @@
prev_item_last_slide = None
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
while service_iterator.value():
+ # Found the selected/current service item
if service_iterator.value() == selected:
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)
check_expanded = self.service_items[pos - 1]['expanded']
self.service_manager_list.setCurrentItem(prev_item_last_slide)
@@ -986,13 +988,17 @@
self.make_live()
self.service_manager_list.setCurrentItem(prev_item)
elif prev_item:
+ # Go to the first slide of the previous service item
self.service_manager_list.setCurrentItem(prev_item)
self.make_live()
return
+ # Found the previous service item root
if service_iterator.value().parent() is None:
prev_item = service_iterator.value()
+ # Found the last slide of the previous item
if service_iterator.value().parent() is prev_item:
prev_item_last_slide = service_iterator.value()
+ # Go to next item in the tree
service_iterator += 1
def on_set_item(self, message):
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2019-05-22 06:47:00 +0000
+++ openlp/core/ui/slidecontroller.py 2019-05-29 19:24:56 +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,16 @@
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]:
+ 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-05-22 06:47:00 +0000
+++ openlp/plugins/presentations/lib/impresscontroller.py 2019-05-29 19:24:56 +0000
@@ -36,31 +36,49 @@
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, \
TextType
-
+# Load the XSlideShowListener class so we can inherit from it
if is_win():
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:
uno_available = False
+ class SlideShowListenerImport():
+ pass
+
log = logging.getLogger(__name__)
@@ -82,6 +100,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 +110,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 +150,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 +168,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 +187,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 +218,54 @@
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 is 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 +284,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 +306,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 +333,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 +355,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 +419,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 +446,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 +479,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):
"""
@@ -483,3 +563,97 @@
note = ' '
notes.append(note)
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.
+ """
+ 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-22 06:47:00 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py 2019-05-29 19:24:56 +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,21 @@
"""
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()
+ return ret
def shutdown(self):
"""
@@ -418,11 +416,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 +431,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-05-22 06:47:00 +0000
+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2019-05-29 19:24:56 +0000
@@ -145,8 +145,8 @@
try:
if not self.controller.process:
self.controller.start_process()
- self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
- self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
+ self.presentation = self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
+ log.debug('Loaded presentation %s' % self.presentation.FullName)
self.create_thumbnails()
self.create_titles_and_notes()
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
@@ -170,14 +170,17 @@
However, for the moment, we want a physical file since it makes life easier elsewhere.
"""
log.debug('create_thumbnails')
+ generate_thumbs = True
if self.check_thumbnails():
- return
+ # No need for thumbnails but we still need the index
+ generate_thumbs = False
key = 1
for num in range(self.presentation.Slides.Count):
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
self.index_map[key] = num + 1
- self.presentation.Slides(num + 1).Export(
- str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
+ if generate_thumbs:
+ self.presentation.Slides(num + 1).Export(
+ str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
key += 1
self.slide_count = key - 1
@@ -318,6 +321,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 +443,12 @@
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 +456,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-05-22 06:47:00 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py 2019-05-29 19:24:56 +0000
@@ -248,15 +248,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