← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 into lp:openlp

 

Ian Knight has proposed merging lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)
Related bugs:
  Bug #1080596 in OpenLP: "Presentation preview shows desktop when live shows desktop"
  https://bugs.launchpad.net/openlp/+bug/1080596

For more details, see:
https://code.launchpad.net/~knightrider0xd/openlp/preview-shows-live-fix-1080596/+merge/294803

Fixes bug 1080596 where presentations in the preview pane display live view rather than preview of selected slide.
In addition, fixes the aspect ratio & quality of thumbnails by saving them in the correct aspect ratio at a higher resolution, and loading them through the image manager.

New test cases implemented, or existing cases modified to test coverage complete for changes.

lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 (revision 2652)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1554/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1465/

Failing interface & coverage tests due to issue with unrelated crosswalk import module.
-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2016-04-23 19:28:52 +0000
+++ openlp/core/lib/__init__.py	2016-05-16 12:42:11 +0000
@@ -55,9 +55,13 @@
 
     ``Theme``
         This says, that the image is used by a theme.
+
+    ``CommandPlugins``
+        This states that an image is being used by a command plugin.
     """
     ImagePlugin = 1
     Theme = 2
+    CommandPlugins = 3
 
 
 class MediaType(object):
@@ -174,10 +178,30 @@
     ext = os.path.splitext(thumb_path)[1].lower()
     reader = QtGui.QImageReader(image_path)
     if size is None:
-        ratio = reader.size().width() / reader.size().height()
+        # No size given; use default height of 88
+        if reader.size().isEmpty():
+            ratio = 1
+        else:
+            ratio = reader.size().width() / reader.size().height()
         reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
-    else:
+    elif size.isValid():
+        # Complete size given
         reader.setScaledSize(size)
+    else:
+        # Invalid size given
+        if reader.size().isEmpty():
+            ratio = 1
+        else:
+            ratio = reader.size().width() / reader.size().height()
+        if size.width() >= 0:
+            # Valid width; scale height
+            reader.setScaledSize(QtCore.QSize(size.width(), int(size.width() / ratio)))
+        elif size.height() >= 0:
+            # Valid height; scale width
+            reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height()))
+        else:
+            # Invalid; use default height of 88
+            reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
     thumb = reader.read()
     thumb.save(thumb_path, ext[1:])
     if not return_icon:

=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2016-02-14 17:53:16 +0000
+++ openlp/core/lib/serviceitem.py	2016-05-16 12:42:11 +0000
@@ -334,6 +334,8 @@
                                  file_location_hash, ntpath.basename(image))
         self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
                                  'display_title': display_title, 'notes': notes})
+        if self.is_capable(ItemCapabilities.HasThumbnails):
+            self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000')
         self._new_item()
 
     def get_service_repr(self, lite_save):

=== modified file 'openlp/core/ui/lib/listpreviewwidget.py'
--- openlp/core/ui/lib/listpreviewwidget.py	2016-04-22 18:32:59 +0000
+++ openlp/core/ui/lib/listpreviewwidget.py	2016-05-16 12:42:11 +0000
@@ -27,7 +27,7 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.common import RegistryProperties, Settings
-from openlp.core.lib import ImageSource, ServiceItem
+from openlp.core.lib import ImageSource, ItemCapabilities, ServiceItem
 
 
 class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
@@ -152,14 +152,16 @@
                 else:
                     label.setScaledContents(True)
                 if self.service_item.is_command():
-                    pixmap = QtGui.QPixmap(frame['image'])
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
+                    if self.service_item.is_capable(ItemCapabilities.HasThumbnails):
+                        image = self.image_manager.get_image(frame['image'], ImageSource.CommandPlugins)
+                        pixmap = QtGui.QPixmap.fromImage(image)
+                    else:
+                        pixmap = QtGui.QPixmap(frame['image'])
                 else:
                     image = self.image_manager.get_image(frame['path'], ImageSource.ImagePlugin)
                     pixmap = QtGui.QPixmap.fromImage(image)
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
+                pixmap.setDevicePixelRatio(label.devicePixelRatio())
+                label.setPixmap(pixmap)
                 slide_height = width // self.screen_ratio
                 # Setup and validate row height cap if in use.
                 max_img_row_height = Settings().value('advanced/slide max height')

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2016-04-22 18:32:45 +0000
+++ openlp/core/ui/slidecontroller.py	2016-05-16 12:42:11 +0000
@@ -1135,9 +1135,21 @@
         """
         self.log_debug('update_preview %s ' % self.screens.current['primary'])
         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(500, self.grab_maindisplay)
