← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~phill-ridout/openlp/no-results into lp:openlp

 

Phill has proposed merging lp:~phill-ridout/openlp/no-results into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)

For more details, see:
https://code.launchpad.net/~phill-ridout/openlp/no-results/+merge/310703

Move the "no results" code in to the list widget.

lp:~phill-ridout/openlp/no-results (revision 2709)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1839/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1750/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1688/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1434/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/1024/
[SUCCESS] https://ci.openlp.io/job/Branch-05a-Code_Analysis/1092/
[SUCCESS] https://ci.openlp.io/job/Branch-05b-Test_Coverage/960/
[SUCCESS] https://ci.openlp.io/job/Branch-05c-Code_Analysis2/115/
-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/common/uistrings.py'
--- openlp/core/common/uistrings.py	2016-08-10 18:31:33 +0000
+++ openlp/core/common/uistrings.py	2016-11-12 12:01:42 +0000
@@ -112,6 +112,7 @@
         self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural')
         self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
         self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
+        self.NoResults = translate('OpenLP.Ui', 'No Search Results')
         self.OLP = translate('OpenLP.Ui', 'OpenLP')
         self.OLPV2 = "{name} {version}".format(name=self.OLP, version="2")
         self.OLPV2x = "{name} {version}".format(name=self.OLP, version="2.4")
@@ -139,6 +140,7 @@
         self.Settings = translate('OpenLP.Ui', 'Settings')
         self.SaveService = translate('OpenLP.Ui', 'Save Service')
         self.Service = translate('OpenLP.Ui', 'Service')
+        self.ShortResults = translate('OpenLP.Ui', 'Please type more text to use \'Search As You Type\'')
         self.Split = translate('OpenLP.Ui', 'Optional &Split')
         self.SplitToolTip = translate('OpenLP.Ui',
                                       'Split a slide into two only if it does not fit on the screen as one slide.')

=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2016-08-09 21:37:59 +0000
+++ openlp/core/lib/mediamanageritem.py	2016-11-12 12:01:42 +0000
@@ -397,8 +397,6 @@
         # Decide if we have to show the context menu or not.
         if item is None:
             return
-        if not item.flags() & QtCore.Qt.ItemIsSelectable:
-            return
         self.menu.exec(self.list_view.mapToGlobal(point))
 
     def get_file_list(self):
@@ -638,34 +636,6 @@
         """
         return item
 
-    def check_search_result(self):
-        """
-        Checks if the list_view is empty and adds a "No Search Results" item.
-        """
-        if self.list_view.count():
-            return
-        message = translate('OpenLP.MediaManagerItem', 'No Search Results')
-        item = QtWidgets.QListWidgetItem(message)
-        item.setFlags(QtCore.Qt.NoItemFlags)
-        font = QtGui.QFont()
-        font.setItalic(True)
-        item.setFont(font)
-        self.list_view.addItem(item)
-
-    def check_search_result_search_while_typing_short(self):
-        """
-        This is used in Bible "Search while typing" if the search is shorter than the min required len.
-        """
-        if self.list_view.count():
-            return
-        message = translate('OpenLP.MediaManagerItem', 'Search is too short to be used in: "Search while typing"')
-        item = QtWidgets.QListWidgetItem(message)
-        item.setFlags(QtCore.Qt.NoItemFlags)
-        font = QtGui.QFont()
-        font.setItalic(True)
-        item.setFont(font)
-        self.list_view.addItem(item)
-
     def _get_id_of_item_to_generate(self, item, remote_item):
         """
         Utility method to check items being submitted for slide generation.

=== modified file 'openlp/core/ui/lib/listwidgetwithdnd.py'
--- openlp/core/ui/lib/listwidgetwithdnd.py	2016-04-17 19:09:46 +0000
+++ openlp/core/ui/lib/listwidgetwithdnd.py	2016-11-12 12:01:42 +0000
@@ -26,7 +26,7 @@
 
 from PyQt5 import QtCore, QtGui, QtWidgets
 
-from openlp.core.common import Registry
+from openlp.core.common import Registry, UiStrings
 
 
 class ListWidgetWithDnD(QtWidgets.QListWidget):
@@ -37,8 +37,9 @@
         """
         Initialise the list widget
         """
-        super(ListWidgetWithDnD, self).__init__(parent)
+        super().__init__(parent)
         self.mime_data_text = name
+        self.no_results_text = UiStrings().NoResults
 
     def activateDnD(self):
         """
@@ -48,6 +49,19 @@
         self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
         Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
 
+    def clear(self, search_while_typing=False):
+        """
+        Re-implement clear, so that we can customise feedback when using 'Search as you type'
+
+        :param search_while_typing: True if we want to display the customised message
+        :return: None
+        """
+        if search_while_typing:
+            self.no_results_text = UiStrings().ShortResults
+        else:
+            self.no_results_text = UiStrings().NoResults
+        super().clear()
+
     def mouseMoveEvent(self, event):
         """
         Drag and drop event does not care what data is selected as the recipient will use events to request the data
