Merge lp:~tomasgroth/openlp/bugfixes20 into lp:openlp

Proposed by Tomas Groth
Status: Merged
Approved by: Tim Bentley
Approved revision: 2562
Merged at revision: 2539
Proposed branch: lp:~tomasgroth/openlp/bugfixes20
Merge into: lp:openlp
Diff against target: 723 lines (+293/-108)
13 files modified
openlp/.version (+1/-1)
openlp/core/ui/mainwindow.py (+4/-1)
openlp/core/ui/slidecontroller.py (+20/-8)
openlp/plugins/bibles/lib/mediaitem.py (+1/-1)
openlp/plugins/presentations/lib/messagelistener.py (+3/-2)
openlp/plugins/presentations/lib/powerpointcontroller.py (+111/-59)
openlp/plugins/presentations/lib/presentationtab.py (+13/-1)
openlp/plugins/presentations/presentationplugin.py (+2/-1)
openlp/plugins/songs/lib/importers/worshipassistant.py (+1/-0)
tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py (+82/-34)
tests/functional/openlp_plugins/songs/test_worshipassistantimport.py (+2/-0)
tests/resources/worshipassistantsongs/lift_up_your_heads.csv (+40/-0)
tests/resources/worshipassistantsongs/lift_up_your_heads.json (+13/-0)
To merge this branch: bzr merge lp:~tomasgroth/openlp/bugfixes20
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Raoul Snyman Approve
Review via email: mp+260252@code.launchpad.net

This proposal supersedes a proposal from 2015-05-26.

Description of the change

For worshipassistant add a default verse-id for lyrics to use if none is given. Fixes bug 1458056.
Don't import setting keys that does not exists. Fixes bug 1458672.
When going from a theme-blanked item to item which doesn't support theme-blanking, switch to black-blank.
Only use transitions if we are changing slide. Fixes bug 1449064.
Make translation of 'Advanced' specific to the bible plugin.
Many PowerPoint fixes/improvements:
 * Make screenshots for main webview, even on single screen setup. Fixes bug 1449041.
 * Implement workaround for unblanking bug in PowerPoint 2010.
 * Open PowerPoint hidden so the main application window isn't visible.
 * Added support for odp for PowerPoint 2007 and newer.
 * Added support for Powerpoint events, which is used to update the slidecontroller if OpenLP is not in focus.
 * Minimized the flashing of the PowerPoint presentation window in the taskbar.

To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal
Revision history for this message
Tomas Groth (tomasgroth) wrote :
Revision history for this message
Tim Bentley (trb143) :
review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) wrote :

