← Back to team overview

openlp-core team mailing list archive

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

 

Tomas Groth has proposed merging lp:~tomasgroth/openlp/bugfixes20 into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)
Related bugs:
  Bug #1449041 in OpenLP: "Main vebview doesn't mirror PowerPoint when using a single-screen setup"
  https://bugs.launchpad.net/openlp/+bug/1449041
  Bug #1449064 in OpenLP: "Transitions happens in the same slide"
  https://bugs.launchpad.net/openlp/+bug/1449064
  Bug #1458056 in OpenLP: "Traceback when importing a Worship Assistant CSV"
  https://bugs.launchpad.net/openlp/+bug/1458056
  Bug #1458672 in OpenLP: "Import of settings file with non-existing keys causes traceback"
  https://bugs.launchpad.net/openlp/+bug/1458672

For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/bugfixes20/+merge/260252

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.
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/bugfixes20 into lp:openlp.
=== 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 @@
-2.1.3
+2.1.4

=== 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 @@
         for section_key in import_keys:
             if 'eneral' in section_key:
                 section_key = section_key.lower()
-            value = import_settings.value(section_key)
+            try:
+                value = import_settings.value(section_key)
+            except KeyError:
+                log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key)
             if value is not None:
                 settings.setValue('%s' % (section_key), value)
         now = datetime.now()

=== 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 @@
         self.selected_row = 0
         # take a copy not a link to the servicemanager copy.
         self.service_item = copy.copy(service_item)
-        if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
-            self._reset_blank()
+        # Reset blanking if needed
+        if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
+                                          self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
+            self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
         if service_item.is_command():
             Registry().execute(
                 '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
@@ -1080,6 +1082,7 @@
                                % timeout)
             return
         row = self.preview_widget.current_slide_number()
+        old_selected_row = self.selected_row
         self.selected_row = 0
         if -1 < row < self.preview_widget.slide_count():
             if self.service_item.is_command():
@@ -1089,7 +1092,7 @@
             else:
                 to_display = self.service_item.get_rendered_frame(row)
                 if self.service_item.is_text():
-                    self.display.text(to_display)
+                    self.display.text(to_display, row != old_selected_row)
                 else:
                     if start:
                         self.display.build_html(self.service_item, to_display)
@@ -1119,8 +1122,7 @@
         This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
         """
         self.log_debug('update_preview %s ' % self.screens.current['primary'])
-        if not self.screens.current['primary'] and self.service_item and \
-                self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
+        if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
             # Grab now, but try again in a couple of seconds if slide change is slow
             QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
             QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
@@ -1349,7 +1351,11 @@
 
         :param item: The service item to be processed
         """
-        self.media_controller.video(self.controller_type, item, self.hide_mode())
+        if self.is_live and self.hide_mode() == HideMode.Theme:
+            self.media_controller.video(self.controller_type, item, HideMode.Blank)
+            self.on_blank_display(True)
+        else:
+            self.media_controller.video(self.controller_type, item, self.hide_mode())
         if not self.is_live:
             self.preview_display.show()
             self.slide_preview.hide()
@@ -1362,16 +1368,22 @@
         self.preview_display.hide()
         self.slide_preview.show()
 
-    def _reset_blank(self):
+    def _reset_blank(self, no_theme):
         """
         Used by command items which provide their own displays to reset the
         screen hide attributes
+
+        :param no_theme: Does the new item support theme-blanking.
         """
         hide_mode = self.hide_mode()
         if hide_mode == HideMode.Blank:
             self.on_blank_display(True)
         elif hide_mode == HideMode.Theme:
-            self.on_theme_display(True)
+            # The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
+            if no_theme:
+                self.on_blank_display(True)
+            else:
+                self.on_theme_display(True)
         elif hide_mode == HideMode.Screen:
             self.on_hide_display(True)
         else:

=== 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 @@
         self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
         self.quickTab.setVisible(True)
         # Add the Advanced Search tab.
-        self.add_search_tab('advanced', UiStrings().Advanced)
+        self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Advanced'))
         self.advanced_book_label = QtGui.QLabel(self.advancedTab)
         self.advanced_book_label.setObjectName('advanced_book_label')
         self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)

