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