-            QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
+            if self.is_live:
+                # If live, grab screen-cap of main display now
+                QtCore.QTimer.singleShot(500, self.grab_maindisplay)
+                # but take another in a couple of seconds in case slide change is slow
+                QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
+            else:
+                # If not live, use the slide's thumbnail/icon instead
+                image_path = self.service_item.get_rendered_frame(self.selected_row)
+                if self.service_item.is_capable(ItemCapabilities.HasThumbnails):
+                    image = self.image_manager.get_image(image_path, ImageSource.CommandPlugins)
+                    self.slide_image = QtGui.QPixmap.fromImage(image)
+                else:
+                    self.slide_image = QtGui.QPixmap(image_path)
+                self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
+                self.slide_preview.setPixmap(self.slide_image)
         else:
             self.slide_image = self.display.preview()
             self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())

=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py	2016-05-16 12:42:11 +0000
@@ -242,13 +242,13 @@
 
     def convert_thumbnail(self, file, idx):
         """
-        Convert the slide image the application made to a standard 320x240 .png image.
+        Convert the slide image the application made to a scaled 360px height .png image.
         """
         if self.check_thumbnails():
             return
         if os.path.isfile(file):
             thumb_path = self.get_thumbnail_path(idx, False)
-            create_thumb(file, thumb_path, False, QtCore.QSize(320, 240))
+            create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
 
     def get_thumbnail_path(self, slide_no, check_exists):
         """

=== modified file 'tests/functional/openlp_core_lib/test_lib.py'
--- tests/functional/openlp_core_lib/test_lib.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_lib/test_lib.py	2016-05-16 12:42:11 +0000
@@ -250,7 +250,7 @@
 
     def create_thumb_with_size_test(self):
         """
-        Test the create_thumb() function
+        Test the create_thumb() function with a given size.
         """
         # GIVEN: An image to create a thumb of.
         image_path = os.path.join(TEST_PATH, 'church.jpg')
@@ -270,7 +270,7 @@
         # WHEN: Create the thumb.
         icon = create_thumb(image_path, thumb_path, size=thumb_size)
 
-        # THEN: Check if the thumb was created.
+        # THEN: Check if the thumb was created and scaled to the given size.
         self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
         self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
         self.assertFalse(icon.isNull(), 'The icon should not be null')
@@ -282,6 +282,194 @@
         except:
             pass
 
+    def create_thumb_no_size_test(self):
+        """
+        Test the create_thumb() function with no size specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        expected_size = QtCore.QSize(63, 88)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_invalid_size_test(self):
+        """
+        Test the create_thumb() function with invalid size specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(-1, -1)
+        expected_size = QtCore.QSize(63, 88)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_width_only_test(self):
+        """
+        Test the create_thumb() function with a size of only width specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(100, -1)
+        expected_size = QtCore.QSize(100, 137)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_height_only_test(self):
+        """
+        Test the create_thumb() function with a size of only height specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(-1, 100)
+        expected_size = QtCore.QSize(72, 100)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_empty_img_test(self):
+        """
+        Test the create_thumb() function with a size of only height specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(-1, 100)
+        expected_size_1 = QtCore.QSize(88, 88)
+        expected_size_2 = QtCore.QSize(100, 100)
+        
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size:
+            mocked_size.return_value = QtCore.QSize(0, 0)
+            icon = create_thumb(image_path, thumb_path, size=None)
+
+        # THEN: Check if the thumb was created with aspect ratio of 1.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size_1, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # WHEN: Create the thumb.
+        with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size:
+            mocked_size.return_value = QtCore.QSize(0, 0)
+            icon = create_thumb(image_path, thumb_path, size=thumb_size)
+        
+        # THEN: Check if the thumb was created with aspect ratio of 1.
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size_2, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
     def check_item_selected_true_test(self):
         """
         Test that the check_item_selected() function returns True when there are selected indexes

=== modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
--- tests/functional/openlp_core_lib/test_serviceitem.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_lib/test_serviceitem.py	2016-05-16 12:42:11 +0000
@@ -244,14 +244,16 @@
         self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
         self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
 
+    @patch(u'openlp.core.lib.serviceitem.ServiceItem.image_manager')
     @patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path')
-    def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path):
+    def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path, mocked_image_manager):
         """