=== 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 @@
         Instruct the controller to stop and hide the presentation.
         """
         log.debug('Live = %s, stop' % self.is_live)
+        # Save the current slide number to be able to return to this slide if the presentation is activated again.
+        if self.doc.is_active():
+            self.doc.slidenumber = self.doc.get_slide_number()
         self.hide_mode = HideMode.Screen
         if not self.doc:
             return
@@ -266,8 +269,6 @@
             return
         if not self.activate():
             return
-        if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number():
-            self.doc.goto_slide(self.doc.slidenumber)
         self.doc.unblank_screen()
         Registry().execute('live_display_hide', HideMode.Screen)
 

=== 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 @@
 from openlp.core.common import is_win, Settings
 
 if is_win():
-    from win32com.client import Dispatch
+    from win32com.client import DispatchWithEvents
     import win32com
+    import win32con
     import winreg
     import win32ui
+    import win32gui
     import pywintypes
 
+
 from openlp.core.lib import ScreenList
 from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
 from openlp.core.common import trace_error_handler, Registry
@@ -70,8 +73,18 @@
         if is_win():
             try:
                 winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()
+                try:
+                    # Try to detect if the version is 12 (2007) or above, and if so add 'odp' as a support filetype
+                    version_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application\\CurVer')
+                    tmp1, app_version_string, tmp2 = winreg.EnumValue(version_key, 0)
+                    version_key.Close()
+                    app_version = int(app_version_string[-2:])
+                    if app_version >= 12:
+                        self.also_supports = ['odp']
+                except (OSError, ValueError):
+                    log.warning('Detection of powerpoint version using registry failed.')
                 return True
-            except WindowsError:
+            except OSError:
                 pass
         return False
 
@@ -80,12 +93,22 @@
             """
             Loads PowerPoint process.
             """
+            class PowerPointEvents:
+                """
+                Class to catch events from PowerPoint.
+                """
+                def OnSlideShowNextClick(self, slideshow_window, effect):
+                    """
+                    Occurs on the next click of the slide.
+                    If the main OpenLP window is not in focus force update of the slidecontroller.
+                    """
+                    if not Registry().get('main_window').isActiveWindow():
+                        log.debug('main window is not in focus - should update slidecontroller')
+                        Registry().execute('slidecontroller_live_change', slideshow_window.View.CurrentShowPosition)
+
             log.debug('start_process')
             if not self.process:
-                self.process = Dispatch('PowerPoint.Application')
-            self.process.Visible = True
-            # ppWindowMinimized = 2
-            self.process.WindowState = 2
+                self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
 
         def kill(self):
             """
@@ -124,6 +147,9 @@
         self.presentation = None
         self.index_map = {}
         self.slide_count = 0
+        self.blank_slide = 1
+        self.blank_click = None
+        self.presentation_hwnd = None
 
     def load_presentation(self):
         """
@@ -132,23 +158,15 @@
         """
         log.debug('load_presentation')
         try:
-            if not self.controller.process or not self.controller.process.Visible:
+            if not self.controller.process:
                 self.controller.start_process()
-            self.controller.process.Presentations.Open(self.file_path, False, False, True)
+            self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
             self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
             self.create_thumbnails()
             self.create_titles_and_notes()
-            # Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again
-            if float(self.presentation.Application.Version) >= 14.0:
-                try:
-                    # ppWindowMinimized = 2
-                    self.presentation.Application.WindowState = 2
-                except (AttributeError, pywintypes.com_error) as e:
-                    log.exception('Failed to minimize main powerpoint window')
-                    log.exception(e)
-                    trace_error_handler(log)
-            # Make sure powerpoint doesn't steal focus
-            Registry().get('main_window').activateWindow()
+            # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
+            if len(ScreenList().screen_list) > 1:
+                Registry().get('main_window').activateWindow()
             return True
         except (AttributeError, pywintypes.com_error) as e:
             log.exception('Exception caught while loading Powerpoint presentation')
@@ -194,8 +212,9 @@
                 trace_error_handler(log)
         self.presentation = None
         self.controller.remove_doc(self)
