Merge lp:~tomasgroth/openlp/presentation-beyond-last into lp:openlp

Proposed by Tomas Groth
Status: Superseded
Proposed branch: lp:~tomasgroth/openlp/presentation-beyond-last
Merge into: lp:openlp
Diff against target: 649 lines (+272/-55)
7 files modified
openlp/core/common/registry.py (+1/-1)
openlp/core/ui/servicemanager.py (+6/-0)
openlp/core/ui/slidecontroller.py (+22/-6)
openlp/plugins/presentations/lib/impresscontroller.py (+195/-21)
openlp/plugins/presentations/lib/messagelistener.py (+20/-20)
openlp/plugins/presentations/lib/powerpointcontroller.py (+23/-4)
openlp/plugins/presentations/lib/presentationcontroller.py (+5/-3)
To merge this branch: bzr merge lp:~tomasgroth/openlp/presentation-beyond-last
Reviewer Review Type Date Requested Status
OpenLP Core Pending
Review via email: mp+367863@code.launchpad.net

This proposal supersedes a proposal from 2019-05-21.

This proposal has been superseded by a proposal from 2019-05-24.

Commit message

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

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/158/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/103/ for more details

2670. By Tomas Groth

pep8 fixes

Revision history for this message
Phill (phill-ridout) wrote :

Whats happrning with the commented out code?

2671. By Tomas Groth

Reenable setting slidecontroller index when openlp is not in focus.

2672. By Tomas Groth

Remove unused code

2673. By Tomas Groth

Fix traceback on Mac tests

2674. By Tomas Groth

pep8

2675. By Tomas Groth

trunk

2676. By Tomas Groth

Fix as suggested

2677. By Tomas Groth