-        Test the Service Item - adding a presentation, and updating the thumb path
+        Test the Service Item - adding a presentation, updating the thumb path & adding the thumb to image_manager
         """
         # GIVEN: A service item, a mocked AppLocation and presentation data
         mocked_get_section_data_path.return_value = os.path.join('mocked', 'section', 'path')
         service_item = ServiceItem(None)
+        service_item.add_capability(ItemCapabilities.HasThumbnails)
         service_item.has_original_files = False
         service_item.name = 'presentations'
         presentation_name = 'test.pptx'
@@ -270,6 +272,7 @@
         # THEN: verify that it is setup as a Command and that the frame data matches
         self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
         self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
+        self.assertEqual(1, mocked_image_manager.add_image.call_count, 'image_manager should be used')
 
     def service_item_load_optical_media_from_service_test(self):
         """

=== modified file 'tests/functional/openlp_core_ui/test_slidecontroller.py'
--- tests/functional/openlp_core_ui/test_slidecontroller.py	2016-02-27 14:25:31 +0000
+++ tests/functional/openlp_core_ui/test_slidecontroller.py	2016-05-16 12:42:11 +0000
@@ -26,7 +26,7 @@
 
 from unittest import TestCase
 from openlp.core import Registry
-from openlp.core.lib import ServiceItemAction
+from openlp.core.lib import ImageSource, ServiceItemAction
 from openlp.core.ui import SlideController, LiveController, PreviewController
 from openlp.core.ui.slidecontroller import InfoLabel, WIDE_MENU, NON_TEXT_MENU
 