-        # Make sure powerpoint doesn't steal focus
-        Registry().get('main_window').activateWindow()
+        # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
+        if len(ScreenList().screen_list) > 1:
+            Registry().get('main_window').activateWindow()
 
     def is_loaded(self):
         """
@@ -203,10 +222,6 @@
         """
         log.debug('is_loaded')
         try:
-            if not self.controller.process.Visible:
-                return False
-            if self.controller.process.Windows.Count == 0:
-                return False
             if self.controller.process.Presentations.Count == 0:
                 return False
         except (AttributeError, pywintypes.com_error) as e:
@@ -241,13 +256,11 @@
         """
         log.debug('unblank_screen')
         try:
-            self.presentation.SlideShowSettings.Run()
-            # ppSlideShowRunning = 1
+            self.presentation.SlideShowWindow.Activate()
             self.presentation.SlideShowWindow.View.State = 1
-            self.presentation.SlideShowWindow.Activate()
-            # Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay
-            if float(self.presentation.Application.Version) >= 14.0:
-                self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
+            # Unblanking is broken in PowerPoint 2010 (14.0), need to redisplay
+            if 15.0 > float(self.presentation.Application.Version) >= 14.0:
+                self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False)
                 if self.blank_click:
                     self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
         except (AttributeError, pywintypes.com_error) as e:
@@ -255,8 +268,12 @@
             log.exception(e)
             trace_error_handler(log)
             self.show_error_msg()
-        # Make sure powerpoint doesn't steal focus
-        Registry().get('main_window').activateWindow()
+        # Stop powerpoint from flashing in the taskbar
+        if self.presentation_hwnd:
+            win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
+        # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
+        if len(ScreenList().screen_list) > 1:
+            Registry().get('main_window').activateWindow()
 
     def blank_screen(self):
         """
@@ -264,8 +281,8 @@
         """
         log.debug('blank_screen')
         try:
-            # Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later
-            if float(self.presentation.Application.Version) >= 14.0:
+            # Unblanking is broken in PowerPoint 2010 (14.0), need to save info for later
+            if 15.0 > float(self.presentation.Application.Version) >= 14.0:
                 self.blank_slide = self.get_slide_number()
                 self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
             # ppSlideShowBlackScreen = 3
@@ -295,7 +312,7 @@
 
     def stop_presentation(self):
         """
-        Stops the current presentation and hides the output.
+        Stops the current presentation and hides the output. Used when blanking to desktop.
         """
         log.debug('stop_presentation')
         try:
@@ -321,28 +338,52 @@
                 except win32ui.error:
                     dpi = 96
             size = ScreenList().current['size']
-            ppt_window = self.presentation.SlideShowSettings.Run()
-            if not ppt_window:
-                return
+            ppt_window = None
             try:
-                ppt_window.Top = size.y() * 72 / dpi
-                ppt_window.Height = size.height() * 72 / dpi
-                ppt_window.Left = size.x() * 72 / dpi
-                ppt_window.Width = size.width() * 72 / dpi
-            except AttributeError as e:
-                log.exception('AttributeError while in start_presentation')
+                ppt_window = self.presentation.SlideShowSettings.Run()
+            except (AttributeError, pywintypes.com_error) as e:
+                log.exception('Caught exception while in start_presentation')
                 log.exception(e)
-            # Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again
-            if float(self.presentation.Application.Version) >= 14.0:
+                trace_error_handler(log)
+                self.show_error_msg()
+            if ppt_window and not Settings().value('presentations/powerpoint control window'):
                 try:
-                    # ppWindowMinimized = 2
-                    self.presentation.Application.WindowState = 2
-                except (AttributeError, pywintypes.com_error) as e:
-                    log.exception('Failed to minimize main powerpoint window')
+                    ppt_window.Top = size.y() * 72 / dpi
+                    ppt_window.Height = size.height() * 72 / dpi
+                    ppt_window.Left = size.x() * 72 / dpi
+                    ppt_window.Width = size.width() * 72 / dpi
+                except AttributeError as e:
+                    log.exception('AttributeError while in start_presentation')
                     log.exception(e)
-                    trace_error_handler(log)
-            # Make sure powerpoint doesn't steal focus
-            Registry().get('main_window').activateWindow()
+            # Find the presentation window and save the handle for later
+            self.presentation_hwnd = None
+            if ppt_window:
+                log.debug('main display size:  y=%d, height=%d, x=%d, width=%d'
+                          % (size.y(), size.height(), size.x(), size.width()))
+                win32gui.EnumWindows(self._window_enum_callback, size)
+            # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
+            if len(ScreenList().screen_list) > 1:
+                Registry().get('main_window').activateWindow()
+
+    def _window_enum_callback(self, hwnd, size):
+        """
+        Method for callback from win32gui.EnumWindows.
+        Used to find the powerpoint presentation window and stop it flashing in the taskbar.
+        """
+        # Get the size of the current window and if it matches the size of our main display we assume
+        # it is the powerpoint presentation window.
+        (left, top, right, bottom) = win32gui.GetWindowRect(hwnd)
+        window_title = win32gui.GetWindowText(hwnd)
+        log.debug('window size:  left=%d, top=%d, right=%d, width=%d' % (left, top, right, bottom))
+        log.debug('compare size:  %d and %d, %d and %d, %d and %d, %d and %d'
+                  % (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
+        log.debug('window title: %s' % window_title)
+        if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
+                size.width() == (right - left) and os.path.basename(self.file_path) in window_title:
+            log.debug('Found a match and will save the handle')
+            self.presentation_hwnd = hwnd
+            # Stop powerpoint from flashing in the taskbar
+            win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
 
     def get_slide_number(self):
         """
