Merge lp:~tomasgroth/openlp/ppt-fixes into lp:openlp

Proposed by Tomas Groth
Status: Merged
Approved by: Raoul Snyman
Approved revision: 2529
Merged at revision: 2527
Proposed branch: lp:~tomasgroth/openlp/ppt-fixes
Merge into: lp:openlp
Diff against target: 553 lines (+180/-65)
6 files modified
openlp/plugins/presentations/lib/impresscontroller.py (+1/-1)
openlp/plugins/presentations/lib/messagelistener.py (+2/-2)
openlp/plugins/presentations/lib/powerpointcontroller.py (+123/-60)
openlp/plugins/presentations/lib/presentationtab.py (+25/-0)
openlp/plugins/presentations/presentationplugin.py (+2/-1)
tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py (+27/-1)
To merge this branch: bzr merge lp:~tomasgroth/openlp/ppt-fixes
Reviewer Review Type Date Requested Status
Raoul Snyman Approve
Tim Bentley Approve
Review via email: mp+255050@code.launchpad.net

Description of the change

Take focus back if Powerpoint steals it - fixes bug 1423913.
Optionally advance a Powerpoint slides animation when clicked in the slidecontroller - fixes bug 1194847.
Made OpenLP respect hidden slides. Improved logging in case of errors.
For Impress, go to previous effect instead of the previous slide.

To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) wrote :

lp:~tomasgroth/openlp/ppt-fixes (revision 2529)
[SUCCESS] https//ci.openlp.io/job/Branch-01-Pull/999/
[SUCCESS] https//ci.openlp.io/job/Branch-02-Functional-Tests/922/
[SUCCESS] https//ci.openlp.io/job/Branch-03-Interface-Tests/864/
[SUCCESS] https//ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/753/
[SUCCESS] https//ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/352/
[SUCCESS] https//ci.openlp.io/job/Branch-05a-Code_Analysis/487/
[SUCCESS] https//ci.openlp.io/job/Branch-05b-Test_Coverage/358/

Revision history for this message
Tim Bentley (trb143) wrote :