@@ -102,6 +116,24 @@
                     listing = os.listdir(local_file)
                     for file in listing:
                         files.append(os.path.join(local_file, file))
-            Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
+            Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text),
+                               {'files': files, 'target': self.itemAt(event.pos())})
         else:
             event.ignore()
+
+    def paintEvent(self, event):
+        """
+        Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty.
+
+        :param event: A QPaintEvent
+        :return: None
+        """
+        super().paintEvent(event)
+        if not self.count():
+            viewport = self.viewport()
+            painter = QtGui.QPainter(viewport)
+            font = QtGui.QFont()
+            font.setItalic(True)
+            painter.setFont(font)
+            painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()),
+                             (QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text)

=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py	2016-09-16 21:43:30 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py	2016-11-12 12:01:42 +0000
@@ -75,21 +75,16 @@
         self.has_search = True
         self.search_results = {}
         self.second_search_results = {}
-        self.check_search_result()
         Registry().register_function('bibles_load_list', self.reload_bibles)
 
     def __check_second_bible(self, bible, second_bible):
         """
         Check if the first item is a second bible item or not.
         """
-        bitem = self.list_view.item(0)
-        if not bitem.flags() & QtCore.Qt.ItemIsSelectable:
-            # The item is the "No Search Results" item.
-            self.list_view.clear()
+        if not self.list_view.count():
             self.display_results(bible, second_bible)
             return
-        else:
-            item_second_bible = self._decode_qt_object(bitem, 'second_bible')
+        item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible')
         if item_second_bible and second_bible or not item_second_bible and not second_bible:
             self.display_results(bible, second_bible)
         elif critical_error_message_box(
@@ -551,14 +546,12 @@
     def on_clear_button_clicked(self):
         # Clear the list, then set the "No search Results" message, then clear the text field and give it focus.
         self.list_view.clear()
-        self.check_search_result()
         self.quick_search_edit.clear()
         self.quick_search_edit.setFocus()
 
     def on_advanced_clear_button_clicked(self):
         # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced).
         self.list_view.clear()
-        self.check_search_result()
         self.advanced_book_combo_box.setFocus()
 
     def on_lock_button_toggled(self, checked):
@@ -692,7 +685,6 @@
         elif self.search_results:
             self.display_results(bible, second_bible)
         self.advancedSearchButton.setEnabled(True)
-        self.check_search_result()
         self.application.set_normal_cursor()
 
     def on_quick_reference_search(self):
@@ -886,7 +878,6 @@
         elif self.search_results:
             self.display_results(bible, second_bible)
         self.quickSearchButton.setEnabled(True)
-        self.check_search_result()
         self.application.set_normal_cursor()
 
     def on_quick_search_while_typing(self):
@@ -917,7 +908,6 @@
             self.__check_second_bible(bible, second_bible)
         elif self.search_results:
             self.display_results(bible, second_bible)
-        self.check_search_result()
         self.application.set_normal_cursor()
 
     def on_search_text_edit_changed(self):
@@ -956,17 +946,13 @@
             if len(text) == 0:
                 if not self.quickLockButton.isChecked():
                     self.list_view.clear()
-                self.check_search_result()
             else:
                 if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0):
                     if not self.quickLockButton.isChecked():
                         self.list_view.clear()
-                    self.check_search_result()
-                elif (limit == 8 and (len(text) < limit or len(count_spaces_two_chars_text) == 0 or
-                                      len(count_two_chars_text) < 2)):
+                elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2):
                     if not self.quickLockButton.isChecked():
-                        self.list_view.clear()
-                    self.check_search_result_search_while_typing_short()
+                        self.list_view.clear(search_while_typing=True)
                 else:
                     """
                     Start search if no chars are entered or deleted for 0.2 s

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2016-10-23 19:24:53 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2016-11-12 12:01:42 +0000
@@ -131,7 +131,6 @@
         # Called to redisplay the custom list screen edith from a search
         # or from the exit of the Custom edit dialog. If remote editing is
         # active trigger it and clean up so it will not update again.
-        self.check_search_result()
 
     def on_new_click(self):
         """
@@ -268,7 +267,6 @@
                                                                     CustomSlide.theme_name.like(search_keywords),
                                                                     order_by_ref=CustomSlide.title)
             self.load_list(search_results)