@@ -384,7 +425,7 @@
         log.debug('goto_slide')
         try:
             if Settings().value('presentations/powerpoint slide click advance') \
-                    and self.get_slide_number() == self.index_map[slide_no]:
+                    and self.get_slide_number() == slide_no:
                 click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
                 click_count = self.presentation.SlideShowWindow.View.GetClickCount()
                 log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
@@ -405,6 +446,7 @@
         """
         log.debug('next_step')
         try:
+            self.presentation.SlideShowWindow.Activate()
             self.presentation.SlideShowWindow.View.Next()
         except (AttributeError, pywintypes.com_error) as e:
             log.exception('Caught exception while in next_step')
@@ -415,6 +457,12 @@
         if self.get_slide_number() > self.get_slide_count():
             log.debug('past end, stepping back to previous')
             self.previous_step()
+        # Stop powerpoint from flashing in the taskbar
+        if self.presentation_hwnd:
+            win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
+        # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
+        if len(ScreenList().screen_list) > 1:
+            Registry().get('main_window').activateWindow()
 
     def previous_step(self):
         """
@@ -490,8 +538,12 @@
     :param shapes: A set of shapes to search for text.
     """
     text = ''
-    for shape in shapes:
-        if shape.PlaceholderFormat.Type == 2:  # 2 from is enum PpPlaceholderType.ppPlaceholderBody
-            if shape.HasTextFrame and shape.TextFrame.HasText:
-                text += shape.TextFrame.TextRange.Text + '\n'
+    try:
+        for shape in shapes:
+            if shape.PlaceholderFormat.Type == 2:  # 2 from is enum PpPlaceholderType.ppPlaceholderBody
+                if shape.HasTextFrame and shape.TextFrame.HasText:
+                    text += shape.TextFrame.TextRange.Text + '\n'
+    except pywintypes.com_error as e:
+        log.warning('Failed to extract text from powerpoint slide')
+        log.warning(e)
     return text

=== 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 @@
         self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
         self.powerpoint_layout.setObjectName('powerpoint_layout')
         self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
-        self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')
+        self.ppt_slide_click_check_box.setObjectName('ppt_slide_click_check_box')
         self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
+        self.ppt_window_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
+        self.ppt_window_check_box.setObjectName('ppt_window_check_box')
+        self.powerpoint_layout.addWidget(self.ppt_window_check_box)
         self.left_layout.addWidget(self.powerpoint_group_box)
         # Pdf options
         self.pdf_group_box = QtGui.QGroupBox(self.left_column)
@@ -123,6 +126,9 @@
         self.ppt_slide_click_check_box.setText(
             translate('PresentationPlugin.PresentationTab',
                       'Clicking on a selected slide in the slidecontroller advances to next effect.'))
+        self.ppt_window_check_box.setText(
+            translate('PresentationPlugin.PresentationTab',
+                      'Let PowerPoint control the size and position of the presentation window.'))
         self.pdf_program_check_box.setText(
             translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
 
@@ -148,6 +154,8 @@
         self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
                                                                    '/powerpoint slide click advance'))
         self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
+        self.ppt_window_check_box.setChecked(Settings().value(self.settings_section + '/powerpoint control window'))
+        self.ppt_window_check_box.setEnabled(powerpoint_available)
         # load pdf-program settings
         enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
         self.pdf_program_check_box.setChecked(enable_pdf_program)
@@ -186,6 +194,10 @@
         if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
             Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
             changed = True
+        setting_key = self.settings_section + '/powerpoint control window'
+        if Settings().value(setting_key) != self.ppt_window_check_box.checkState():
+            Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
+            changed = True
         # Save pdf-settings
         pdf_program = self.pdf_program_path.text()
         enable_pdf_program = self.pdf_program_check_box.checkState()

=== 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 @@
                         'presentations/Pdf': QtCore.Qt.Checked,
                         'presentations/presentations files': [],
                         'presentations/thumbnail_scheme': '',
-                        'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked
+                        'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
+                        'presentations/powerpoint control window': QtCore.Qt.Unchecked
                         }
 
 

=== 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 @@
                 return
             verse = ''
             used_verses = []
+            verse_id = VerseType.tags[VerseType.Verse] + '1'
             for line in lyrics.splitlines():
                 if line.startswith('['):  # verse marker
                     # Add previous verse

=== 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 @@
         """
         Test creating the titles from PowerPoint
         """
-        if is_win() and self.real_controller.check_available():
-            # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
-            self.doc = PowerpointDocument(self.real_controller, self.file_name)
-            self.doc.save_titles_and_notes = MagicMock()
-            self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
-            slide = MagicMock()
-            slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
-            pres = MagicMock()
-            pres.Slides = [slide, slide]
-            self.doc.presentation = pres
-
-            # WHEN reading the titles and notes
-            self.doc.create_titles_and_notes()
-
-            # THEN the save should have been called exactly once with 2 titles and 2 notes
-            self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
-        else:
-            self.skipTest('Powerpoint not available, skipping test.')
+        # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
+        self.doc = PowerpointDocument(self.mock_controller, self.file_name)
+        self.doc.get_slide_count = MagicMock()
+        self.doc.get_slide_count.return_value = 2
+        self.doc.index_map = {1: 1, 2: 2}
+        self.doc.save_titles_and_notes = MagicMock()
+        self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
+        slide = MagicMock()
+        slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
+        pres = MagicMock()
+        pres.Slides = MagicMock(side_effect=[slide, slide])
+        self.doc.presentation = pres
+
+        # WHEN reading the titles and notes
+        self.doc.create_titles_and_notes()
+
+        # THEN the save should have been called exactly once with 2 titles and 2 notes
+        self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
 
     def create_titles_and_notes_with_no_slides_test(self):
         """
         Test creating the titles from PowerPoint when it returns no slides
         """
-        if is_win() and self.real_controller.check_available():
-            # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
-            doc = PowerpointDocument(self.real_controller, self.file_name)
-            doc.save_titles_and_notes = MagicMock()
-            doc._PowerpointDocument__get_text_from_shapes = MagicMock()
-            pres = MagicMock()
-            pres.Slides = []
-            doc.presentation = pres
-
-            # WHEN reading the titles and notes
-            doc.create_titles_and_notes()
-
-            # THEN the save should have been called exactly once with empty titles and notes
-            doc.save_titles_and_notes.assert_called_once_with([], [])
-        else:
-            self.skipTest('Powerpoint not available, skipping test.')
+        # GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
+        doc = PowerpointDocument(self.mock_controller, self.file_name)
+        doc.save_titles_and_notes = MagicMock()
+        doc._PowerpointDocument__get_text_from_shapes = MagicMock()
+        pres = MagicMock()
+        pres.Slides = []
+        doc.presentation = pres
+
+        # WHEN reading the titles and notes
+        doc.create_titles_and_notes()
+
+        # THEN the save should have been called exactly once with empty titles and notes
+        doc.save_titles_and_notes.assert_called_once_with([], [])
 
     def get_text_from_shapes_test(self):
         """