@@ -713,6 +713,175 @@
             slide_controller.theme_screen, slide_controller.blank_screen
         ])
 
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_live(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with a screen grab for live service items
+        """
+        # GIVEN: A mocked live service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Live Item
+        mocked_live_item = MagicMock()
+        mocked_live_item.get_rendered_frame.return_value = ''
+        mocked_live_item.is_capable = MagicMock()
+        mocked_live_item.is_capable.side_effect = [True, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_live_item
+        slide_controller.is_live = True
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: A screen_grab should have been called
+        self.assertEqual(0, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should not be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(2, mocked_singleShot.call_count,
+                         'Timer to grab_maindisplay should have been called 2 times')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager not be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_pres(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for presentation service items
+        """
+        # GIVEN: A mocked presentation service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Presentation Item
+        mocked_pres_item = MagicMock()
+        mocked_pres_item.get_rendered_frame.return_value = ''
+        mocked_pres_item.is_capable = MagicMock()
+        mocked_pres_item.is_capable.side_effect = [True, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_pres_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap and the image_manager should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(1, mocked_image_manager.get_image.call_count, 'image_manager should be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_media(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for media service items
+        """
+        # GIVEN: A mocked media service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Media Item
+        mocked_media_item = MagicMock()
+        mocked_media_item.get_rendered_frame.return_value = ''
+        mocked_media_item.is_capable = MagicMock()
+        mocked_media_item.is_capable.side_effect = [True, False]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_media_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager should not be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_image(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for image service items
+        """
+        # GIVEN: A mocked image service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Image Item
+        mocked_img_item = MagicMock()
+        mocked_img_item.get_rendered_frame.return_value = ''
+        mocked_img_item.is_capable = MagicMock()
+        mocked_img_item.is_capable.side_effect = [False, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_img_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap and display.preview should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(1, slide_controller.display.preview.call_count, 'display.preview() should be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager should not be called')
+
 
 class TestInfoLabel(TestCase):
 

=== modified file 'tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py'
--- tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	2016-04-22 18:35:23 +0000
+++ tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	2016-05-16 12:42:11 +0000
@@ -24,9 +24,11 @@
 """
 from unittest import TestCase
 
+from PyQt5 import QtGui
+
 from openlp.core.common import Settings
 from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
-from openlp.core.lib import ServiceItem
+from openlp.core.lib import ImageSource, ServiceItem
 
 from tests.functional import MagicMock, patch, call
 
@@ -72,6 +74,53 @@
         self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None')
         self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called')
 
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.image_manager')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    def replace_service_item_test_thumbs(self, mocked_setRowHeight, mocked_resizeRowsToContents,
+                                         mocked_image_manager):
+        """
+        Test that thubmails for different slides are loaded properly in replace_service_item.
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        different ServiceItem(s), an ImageManager, and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 0
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock Image service item
+        mocked_img_service_item = MagicMock()
+        mocked_img_service_item.is_text.return_value = False
+        mocked_img_service_item.is_media.return_value = False
+        mocked_img_service_item.is_command.return_value = False
+        mocked_img_service_item.is_capable.return_value = False
+        mocked_img_service_item.get_frames.return_value = [{'title': None, 'path': 'TEST1', 'image': 'FAIL'},
+                                                           {'title': None, 'path': 'TEST2', 'image': 'FAIL'}]
+        # Mock Command service item
+        mocked_cmd_service_item = MagicMock()
+        mocked_cmd_service_item.is_text.return_value = False
+        mocked_cmd_service_item.is_media.return_value = False
+        mocked_cmd_service_item.is_command.return_value = True
+        mocked_cmd_service_item.is_capable.return_value = True
+        mocked_cmd_service_item.get_frames.return_value = [{'title': None, 'path': 'FAIL', 'image': 'TEST3'},
+                                                           {'title': None, 'path': 'FAIL', 'image': 'TEST4'}]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # WHEN: replace_service_item is called
+        list_preview_widget.replace_service_item(mocked_img_service_item, 200, 0)
+        list_preview_widget.replace_service_item(mocked_cmd_service_item, 200, 0)
+
+        # THEN: The ImageManager should be called in the appriopriate manner for each service item.
+        self.assertEquals(mocked_image_manager.get_image.call_count, 4, 'Should be called once for each slide')
+        calls = [call('TEST1', ImageSource.ImagePlugin), call('TEST2', ImageSource.ImagePlugin),
+                 call('TEST3', ImageSource.CommandPlugins), call('TEST4', ImageSource.CommandPlugins)]
+        mocked_image_manager.get_image.assert_has_calls(calls)
+
     @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
     @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
     def replace_recalculate_layout_test_text(self, mocked_setRowHeight, mocked_resizeRowsToContents):
@@ -120,6 +169,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # init ListPreviewWidget and load service item
@@ -156,6 +206,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # init ListPreviewWidget and load service item
@@ -225,6 +276,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # Mock self.cellWidget().children().setMaximumWidth()
@@ -261,6 +313,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # Mock self.cellWidget().children().setMaximumWidth()


Follow ups