-        self.check_search_result()
 
     def on_search_text_edit_changed(self, text):
         """

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2016-06-08 20:26:01 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2016-11-12 12:01:42 +0000
@@ -232,7 +232,6 @@
             search_results = self.plugin.manager.get_all_objects(
                 Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
             self.display_results_cclinumber(search_results)
-        self.check_search_result()
 
     def search_entire(self, search_keywords):
         search_string = '%{text}%'.format(text=clean_string(search_keywords))

=== added file 'tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py'
--- tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py	2016-11-12 12:01:42 +0000
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is    free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+This module contains tests for the openlp.core.lib.listwidgetwithdnd module
+"""
+from unittest import TestCase
+
+from openlp.core.common.uistrings import UiStrings
+from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
+from unittest.mock import MagicMock, patch
+
+
+class TestListWidgetWithDnD(TestCase):
+    """
+    Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
+    """
+    def test_clear(self):
+        """
+        Test the clear method when called without any arguments.
+        """
+        # GIVEN: An instance of ListWidgetWithDnD
+        widget = ListWidgetWithDnD()
+
+        # WHEN: Calling clear with out any arguments
+        widget.clear()
+
+        # THEN: The results text should be the standard 'no results' text.
+        self.assertEqual(widget.no_results_text, UiStrings().NoResults)
+
+    def test_clear_search_while_typing(self):
+        """
+        Test the clear method when called with the search_while_typing argument set to True
+        """
+        # GIVEN: An instance of ListWidgetWithDnD
+        widget = ListWidgetWithDnD()
+
+        # WHEN: Calling clear with search_while_typing set to True
+        widget.clear(search_while_typing=True)
+
+        # THEN: The results text should be the 'short results' text.
+        self.assertEqual(widget.no_results_text, UiStrings().ShortResults)
+
+    def test_paint_event(self):
+        """
+        Test the paintEvent method when the list is not empty
+        """
+        # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 1
+        #       (i.e the list has an item)
+        widget = ListWidgetWithDnD()
+        with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \
+                patch.object(widget, 'count', return_value=1), \
+                patch.object(widget, 'viewport') as mocked_viewport:
+            mocked_event = MagicMock()
+
+            # WHEN: Calling paintEvent
+            widget.paintEvent(mocked_event)
+
+            # THEN: The overridden paintEvnet should have been called
+            mocked_paint_event.assert_called_once_with(mocked_event)
+            self.assertFalse(mocked_viewport.called)
+
+    def test_paint_event_no_items(self):
+        """
+        Test the paintEvent method when the list is empty
+        """
+        # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 0
+        #       (i.e the list is empty)
+        widget = ListWidgetWithDnD()
+        mocked_painter_instance = MagicMock()
+        mocked_qrect = MagicMock()
+        with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \
+                patch.object(widget, 'count', return_value=0), \
+                patch.object(widget, 'viewport'), \
+                patch('openlp.core.ui.lib.listwidgetwithdnd.QtGui.QPainter',
+                      return_value=mocked_painter_instance) as mocked_qpainter, \
+                patch('openlp.core.ui.lib.listwidgetwithdnd.QtCore.QRect', return_value=mocked_qrect):
+            mocked_event = MagicMock()
+
+            # WHEN: Calling paintEvent
+            widget.paintEvent(mocked_event)
+
+            # THEN: The overridden paintEvnet should have been called, and some text should be drawn.
+            mocked_paint_event.assert_called_once_with(mocked_event)
+            mocked_qpainter.assert_called_once_with(widget.viewport())
+            mocked_painter_instance.drawText.assert_called_once_with(mocked_qrect, 4100, 'No Search Results')

=== modified file 'tests/functional/openlp_plugins/bibles/test_mediaitem.py'
--- tests/functional/openlp_plugins/bibles/test_mediaitem.py	2016-10-17 17:40:45 +0000
+++ tests/functional/openlp_plugins/bibles/test_mediaitem.py	2016-11-12 12:01:42 +0000
@@ -155,7 +155,6 @@
         self.media_item.list_view = MagicMock()
         self.media_item.search_results = MagicMock()
         self.media_item.display_results = MagicMock()
-        self.media_item.check_search_result = MagicMock()
         self.app.set_normal_cursor = MagicMock()
 
         # WHEN: on_quick_search_button is called
@@ -169,7 +168,6 @@
         self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once')
         self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once')
         self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')
-        self.assertEqual(1, self.media_item.check_search_result.call_count, 'Check results Should had been called once')
         self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')
 
     def test_on_clear_button_clicked(self):
@@ -178,7 +176,6 @@
         """
         # GIVEN: Mocked list_view, check_search_results & quick_search_edit.
         self.media_item.list_view = MagicMock()
-        self.media_item.check_search_result = MagicMock()
         self.media_item.quick_search_edit = MagicMock()
 
         # WHEN: on_clear_button_clicked is called
@@ -186,7 +183,6 @@
 
         # THEN: Search result should be reset and search field should receive focus.
         self.media_item.list_view.clear.assert_called_once_with(),
-        self.media_item.check_search_result.assert_called_once_with(),
         self.media_item.quick_search_edit.clear.assert_called_once_with(),
         self.media_item.quick_search_edit.setFocus.assert_called_once_with()
 


Follow ups