@@ -253,3 +250,54 @@
 
         # THEN: next_step() should be call to try to advance to the next effect.
         self.assertTrue(doc.next_step.called, 'next_step() should have been called!')
+
+    def blank_screen_test(self):
+        """
+        Test that blank_screen works as expected
+        """
+        # GIVEN: A Document with mocked controller, presentation, and mocked function get_slide_number
+        doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
+        doc.presentation = MagicMock()
+        doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
+        doc.presentation.Application.Version = 14.0
+        doc.get_slide_number = MagicMock()
+        doc.get_slide_number.return_value = 2
+
+        # WHEN: Calling goto_slide
+        doc.blank_screen()
+
+        # THEN: The view state, doc.blank_slide and doc.blank_click should have new values
+        self.assertEquals(doc.presentation.SlideShowWindow.View.State, 3, 'The View State should be 3')
+        self.assertEquals(doc.blank_slide, 2, 'doc.blank_slide should be 2 because of the PowerPoint version')
+        self.assertEquals(doc.blank_click, 3, 'doc.blank_click should be 3 because of the PowerPoint version')
+
+    def unblank_screen_test(self):
+        """
+        Test that unblank_screen works as expected
+        """
+        # GIVEN: A Document with mocked controller, presentation, ScreenList, and mocked function get_slide_number
+        with patch('openlp.plugins.presentations.lib.powerpointcontroller.ScreenList') as mocked_screen_list:
+            mocked_screen_list_ret = MagicMock()
+            mocked_screen_list_ret.screen_list = [1]
+            mocked_screen_list.return_value = mocked_screen_list_ret
+            doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
+            doc.presentation = MagicMock()
+            doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
+            doc.presentation.Application.Version = 14.0
+            doc.get_slide_number = MagicMock()
+            doc.get_slide_number.return_value = 2
+            doc.index_map[1] = 1
+            doc.blank_slide = 1
+            doc.blank_click = 1
+
+            # WHEN: Calling goto_slide
+            doc.unblank_screen()
+
+            # THEN: The view state have new value, and several function should have been called
+            self.assertEquals(doc.presentation.SlideShowWindow.View.State, 1, 'The View State should be 1')
+            self.assertEquals(doc.presentation.SlideShowWindow.Activate.called, True,
+                              'SlideShowWindow.Activate should have been called')
+            self.assertEquals(doc.presentation.SlideShowWindow.View.GotoSlide.called, True,
+                              'View.GotoSlide should have been called because of the PowerPoint version')
+            self.assertEquals(doc.presentation.SlideShowWindow.View.GotoClick.called, True,
+                              'View.GotoClick should have been called because of the PowerPoint version')