Se my reply to your comment below.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/.version'
2--- openlp/.version 2015-02-21 20:05:25 +0000
3+++ openlp/.version 2015-05-27 08:50:25 +0000
4@@ -1,1 +1,1 @@
5-2.1.3
6+2.1.4
7
8=== modified file 'openlp/core/ui/mainwindow.py'
9--- openlp/core/ui/mainwindow.py 2015-02-21 20:05:25 +0000
10+++ openlp/core/ui/mainwindow.py 2015-05-27 08:50:25 +0000
11@@ -900,7 +900,10 @@
12 for section_key in import_keys:
13 if 'eneral' in section_key:
14 section_key = section_key.lower()
15- value = import_settings.value(section_key)
16+ try:
17+ value = import_settings.value(section_key)
18+ except KeyError:
19+ log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key)
20 if value is not None:
21 settings.setValue('%s' % (section_key), value)
22 now = datetime.now()
23
24=== modified file 'openlp/core/ui/slidecontroller.py'
25--- openlp/core/ui/slidecontroller.py 2015-05-25 19:31:12 +0000
26+++ openlp/core/ui/slidecontroller.py 2015-05-27 08:50:25 +0000
27@@ -828,8 +828,10 @@
28 self.selected_row = 0
29 # take a copy not a link to the servicemanager copy.
30 self.service_item = copy.copy(service_item)
31- if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
32- self._reset_blank()
33+ # Reset blanking if needed
34+ if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
35+ self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
36+ self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
37 if service_item.is_command():
38 Registry().execute(
39 '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
40@@ -1080,6 +1082,7 @@
41 % timeout)
42 return
43 row = self.preview_widget.current_slide_number()
44+ old_selected_row = self.selected_row
45 self.selected_row = 0
46 if -1 < row < self.preview_widget.slide_count():
47 if self.service_item.is_command():
48@@ -1089,7 +1092,7 @@
49 else:
50 to_display = self.service_item.get_rendered_frame(row)
51 if self.service_item.is_text():
52- self.display.text(to_display)
53+ self.display.text(to_display, row != old_selected_row)
54 else:
55 if start:
56 self.display.build_html(self.service_item, to_display)
57@@ -1119,8 +1122,7 @@
58 This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
59 """
60 self.log_debug('update_preview %s ' % self.screens.current['primary'])
61- if not self.screens.current['primary'] and self.service_item and \
62- self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
63+ if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
64 # Grab now, but try again in a couple of seconds if slide change is slow
65 QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
66 QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
67@@ -1349,7 +1351,11 @@
68
69 :param item: The service item to be processed
70 """
71- self.media_controller.video(self.controller_type, item, self.hide_mode())
72+ if self.is_live and self.hide_mode() == HideMode.Theme:
73+ self.media_controller.video(self.controller_type, item, HideMode.Blank)
74+ self.on_blank_display(True)
75+ else:
76+ self.media_controller.video(self.controller_type, item, self.hide_mode())
77 if not self.is_live:
78 self.preview_display.show()
79 self.slide_preview.hide()
80@@ -1362,16 +1368,22 @@
81 self.preview_display.hide()
82 self.slide_preview.show()
83
84- def _reset_blank(self):
85+ def _reset_blank(self, no_theme):
86 """
87 Used by command items which provide their own displays to reset the
88 screen hide attributes
89+
90+ :param no_theme: Does the new item support theme-blanking.
91 """
92 hide_mode = self.hide_mode()
93 if hide_mode == HideMode.Blank:
94 self.on_blank_display(True)
95 elif hide_mode == HideMode.Theme:
96- self.on_theme_display(True)
97+ # The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
98+ if no_theme:
99+ self.on_blank_display(True)
100+ else:
101+ self.on_theme_display(True)
102 elif hide_mode == HideMode.Screen:
103 self.on_hide_display(True)
104 else:
105
106=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
107--- openlp/plugins/bibles/lib/mediaitem.py 2015-04-07 22:01:52 +0000
108+++ openlp/plugins/bibles/lib/mediaitem.py 2015-05-27 08:50:25 +0000
109@@ -193,7 +193,7 @@
110 self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
111 self.quickTab.setVisible(True)
112 # Add the Advanced Search tab.
113- self.add_search_tab('advanced', UiStrings().Advanced)
114+ self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Advanced'))
115 self.advanced_book_label = QtGui.QLabel(self.advancedTab)
116 self.advanced_book_label.setObjectName('advanced_book_label')
117 self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
118
119=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
120--- openlp/plugins/presentations/lib/messagelistener.py 2015-03-26 14:17:14 +0000
121+++ openlp/plugins/presentations/lib/messagelistener.py 2015-05-27 08:50:25 +0000
122@@ -243,6 +243,9 @@
123 Instruct the controller to stop and hide the presentation.
124 """
125 log.debug('Live = %s, stop' % self.is_live)
126+ # Save the current slide number to be able to return to this slide if the presentation is activated again.
127+ if self.doc.is_active():
128+ self.doc.slidenumber = self.doc.get_slide_number()
129 self.hide_mode = HideMode.Screen
130 if not self.doc:
131 return
132@@ -266,8 +269,6 @@
133 return
134 if not self.activate():
135 return
136- if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number():
137- self.doc.goto_slide(self.doc.slidenumber)
138 self.doc.unblank_screen()
139 Registry().execute('live_display_hide', HideMode.Screen)
140
141
142=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
143--- openlp/plugins/presentations/lib/powerpointcontroller.py 2015-04-02 08:33:46 +0000
144+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2015-05-27 08:50:25 +0000
145@@ -32,12 +32,15 @@
146 from openlp.core.common import is_win, Settings
147
148 if is_win():
149- from win32com.client import Dispatch
150+ from win32com.client import DispatchWithEvents
151 import win32com
152+ import win32con
153 import winreg
154 import win32ui
155+ import win32gui
156 import pywintypes
157
158+
159 from openlp.core.lib import ScreenList
160 from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
161 from openlp.core.common import trace_error_handler, Registry
162@@ -70,8 +73,18 @@
163 if is_win():
164 try:
165 winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()
166+ try:
167+ # Try to detect if the version is 12 (2007) or above, and if so add 'odp' as a support filetype
168+ version_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application\\CurVer')
169+ tmp1, app_version_string, tmp2 = winreg.EnumValue(version_key, 0)
170+ version_key.Close()
171+ app_version = int(app_version_string[-2:])
172+ if app_version >= 12:
173+ self.also_supports = ['odp']
174+ except (OSError, ValueError):
175+ log.warning('Detection of powerpoint version using registry failed.')
176 return True
177- except WindowsError:
178+ except OSError:
179 pass
180 return False
181
182@@ -80,12 +93,22 @@
183 """
184 Loads PowerPoint process.
185 """
186+ class PowerPointEvents:
187+ """
188+ Class to catch events from PowerPoint.
189+ """
190+ def OnSlideShowNextClick(self, slideshow_window, effect):
191+ """
192+ Occurs on the next click of the slide.
193+ If the main OpenLP window is not in focus force update of the slidecontroller.
194+ """
195+ if not Registry().get('main_window').isActiveWindow():
196+ log.debug('main window is not in focus - should update slidecontroller')
197+ Registry().execute('slidecontroller_live_change', slideshow_window.View.CurrentShowPosition)
198+
199 log.debug('start_process')
200 if not self.process:
201- self.process = Dispatch('PowerPoint.Application')
202- self.process.Visible = True
203- # ppWindowMinimized = 2
204- self.process.WindowState = 2
205+ self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
206
207 def kill(self):
208 """
209@@ -124,6 +147,9 @@
210 self.presentation = None
211 self.index_map = {}
212 self.slide_count = 0
213+ self.blank_slide = 1
214+ self.blank_click = None
215+ self.presentation_hwnd = None
216
217 def load_presentation(self):
218 """
219@@ -132,23 +158,15 @@
220 """
221 log.debug('load_presentation')
222 try:
223- if not self.controller.process or not self.controller.process.Visible:
224+ if not self.controller.process:
225 self.controller.start_process()
226- self.controller.process.Presentations.Open(self.file_path, False, False, True)
227+ self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
228 self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
229 self.create_thumbnails()
230 self.create_titles_and_notes()
231- # Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again
232- if float(self.presentation.Application.Version) >= 14.0:
233- try:
234- # ppWindowMinimized = 2
235- self.presentation.Application.WindowState = 2
236- except (AttributeError, pywintypes.com_error) as e:
237- log.exception('Failed to minimize main powerpoint window')
238- log.exception(e)
239- trace_error_handler(log)
240- # Make sure powerpoint doesn't steal focus
241- Registry().get('main_window').activateWindow()
242+ # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
243+ if len(ScreenList().screen_list) > 1:
244+ Registry().get('main_window').activateWindow()
245 return True
246 except (AttributeError, pywintypes.com_error) as e:
247 log.exception('Exception caught while loading Powerpoint presentation')
248@@ -194,8 +212,9 @@
249 trace_error_handler(log)
250 self.presentation = None
251 self.controller.remove_doc(self)
252- # Make sure powerpoint doesn't steal focus
253- Registry().get('main_window').activateWindow()
254+ # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
255+ if len(ScreenList().screen_list) > 1:
256+ Registry().get('main_window').activateWindow()
257
258 def is_loaded(self):
259 """
260@@ -203,10 +222,6 @@
261 """
262 log.debug('is_loaded')
263 try:
264- if not self.controller.process.Visible:
265- return False
266- if self.controller.process.Windows.Count == 0:
267- return False
268 if self.controller.process.Presentations.Count == 0:
269 return False
270 except (AttributeError, pywintypes.com_error) as e:
271@@ -241,13 +256,11 @@
272 """
273 log.debug('unblank_screen')
274 try:
275- self.presentation.SlideShowSettings.Run()
276- # ppSlideShowRunning = 1
277+ self.presentation.SlideShowWindow.Activate()
278 self.presentation.SlideShowWindow.View.State = 1
279- self.presentation.SlideShowWindow.Activate()
280- # Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay
281- if float(self.presentation.Application.Version) >= 14.0:
282- self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
283+ # Unblanking is broken in PowerPoint 2010 (14.0), need to redisplay
284+ if 15.0 > float(self.presentation.Application.Version) >= 14.0:
285+ self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False)
286 if self.blank_click:
287 self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
288 except (AttributeError, pywintypes.com_error) as e:
289@@ -255,8 +268,12 @@
290 log.exception(e)
291 trace_error_handler(log)
292 self.show_error_msg()
293- # Make sure powerpoint doesn't steal focus
294- Registry().get('main_window').activateWindow()
295+ # Stop powerpoint from flashing in the taskbar
296+ if self.presentation_hwnd:
297+ win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
298+ # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
299+ if len(ScreenList().screen_list) > 1:
300+ Registry().get('main_window').activateWindow()
301
302 def blank_screen(self):
303 """
304@@ -264,8 +281,8 @@
305 """
306 log.debug('blank_screen')
307 try:
308- # Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later
309- if float(self.presentation.Application.Version) >= 14.0:
310+ # Unblanking is broken in PowerPoint 2010 (14.0), need to save info for later
311+ if 15.0 > float(self.presentation.Application.Version) >= 14.0:
312 self.blank_slide = self.get_slide_number()
313 self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
314 # ppSlideShowBlackScreen = 3
315@@ -295,7 +312,7 @@
316
317 def stop_presentation(self):
318 """
319- Stops the current presentation and hides the output.
320+ Stops the current presentation and hides the output. Used when blanking to desktop.
321 """
322 log.debug('stop_presentation')
323 try:
324@@ -321,28 +338,52 @@
325 except win32ui.error:
326 dpi = 96
327 size = ScreenList().current['size']
328- ppt_window = self.presentation.SlideShowSettings.Run()
329- if not ppt_window:
330- return
331+ ppt_window = None
332 try:
333- ppt_window.Top = size.y() * 72 / dpi
334- ppt_window.Height = size.height() * 72 / dpi
335- ppt_window.Left = size.x() * 72 / dpi
336- ppt_window.Width = size.width() * 72 / dpi
337- except AttributeError as e:
338- log.exception('AttributeError while in start_presentation')
339+ ppt_window = self.presentation.SlideShowSettings.Run()
340+ except (AttributeError, pywintypes.com_error) as e:
341+ log.exception('Caught exception while in start_presentation')
342 log.exception(e)
343- # Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again
344- if float(self.presentation.Application.Version) >= 14.0:
345+ trace_error_handler(log)
346+ self.show_error_msg()
347+ if ppt_window and not Settings().value('presentations/powerpoint control window'):
348 try:
349- # ppWindowMinimized = 2
350- self.presentation.Application.WindowState = 2
351- except (AttributeError, pywintypes.com_error) as e:
352- log.exception('Failed to minimize main powerpoint window')
353+ ppt_window.Top = size.y() * 72 / dpi
354+ ppt_window.Height = size.height() * 72 / dpi
355+ ppt_window.Left = size.x() * 72 / dpi
356+ ppt_window.Width = size.width() * 72 / dpi
357+ except AttributeError as e:
358+ log.exception('AttributeError while in start_presentation')
359 log.exception(e)
360- trace_error_handler(log)
361- # Make sure powerpoint doesn't steal focus
362- Registry().get('main_window').activateWindow()
363+ # Find the presentation window and save the handle for later
364+ self.presentation_hwnd = None
365+ if ppt_window:
366+ log.debug('main display size: y=%d, height=%d, x=%d, width=%d'
367+ % (size.y(), size.height(), size.x(), size.width()))
368+ win32gui.EnumWindows(self._window_enum_callback, size)
369+ # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
370+ if len(ScreenList().screen_list) > 1:
371+ Registry().get('main_window').activateWindow()
372+
373+ def _window_enum_callback(self, hwnd, size):
374+ """
375+ Method for callback from win32gui.EnumWindows.
376+ Used to find the powerpoint presentation window and stop it flashing in the taskbar.
377+ """
378+ # Get the size of the current window and if it matches the size of our main display we assume
379+ # it is the powerpoint presentation window.
380+ (left, top, right, bottom) = win32gui.GetWindowRect(hwnd)
381+ window_title = win32gui.GetWindowText(hwnd)
382+ log.debug('window size: left=%d, top=%d, right=%d, width=%d' % (left, top, right, bottom))
383+ log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d'
384+ % (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
385+ log.debug('window title: %s' % window_title)
386+ if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
387+ size.width() == (right - left) and os.path.basename(self.file_path) in window_title:
388+ log.debug('Found a match and will save the handle')
389+ self.presentation_hwnd = hwnd
390+ # Stop powerpoint from flashing in the taskbar
391+ win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
392
393 def get_slide_number(self):
394 """
395@@ -384,7 +425,7 @@
396 log.debug('goto_slide')
397 try:
398 if Settings().value('presentations/powerpoint slide click advance') \
399- and self.get_slide_number() == self.index_map[slide_no]:
400+ and self.get_slide_number() == slide_no:
401 click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
402 click_count = self.presentation.SlideShowWindow.View.GetClickCount()
403 log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
404@@ -405,6 +446,7 @@
405 """
406 log.debug('next_step')
407 try:
408+ self.presentation.SlideShowWindow.Activate()
409 self.presentation.SlideShowWindow.View.Next()
410 except (AttributeError, pywintypes.com_error) as e:
411 log.exception('Caught exception while in next_step')
412@@ -415,6 +457,12 @@
413 if self.get_slide_number() > self.get_slide_count():
414 log.debug('past end, stepping back to previous')
415 self.previous_step()
416+ # Stop powerpoint from flashing in the taskbar
417+ if self.presentation_hwnd:
418+ win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
419+ # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
420+ if len(ScreenList().screen_list) > 1:
421+ Registry().get('main_window').activateWindow()
422
423 def previous_step(self):
424 """
425@@ -490,8 +538,12 @@
426 :param shapes: A set of shapes to search for text.
427 """
428 text = ''
429- for shape in shapes:
430- if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
431- if shape.HasTextFrame and shape.TextFrame.HasText:
432- text += shape.TextFrame.TextRange.Text + '\n'
433+ try:
434+ for shape in shapes:
435+ if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
436+ if shape.HasTextFrame and shape.TextFrame.HasText:
437+ text += shape.TextFrame.TextRange.Text + '\n'
438+ except pywintypes.com_error as e:
439+ log.warning('Failed to extract text from powerpoint slide')
440+ log.warning(e)
441 return text
442
443=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
444--- openlp/plugins/presentations/lib/presentationtab.py 2015-04-02 08:33:46 +0000
445+++ openlp/plugins/presentations/lib/presentationtab.py 2015-05-27 08:50:25 +0000
446@@ -74,8 +74,11 @@
447 self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
448 self.powerpoint_layout.setObjectName('powerpoint_layout')
449 self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
450- self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')
451+ self.ppt_slide_click_check_box.setObjectName('ppt_slide_click_check_box')
452 self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
453+ self.ppt_window_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
454+ self.ppt_window_check_box.setObjectName('ppt_window_check_box')
455+ self.powerpoint_layout.addWidget(self.ppt_window_check_box)
456 self.left_layout.addWidget(self.powerpoint_group_box)
457 # Pdf options
458 self.pdf_group_box = QtGui.QGroupBox(self.left_column)
459@@ -123,6 +126,9 @@
460 self.ppt_slide_click_check_box.setText(
461 translate('PresentationPlugin.PresentationTab',
462 'Clicking on a selected slide in the slidecontroller advances to next effect.'))
463+ self.ppt_window_check_box.setText(
464+ translate('PresentationPlugin.PresentationTab',
465+ 'Let PowerPoint control the size and position of the presentation window.'))
466 self.pdf_program_check_box.setText(
467 translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
468
469@@ -148,6 +154,8 @@
470 self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
471 '/powerpoint slide click advance'))
472 self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
473+ self.ppt_window_check_box.setChecked(Settings().value(self.settings_section + '/powerpoint control window'))
474+ self.ppt_window_check_box.setEnabled(powerpoint_available)
475 # load pdf-program settings
476 enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
477 self.pdf_program_check_box.setChecked(enable_pdf_program)
478@@ -186,6 +194,10 @@
479 if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
480 Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
481 changed = True
482+ setting_key = self.settings_section + '/powerpoint control window'
483+ if Settings().value(setting_key) != self.ppt_window_check_box.checkState():
484+ Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
485+ changed = True
486 # Save pdf-settings
487 pdf_program = self.pdf_program_path.text()
488 enable_pdf_program = self.pdf_program_check_box.checkState()
489
490=== modified file 'openlp/plugins/presentations/presentationplugin.py'
491--- openlp/plugins/presentations/presentationplugin.py 2015-03-26 14:22:23 +0000
492+++ openlp/plugins/presentations/presentationplugin.py 2015-05-27 08:50:25 +0000
493@@ -45,7 +45,8 @@
494 'presentations/Pdf': QtCore.Qt.Checked,
495 'presentations/presentations files': [],
496 'presentations/thumbnail_scheme': '',
497- 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked
498+ 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
499+ 'presentations/powerpoint control window': QtCore.Qt.Unchecked
500 }
501
502
503
504=== modified file 'openlp/plugins/songs/lib/importers/worshipassistant.py'
505--- openlp/plugins/songs/lib/importers/worshipassistant.py 2015-03-09 20:57:39 +0000
506+++ openlp/plugins/songs/lib/importers/worshipassistant.py 2015-05-27 08:50:25 +0000
507@@ -131,6 +131,7 @@
508 return
509 verse = ''
510 used_verses = []
511+ verse_id = VerseType.tags[VerseType.Verse] + '1'
512 for line in lyrics.splitlines():
513 if line.startswith('['): # verse marker
514 # Add previous verse
515
516=== modified file 'tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py'
517--- tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-04-02 08:33:46 +0000
518+++ tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-05-27 08:50:25 +0000
519@@ -164,45 +164,42 @@
520 """
521 Test creating the titles from PowerPoint
522 """
523- if is_win() and self.real_controller.check_available():
524- # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
525- self.doc = PowerpointDocument(self.real_controller, self.file_name)
526- self.doc.save_titles_and_notes = MagicMock()
527- self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
528- slide = MagicMock()
529- slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
530- pres = MagicMock()
531- pres.Slides = [slide, slide]
532- self.doc.presentation = pres
533-
534- # WHEN reading the titles and notes
535- self.doc.create_titles_and_notes()
536-
537- # THEN the save should have been called exactly once with 2 titles and 2 notes
538- self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
539- else:
540- self.skipTest('Powerpoint not available, skipping test.')
541+ # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
542+ self.doc = PowerpointDocument(self.mock_controller, self.file_name)
543+ self.doc.get_slide_count = MagicMock()
544+ self.doc.get_slide_count.return_value = 2
545+ self.doc.index_map = {1: 1, 2: 2}
546+ self.doc.save_titles_and_notes = MagicMock()
547+ self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
548+ slide = MagicMock()
549+ slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
550+ pres = MagicMock()
551+ pres.Slides = MagicMock(side_effect=[slide, slide])
552+ self.doc.presentation = pres
553+
554+ # WHEN reading the titles and notes
555+ self.doc.create_titles_and_notes()
556+
557+ # THEN the save should have been called exactly once with 2 titles and 2 notes
558+ self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
559
560 def create_titles_and_notes_with_no_slides_test(self):
561 """
562 Test creating the titles from PowerPoint when it returns no slides
563 """
564- if is_win() and self.real_controller.check_available():
565- # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
566- doc = PowerpointDocument(self.real_controller, self.file_name)
567- doc.save_titles_and_notes = MagicMock()
568- doc._PowerpointDocument__get_text_from_shapes = MagicMock()
569- pres = MagicMock()
570- pres.Slides = []
571- doc.presentation = pres
572-
573- # WHEN reading the titles and notes
574- doc.create_titles_and_notes()
575-
576- # THEN the save should have been called exactly once with empty titles and notes
577- doc.save_titles_and_notes.assert_called_once_with([], [])
578- else:
579- self.skipTest('Powerpoint not available, skipping test.')
580+ # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
581+ doc = PowerpointDocument(self.mock_controller, self.file_name)
582+ doc.save_titles_and_notes = MagicMock()
583+ doc._PowerpointDocument__get_text_from_shapes = MagicMock()
584+ pres = MagicMock()
585+ pres.Slides = []
586+ doc.presentation = pres
587+
588+ # WHEN reading the titles and notes
589+ doc.create_titles_and_notes()
590+
591+ # THEN the save should have been called exactly once with empty titles and notes
592+ doc.save_titles_and_notes.assert_called_once_with([], [])
593
594 def get_text_from_shapes_test(self):
595 """
596@@ -253,3 +250,54 @@
597
598 # THEN: next_step() should be call to try to advance to the next effect.
599 self.assertTrue(doc.next_step.called, 'next_step() should have been called!')
600+
601+ def blank_screen_test(self):
602+ """
603+ Test that blank_screen works as expected
604+ """
605+ # GIVEN: A Document with mocked controller, presentation, and mocked function get_slide_number
606+ doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
607+ doc.presentation = MagicMock()
608+ doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
609+ doc.presentation.Application.Version = 14.0
610+ doc.get_slide_number = MagicMock()
611+ doc.get_slide_number.return_value = 2
612+
613+ # WHEN: Calling goto_slide
614+ doc.blank_screen()
615+
616+ # THEN: The view state, doc.blank_slide and doc.blank_click should have new values
617+ self.assertEquals(doc.presentation.SlideShowWindow.View.State, 3, 'The View State should be 3')
618+ self.assertEquals(doc.blank_slide, 2, 'doc.blank_slide should be 2 because of the PowerPoint version')
619+ self.assertEquals(doc.blank_click, 3, 'doc.blank_click should be 3 because of the PowerPoint version')
620+
621+ def unblank_screen_test(self):
622+ """
623+ Test that unblank_screen works as expected
624+ """
625+ # GIVEN: A Document with mocked controller, presentation, ScreenList, and mocked function get_slide_number
626+ with patch('openlp.plugins.presentations.lib.powerpointcontroller.ScreenList') as mocked_screen_list:
627+ mocked_screen_list_ret = MagicMock()
628+ mocked_screen_list_ret.screen_list = [1]
629+ mocked_screen_list.return_value = mocked_screen_list_ret
630+ doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
631+ doc.presentation = MagicMock()
632+ doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
633+ doc.presentation.Application.Version = 14.0
634+ doc.get_slide_number = MagicMock()
635+ doc.get_slide_number.return_value = 2
636+ doc.index_map[1] = 1
637+ doc.blank_slide = 1
638+ doc.blank_click = 1
639+
640+ # WHEN: Calling goto_slide
641+ doc.unblank_screen()
642+
643+ # THEN: The view state have new value, and several function should have been called
644+ self.assertEquals(doc.presentation.SlideShowWindow.View.State, 1, 'The View State should be 1')
645+ self.assertEquals(doc.presentation.SlideShowWindow.Activate.called, True,
646+ 'SlideShowWindow.Activate should have been called')
647+ self.assertEquals(doc.presentation.SlideShowWindow.View.GotoSlide.called, True,
648+ 'View.GotoSlide should have been called because of the PowerPoint version')
649+ self.assertEquals(doc.presentation.SlideShowWindow.View.GotoClick.called, True,
650+ 'View.GotoClick should have been called because of the PowerPoint version')
651
652=== modified file 'tests/functional/openlp_plugins/songs/test_worshipassistantimport.py'
653--- tests/functional/openlp_plugins/songs/test_worshipassistantimport.py 2015-01-22 17:42:29 +0000
654+++ tests/functional/openlp_plugins/songs/test_worshipassistantimport.py 2015-05-27 08:50:25 +0000
655@@ -49,3 +49,5 @@
656 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
657 self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
658 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
659+ self.file_import(os.path.join(TEST_PATH, 'lift_up_your_heads.csv'),
660+ self.load_external_result_data(os.path.join(TEST_PATH, 'lift_up_your_heads.json')))
661
662=== added file 'tests/resources/worshipassistantsongs/lift_up_your_heads.csv'
663--- tests/resources/worshipassistantsongs/lift_up_your_heads.csv 1970-01-01 00:00:00 +0000
664+++ tests/resources/worshipassistantsongs/lift_up_your_heads.csv 2015-05-27 08:50:25 +0000
665@@ -0,0 +1,40 @@
666+"SongID","SongNr","Title","Author","Copyright","FirstLine","PriKey","AltKey","Tempo","Focus","Theme","Scripture","Active","Songbook","TimeSig","Introduced","LastUsed","TimesUsed","CCLINr","User1","User2","User3","User4","User5","Roadmap","Overmap","FileLink1","FileLink2","Updated","Lyrics","Info","Lyrics2","Background"
667+"000013ab-0000-0000-0000-000000000000","0","Lift Up Your Heads"," Bryan Mierau","Public Domain","Lift up your heads and the doors","Em","NULL","NULL","NULL","NULL","NULL","1","1","NULL","NULL","NULL","0","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","2004-04-07 06:36:18.952",".Em D C D
668+ Lift up your heads and the doors of your heart
669+. Am B7 Em
670+ And the King of glory will come in
671+(Repeat)
672+
673+.G Am D
674+ Who is this King of Glory?
675+. B7 Em
676+ The Lord strong and mighty!
677+.G Am D
678+ Who is this King of Glory?
679+. B7
680+ The Lord, mighty in battle!
681+
682+.G Am D
683+ Who is this King of Glory?
684+.B7 Em
685+ Jesus our Messiah!
686+.G Am D
687+ Who is this King of Glory?
688+.B7 Em
689+ Jesus, Lord of Lords!
690+
691+","NULL","Lift up your heads and the doors of your heart
692+And the King of glory will come in
693+(Repeat)
694+
695+Who is this King of Glory?
696+The Lord strong and mighty!
697+Who is this King of Glory?
698+The Lord, mighty in battle!
699+
700+Who is this King of Glory?
701+Jesus our Messiah!
702+Who is this King of Glory?
703+Jesus, Lord of Lords!
704+
705+","NULL"
706
707=== added file 'tests/resources/worshipassistantsongs/lift_up_your_heads.json'
708--- tests/resources/worshipassistantsongs/lift_up_your_heads.json 1970-01-01 00:00:00 +0000
709+++ tests/resources/worshipassistantsongs/lift_up_your_heads.json 2015-05-27 08:50:25 +0000
710@@ -0,0 +1,13 @@
711+{
712+ "authors": [
713+ "Bryan Mierau"
714+ ],
715+ "title": "Lift Up Your Heads",
716+ "verse_order_list": [],
717+ "verses": [
718+ [
719+ "Lift up your heads and the doors of your heart\nAnd the King of glory will come in\n(Repeat)\n\nWho is this King of Glory?\nThe Lord strong and mighty!\nWho is this King of Glory?\nThe Lord, mighty in battle!\n\nWho is this King of Glory?\nJesus our Messiah!\nWho is this King of Glory?\nJesus, Lord of Lords!\n",
720+ "v1"
721+ ]
722+ ]
723+}