merge trunk

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/registry.py'
2--- openlp/core/common/registry.py 2019-04-13 13:00:22 +0000
3+++ openlp/core/common/registry.py 2019-05-24 19:21:37 +0000
4@@ -146,7 +146,7 @@
5 try:
6 log.debug('Running function {} for {}'.format(function, event))
7 result = function(*args, **kwargs)
8- if result:
9+ if result is not None:
10 results.append(result)
11 except TypeError:
12 # Who has called me can help in debugging
13
14=== modified file 'openlp/core/ui/servicemanager.py'
15--- openlp/core/ui/servicemanager.py 2019-05-24 18:50:51 +0000
16+++ openlp/core/ui/servicemanager.py 2019-05-24 19:21:37 +0000
17@@ -976,8 +976,10 @@
18 prev_item_last_slide = None
19 service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
20 while service_iterator.value():
21+ # Found the selected/current service item
22 if service_iterator.value() == selected:
23 if last_slide and prev_item_last_slide:
24+ # Go to the last slide of the previous service item
25 pos = prev_item.data(0, QtCore.Qt.UserRole)
26 check_expanded = self.service_items[pos - 1]['expanded']
27 self.service_manager_list.setCurrentItem(prev_item_last_slide)
28@@ -986,13 +988,17 @@
29 self.make_live()
30 self.service_manager_list.setCurrentItem(prev_item)
31 elif prev_item:
32+ # Go to the first slide of the previous service item
33 self.service_manager_list.setCurrentItem(prev_item)
34 self.make_live()
35 return
36+ # Found the previous service item root
37 if service_iterator.value().parent() is None:
38 prev_item = service_iterator.value()
39+ # Found the last slide of the previous item
40 if service_iterator.value().parent() is prev_item:
41 prev_item_last_slide = service_iterator.value()
42+ # Go to next item in the tree
43 service_iterator += 1
44
45 def on_set_item(self, message):
46
47=== modified file 'openlp/core/ui/slidecontroller.py'
48--- openlp/core/ui/slidecontroller.py 2019-05-22 06:47:00 +0000
49+++ openlp/core/ui/slidecontroller.py 2019-05-24 19:21:37 +0000
50@@ -1261,9 +1261,18 @@
51 if not self.service_item:
52 return
53 if self.service_item.is_command():
54- Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
55- [self.service_item, self.is_live])
56- if self.is_live:
57+ past_end = Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
58+ [self.service_item, self.is_live])
59+ # Check if we have gone past the end of the last slide
60+ if self.is_live and past_end and past_end[0]:
61+ if wrap is None:
62+ if self.slide_limits == SlideLimits.Wrap:
63+ self.on_slide_selected_index([0])
64+ elif self.is_live and self.slide_limits == SlideLimits.Next:
65+ self.service_next()
66+ elif wrap:
67+ self.on_slide_selected_index([0])
68+ elif self.is_live:
69 self.update_preview()
70 else:
71 row = self.preview_widget.current_slide_number() + 1
72@@ -1290,9 +1299,16 @@
73 if not self.service_item:
74 return
75 if self.service_item.is_command():
76- Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
77- [self.service_item, self.is_live])
78- if self.is_live:
79+ before_start = Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
80+ [self.service_item, self.is_live])
81+ # Check id we have tried to go before that start slide
82+ if self.is_live and before_start and before_start[0]:
83+ if self.slide_limits == SlideLimits.Wrap:
84+ self.on_slide_selected_index([self.preview_widget.slide_count() - 1])
85+ elif self.is_live and self.slide_limits == SlideLimits.Next:
86+ self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
87+ self._process_queue()
88+ elif self.is_live:
89 self.update_preview()
90 else:
91 row = self.preview_widget.current_slide_number() - 1
92
93=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
94--- openlp/plugins/presentations/lib/impresscontroller.py 2019-05-22 06:47:00 +0000
95+++ openlp/plugins/presentations/lib/impresscontroller.py 2019-05-24 19:21:37 +0000
96@@ -36,7 +36,7 @@
97
98 from PyQt5 import QtCore
99
100-from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win
101+from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win, trace_error_handler
102 from openlp.core.common.registry import Registry
103 from openlp.core.display.screens import ScreenList
104 from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
105@@ -47,15 +47,30 @@
106 from win32com.client import Dispatch
107 import pywintypes
108 uno_available = False
109+ try:
110+ service_manager = Dispatch('com.sun.star.ServiceManager')
111+ service_manager._FlagAsMethod('Bridge_GetStruct')
112+ XSlideShowListenerObj = service_manager.Bridge_GetStruct('com.sun.star.presentation.XSlideShowListener')
113+
114+ class SlideShowListenerImport(XSlideShowListenerObj.__class__):
115+ pass
116+ except (AttributeError, pywintypes.com_error):
117+ class SlideShowListenerImport():
118+ pass
119+
120 # Declare an empty exception to match the exception imported from UNO
121-
122 class ErrorCodeIOException(Exception):
123 pass
124 else:
125 try:
126 import uno
127+ import unohelper
128 from com.sun.star.beans import PropertyValue
129 from com.sun.star.task import ErrorCodeIOException
130+ from com.sun.star.presentation import XSlideShowListener
131+
132+ class SlideShowListenerImport(unohelper.Base, XSlideShowListener):
133+ pass
134
135 uno_available = True
136 except ImportError:
137@@ -82,6 +97,8 @@
138 self.process = None
139 self.desktop = None
140 self.manager = None
141+ self.conf_provider = None
142+ self.presenter_screen_disabled_by_openlp = False
143
144 def check_available(self):
145 """
146@@ -90,8 +107,7 @@
147 log.debug('check_available')
148 if is_win():
149 return self.get_com_servicemanager() is not None
150- else:
151- return uno_available
152+ return uno_available
153
154 def start_process(self):
155 """
156@@ -131,6 +147,7 @@
157 self.manager = uno_instance.ServiceManager
158 log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop')
159 desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance)
160+ self.toggle_presentation_screen(False)
161 return desktop
162 except Exception:
163 log.warning('Failed to get UNO desktop')
164@@ -148,6 +165,7 @@
165 desktop = self.manager.createInstance('com.sun.star.frame.Desktop')
166 except (AttributeError, pywintypes.com_error):
167 log.warning('Failure to find desktop - Impress may have closed')
168+ self.toggle_presentation_screen(False)
169 return desktop if desktop else None
170
171 def get_com_servicemanager(self):
172@@ -166,6 +184,8 @@
173 Called at system exit to clean up any running presentations.
174 """
175 log.debug('Kill OpenOffice')
176+ if self.presenter_screen_disabled_by_openlp:
177+ self._toggle_presentation_screen(True)
178 while self.docs:
179 self.docs[0].close_presentation()
180 desktop = None
181@@ -195,6 +215,54 @@
182 except Exception:
183 log.warning('Failed to terminate OpenOffice')
184
185+ def toggle_presentation_screen(self, target_value):
186+ """
187+ Enable or disable the Presentation Screen/Console
188+ """
189+ # Create Instance of ConfigurationProvider
190+ if not self.conf_provider:
191+ if is_win():
192+ self.conf_provider = self.manager.createInstance('com.sun.star.configuration.ConfigurationProvider')
193+ else:
194+ self.conf_provider = self.manager.createInstanceWithContext(
195+ 'com.sun.star.configuration.ConfigurationProvider', uno.getComponentContext())
196+ # Setup lookup properties to get Impress settings
197+ properties = []
198+ properties.append(self.create_property('nodepath', 'org.openoffice.Office.Impress'))
199+ properties = tuple(properties)
200+ try:
201+ # Get an updateable configuration view
202+ impress_conf_props = self.conf_provider.createInstanceWithArguments(
203+ 'com.sun.star.configuration.ConfigurationUpdateAccess', properties)
204+ # Get the specific setting for presentation screen
205+ presenter_screen_enabled = impress_conf_props.getHierarchicalPropertyValue(
206+ 'Misc/Start/EnablePresenterScreen')
207+ # If the presentation screen is enabled we disable it
208+ if presenter_screen_enabled != target_value:
209+ impress_conf_props.setHierarchicalPropertyValue('Misc/Start/EnablePresenterScreen', target_value)
210+ impress_conf_props.commitChanges()
211+ # if target_value is False this is an attempt to disable the Presenter Screen
212+ # so we make a note that it has been disabled, so it can be enabled again on close.
213+ if target_value is False:
214+ self.presenter_screen_disabled_by_openlp = True
215+ except Exception as e:
216+ log.exception(e)
217+ trace_error_handler(log)
218+ return
219+
220+ def create_property(self, name, value):
221+ """
222+ Create an OOo style property object which are passed into some Uno methods.
223+ """
224+ log.debug('create property OpenOffice')
225+ if is_win():
226+ property_object = self.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
227+ else:
228+ property_object = PropertyValue()
229+ property_object.Name = name
230+ property_object.Value = value
231+ return property_object
232+
233
234 class ImpressDocument(PresentationDocument):
235 """
236@@ -213,6 +281,8 @@
237 self.document = None
238 self.presentation = None
239 self.control = None
240+ self.slide_ended = False
241+ self.slide_ended_reverse = False
242
243 def load_presentation(self):
244 """
245@@ -233,13 +303,16 @@
246 return False
247 self.desktop = desktop
248 properties = []
249- properties.append(self.create_property('Hidden', True))
250+ properties.append(self.controller.create_property('Hidden', True))
251 properties = tuple(properties)
252 try:
253 self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
254 except Exception:
255 log.warning('Failed to load presentation {url}'.format(url=url))
256 return False
257+ if self.document is None:
258+ log.warning('Presentation {url} could not be loaded'.format(url=url))
259+ return False
260 self.presentation = self.document.getPresentation()
261 self.presentation.Display = ScreenList().current.number + 1
262 self.control = None
263@@ -257,7 +330,7 @@
264 temp_folder_path = self.get_temp_folder()
265 thumb_dir_url = temp_folder_path.as_uri()
266 properties = []
267- properties.append(self.create_property('FilterName', 'impress_png_Export'))
268+ properties.append(self.controller.create_property('FilterName', 'impress_png_Export'))
269 properties = tuple(properties)
270 doc = self.document
271 pages = doc.getDrawPages()
272@@ -279,19 +352,6 @@
273 except Exception:
274 log.exception('{path} - Unable to store openoffice preview'.format(path=path))
275
276- def create_property(self, name, value):
277- """
278- Create an OOo style property object which are passed into some Uno methods.
279- """
280- log.debug('create property OpenOffice')
281- if is_win():
282- property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
283- else:
284- property_object = PropertyValue()
285- property_object.Name = name
286- property_object.Value = value
287- return property_object
288-
289 def close_presentation(self):
290 """
291 Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
292@@ -356,8 +416,7 @@
293 log.debug('is blank OpenOffice')
294 if self.control and self.control.isRunning():
295 return self.control.isPaused()
296- else:
297- return False
298+ return False
299
300 def stop_presentation(self):
301 """
302@@ -384,6 +443,8 @@
303 sleep_count += 1
304 self.control = self.presentation.getController()
305 window.setVisible(False)
306+ listener = SlideShowListener(self)
307+ self.control.getSlideShow().addSlideShowListener(listener)
308 else:
309 self.control.activate()
310 self.goto_slide(1)
311@@ -415,17 +476,33 @@
312 """
313 Triggers the next effect of slide on the running presentation.
314 """
315+ # if we are at the presentations end don't go further, just return True
316+ if self.slide_ended and self.get_slide_count() == self.get_slide_number():
317+ return True
318+ self.slide_ended = False
319+ self.slide_ended_reverse = False
320+ past_end = False
321 is_paused = self.control.isPaused()
322 self.control.gotoNextEffect()
323 time.sleep(0.1)
324+ # If for some reason the presentation end was not detected above, this will catch it.
325+ # The presentation is set to paused when going past the end.
326 if not is_paused and self.control.isPaused():
327 self.control.gotoPreviousEffect()
328+ past_end = True
329+ return past_end
330
331 def previous_step(self):
332 """
333 Triggers the previous slide on the running presentation.
334 """
335+ # if we are at the presentations start don't go further back, just return True
336+ if self.slide_ended_reverse and self.get_slide_number() == 1:
337+ return True
338+ self.slide_ended = False
339+ self.slide_ended_reverse = False
340 self.control.gotoPreviousEffect()
341+ return False
342
343 def get_slide_text(self, slide_no):
344 """
345@@ -483,3 +560,100 @@
346 note = ' '
347 notes.append(note)
348 self.save_titles_and_notes(titles, notes)
349+
350+ if is_win():
351+ property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
352+
353+
354+class SlideShowListener(SlideShowListenerImport):
355+ """
356+ Listener interface to receive global slide show events.
357+ """
358+
359+ def __init__(self, document):
360+ """
361+ Constructor
362+
363+ :param document: The ImpressDocument being presented
364+ """
365+ self.document = document
366+
367+ def paused(self):
368+ """
369+ Notify that the slide show is paused
370+ """
371+ log.debug('LibreOffice SlideShowListener event: paused')
372+
373+ def resumed(self):
374+ """
375+ Notify that the slide show is resumed from a paused state
376+ """
377+ log.debug('LibreOffice SlideShowListener event: resumed')
378+
379+ def slideTransitionStarted(self):
380+ """
381+ Notify that a new slide starts to become visible.
382+ """
383+ log.debug('LibreOffice SlideShowListener event: slideTransitionStarted')
384+
385+ def slideTransitionEnded(self):
386+ """
387+ Notify that the slide transtion of the current slide ended.
388+ """
389+ log.debug('LibreOffice SlideShowListener event: slideTransitionEnded')
390+
391+ def slideAnimationsEnded(self):
392+ """
393+ Notify that the last animation from the main sequence of the current slide has ended.
394+ """
395+ log.debug('LibreOffice SlideShowListener event: slideAnimationsEnded')
396+ if not Registry().get('main_window').isActiveWindow():
397+ log.debug('main window is not in focus - should update slidecontroller')
398+ Registry().execute('slidecontroller_live_change', self.document.control.getCurrentSlideIndex() + 1)
399+
400+ def slideEnded(self, reverse):
401+ """
402+ Notify that the current slide has ended, e.g. the user has clicked on the slide. Calling displaySlide()
403+ twice will not issue this event.
404+ """
405+ log.debug('LibreOffice SlideShowListener event: slideEnded %d' % reverse)
406+ if reverse:
407+ self.document.slide_ended = False
408+ self.document.slide_ended_reverse = True
409+ else:
410+ self.document.slide_ended = True
411+ self.document.slide_ended_reverse = False
412+
413+ def hyperLinkClicked(self, hyperLink):
414+ """
415+ Notifies that a hyperlink has been clicked.
416+ """
417+ log.debug('LibreOffice SlideShowListener event: hyperLinkClicked %s' % hyperLink)
418+
419+ def disposing(self, source):
420+ """
421+ gets called when the broadcaster is about to be disposed.
422+ :param source:
423+ """
424+ log.debug('LibreOffice SlideShowListener event: disposing')
425+
426+ def beginEvent(self, node):
427+ """
428+ This event is raised when the element local timeline begins to play.
429+ :param node:
430+ """
431+ log.debug('LibreOffice SlideShowListener event: beginEvent')
432+
433+ def endEvent(self, node):
434+ """
435+ This event is raised at the active end of the element.
436+ :param node:
437+ """
438+ log.debug('LibreOffice SlideShowListener event: endEvent')
439+
440+ def repeat(self, node):
441+ """
442+ This event is raised when the element local timeline repeats.
443+ :param node:
444+ """
445+ log.debug('LibreOffice SlideShowListener event: repeat')
446
447=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
448--- openlp/plugins/presentations/lib/messagelistener.py 2019-05-22 06:47:00 +0000
449+++ openlp/plugins/presentations/lib/messagelistener.py 2019-05-24 19:21:37 +0000
450@@ -169,24 +169,21 @@
451 """
452 log.debug('Live = {live}, next'.format(live=self.is_live))
453 if not self.doc:
454- return
455+ return False
456 if not self.is_live:
457- return
458+ return False
459 if self.hide_mode:
460 if not self.doc.is_active():
461- return
462+ return False
463 if self.doc.slidenumber < self.doc.get_slide_count():
464 self.doc.slidenumber += 1
465 self.poll()
466- return
467+ return False
468 if not self.activate():
469- return
470- # The "End of slideshow" screen is after the last slide. Note, we can't just stop on the last slide, since it
471- # may contain animations that need to be stepped through.
472- if self.doc.slidenumber > self.doc.get_slide_count():
473- return
474- self.doc.next_step()
475+ return False
476+ ret = self.doc.next_step()
477 self.poll()
478+ return ret
479
480 def previous(self):
481 """
482@@ -194,20 +191,21 @@
483 """
484 log.debug('Live = {live}, previous'.format(live=self.is_live))
485 if not self.doc:
486- return
487+ return False
488 if not self.is_live:
489- return
490+ return False
491 if self.hide_mode:
492 if not self.doc.is_active():
493- return
494+ return False
495 if self.doc.slidenumber > 1:
496 self.doc.slidenumber -= 1
497 self.poll()
498- return
499+ return False
500 if not self.activate():
501- return
502- self.doc.previous_step()
503+ return False
504+ ret = self.doc.previous_step()
505 self.poll()
506+ return ret
507
508 def shutdown(self):
509 """
510@@ -418,11 +416,12 @@
511 """
512 is_live = message[1]
513 if is_live:
514- self.live_handler.next()
515+ ret = self.live_handler.next()
516 if Settings().value('core/click live slide to unblank'):
517 Registry().execute('slidecontroller_live_unblank')
518+ return ret
519 else:
520- self.preview_handler.next()
521+ return self.preview_handler.next()
522
523 def previous(self, message):
524 """
525@@ -432,11 +431,12 @@
526 """
527 is_live = message[1]
528 if is_live:
529- self.live_handler.previous()
530+ ret = self.live_handler.previous()
531 if Settings().value('core/click live slide to unblank'):
532 Registry().execute('slidecontroller_live_unblank')
533+ return ret
534 else:
535- self.preview_handler.previous()
536+ return self.preview_handler.previous()
537
538 def shutdown(self, message):
539 """
540
541=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
542--- openlp/plugins/presentations/lib/powerpointcontroller.py 2019-05-22 06:47:00 +0000
543+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2019-05-24 19:21:37 +0000
544@@ -170,14 +170,17 @@
545 However, for the moment, we want a physical file since it makes life easier elsewhere.
546 """
547 log.debug('create_thumbnails')
548+ generate_thumbs = True
549 if self.check_thumbnails():
550- return
551+ # No need for thumbnails but we still need the index
552+ generate_thumbs = False
553 key = 1
554 for num in range(self.presentation.Slides.Count):
555 if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
556 self.index_map[key] = num + 1
557- self.presentation.Slides(num + 1).Export(
558- str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
559+ if generate_thumbs:
560+ self.presentation.Slides(num + 1).Export(
561+ str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
562 key += 1
563 self.slide_count = key - 1
564
565@@ -318,6 +321,9 @@
566 size = ScreenList().current.display_geometry
567 ppt_window = None
568 try:
569+ # Disable the presentation console
570+ self.presentation.SlideShowSettings.ShowPresenterView = 0
571+ # Start the presentation
572 ppt_window = self.presentation.SlideShowSettings.Run()
573 except (AttributeError, pywintypes.com_error):
574 log.exception('Caught exception while in start_presentation')
575@@ -437,6 +443,12 @@
576 Triggers the next effect of slide on the running presentation.
577 """
578 log.debug('next_step')
579+ # if we are at the presentations end don't go further, just return True
580+ if self.presentation.SlideShowWindow.View.GetClickCount() == \
581+ self.presentation.SlideShowWindow.View.GetClickIndex() \
582+ and self.get_slide_number() == self.get_slide_count():
583+ return True
584+ past_end = False
585 try:
586 self.presentation.SlideShowWindow.Activate()
587 self.presentation.SlideShowWindow.View.Next()
588@@ -444,28 +456,35 @@
589 log.exception('Caught exception while in next_step')
590 trace_error_handler(log)
591 self.show_error_msg()
592- return
593+ return past_end
594+ # If for some reason the presentation end was not detected above, this will catch it.
595 if self.get_slide_number() > self.get_slide_count():
596 log.debug('past end, stepping back to previous')
597 self.previous_step()
598+ past_end = True
599 # Stop powerpoint from flashing in the taskbar
600 if self.presentation_hwnd:
601 win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
602 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
603 if len(ScreenList()) > 1:
604 Registry().get('main_window').activateWindow()
605+ return past_end
606
607 def previous_step(self):
608 """
609 Triggers the previous slide on the running presentation.
610 """
611 log.debug('previous_step')
612+ # if we are at the presentations start we can't go further back, just return True
613+ if self.presentation.SlideShowWindow.View.GetClickIndex() == 0 and self.get_slide_number() == 1:
614+ return True
615 try:
616 self.presentation.SlideShowWindow.View.Previous()
617 except (AttributeError, pywintypes.com_error):
618 log.exception('Caught exception while in previous_step')
619 trace_error_handler(log)
620 self.show_error_msg()
621+ return False
622
623 def get_slide_text(self, slide_no):
624 """
625
626=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
627--- openlp/plugins/presentations/lib/presentationcontroller.py 2019-05-22 06:47:00 +0000
628+++ openlp/plugins/presentations/lib/presentationcontroller.py 2019-05-24 19:21:37 +0000
629@@ -248,15 +248,17 @@
630 def next_step(self):
631 """
632 Triggers the next effect of slide on the running presentation. This might be the next animation on the current
633- slide, or the next slide
634+ slide, or the next slide.
635+ Returns True if we stepped beyond the slides of the presentation
636 """
637- pass
638+ return False
639
640 def previous_step(self):
641 """
642 Triggers the previous slide on the running presentation
643+ Returns True if we stepped beyond the slides of the presentation
644 """
645- pass
646+ return False
647
648 def convert_thumbnail(self, image_path, index):
649 """