Looks OK

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
2--- openlp/plugins/presentations/lib/impresscontroller.py 2015-01-18 13:39:21 +0000
3+++ openlp/plugins/presentations/lib/impresscontroller.py 2015-04-02 08:41:26 +0000
4@@ -428,7 +428,7 @@
5 """
6 Triggers the previous slide on the running presentation.
7 """
8- self.control.gotoPreviousSlide()
9+ self.control.gotoPreviousEffect()
10
11 def get_slide_text(self, slide_no):
12 """
13
14=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
15--- openlp/plugins/presentations/lib/messagelistener.py 2015-01-18 13:39:21 +0000
16+++ openlp/plugins/presentations/lib/messagelistener.py 2015-04-02 08:41:26 +0000
17@@ -93,7 +93,7 @@
18 return True
19 if not self.doc.is_loaded():
20 if not self.doc.load_presentation():
21- log.warning('Failed to activate %s' % self.doc.filepath)
22+ log.warning('Failed to activate %s' % self.doc.file_path)
23 return False
24 if self.is_live:
25 self.doc.start_presentation()
26@@ -104,7 +104,7 @@
27 if self.doc.is_active():
28 return True
29 else:
30- log.warning('Failed to activate %s' % self.doc.filepath)
31+ log.warning('Failed to activate %s' % self.doc.file_path)
32 return False
33
34 def slide(self, slide):
35
36=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
37--- openlp/plugins/presentations/lib/powerpointcontroller.py 2015-01-18 13:39:21 +0000
38+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2015-04-02 08:41:26 +0000
39@@ -22,11 +22,14 @@
40 """
41 This module is for controlling powerpoint. PPT API documentation:
42 `http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
43+2010: https://msdn.microsoft.com/en-us/library/office/ff743835%28v=office.14%29.aspx
44+2013: https://msdn.microsoft.com/en-us/library/office/ff743835.aspx
45 """
46 import os
47 import logging
48+import time
49
50-from openlp.core.common import is_win
51+from openlp.core.common import is_win, Settings
52
53 if is_win():
54 from win32com.client import Dispatch
55@@ -36,9 +39,8 @@
56 import pywintypes
57
58 from openlp.core.lib import ScreenList
59-from openlp.core.common import Registry
60 from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
61-from openlp.core.common import trace_error_handler
62+from openlp.core.common import trace_error_handler, Registry
63 from .presentationcontroller import PresentationController, PresentationDocument
64
65 log = logging.getLogger(__name__)
66@@ -82,6 +84,7 @@
67 if not self.process:
68 self.process = Dispatch('PowerPoint.Application')
69 self.process.Visible = True
70+ # ppWindowMinimized = 2
71 self.process.WindowState = 2
72
73 def kill(self):
74@@ -97,8 +100,10 @@
75 if self.process.Presentations.Count > 0:
76 return
77 self.process.Quit()
78- except (AttributeError, pywintypes.com_error):
79- pass
80+ except (AttributeError, pywintypes.com_error) as e:
81+ log.exception('Exception caught while killing powerpoint process')
82+ log.exception(e)
83+ trace_error_handler(log)
84 self.process = None
85
86
87@@ -117,6 +122,8 @@
88 log.debug('Init Presentation Powerpoint')
89 super(PowerpointDocument, self).__init__(controller, presentation)
90 self.presentation = None
91+ self.index_map = {}
92+ self.slide_count = 0
93
94 def load_presentation(self):
95 """
96@@ -131,16 +138,21 @@
97 self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
98 self.create_thumbnails()
99 self.create_titles_and_notes()
100- # Powerpoint 2013 pops up when loading a file, so we minimize it again
101- if self.presentation.Application.Version == u'15.0':
102+ # Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again
103+ if float(self.presentation.Application.Version) >= 14.0:
104 try:
105+ # ppWindowMinimized = 2
106 self.presentation.Application.WindowState = 2
107- except:
108- log.error('Failed to minimize main powerpoint window')
109+ except (AttributeError, pywintypes.com_error) as e:
110+ log.exception('Failed to minimize main powerpoint window')
111+ log.exception(e)
112 trace_error_handler(log)
113+ # Make sure powerpoint doesn't steal focus
114+ Registry().get('main_window').activateWindow()
115 return True
116- except pywintypes.com_error:
117- log.error('PPT open failed')
118+ except (AttributeError, pywintypes.com_error) as e:
119+ log.exception('Exception caught while loading Powerpoint presentation')
120+ log.exception(e)
121 trace_error_handler(log)
122 return False
123
124@@ -158,9 +170,14 @@
125 log.debug('create_thumbnails')
126 if self.check_thumbnails():
127 return
128+ key = 1
129 for num in range(self.presentation.Slides.Count):
130- self.presentation.Slides(num + 1).Export(
131- os.path.join(self.get_thumbnail_folder(), 'slide%d.png' % (num + 1)), 'png', 320, 240)
132+ if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
133+ self.index_map[key] = num + 1
134+ self.presentation.Slides(num + 1).Export(
135+ os.path.join(self.get_thumbnail_folder(), 'slide%d.png' % (key)), 'png', 320, 240)
136+ key += 1
137+ self.slide_count = key - 1
138
139 def close_presentation(self):
140 """
141@@ -171,10 +188,14 @@
142 if self.presentation:
143 try:
144 self.presentation.Close()
145- except pywintypes.com_error:
146- pass
147+ except (AttributeError, pywintypes.com_error) as e:
148+ log.exception('Caught exception while closing powerpoint presentation')
149+ log.exception(e)
150+ trace_error_handler(log)
151 self.presentation = None
152 self.controller.remove_doc(self)
153+ # Make sure powerpoint doesn't steal focus
154+ Registry().get('main_window').activateWindow()
155
156 def is_loaded(self):
157 """
158@@ -188,7 +209,10 @@
159 return False
160 if self.controller.process.Presentations.Count == 0:
161 return False
162- except (AttributeError, pywintypes.com_error):
163+ except (AttributeError, pywintypes.com_error) as e:
164+ log.exception('Caught exception while in is_loaded')
165+ log.exception(e)
166+ trace_error_handler(log)
167 return False
168 return True
169
170@@ -204,7 +228,10 @@
171 return False
172 if self.presentation.SlideShowWindow.View is None:
173 return False
174- except (AttributeError, pywintypes.com_error):
175+ except (AttributeError, pywintypes.com_error) as e:
176+ log.exception('Caught exception while in is_active')
177+ log.exception(e)
178+ trace_error_handler(log)
179 return False
180 return True
181
182@@ -215,19 +242,21 @@
183 log.debug('unblank_screen')
184 try:
185 self.presentation.SlideShowSettings.Run()
186+ # ppSlideShowRunning = 1
187 self.presentation.SlideShowWindow.View.State = 1
188 self.presentation.SlideShowWindow.Activate()
189- if self.presentation.Application.Version == '14.0':
190- # Unblanking is broken in PowerPoint 2010, need to redisplay
191- slide = self.presentation.SlideShowWindow.View.CurrentShowPosition
192- click = self.presentation.SlideShowWindow.View.GetClickIndex()
193- self.presentation.SlideShowWindow.View.GotoSlide(slide)
194- if click:
195- self.presentation.SlideShowWindow.View.GotoClick(click)
196- except pywintypes.com_error:
197- log.error('COM error while in unblank_screen')
198+ # Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay
199+ if float(self.presentation.Application.Version) >= 14.0:
200+ self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
201+ if self.blank_click:
202+ self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
203+ except (AttributeError, pywintypes.com_error) as e:
204+ log.exception('Caught exception while in unblank_screen')
205+ log.exception(e)
206 trace_error_handler(log)
207 self.show_error_msg()
208+ # Make sure powerpoint doesn't steal focus
209+ Registry().get('main_window').activateWindow()
210
211 def blank_screen(self):
212 """
213@@ -235,9 +264,15 @@
214 """
215 log.debug('blank_screen')
216 try:
217+ # Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later
218+ if float(self.presentation.Application.Version) >= 14.0:
219+ self.blank_slide = self.get_slide_number()
220+ self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
221+ # ppSlideShowBlackScreen = 3
222 self.presentation.SlideShowWindow.View.State = 3
223- except pywintypes.com_error:
224- log.error('COM error while in blank_screen')
225+ except (AttributeError, pywintypes.com_error) as e:
226+ log.exception('Caught exception while in blank_screen')
227+ log.exception(e)
228 trace_error_handler(log)
229 self.show_error_msg()
230
231@@ -248,9 +283,11 @@
232 log.debug('is_blank')
233 if self.is_active():
234 try:
235+ # ppSlideShowBlackScreen = 3
236 return self.presentation.SlideShowWindow.View.State == 3
237- except pywintypes.com_error:
238- log.error('COM error while in is_blank')
239+ except (AttributeError, pywintypes.com_error) as e:
240+ log.exception('Caught exception while in is_blank')
241+ log.exception(e)
242 trace_error_handler(log)
243 self.show_error_msg()
244 else:
245@@ -263,8 +300,9 @@
246 log.debug('stop_presentation')
247 try:
248 self.presentation.SlideShowWindow.View.Exit()
249- except pywintypes.com_error:
250- log.error('COM error while in stop_presentation')
251+ except (AttributeError, pywintypes.com_error) as e:
252+ log.exception('Caught exception while in stop_presentation')
253+ log.exception(e)
254 trace_error_handler(log)
255 self.show_error_msg()
256
257@@ -292,15 +330,19 @@
258 ppt_window.Left = size.x() * 72 / dpi
259 ppt_window.Width = size.width() * 72 / dpi
260 except AttributeError as e:
261- log.error('AttributeError while in start_presentation')
262- log.error(e)
263- # Powerpoint 2013 pops up when starting a file, so we minimize it again
264- if self.presentation.Application.Version == u'15.0':
265+ log.exception('AttributeError while in start_presentation')
266+ log.exception(e)
267+ # Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again
268+ if float(self.presentation.Application.Version) >= 14.0:
269 try:
270+ # ppWindowMinimized = 2
271 self.presentation.Application.WindowState = 2
272- except:
273- log.error('Failed to minimize main powerpoint window')
274+ except (AttributeError, pywintypes.com_error) as e:
275+ log.exception('Failed to minimize main powerpoint window')
276+ log.exception(e)
277 trace_error_handler(log)
278+ # Make sure powerpoint doesn't steal focus
279+ Registry().get('main_window').activateWindow()
280
281 def get_slide_number(self):
282 """
283@@ -309,9 +351,19 @@
284 log.debug('get_slide_number')
285 ret = 0
286 try:
287- ret = self.presentation.SlideShowWindow.View.CurrentShowPosition
288- except pywintypes.com_error:
289- log.error('COM error while in get_slide_number')
290+ # We need 2 approaches to getting the current slide number, because
291+ # SlideShowWindow.View.Slide.SlideIndex wont work on the end-slide where Slide isn't available, and
292+ # SlideShowWindow.View.CurrentShowPosition returns 0 when called when a transistion is executing (in 2013)
293+ # So we use SlideShowWindow.View.Slide.SlideIndex unless the state is done (ppSlideShowDone = 5)
294+ if self.presentation.SlideShowWindow.View.State != 5:
295+ ret = self.presentation.SlideShowWindow.View.Slide.SlideNumber
296+ # Do reverse lookup in the index_map to find the slide number to return
297+ ret = next((key for key, slidenum in self.index_map.items() if slidenum == ret), None)
298+ else:
299+ ret = self.presentation.SlideShowWindow.View.CurrentShowPosition
300+ except (AttributeError, pywintypes.com_error) as e:
301+ log.exception('Caught exception while in get_slide_number')
302+ log.exception(e)
303 trace_error_handler(log)
304 self.show_error_msg()
305 return ret
306@@ -321,14 +373,7 @@
307 Returns total number of slides.
308 """
309 log.debug('get_slide_count')
310- ret = 0
311- try:
312- ret = self.presentation.Slides.Count
313- except pywintypes.com_error:
314- log.error('COM error while in get_slide_count')
315- trace_error_handler(log)
316- self.show_error_msg()
317- return ret
318+ return self.slide_count
319
320 def goto_slide(self, slide_no):
321 """
322@@ -338,9 +383,19 @@
323 """
324 log.debug('goto_slide')
325 try:
326- self.presentation.SlideShowWindow.View.GotoSlide(slide_no)
327- except pywintypes.com_error:
328- log.error('COM error while in goto_slide')
329+ if Settings().value('presentations/powerpoint slide click advance') \
330+ and self.get_slide_number() == self.index_map[slide_no]:
331+ click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
332+ click_count = self.presentation.SlideShowWindow.View.GetClickCount()
333+ log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
334+ % (click_index, click_count))
335+ if click_index < click_count:
336+ self.next_step()
337+ else:
338+ self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[slide_no])
339+ except (AttributeError, pywintypes.com_error) as e:
340+ log.exception('Caught exception while in goto_slide')
341+ log.exception(e)
342 trace_error_handler(log)
343 self.show_error_msg()
344
345@@ -351,12 +406,14 @@
346 log.debug('next_step')
347 try:
348 self.presentation.SlideShowWindow.View.Next()
349- except pywintypes.com_error:
350- log.error('COM error while in next_step')
351+ except (AttributeError, pywintypes.com_error) as e:
352+ log.exception('Caught exception while in next_step')
353+ log.exception(e)
354 trace_error_handler(log)
355 self.show_error_msg()
356 return
357 if self.get_slide_number() > self.get_slide_count():
358+ log.debug('past end, stepping back to previous')
359 self.previous_step()
360
361 def previous_step(self):
362@@ -366,8 +423,9 @@
363 log.debug('previous_step')
364 try:
365 self.presentation.SlideShowWindow.View.Previous()
366- except pywintypes.com_error:
367- log.error('COM error while in previous_step')
368+ except (AttributeError, pywintypes.com_error) as e:
369+ log.exception('Caught exception while in previous_step')
370+ log.exception(e)
371 trace_error_handler(log)
372 self.show_error_msg()
373
374@@ -377,7 +435,7 @@
375
376 :param slide_no: The slide the text is required for, starting at 1
377 """
378- return _get_text_from_shapes(self.presentation.Slides(slide_no).Shapes)
379+ return _get_text_from_shapes(self.presentation.Slides(self.index_map[slide_no]).Shapes)
380
381 def get_slide_notes(self, slide_no):
382 """
383@@ -385,7 +443,7 @@
384
385 :param slide_no: The slide the text is required for, starting at 1
386 """
387- return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
388+ return _get_text_from_shapes(self.presentation.Slides(self.index_map[slide_no]).NotesPage.Shapes)
389
390 def create_titles_and_notes(self):
391 """
392@@ -396,7 +454,8 @@
393 """
394 titles = []
395 notes = []
396- for slide in self.presentation.Slides:
397+ for num in range(self.get_slide_count()):
398+ slide = self.presentation.Slides(self.index_map[num + 1])
399 try:
400 text = slide.Shapes.Title.TextFrame.TextRange.Text
401 except Exception as e:
402@@ -413,7 +472,11 @@
403 """
404 Stop presentation and display an error message.
405 """
406- self.stop_presentation()
407+ try:
408+ self.presentation.SlideShowWindow.View.Exit()
409+ except (AttributeError, pywintypes.com_error) as e:
410+ log.exception('Failed to exit Powerpoint presentation after error')
411+ log.exception(e)
412 critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PowerpointDocument',
413 'An error occurred in the Powerpoint integration '
414 'and the presentation will be stopped. '
415
416=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
417--- openlp/plugins/presentations/lib/presentationtab.py 2015-01-18 13:39:21 +0000
418+++ openlp/plugins/presentations/lib/presentationtab.py 2015-04-02 08:41:26 +0000
419@@ -68,6 +68,15 @@
420 self.override_app_check_box.setObjectName('override_app_check_box')
421 self.advanced_layout.addWidget(self.override_app_check_box)
422 self.left_layout.addWidget(self.advanced_group_box)
423+ # PowerPoint
424+ self.powerpoint_group_box = QtGui.QGroupBox(self.left_column)
425+ self.powerpoint_group_box.setObjectName('powerpoint_group_box')
426+ self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
427+ self.powerpoint_layout.setObjectName('powerpoint_layout')
428+ self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
429+ self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')
430+ self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
431+ self.left_layout.addWidget(self.powerpoint_group_box)
432 # Pdf options
433 self.pdf_group_box = QtGui.QGroupBox(self.left_column)
434 self.pdf_group_box.setObjectName('pdf_group_box')
435@@ -108,8 +117,12 @@
436 self.set_controller_text(checkbox, controller)
437 self.advanced_group_box.setTitle(UiStrings().Advanced)
438 self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
439+ self.powerpoint_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PowerPoint options'))
440 self.override_app_check_box.setText(
441 translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
442+ self.ppt_slide_click_check_box.setText(
443+ translate('PresentationPlugin.PresentationTab',
444+ 'Clicking on a selected slide in the slidecontroller advances to next effect.'))
445 self.pdf_program_check_box.setText(
446 translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
447
448@@ -123,11 +136,18 @@
449 """
450 Load the settings.
451 """
452+ powerpoint_available = False
453 for key in self.controllers:
454 controller = self.controllers[key]
455 checkbox = self.presenter_check_boxes[controller.name]
456 checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
457+ if controller.name == 'Powerpoint' and controller.is_available():
458+ powerpoint_available = True
459 self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
460+ # Load Powerpoint settings
461+ self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
462+ '/powerpoint slide click advance'))
463+ self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
464 # load pdf-program settings
465 enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
466 self.pdf_program_check_box.setChecked(enable_pdf_program)
467@@ -161,6 +181,11 @@
468 if Settings().value(setting_key) != self.override_app_check_box.checkState():
469 Settings().setValue(setting_key, self.override_app_check_box.checkState())
470 changed = True
471+ # Save powerpoint settings
472+ setting_key = self.settings_section + '/powerpoint slide click advance'
473+ if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
474+ Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
475+ changed = True
476 # Save pdf-settings
477 pdf_program = self.pdf_program_path.text()
478 enable_pdf_program = self.pdf_program_check_box.checkState()
479
480=== modified file 'openlp/plugins/presentations/presentationplugin.py'
481--- openlp/plugins/presentations/presentationplugin.py 2015-03-10 22:00:32 +0000
482+++ openlp/plugins/presentations/presentationplugin.py 2015-04-02 08:41:26 +0000
483@@ -44,7 +44,8 @@
484 'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
485 'presentations/Pdf': QtCore.Qt.Checked,
486 'presentations/presentations files': [],
487- 'presentations/thumbnail_scheme': ''
488+ 'presentations/thumbnail_scheme': '',
489+ 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked
490 }
491
492
493
494=== modified file 'tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py'
495--- tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-01-18 13:39:21 +0000
496+++ tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-04-02 08:41:26 +0000
497@@ -33,11 +33,15 @@
498
499 from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument,\
500 _get_text_from_shapes
501-from openlp.core.common import is_win
502+from openlp.core.common import is_win, Settings
503
504 if is_win():
505 import pywintypes
506
507+__default_settings__ = {
508+ 'presentations/powerpoint slide click advance': True
509+}
510+
511
512 class TestPowerpointController(TestCase, TestMixin):
513 """
514@@ -104,6 +108,7 @@
515 self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
516 self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
517 self.real_controller = PowerpointController(self.mock_plugin)
518+ Settings().extend_default_settings(__default_settings__)
519
520 def tearDown(self):
521 """
522@@ -126,6 +131,7 @@
523 instance = PowerpointDocument(self.mock_controller, self.mock_presentation)
524 instance.presentation = MagicMock()
525 instance.presentation.SlideShowWindow.View.GotoSlide = MagicMock(side_effect=pywintypes.com_error('1'))
526+ instance.index_map[42] = 42
527
528 # WHEN: Calling goto_slide which will throw an exception
529 instance.goto_slide(42)
530@@ -227,3 +233,23 @@
531
532 # THEN: it should not fail but return empty string
533 self.assertEqual(result, '', 'result should be empty')
534+
535+ def goto_slide_test(self):
536+ """
537+ Test that goto_slide goes to next effect if the slide is already displayed
538+ """
539+ # GIVEN: A Document with mocked controller, presentation, and mocked functions get_slide_number and next_step
540+ doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
541+ doc.presentation = MagicMock()
542+ doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 1
543+ doc.presentation.SlideShowWindow.View.GetClickCount.return_value = 2
544+ doc.get_slide_number = MagicMock()
545+ doc.get_slide_number.return_value = 1
546+ doc.next_step = MagicMock()
547+ doc.index_map[1] = 1
548+
549+ # WHEN: Calling goto_slide
550+ doc.goto_slide(1)
551+
552+ # THEN: next_step() should be call to try to advance to the next effect.
553+ self.assertTrue(doc.next_step.called, 'next_step() should have been called!')