=== 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 @@
                          self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
         self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
                          self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
+        self.file_import(os.path.join(TEST_PATH, 'lift_up_your_heads.csv'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'lift_up_your_heads.json')))

=== 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 @@
+"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"
+"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   
+ Lift up your heads and the doors of your heart
+.        Am      B7              Em
+ And the King of glory will come in
+(Repeat)
+
+.G                   Am  D
+ Who is this King of Glory?
+.    B7              Em
+ The Lord strong and mighty!
+.G                   Am    D
+ Who is this King of Glory?
+.    B7        
+ The Lord, mighty in battle!
+
+.G                   Am   D
+ Who is this King of Glory?
+.B7           Em
+ Jesus our Messiah!
+.G                   Am  D 
+ Who is this King of Glory?
+.B7             Em
+ Jesus, Lord of Lords!
+ 
+","NULL","Lift up your heads and the doors of your heart
+And the King of glory will come in
+(Repeat)
+
+Who is this King of Glory?
+The Lord strong and mighty!
+Who is this King of Glory?
+The Lord, mighty in battle!
+
+Who is this King of Glory?
+Jesus our Messiah!
+Who is this King of Glory?
+Jesus, Lord of Lords!
+
+","NULL"

=== 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 @@
+{
+    "authors": [
+        "Bryan Mierau"
+    ],
+    "title": "Lift Up Your Heads",
+    "verse_order_list": [],
+    "verses": [
+        [
+            "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",
+            "v1"
+        ]
+    ]
+}