From f0d7cf7f36e6942c8aa795c180edae8a9112dd37 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Wed, 4 May 2016 21:40:42 +0930 Subject: [PATCH 1/6] Corrected aspect ratio on slide previews --- openlp/core/lib/__init__.py | 20 ++++++++++++++++++- openlp/core/lib/serviceitem.py | 1 + openlp/core/ui/listpreviewwidget.py | 13 ++++++------ openlp/core/ui/slidecontroller.py | 13 +++++++++--- .../lib/presentationcontroller.py | 4 ++-- 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 6e62bbf9c..4e85fe7e4 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -55,9 +55,13 @@ class ImageSource(object): ``Theme`` This says, that the image is used by a theme. + + ``PresentationPlugin`` + This states that an image is being used by the presentation plugin. """ ImagePlugin = 1 Theme = 2 + PresentationPlugin = 3 class MediaType(object): @@ -174,10 +178,24 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): ext = os.path.splitext(thumb_path)[1].lower() reader = QtGui.QImageReader(image_path) if size is None: + # No size given; use default height of 88 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 + 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: diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index e45aa6e61..32afbfe4f 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -334,6 +334,7 @@ class ServiceItem(RegistryProperties): file_location_hash, ntpath.basename(image)) self._raw_frames.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title, 'notes': notes}) + self.image_manager.add_image(image, ImageSource.PresentationPlugin, '#000000') self._new_item() def get_service_repr(self, lite_save): diff --git a/openlp/core/ui/listpreviewwidget.py b/openlp/core/ui/listpreviewwidget.py index 88aef818a..4d62e3173 100644 --- a/openlp/core/ui/listpreviewwidget.py +++ b/openlp/core/ui/listpreviewwidget.py @@ -152,14 +152,15 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties): else: label.setScaledContents(True) if self.service_item.is_command(): - pixmap = QtGui.QPixmap(frame['image']) - pixmap.setDevicePixelRatio(label.devicePixelRatio()) - label.setPixmap(pixmap) + #pixmap = QtGui.QPixmap(frame['image']) + #pixmap.setDevicePixelRatio(label.devicePixelRatio()) + #label.setPixmap(pixmap) + image = self.image_manager.get_image(frame['image'], ImageSource.PresentationPlugin) else: image = self.image_manager.get_image(frame['path'], ImageSource.ImagePlugin) - pixmap = QtGui.QPixmap.fromImage(image) - pixmap.setDevicePixelRatio(label.devicePixelRatio()) - label.setPixmap(pixmap) + pixmap = QtGui.QPixmap.fromImage(image) + 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') diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 96ce82868..f04bff8fa 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1132,9 +1132,16 @@ class SlideController(DisplayController, RegistryProperties): """ 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 instead + self.slide_image = QtGui.QPixmap.fromImage(self.image_manager.get_image(self.service_item.get_rendered_frame(self.selected_row), ImageSource.PresentationPlugin)) #QtGui.QPixmap(self.service_item.get_rendered_frame(self.selected_row)) + 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()) diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index abc71f867..fc90ddb0d 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -242,13 +242,13 @@ class PresentationDocument(object): 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): """ From bc6253a6279f7fed580eb4b8802ceb811ce82883 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Thu, 5 May 2016 13:27:04 +0930 Subject: [PATCH 2/6] Housekeeping, added check for thumbnails to avoid icon scaling issues --- openlp/core/lib/__init__.py | 6 +++--- openlp/core/lib/serviceitem.py | 3 ++- openlp/core/ui/lib/listpreviewwidget.py | 10 +++++++--- openlp/core/ui/slidecontroller.py | 9 +++++++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 5a1006893..1fee2fe5a 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -56,12 +56,12 @@ class ImageSource(object): ``Theme`` This says, that the image is used by a theme. - ``PresentationPlugin`` - This states that an image is being used by the presentation plugin. + ``CommandPlugins`` + This states that an image is being used by a command plugin. """ ImagePlugin = 1 Theme = 2 - PresentationPlugin = 3 + CommandPlugins = 3 class MediaType(object): diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 32afbfe4f..2cbbb98e9 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -334,7 +334,8 @@ class ServiceItem(RegistryProperties): file_location_hash, ntpath.basename(image)) self._raw_frames.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title, 'notes': notes}) - self.image_manager.add_image(image, ImageSource.PresentationPlugin, '#000000') + 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): diff --git a/openlp/core/ui/lib/listpreviewwidget.py b/openlp/core/ui/lib/listpreviewwidget.py index e18292129..2383cc35f 100644 --- a/openlp/core/ui/lib/listpreviewwidget.py +++ b/openlp/core/ui/lib/listpreviewwidget.py @@ -27,7 +27,7 @@ It is based on a QTableWidget but represents its contents in list form. 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,10 +152,14 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties): else: label.setScaledContents(True) if self.service_item.is_command(): - image = self.image_manager.get_image(frame['image'], ImageSource.PresentationPlugin) + 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 = QtGui.QPixmap.fromImage(image) pixmap.setDevicePixelRatio(label.devicePixelRatio()) label.setPixmap(pixmap) slide_height = width // self.screen_ratio diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index db4e01210..c06860057 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1141,8 +1141,13 @@ class SlideController(DisplayController, RegistryProperties): # 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 instead - self.slide_image = QtGui.QPixmap.fromImage(self.image_manager.get_image(self.service_item.get_rendered_frame(self.selected_row), ImageSource.PresentationPlugin)) #QtGui.QPixmap(self.service_item.get_rendered_frame(self.selected_row)) + # 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: From 55002518efe6ff33865f23c9d37b5730e9e5a643 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Fri, 6 May 2016 04:27:32 +0930 Subject: [PATCH 3/6] Added Functional Tests, cleaned PEP8 errors --- openlp/core/lib/__init__.py | 2 +- tests/functional/openlp_core_lib/test_lib.py | 143 ++++++++++++++- .../openlp_core_lib/test_serviceitem.py | 7 +- .../openlp_core_ui/test_slidecontroller.py | 171 +++++++++++++++++- .../test_listpreviewwidget.py | 56 +++++- 5 files changed, 372 insertions(+), 7 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 1fee2fe5a..d96b43148 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -55,7 +55,7 @@ class ImageSource(object): ``Theme`` This says, that the image is used by a theme. - + ``CommandPlugins`` This states that an image is being used by a command plugin. """ diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 1bd42bec4..8f94276a6 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -250,7 +250,7 @@ class TestLib(TestCase): 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 @@ class TestLib(TestCase): # 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,145 @@ class TestLib(TestCase): 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) + + # 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 check_item_selected_true_test(self): """ Test that the check_item_selected() function returns True when there are selected indexes diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index 449746e12..cda7ad91b 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -244,14 +244,16 @@ class TestServiceItem(TestCase): 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 @@ class TestServiceItem(TestCase): # 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): """ diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index 20e48cbe0..65cce0a45 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtGui 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 @@ class TestSlideController(TestCase): 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 the correct preview for different service items + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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: Registry.execute should have been called to stop the presentation + 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 different service items + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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: Registry.execute should have been called to stop the presentation + 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 different service items + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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: Registry.execute should have been called to stop the presentation + 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 different service items + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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: Registry.execute should have been called to stop the presentation + 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): diff --git a/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py b/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py index 0ed88cc88..275c90af6 100644 --- a/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py +++ b/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py @@ -24,9 +24,11 @@ Package to test the openlp.core.ui.lib.listpreviewwidget package. """ 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,54 @@ class TestListPreviewWidget(TestCase): 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) 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: resizeRowsToContents() should not be called, while setRowHeight() should be called + # twice for each slide. + 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 +170,7 @@ class TestListPreviewWidget(TestCase): # 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 +207,7 @@ class TestListPreviewWidget(TestCase): # 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 +277,7 @@ class TestListPreviewWidget(TestCase): # 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 +314,7 @@ class TestListPreviewWidget(TestCase): # 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() From aac09c0afbc08319a54bace4aee5f20097387cb9 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Fri, 6 May 2016 04:33:12 +0930 Subject: [PATCH 4/6] Fixed create_thumb_invalid_size_test --- tests/functional/openlp_core_lib/test_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 8f94276a6..a792e3aee 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -337,7 +337,7 @@ class TestLib(TestCase): 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) + 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') From 3cdbaff2a65678936b4fed83c296acff83221955 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Fri, 6 May 2016 11:16:49 +0930 Subject: [PATCH 5/6] Housekeeping --- .../openlp_core_ui/test_slidecontroller.py | 24 +++++++++---------- .../test_listpreviewwidget.py | 5 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index 65cce0a45..f47601bbd 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -717,9 +717,9 @@ class TestSlideController(TestCase): @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 the correct preview for different service items + Test that the preview screen is updated with a screen grab for live service items """ - # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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() @@ -749,7 +749,7 @@ class TestSlideController(TestCase): # WHEN: update_preview is called slide_controller.update_preview() - # THEN: Registry.execute should have been called to stop the presentation + # 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, @@ -760,9 +760,9 @@ class TestSlideController(TestCase): @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 different service items + Test that the preview screen is updated with the correct preview for presentation service items """ - # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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() @@ -792,7 +792,7 @@ class TestSlideController(TestCase): # WHEN: update_preview is called slide_controller.update_preview() - # THEN: Registry.execute should have been called to stop the presentation + # 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') @@ -802,9 +802,9 @@ class TestSlideController(TestCase): @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 different service items + Test that the preview screen is updated with the correct preview for media service items """ - # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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() @@ -834,7 +834,7 @@ class TestSlideController(TestCase): # WHEN: update_preview is called slide_controller.update_preview() - # THEN: Registry.execute should have been called to stop the presentation + # 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') @@ -844,9 +844,9 @@ class TestSlideController(TestCase): @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 different service items + Test that the preview screen is updated with the correct preview for image service items """ - # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # 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() @@ -876,7 +876,7 @@ class TestSlideController(TestCase): # WHEN: update_preview is called slide_controller.update_preview() - # THEN: Registry.execute should have been called to stop the presentation + # 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') diff --git a/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py b/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py index 275c90af6..704acc544 100644 --- a/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py +++ b/tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py @@ -83,7 +83,7 @@ class TestListPreviewWidget(TestCase): 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) and a ListPreviewWidget. + # different ServiceItem(s), an ImageManager, and a ListPreviewWidget. # Mock Settings().value('advanced/slide max height') self.mocked_Settings_obj.value.return_value = 0 @@ -115,8 +115,7 @@ class TestListPreviewWidget(TestCase): 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: resizeRowsToContents() should not be called, while setRowHeight() should be called - # twice for each slide. + # 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)] From a7466f1ea77898801154da4e735fc67d7b0fa44c Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Mon, 16 May 2016 22:05:58 +0930 Subject: [PATCH 6/6] Check for Div0 and added unit test cases --- openlp/core/lib/__init__.py | 10 +++- tests/functional/openlp_core_lib/test_lib.py | 49 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index d96b43148..e6bf32ed6 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -179,14 +179,20 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): reader = QtGui.QImageReader(image_path) if size is None: # No size given; use default height of 88 - ratio = reader.size().width() / reader.size().height() + if reader.size().isEmpty(): + ratio = 1 + else: + ratio = reader.size().width() / reader.size().height() reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) elif size.isValid(): # Complete size given reader.setScaledSize(size) else: # Invalid size given - ratio = reader.size().width() / reader.size().height() + 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))) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index a792e3aee..c8493d005 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -421,6 +421,55 @@ class TestLib(TestCase): 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