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
=== modified file 'openlp/.version'
--- openlp/.version 2015-02-21 20:05:25 +0000
+++ openlp/.version 2015-05-27 08:50:25 +0000
@@ -1,1 +1,1 @@
12.1.312.1.4
22
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2015-02-21 20:05:25 +0000
+++ openlp/core/ui/mainwindow.py 2015-05-27 08:50:25 +0000
@@ -900,7 +900,10 @@
900 for section_key in import_keys:900 for section_key in import_keys:
901 if 'eneral' in section_key:901 if 'eneral' in section_key:
902 section_key = section_key.lower()902 section_key = section_key.lower()
903 value = import_settings.value(section_key)903 try:
904 value = import_settings.value(section_key)
905 except KeyError:
906 log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key)
904 if value is not None:907 if value is not None:
905 settings.setValue('%s' % (section_key), value)908 settings.setValue('%s' % (section_key), value)
906 now = datetime.now()909 now = datetime.now()
907910
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2015-05-25 19:31:12 +0000
+++ openlp/core/ui/slidecontroller.py 2015-05-27 08:50:25 +0000
@@ -828,8 +828,10 @@
828 self.selected_row = 0828 self.selected_row = 0
829 # take a copy not a link to the servicemanager copy.829 # take a copy not a link to the servicemanager copy.
830 self.service_item = copy.copy(service_item)830 self.service_item = copy.copy(service_item)
831 if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):831 # Reset blanking if needed
832 self._reset_blank()832 if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
833 self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
834 self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
833 if service_item.is_command():835 if service_item.is_command():
834 Registry().execute(836 Registry().execute(
835 '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])837 '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
@@ -1080,6 +1082,7 @@
1080 % timeout)1082 % timeout)
1081 return1083 return
1082 row = self.preview_widget.current_slide_number()1084 row = self.preview_widget.current_slide_number()
1085 old_selected_row = self.selected_row
1083 self.selected_row = 01086 self.selected_row = 0
1084 if -1 < row < self.preview_widget.slide_count():1087 if -1 < row < self.preview_widget.slide_count():
1085 if self.service_item.is_command():1088 if self.service_item.is_command():
@@ -1089,7 +1092,7 @@
1089 else:1092 else:
1090 to_display = self.service_item.get_rendered_frame(row)1093 to_display = self.service_item.get_rendered_frame(row)
1091 if self.service_item.is_text():1094 if self.service_item.is_text():
1092 self.display.text(to_display)1095 self.display.text(to_display, row != old_selected_row)
1093 else:1096 else:
1094 if start:1097 if start:
1095 self.display.build_html(self.service_item, to_display)1098 self.display.build_html(self.service_item, to_display)
@@ -1119,8 +1122,7 @@
1119 This updates the preview frame, for example after changing a slide or using *Blank to Theme*.1122 This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
1120 """1123 """
1121 self.log_debug('update_preview %s ' % self.screens.current['primary'])1124 self.log_debug('update_preview %s ' % self.screens.current['primary'])
1122 if not self.screens.current['primary'] and self.service_item and \1125 if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
1123 self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
1124 # Grab now, but try again in a couple of seconds if slide change is slow1126 # Grab now, but try again in a couple of seconds if slide change is slow
1125 QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)1127 QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
1126 QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)1128 QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
@@ -1349,7 +1351,11 @@
13491351
1350 :param item: The service item to be processed1352 :param item: The service item to be processed
1351 """1353 """
1352 self.media_controller.video(self.controller_type, item, self.hide_mode())1354 if self.is_live and self.hide_mode() == HideMode.Theme:
1355 self.media_controller.video(self.controller_type, item, HideMode.Blank)
1356 self.on_blank_display(True)
1357 else:
1358 self.media_controller.video(self.controller_type, item, self.hide_mode())
1353 if not self.is_live:1359 if not self.is_live:
1354 self.preview_display.show()1360 self.preview_display.show()
1355 self.slide_preview.hide()1361 self.slide_preview.hide()
@@ -1362,16 +1368,22 @@
1362 self.preview_display.hide()1368 self.preview_display.hide()
1363 self.slide_preview.show()1369 self.slide_preview.show()
13641370
1365 def _reset_blank(self):1371 def _reset_blank(self, no_theme):
1366 """1372 """
1367 Used by command items which provide their own displays to reset the1373 Used by command items which provide their own displays to reset the
1368 screen hide attributes1374 screen hide attributes
1375
1376 :param no_theme: Does the new item support theme-blanking.
1369 """1377 """
1370 hide_mode = self.hide_mode()1378 hide_mode = self.hide_mode()
1371 if hide_mode == HideMode.Blank:1379 if hide_mode == HideMode.Blank:
1372 self.on_blank_display(True)1380 self.on_blank_display(True)
1373 elif hide_mode == HideMode.Theme:1381 elif hide_mode == HideMode.Theme:
1374 self.on_theme_display(True)1382 # The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
1383 if no_theme:
1384 self.on_blank_display(True)
1385 else:
1386 self.on_theme_display(True)
1375 elif hide_mode == HideMode.Screen:1387 elif hide_mode == HideMode.Screen:
1376 self.on_hide_display(True)1388 self.on_hide_display(True)
1377 else:1389 else:
13781390
=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py 2015-04-07 22:01:52 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py 2015-05-27 08:50:25 +0000
@@ -193,7 +193,7 @@
193 self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))193 self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
194 self.quickTab.setVisible(True)194 self.quickTab.setVisible(True)
195 # Add the Advanced Search tab.195 # Add the Advanced Search tab.
196 self.add_search_tab('advanced', UiStrings().Advanced)196 self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Advanced'))
197 self.advanced_book_label = QtGui.QLabel(self.advancedTab)197 self.advanced_book_label = QtGui.QLabel(self.advancedTab)
198 self.advanced_book_label.setObjectName('advanced_book_label')198 self.advanced_book_label.setObjectName('advanced_book_label')
199 self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)199 self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
200200
=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py 2015-03-26 14:17:14 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py 2015-05-27 08:50:25 +0000
@@ -243,6 +243,9 @@
243 Instruct the controller to stop and hide the presentation.243 Instruct the controller to stop and hide the presentation.
244 """244 """
245 log.debug('Live = %s, stop' % self.is_live)245 log.debug('Live = %s, stop' % self.is_live)
246 # Save the current slide number to be able to return to this slide if the presentation is activated again.
247 if self.doc.is_active():
248 self.doc.slidenumber = self.doc.get_slide_number()
246 self.hide_mode = HideMode.Screen249 self.hide_mode = HideMode.Screen
247 if not self.doc:250 if not self.doc:
248 return251 return
@@ -266,8 +269,6 @@
266 return269 return
267 if not self.activate():270 if not self.activate():
268 return271 return
269 if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number():
270 self.doc.goto_slide(self.doc.slidenumber)
271 self.doc.unblank_screen()272 self.doc.unblank_screen()
272 Registry().execute('live_display_hide', HideMode.Screen)273 Registry().execute('live_display_hide', HideMode.Screen)
273274
274275
=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
--- openlp/plugins/presentations/lib/powerpointcontroller.py 2015-04-02 08:33:46 +0000
+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2015-05-27 08:50:25 +0000
@@ -32,12 +32,15 @@
32from openlp.core.common import is_win, Settings32from openlp.core.common import is_win, Settings
3333
34if is_win():34if is_win():
35 from win32com.client import Dispatch35 from win32com.client import DispatchWithEvents
36 import win32com36 import win32com
37 import win32con
37 import winreg38 import winreg
38 import win32ui39 import win32ui
40 import win32gui
39 import pywintypes41 import pywintypes
4042
43
41from openlp.core.lib import ScreenList44from openlp.core.lib import ScreenList
42from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate45from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
43from openlp.core.common import trace_error_handler, Registry46from openlp.core.common import trace_error_handler, Registry
@@ -70,8 +73,18 @@
70 if is_win():73 if is_win():
71 try:74 try:
72 winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()75 winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()
76 try:
77 # Try to detect if the version is 12 (2007) or above, and if so add 'odp' as a support filetype
78 version_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application\\CurVer')
79 tmp1, app_version_string, tmp2 = winreg.EnumValue(version_key, 0)
80 version_key.Close()
81 app_version = int(app_version_string[-2:])
82 if app_version >= 12:
83 self.also_supports = ['odp']
84 except (OSError, ValueError):
85 log.warning('Detection of powerpoint version using registry failed.')
73 return True86 return True
74 except WindowsError:87 except OSError:
75 pass88 pass
76 return False89 return False
7790
@@ -80,12 +93,22 @@
80 """93 """
81 Loads PowerPoint process.94 Loads PowerPoint process.
82 """95 """
96 class PowerPointEvents:
97 """
98 Class to catch events from PowerPoint.
99 """
100 def OnSlideShowNextClick(self, slideshow_window, effect):
101 """
102 Occurs on the next click of the slide.
103 If the main OpenLP window is not in focus force update of the slidecontroller.
104 """
105 if not Registry().get('main_window').isActiveWindow():
106 log.debug('main window is not in focus - should update slidecontroller')
107 Registry().execute('slidecontroller_live_change', slideshow_window.View.CurrentShowPosition)
108
83 log.debug('start_process')109 log.debug('start_process')
84 if not self.process:110 if not self.process:
85 self.process = Dispatch('PowerPoint.Application')111 self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
86 self.process.Visible = True
87 # ppWindowMinimized = 2
88 self.process.WindowState = 2
89112
90 def kill(self):113 def kill(self):
91 """114 """
@@ -124,6 +147,9 @@
124 self.presentation = None147 self.presentation = None
125 self.index_map = {}148 self.index_map = {}
126 self.slide_count = 0149 self.slide_count = 0
150 self.blank_slide = 1
151 self.blank_click = None
152 self.presentation_hwnd = None
127153
128 def load_presentation(self):154 def load_presentation(self):
129 """155 """
@@ -132,23 +158,15 @@
132 """158 """
133 log.debug('load_presentation')159 log.debug('load_presentation')
134 try:160 try:
135 if not self.controller.process or not self.controller.process.Visible:161 if not self.controller.process:
136 self.controller.start_process()162 self.controller.start_process()
137 self.controller.process.Presentations.Open(self.file_path, False, False, True)163 self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
138 self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)164 self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
139 self.create_thumbnails()165 self.create_thumbnails()
140 self.create_titles_and_notes()166 self.create_titles_and_notes()
141 # Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again167 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
142 if float(self.presentation.Application.Version) >= 14.0:168 if len(ScreenList().screen_list) > 1:
143 try:169 Registry().get('main_window').activateWindow()
144 # ppWindowMinimized = 2
145 self.presentation.Application.WindowState = 2
146 except (AttributeError, pywintypes.com_error) as e:
147 log.exception('Failed to minimize main powerpoint window')
148 log.exception(e)
149 trace_error_handler(log)
150 # Make sure powerpoint doesn't steal focus
151 Registry().get('main_window').activateWindow()
152 return True170 return True
153 except (AttributeError, pywintypes.com_error) as e:171 except (AttributeError, pywintypes.com_error) as e:
154 log.exception('Exception caught while loading Powerpoint presentation')172 log.exception('Exception caught while loading Powerpoint presentation')
@@ -194,8 +212,9 @@
194 trace_error_handler(log)212 trace_error_handler(log)
195 self.presentation = None213 self.presentation = None
196 self.controller.remove_doc(self)214 self.controller.remove_doc(self)
197 # Make sure powerpoint doesn't steal focus215 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
198 Registry().get('main_window').activateWindow()216 if len(ScreenList().screen_list) > 1:
217 Registry().get('main_window').activateWindow()
199218
200 def is_loaded(self):219 def is_loaded(self):
201 """220 """
@@ -203,10 +222,6 @@
203 """222 """
204 log.debug('is_loaded')223 log.debug('is_loaded')
205 try:224 try:
206 if not self.controller.process.Visible:
207 return False
208 if self.controller.process.Windows.Count == 0:
209 return False
210 if self.controller.process.Presentations.Count == 0:225 if self.controller.process.Presentations.Count == 0:
211 return False226 return False
212 except (AttributeError, pywintypes.com_error) as e:227 except (AttributeError, pywintypes.com_error) as e:
@@ -241,13 +256,11 @@
241 """256 """
242 log.debug('unblank_screen')257 log.debug('unblank_screen')
243 try:258 try:
244 self.presentation.SlideShowSettings.Run()259 self.presentation.SlideShowWindow.Activate()
245 # ppSlideShowRunning = 1
246 self.presentation.SlideShowWindow.View.State = 1260 self.presentation.SlideShowWindow.View.State = 1
247 self.presentation.SlideShowWindow.Activate()261 # Unblanking is broken in PowerPoint 2010 (14.0), need to redisplay
248 # Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay262 if 15.0 > float(self.presentation.Application.Version) >= 14.0:
249 if float(self.presentation.Application.Version) >= 14.0:263 self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False)
250 self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
251 if self.blank_click:264 if self.blank_click:
252 self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)265 self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
253 except (AttributeError, pywintypes.com_error) as e:266 except (AttributeError, pywintypes.com_error) as e:
@@ -255,8 +268,12 @@
255 log.exception(e)268 log.exception(e)
256 trace_error_handler(log)269 trace_error_handler(log)
257 self.show_error_msg()270 self.show_error_msg()
258 # Make sure powerpoint doesn't steal focus271 # Stop powerpoint from flashing in the taskbar
259 Registry().get('main_window').activateWindow()272 if self.presentation_hwnd:
273 win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
274 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
275 if len(ScreenList().screen_list) > 1:
276 Registry().get('main_window').activateWindow()
260277
261 def blank_screen(self):278 def blank_screen(self):
262 """279 """
@@ -264,8 +281,8 @@
264 """281 """
265 log.debug('blank_screen')282 log.debug('blank_screen')
266 try:283 try:
267 # Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later284 # Unblanking is broken in PowerPoint 2010 (14.0), need to save info for later
268 if float(self.presentation.Application.Version) >= 14.0:285 if 15.0 > float(self.presentation.Application.Version) >= 14.0:
269 self.blank_slide = self.get_slide_number()286 self.blank_slide = self.get_slide_number()
270 self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()287 self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
271 # ppSlideShowBlackScreen = 3288 # ppSlideShowBlackScreen = 3
@@ -295,7 +312,7 @@
295312
296 def stop_presentation(self):313 def stop_presentation(self):
297 """314 """
298 Stops the current presentation and hides the output.315 Stops the current presentation and hides the output. Used when blanking to desktop.
299 """316 """
300 log.debug('stop_presentation')317 log.debug('stop_presentation')
301 try:318 try:
@@ -321,28 +338,52 @@
321 except win32ui.error:338 except win32ui.error:
322 dpi = 96339 dpi = 96
323 size = ScreenList().current['size']340 size = ScreenList().current['size']
324 ppt_window = self.presentation.SlideShowSettings.Run()341 ppt_window = None
325 if not ppt_window:
326 return
327 try:342 try:
328 ppt_window.Top = size.y() * 72 / dpi343 ppt_window = self.presentation.SlideShowSettings.Run()
329 ppt_window.Height = size.height() * 72 / dpi344 except (AttributeError, pywintypes.com_error) as e:
330 ppt_window.Left = size.x() * 72 / dpi345 log.exception('Caught exception while in start_presentation')
331 ppt_window.Width = size.width() * 72 / dpi
332 except AttributeError as e:
333 log.exception('AttributeError while in start_presentation')
334 log.exception(e)346 log.exception(e)
335 # Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again347 trace_error_handler(log)
336 if float(self.presentation.Application.Version) >= 14.0:348 self.show_error_msg()
349 if ppt_window and not Settings().value('presentations/powerpoint control window'):
337 try:350 try:
338 # ppWindowMinimized = 2351 ppt_window.Top = size.y() * 72 / dpi
339 self.presentation.Application.WindowState = 2352 ppt_window.Height = size.height() * 72 / dpi
340 except (AttributeError, pywintypes.com_error) as e:353 ppt_window.Left = size.x() * 72 / dpi
341 log.exception('Failed to minimize main powerpoint window')354 ppt_window.Width = size.width() * 72 / dpi
355 except AttributeError as e:
356 log.exception('AttributeError while in start_presentation')
342 log.exception(e)357 log.exception(e)
343 trace_error_handler(log)358 # Find the presentation window and save the handle for later
344 # Make sure powerpoint doesn't steal focus359 self.presentation_hwnd = None
345 Registry().get('main_window').activateWindow()360 if ppt_window:
361 log.debug('main display size: y=%d, height=%d, x=%d, width=%d'
362 % (size.y(), size.height(), size.x(), size.width()))
363 win32gui.EnumWindows(self._window_enum_callback, size)
364 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
365 if len(ScreenList().screen_list) > 1:
366 Registry().get('main_window').activateWindow()
367
368 def _window_enum_callback(self, hwnd, size):
369 """
370 Method for callback from win32gui.EnumWindows.
371 Used to find the powerpoint presentation window and stop it flashing in the taskbar.
372 """
373 # Get the size of the current window and if it matches the size of our main display we assume
374 # it is the powerpoint presentation window.
375 (left, top, right, bottom) = win32gui.GetWindowRect(hwnd)
376 window_title = win32gui.GetWindowText(hwnd)
377 log.debug('window size: left=%d, top=%d, right=%d, width=%d' % (left, top, right, bottom))
378 log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d'
379 % (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
380 log.debug('window title: %s' % window_title)
381 if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
382 size.width() == (right - left) and os.path.basename(self.file_path) in window_title:
383 log.debug('Found a match and will save the handle')
384 self.presentation_hwnd = hwnd
385 # Stop powerpoint from flashing in the taskbar
386 win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
346387
347 def get_slide_number(self):388 def get_slide_number(self):
348 """389 """
@@ -384,7 +425,7 @@
384 log.debug('goto_slide')425 log.debug('goto_slide')
385 try:426 try:
386 if Settings().value('presentations/powerpoint slide click advance') \427 if Settings().value('presentations/powerpoint slide click advance') \
387 and self.get_slide_number() == self.index_map[slide_no]:428 and self.get_slide_number() == slide_no:
388 click_index = self.presentation.SlideShowWindow.View.GetClickIndex()429 click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
389 click_count = self.presentation.SlideShowWindow.View.GetClickCount()430 click_count = self.presentation.SlideShowWindow.View.GetClickCount()
390 log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'431 log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
@@ -405,6 +446,7 @@
405 """446 """
406 log.debug('next_step')447 log.debug('next_step')
407 try:448 try:
449 self.presentation.SlideShowWindow.Activate()
408 self.presentation.SlideShowWindow.View.Next()450 self.presentation.SlideShowWindow.View.Next()
409 except (AttributeError, pywintypes.com_error) as e:451 except (AttributeError, pywintypes.com_error) as e:
410 log.exception('Caught exception while in next_step')452 log.exception('Caught exception while in next_step')
@@ -415,6 +457,12 @@
415 if self.get_slide_number() > self.get_slide_count():457 if self.get_slide_number() > self.get_slide_count():
416 log.debug('past end, stepping back to previous')458 log.debug('past end, stepping back to previous')
417 self.previous_step()459 self.previous_step()
460 # Stop powerpoint from flashing in the taskbar
461 if self.presentation_hwnd:
462 win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
463 # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
464 if len(ScreenList().screen_list) > 1:
465 Registry().get('main_window').activateWindow()
418466
419 def previous_step(self):467 def previous_step(self):
420 """468 """
@@ -490,8 +538,12 @@
490 :param shapes: A set of shapes to search for text.538 :param shapes: A set of shapes to search for text.
491 """539 """
492 text = ''540 text = ''
493 for shape in shapes:541 try:
494 if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody542 for shape in shapes:
495 if shape.HasTextFrame and shape.TextFrame.HasText:543 if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
496 text += shape.TextFrame.TextRange.Text + '\n'544 if shape.HasTextFrame and shape.TextFrame.HasText:
545 text += shape.TextFrame.TextRange.Text + '\n'
546 except pywintypes.com_error as e:
547 log.warning('Failed to extract text from powerpoint slide')
548 log.warning(e)
497 return text549 return text
498550
=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py 2015-04-02 08:33:46 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py 2015-05-27 08:50:25 +0000
@@ -74,8 +74,11 @@
74 self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)74 self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
75 self.powerpoint_layout.setObjectName('powerpoint_layout')75 self.powerpoint_layout.setObjectName('powerpoint_layout')
76 self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)76 self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
77 self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')77 self.ppt_slide_click_check_box.setObjectName('ppt_slide_click_check_box')
78 self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)78 self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
79 self.ppt_window_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
80 self.ppt_window_check_box.setObjectName('ppt_window_check_box')
81 self.powerpoint_layout.addWidget(self.ppt_window_check_box)
79 self.left_layout.addWidget(self.powerpoint_group_box)82 self.left_layout.addWidget(self.powerpoint_group_box)
80 # Pdf options83 # Pdf options
81 self.pdf_group_box = QtGui.QGroupBox(self.left_column)84 self.pdf_group_box = QtGui.QGroupBox(self.left_column)
@@ -123,6 +126,9 @@
123 self.ppt_slide_click_check_box.setText(126 self.ppt_slide_click_check_box.setText(
124 translate('PresentationPlugin.PresentationTab',127 translate('PresentationPlugin.PresentationTab',
125 'Clicking on a selected slide in the slidecontroller advances to next effect.'))128 'Clicking on a selected slide in the slidecontroller advances to next effect.'))
129 self.ppt_window_check_box.setText(
130 translate('PresentationPlugin.PresentationTab',
131 'Let PowerPoint control the size and position of the presentation window.'))
126 self.pdf_program_check_box.setText(132 self.pdf_program_check_box.setText(
127 translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))133 translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
128134
@@ -148,6 +154,8 @@
148 self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +154 self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
149 '/powerpoint slide click advance'))155 '/powerpoint slide click advance'))
150 self.ppt_slide_click_check_box.setEnabled(powerpoint_available)156 self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
157 self.ppt_window_check_box.setChecked(Settings().value(self.settings_section + '/powerpoint control window'))
158 self.ppt_window_check_box.setEnabled(powerpoint_available)
151 # load pdf-program settings159 # load pdf-program settings
152 enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')160 enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
153 self.pdf_program_check_box.setChecked(enable_pdf_program)161 self.pdf_program_check_box.setChecked(enable_pdf_program)
@@ -186,6 +194,10 @@
186 if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():194 if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
187 Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())195 Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
188 changed = True196 changed = True
197 setting_key = self.settings_section + '/powerpoint control window'
198 if Settings().value(setting_key) != self.ppt_window_check_box.checkState():
199 Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
200 changed = True
189 # Save pdf-settings201 # Save pdf-settings
190 pdf_program = self.pdf_program_path.text()202 pdf_program = self.pdf_program_path.text()
191 enable_pdf_program = self.pdf_program_check_box.checkState()203 enable_pdf_program = self.pdf_program_check_box.checkState()
192204
=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py 2015-03-26 14:22:23 +0000
+++ openlp/plugins/presentations/presentationplugin.py 2015-05-27 08:50:25 +0000
@@ -45,7 +45,8 @@
45 'presentations/Pdf': QtCore.Qt.Checked,45 'presentations/Pdf': QtCore.Qt.Checked,
46 'presentations/presentations files': [],46 'presentations/presentations files': [],
47 'presentations/thumbnail_scheme': '',47 'presentations/thumbnail_scheme': '',
48 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked48 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
49 'presentations/powerpoint control window': QtCore.Qt.Unchecked
49 }50 }
5051
5152
5253
=== modified file 'openlp/plugins/songs/lib/importers/worshipassistant.py'
--- openlp/plugins/songs/lib/importers/worshipassistant.py 2015-03-09 20:57:39 +0000
+++ openlp/plugins/songs/lib/importers/worshipassistant.py 2015-05-27 08:50:25 +0000
@@ -131,6 +131,7 @@
131 return131 return
132 verse = ''132 verse = ''
133 used_verses = []133 used_verses = []
134 verse_id = VerseType.tags[VerseType.Verse] + '1'
134 for line in lyrics.splitlines():135 for line in lyrics.splitlines():
135 if line.startswith('['): # verse marker136 if line.startswith('['): # verse marker
136 # Add previous verse137 # Add previous verse
137138
=== modified file 'tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-04-02 08:33:46 +0000
+++ tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py 2015-05-27 08:50:25 +0000
@@ -164,45 +164,42 @@
164 """164 """
165 Test creating the titles from PowerPoint165 Test creating the titles from PowerPoint
166 """166 """
167 if is_win() and self.real_controller.check_available():167 # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
168 # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides168 self.doc = PowerpointDocument(self.mock_controller, self.file_name)
169 self.doc = PowerpointDocument(self.real_controller, self.file_name)169 self.doc.get_slide_count = MagicMock()
170 self.doc.save_titles_and_notes = MagicMock()170 self.doc.get_slide_count.return_value = 2
171 self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()171 self.doc.index_map = {1: 1, 2: 2}
172 slide = MagicMock()172 self.doc.save_titles_and_notes = MagicMock()
173 slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'173 self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
174 pres = MagicMock()174 slide = MagicMock()
175 pres.Slides = [slide, slide]175 slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
176 self.doc.presentation = pres176 pres = MagicMock()
177177 pres.Slides = MagicMock(side_effect=[slide, slide])
178 # WHEN reading the titles and notes178 self.doc.presentation = pres
179 self.doc.create_titles_and_notes()179
180180 # WHEN reading the titles and notes
181 # THEN the save should have been called exactly once with 2 titles and 2 notes181 self.doc.create_titles_and_notes()
182 self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])182
183 else:183 # THEN the save should have been called exactly once with 2 titles and 2 notes
184 self.skipTest('Powerpoint not available, skipping test.')184 self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
185185
186 def create_titles_and_notes_with_no_slides_test(self):186 def create_titles_and_notes_with_no_slides_test(self):
187 """187 """
188 Test creating the titles from PowerPoint when it returns no slides188 Test creating the titles from PowerPoint when it returns no slides
189 """189 """
190 if is_win() and self.real_controller.check_available():190 # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
191 # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides191 doc = PowerpointDocument(self.mock_controller, self.file_name)
192 doc = PowerpointDocument(self.real_controller, self.file_name)192 doc.save_titles_and_notes = MagicMock()
193 doc.save_titles_and_notes = MagicMock()193 doc._PowerpointDocument__get_text_from_shapes = MagicMock()
194 doc._PowerpointDocument__get_text_from_shapes = MagicMock()194 pres = MagicMock()
195 pres = MagicMock()195 pres.Slides = []
196 pres.Slides = []196 doc.presentation = pres
197 doc.presentation = pres197
198198 # WHEN reading the titles and notes
199 # WHEN reading the titles and notes199 doc.create_titles_and_notes()
200 doc.create_titles_and_notes()200
201201 # THEN the save should have been called exactly once with empty titles and notes
202 # THEN the save should have been called exactly once with empty titles and notes202 doc.save_titles_and_notes.assert_called_once_with([], [])
203 doc.save_titles_and_notes.assert_called_once_with([], [])
204 else:
205 self.skipTest('Powerpoint not available, skipping test.')
206203
207 def get_text_from_shapes_test(self):204 def get_text_from_shapes_test(self):
208 """205 """
@@ -253,3 +250,54 @@
253250
254 # THEN: next_step() should be call to try to advance to the next effect.251 # THEN: next_step() should be call to try to advance to the next effect.
255 self.assertTrue(doc.next_step.called, 'next_step() should have been called!')252 self.assertTrue(doc.next_step.called, 'next_step() should have been called!')
253
254 def blank_screen_test(self):
255 """
256 Test that blank_screen works as expected
257 """
258 # GIVEN: A Document with mocked controller, presentation, and mocked function get_slide_number
259 doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
260 doc.presentation = MagicMock()
261 doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
262 doc.presentation.Application.Version = 14.0
263 doc.get_slide_number = MagicMock()
264 doc.get_slide_number.return_value = 2
265
266 # WHEN: Calling goto_slide
267 doc.blank_screen()
268
269 # THEN: The view state, doc.blank_slide and doc.blank_click should have new values
270 self.assertEquals(doc.presentation.SlideShowWindow.View.State, 3, 'The View State should be 3')
271 self.assertEquals(doc.blank_slide, 2, 'doc.blank_slide should be 2 because of the PowerPoint version')
272 self.assertEquals(doc.blank_click, 3, 'doc.blank_click should be 3 because of the PowerPoint version')
273
274 def unblank_screen_test(self):
275 """
276 Test that unblank_screen works as expected
277 """
278 # GIVEN: A Document with mocked controller, presentation, ScreenList, and mocked function get_slide_number
279 with patch('openlp.plugins.presentations.lib.powerpointcontroller.ScreenList') as mocked_screen_list:
280 mocked_screen_list_ret = MagicMock()
281 mocked_screen_list_ret.screen_list = [1]
282 mocked_screen_list.return_value = mocked_screen_list_ret
283 doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
284 doc.presentation = MagicMock()
285 doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
286 doc.presentation.Application.Version = 14.0
287 doc.get_slide_number = MagicMock()
288 doc.get_slide_number.return_value = 2
289 doc.index_map[1] = 1
290 doc.blank_slide = 1
291 doc.blank_click = 1
292
293 # WHEN: Calling goto_slide
294 doc.unblank_screen()
295
296 # THEN: The view state have new value, and several function should have been called
297 self.assertEquals(doc.presentation.SlideShowWindow.View.State, 1, 'The View State should be 1')
298 self.assertEquals(doc.presentation.SlideShowWindow.Activate.called, True,
299 'SlideShowWindow.Activate should have been called')
300 self.assertEquals(doc.presentation.SlideShowWindow.View.GotoSlide.called, True,
301 'View.GotoSlide should have been called because of the PowerPoint version')
302 self.assertEquals(doc.presentation.SlideShowWindow.View.GotoClick.called, True,
303 'View.GotoClick should have been called because of the PowerPoint version')
256304
=== modified file 'tests/functional/openlp_plugins/songs/test_worshipassistantimport.py'
--- tests/functional/openlp_plugins/songs/test_worshipassistantimport.py 2015-01-22 17:42:29 +0000
+++ tests/functional/openlp_plugins/songs/test_worshipassistantimport.py 2015-05-27 08:50:25 +0000
@@ -49,3 +49,5 @@
49 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))49 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
50 self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),50 self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
51 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))51 self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
52 self.file_import(os.path.join(TEST_PATH, 'lift_up_your_heads.csv'),
53 self.load_external_result_data(os.path.join(TEST_PATH, 'lift_up_your_heads.json')))
5254
=== added file 'tests/resources/worshipassistantsongs/lift_up_your_heads.csv'
--- tests/resources/worshipassistantsongs/lift_up_your_heads.csv 1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/lift_up_your_heads.csv 2015-05-27 08:50:25 +0000
@@ -0,0 +1,40 @@
1"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"
2"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
3 Lift up your heads and the doors of your heart
4. Am B7 Em
5 And the King of glory will come in
6(Repeat)
7
8.G Am D
9 Who is this King of Glory?
10. B7 Em
11 The Lord strong and mighty!
12.G Am D
13 Who is this King of Glory?
14. B7
15 The Lord, mighty in battle!
16
17.G Am D
18 Who is this King of Glory?
19.B7 Em
20 Jesus our Messiah!
21.G Am D
22 Who is this King of Glory?
23.B7 Em
24 Jesus, Lord of Lords!
25
26","NULL","Lift up your heads and the doors of your heart
27And the King of glory will come in
28(Repeat)
29
30Who is this King of Glory?
31The Lord strong and mighty!
32Who is this King of Glory?
33The Lord, mighty in battle!
34
35Who is this King of Glory?
36Jesus our Messiah!
37Who is this King of Glory?
38Jesus, Lord of Lords!
39
40","NULL"
041
=== added file 'tests/resources/worshipassistantsongs/lift_up_your_heads.json'
--- tests/resources/worshipassistantsongs/lift_up_your_heads.json 1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/lift_up_your_heads.json 2015-05-27 08:50:25 +0000
@@ -0,0 +1,13 @@
1{
2 "authors": [
3 "Bryan Mierau"
4 ],
5 "title": "Lift Up Your Heads",
6 "verse_order_list": [],
7 "verses": [
8 [
9 "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",
10 "v1"
11 ]
12 ]
13}