← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~thelinuxguy/openlp/make-methods-static into lp:openlp

 

Simon Hanna has proposed merging lp:~thelinuxguy/openlp/make-methods-static into lp:openlp.

Requested reviews:
  Tomas Groth (tomasgroth)
  Raoul Snyman (raoul-snyman)

For more details, see:
https://code.launchpad.net/~thelinuxguy/openlp/make-methods-static/+merge/281857

Make some methods static where possible
Move some methods out of their classes where it makes sense

Add .coveragerc so that local html reports can be generated
-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file '.bzrignore'
--- .bzrignore	2015-05-07 21:29:43 +0000
+++ .bzrignore	2016-01-07 13:11:16 +0000
@@ -43,3 +43,4 @@
 .coverage
 cover
 *.kdev4
+coverage

=== added file '.coveragerc'
--- .coveragerc	1970-01-01 00:00:00 +0000
+++ .coveragerc	2016-01-07 13:11:16 +0000
@@ -0,0 +1,5 @@
+[run]
+source = openlp
+
+[html]
+directory = coverage

=== modified file 'openlp/core/lib/renderer.py'
--- openlp/core/lib/renderer.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/renderer.py	2016-01-07 13:11:16 +0000
@@ -273,7 +273,7 @@
                             except ValueError:
                                 text_to_render = text.split('\n[---]\n')[0]
                                 text = ''
-                            text_to_render, raw_tags, html_tags = self._get_start_tags(text_to_render)
+                            text_to_render, raw_tags, html_tags = get_start_tags(text_to_render)
                             if text:
                                 text = raw_tags + text
                         else:
@@ -441,7 +441,7 @@
                             previous_raw = line + line_end
                             continue
                 # Figure out how many words of the line will fit on screen as the line will not fit as a whole.
-                raw_words = self._words_split(line)
+                raw_words = Renderer.words_split(line)
                 html_words = list(map(expand_tags, raw_words))
                 previous_html, previous_raw = \
                     self._binary_chop(formatted, previous_html, previous_raw, html_words, raw_words, ' ', line_end)
@@ -451,42 +451,6 @@
         formatted.append(previous_raw)
         return formatted
 
-    def _get_start_tags(self, raw_text):
-        """
-        Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings::
-
-            ('{st}{r}Text text text{/r}{/st}', '{st}{r}', '<strong><span style="-webkit-text-fill-color:red">')
-
-        The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening
-        formatting tags and the third unicode string the html opening formatting tags.
-
-        :param raw_text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags
-        are allowed::
-                {st}{r}Text text text
-        """
-        raw_tags = []
-        html_tags = []
-        for tag in FormattingTags.get_html_tags():
-            if tag['start tag'] == '{br}':
-                continue
-            if raw_text.count(tag['start tag']) != raw_text.count(tag['end tag']):
-                raw_tags.append((raw_text.find(tag['start tag']), tag['start tag'], tag['end tag']))
-                html_tags.append((raw_text.find(tag['start tag']), tag['start html']))
-        # Sort the lists, so that the tags which were opened first on the first slide (the text we are checking) will be
-        # opened first on the next slide as well.
-        raw_tags.sort(key=lambda tag: tag[0])
-        html_tags.sort(key=lambda tag: tag[0])
-        # Create a list with closing tags for the raw_text.
-        end_tags = []
-        start_tags = []
-        for tag in raw_tags:
-            start_tags.append(tag[1])
-            end_tags.append(tag[2])
-        end_tags.reverse()
-        # Remove the indexes.
-        html_tags = [tag[1] for tag in html_tags]
-        return raw_text + ''.join(end_tags), ''.join(start_tags), ''.join(html_tags)
-
     def _binary_chop(self, formatted, previous_html, previous_raw, html_list, raw_list, separator, line_end):
         """
         This implements the binary chop algorithm for faster rendering. This algorithm works line based (line by line)
@@ -521,7 +485,7 @@
             if smallest_index == index or highest_index == index:
                 index = smallest_index
                 text = previous_raw.rstrip('<br>') + separator.join(raw_list[:index + 1])
-                text, raw_tags, html_tags = self._get_start_tags(text)
+                text, raw_tags, html_tags = get_start_tags(text)
                 formatted.append(text)
                 previous_html = ''
                 previous_raw = ''
@@ -556,12 +520,50 @@
         self.web_frame.evaluateJavaScript('show_text("%s")' % text.replace('\\', '\\\\').replace('\"', '\\\"'))
         return self.web_frame.contentsSize().height() <= self.empty_height
 
-    def _words_split(self, line):
-        """
-        Split the slide up by word so can wrap better
-
-        :param line: Line to be split
-        """
-        # this parse we are to be wordy
-        line = line.replace('\n', ' ')
-        return line.split(' ')
+
+def words_split(line):
+    """
+    Split the slide up by word so can wrap better
+
+    :param line: Line to be split
+    """
+    # this parse we are to be wordy
+    line = line.replace('\n', ' ')
+    return line.split(' ')
+
+def get_start_tags(raw_text):
+    """
+    Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings::
+
+        ('{st}{r}Text text text{/r}{/st}', '{st}{r}', '<strong><span style="-webkit-text-fill-color:red">')
+
+    The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening
+    formatting tags and the third unicode string the html opening formatting tags.
+
+    :param raw_text: The text to test. The text must **not** contain html tags, only OpenLP formatting tags
+    are allowed::
+            {st}{r}Text text text
+    """
+    raw_tags = []
+    html_tags = []
+    for tag in FormattingTags.get_html_tags():
+        if tag['start tag'] == '{br}':
+            continue
+        if raw_text.count(tag['start tag']) != raw_text.count(tag['end tag']):
+            raw_tags.append((raw_text.find(tag['start tag']), tag['start tag'], tag['end tag']))
+            html_tags.append((raw_text.find(tag['start tag']), tag['start html']))
+    # Sort the lists, so that the tags which were opened first on the first slide (the text we are checking) will be
+    # opened first on the next slide as well.
+    raw_tags.sort(key=lambda tag: tag[0])
+    html_tags.sort(key=lambda tag: tag[0])
+    # Create a list with closing tags for the raw_text.
+    end_tags = []
+    start_tags = []
+    for tag in raw_tags:
+        start_tags.append(tag[1])
+        end_tags.append(tag[2])
+    end_tags.reverse()
+    # Remove the indexes.
+    html_tags = [tag[1] for tag in html_tags]
+    return raw_text + ''.join(end_tags), ''.join(start_tags), ''.join(html_tags)
+

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2015-12-31 22:46:06 +0000
+++ openlp/core/ui/mainwindow.py	2016-01-07 13:11:16 +0000
@@ -47,6 +47,7 @@
 from openlp.core.utils.actions import ActionList, CategoryOrder
 from openlp.core.ui.firsttimeform import FirstTimeForm
 from openlp.core.ui.projector.manager import ProjectorManager
+from openlp.core.ui.printserviceform import PrintServiceForm
 
 log = logging.getLogger(__name__)
 
@@ -197,7 +198,7 @@
                                                triggers=self.service_manager_contents.save_file_as)
         self.print_service_order_item = create_action(main_window, 'printServiceItem', can_shortcuts=True,
                                                       category=UiStrings().File,
-                                                      triggers=self.service_manager_contents.print_service_order)
+                                                      triggers=lambda x: PrintServiceForm().exec())
         self.file_exit_item = create_action(main_window, 'fileExitItem', icon=':/system/system_exit.png',
                                             can_shortcuts=True,
                                             category=UiStrings().File, triggers=main_window.close)

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2015-12-31 22:46:06 +0000
+++ openlp/core/ui/servicemanager.py	2016-01-07 13:11:16 +0000
@@ -144,8 +144,8 @@
         self.service_manager_list.customContextMenuRequested.connect(self.context_menu)
         self.service_manager_list.setObjectName('service_manager_list')
         # enable drop
-        self.service_manager_list.__class__.dragEnterEvent = self.drag_enter_event
-        self.service_manager_list.__class__.dragMoveEvent = self.drag_enter_event
+        self.service_manager_list.__class__.dragEnterEvent = lambda x, event: event.accept()
+        self.service_manager_list.__class__.dragMoveEvent = lambda x, event: event.accept()
         self.service_manager_list.__class__.dropEvent = self.drop_event
         self.layout.addWidget(self.service_manager_list)
         # Add the bottom toolbar
@@ -293,14 +293,6 @@
         Registry().register_function('theme_update_global', self.theme_change)
         Registry().register_function('mediaitem_suffix_reset', self.reset_supported_suffixes)
 
-    def drag_enter_event(self, event):
-        """
-        Accept Drag events
-
-        :param event: Handle of the event passed
-        """
-        event.accept()
-
 
 class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceManager, RegistryProperties):
     """
@@ -1585,7 +1577,7 @@
                 if item is None:
                     end_pos = len(self.service_items)
                 else:
-                    end_pos = self._get_parent_item_data(item) - 1
+                    end_pos = get_parent_item_data(item) - 1
                 service_item = self.service_items[start_pos]
                 self.service_items.remove(service_item)
                 self.service_items.insert(end_pos, service_item)
@@ -1598,21 +1590,21 @@
                     self.drop_position = len(self.service_items)
                 else:
                     # we are over something so lets investigate
-                    pos = self._get_parent_item_data(item) - 1
+                    pos = get_parent_item_data(item) - 1
                     service_item = self.service_items[pos]
                     if (plugin == service_item['service_item'].name and
                             service_item['service_item'].is_capable(ItemCapabilities.CanAppend)):
                         action = self.dnd_menu.exec(QtGui.QCursor.pos())
                         # New action required
                         if action == self.new_action:
-                            self.drop_position = self._get_parent_item_data(item)
+                            self.drop_position = get_parent_item_data(item)
                         # Append to existing action
                         if action == self.add_to_action:
-                            self.drop_position = self._get_parent_item_data(item)
+                            self.drop_position = get_parent_item_data(item)
                             item.setSelected(True)
                             replace = True
                     else:
-                        self.drop_position = self._get_parent_item_data(item) - 1
+                        self.drop_position = get_parent_item_data(item) - 1
                 Registry().execute('%s_add_service_item' % plugin, replace)
 
     def update_theme_list(self, theme_list):
@@ -1656,27 +1648,22 @@
         self.service_items[item]['service_item'].update_theme(theme)
         self.regenerate_service_items(True)
 
-    def _get_parent_item_data(self, item):
-        """
-        Finds and returns the parent item for any item
-
-        :param item: The service item list item to be checked.
-        """
-        parent_item = item.parent()
-        if parent_item is None:
-            return item.data(0, QtCore.Qt.UserRole)
-        else:
-            return parent_item.data(0, QtCore.Qt.UserRole)
-
-    def print_service_order(self, field=None):
-        """
-        Print a Service Order Sheet.
-        """
-        setting_dialog = PrintServiceForm()
-        setting_dialog.exec()
-
     def get_drop_position(self):
         """
         Getter for drop_position. Used in: MediaManagerItem
         """
         return self.drop_position
+
+
+def get_parent_item_data(item):
+    """
+    Finds and returns the parent item for any item
+
+    :param item: The service item list item to be checked.
+    """
+    parent_item = item.parent()
+    if parent_item is None:
+        return item.data(0, QtCore.Qt.UserRole)
+    else:
+        return parent_item.data(0, QtCore.Qt.UserRole)
+

=== modified file 'openlp/plugins/alerts/alertsplugin.py'
--- openlp/plugins/alerts/alertsplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/alerts/alertsplugin.py	2016-01-07 13:11:16 +0000
@@ -191,7 +191,8 @@
         self.alert_form.load_list()
         self.alert_form.exec()
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Plugin Alerts about method
 
@@ -215,7 +216,8 @@
             'title': translate('AlertsPlugin', 'Alerts', 'container title')
         }
 
-    def get_display_javascript(self):
+    @staticmethod
+    def get_display_javascript():
         """
         Add Javascript to the main display.
         """
@@ -229,7 +231,8 @@
         return CSS % (align, self.settings_tab.font_face, self.settings_tab.font_size, self.settings_tab.font_color,
                       self.settings_tab.background_color)
 
-    def get_display_html(self):
+    @staticmethod
+    def get_display_html():
         """
         Add HTML to the main display.
         """

=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/bibles/bibleplugin.py	2016-01-07 13:11:16 +0000
@@ -167,7 +167,8 @@
         if self.media_item:
             self.media_item.on_import_click()
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Return the about text for the plugin manager
         """

=== modified file 'openlp/plugins/custom/customplugin.py'
--- openlp/plugins/custom/customplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/custom/customplugin.py	2016-01-07 13:11:16 +0000
@@ -62,7 +62,8 @@
         self.icon_path = ':/plugins/plugin_custom.png'
         self.icon = build_icon(self.icon_path)
 
-    def about(self):
+    @staticmethod
+    def about():
         about_text = translate('CustomPlugin', '<strong>Custom Slide Plugin </strong><br />The custom slide plugin '
                                'provides the ability to set up custom text slides that can be displayed on the screen '
                                'the same way songs are. This plugin provides greater freedom over the songs plugin.')

=== modified file 'openlp/plugins/images/imageplugin.py'
--- openlp/plugins/images/imageplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/images/imageplugin.py	2016-01-07 13:11:16 +0000
@@ -53,7 +53,8 @@
         self.icon_path = ':/plugins/plugin_images.png'
         self.icon = build_icon(self.icon_path)
 
-    def about(self):
+    @staticmethod
+    def about():
         about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>'
                                '<br />The image plugin provides displaying of images.<br />One '
                                'of the distinguishing features of this plugin is the ability to '

=== modified file 'openlp/plugins/media/mediaplugin.py'
--- openlp/plugins/media/mediaplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/media/mediaplugin.py	2016-01-07 13:11:16 +0000
@@ -84,7 +84,8 @@
         visible_name = self.get_string(StringContent.VisibleName)
         self.settings_tab = MediaTab(parent, self.name, visible_name['title'], self.icon_path)
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Return the about text for the plugin manager
         """

=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/presentations/presentationplugin.py	2016-01-07 13:11:16 +0000
@@ -137,7 +137,8 @@
             self.register_controllers(controller)
         return bool(self.controllers)
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Return information about this plugin.
         """

=== modified file 'openlp/plugins/remotes/remoteplugin.py'
--- openlp/plugins/remotes/remoteplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/remotes/remoteplugin.py	2016-01-07 13:11:16 +0000
@@ -88,7 +88,8 @@
             self.server.stop_server()
             self.server = None
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Information about this plugin
         """

=== modified file 'openlp/plugins/songs/forms/songexportform.py'
--- openlp/plugins/songs/forms/songexportform.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/forms/songexportform.py	2016-01-07 13:11:16 +0000
@@ -72,7 +72,7 @@
         """
         Song wizard specific signals.
         """
-        self.available_list_widget.itemActivated.connect(self.on_item_activated)
+        self.available_list_widget.itemActivated.connect(on_item_activated)
         self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
         self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
         self.check_button.clicked.connect(self.on_check_button_clicked)
@@ -172,7 +172,7 @@
             return True
         elif self.currentPage() == self.available_songs_page:
             items = [
-                item for item in self._find_list_widget_items(self.available_list_widget) if item.checkState()
+                item for item in find_list_widget_items(self.available_list_widget) if item.checkState()
             ]
             if not items:
                 critical_error_message_box(
@@ -241,7 +241,7 @@
         """
         songs = [
             song.data(QtCore.Qt.UserRole)
-            for song in self._find_list_widget_items(self.selected_list_widget)
+            for song in find_list_widget_items(self.selected_list_widget)
         ]
         exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
         try:
@@ -255,28 +255,6 @@
             self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed because this '
                                                   'error occurred: %s') % ose.strerror)
 
-    def _find_list_widget_items(self, list_widget, text=''):
-        """
-        Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included.
-
-        :param list_widget: The widget to get all items from. (QListWidget)
-        :param text: The text to search for. (unicode string)
-        """
-        return [
-            item for item in list_widget.findItems(text, QtCore.Qt.MatchContains)
-        ]
-
-    def on_item_activated(self, item):
-        """
-        Called, when an item in the *available_list_widget* has been triggered.
-        The item is check if it was not checked, whereas it is unchecked when it
-        was checked.
-
-        :param item:  The *QListWidgetItem* which was triggered.
-        """
-        item.setCheckState(
-            QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked)
-
     def on_search_line_edit_changed(self, text):
         """
         The *search_line_edit*'s text has been changed. Update the list of
@@ -286,9 +264,9 @@
         :param text:  The text of the *search_line_edit*.
         """
         search_result = [
-            song for song in self._find_list_widget_items(self.available_list_widget, text)
+            song for song in find_list_widget_items(self.available_list_widget, text)
         ]
-        for item in self._find_list_widget_items(self.available_list_widget):
+        for item in find_list_widget_items(self.available_list_widget):
             item.setHidden(item not in search_result)
 
     def on_uncheck_button_clicked(self):
@@ -317,3 +295,26 @@
         self.get_folder(
             translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
             self.directory_line_edit, 'last directory export')
+
+
+def find_list_widget_items(list_widget, text=''):
+    """
+    Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included.
+
+    :param list_widget: The widget to get all items from. (QListWidget)
+    :param text: The text to search for. (unicode string)
+    """
+    return [
+        item for item in list_widget.findItems(text, QtCore.Qt.MatchContains)
+    ]
+
+def on_item_activated(item):
+    """
+    Called, when an item in the *available_list_widget* has been triggered.
+    The item is check if it was not checked, whereas it is unchecked when it
+    was checked.
+
+    :param item:  The *QListWidgetItem* which was triggered.
+    """
+    item.setCheckState(QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked)
+

=== modified file 'openlp/plugins/songs/lib/importers/foilpresenter.py'
--- openlp/plugins/songs/lib/importers/foilpresenter.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/foilpresenter.py	2016-01-07 13:11:16 +0000
@@ -234,16 +234,6 @@
             clean_song(self.manager, song)
             self.manager.save_object(song)
 
-    def _child(self, element):
-        """
-        This returns the text of an element as unicode string.
-
-        :param element: The element
-        """
-        if element is not None:
-            return str(element)
-        return ''
-
     def _process_authors(self, foilpresenterfolie, song):
         """
         Adds the authors specified in the XML to the song.
@@ -253,7 +243,7 @@
         """
         authors = []
         try:
-            copyright = self._child(foilpresenterfolie.copyright.text_)
+            copyright = to_str(foilpresenterfolie.copyright.text_)
         except AttributeError:
             copyright = None
         if copyright:
@@ -346,7 +336,7 @@
         :param song: The song object.
         """
         try:
-            song.ccli_number = self._child(foilpresenterfolie.ccliid)
+            song.ccli_number = to_str(foilpresenterfolie.ccliid)
         except AttributeError:
             song.ccli_number = ''
 
@@ -358,7 +348,7 @@
         :param song: The song object.
         """
         try:
-            song.comments = self._child(foilpresenterfolie.notiz)
+            song.comments = to_str(foilpresenterfolie.notiz)
         except AttributeError:
             song.comments = ''
 
@@ -370,7 +360,7 @@
         :param song: The song object.
         """
         try:
-            song.copyright = self._child(foilpresenterfolie.copyright.text_)
+            song.copyright = to_str(foilpresenterfolie.copyright.text_)
         except AttributeError:
             song.copyright = ''
 
@@ -396,19 +386,19 @@
             VerseType.tags[VerseType.PreChorus]: 1
         }
         if not hasattr(foilpresenterfolie.strophen, 'strophe'):
-            self.importer.log_error(self._child(foilpresenterfolie.titel),
+            self.importer.log_error(to_str(foilpresenterfolie.titel),
                                     str(translate('SongsPlugin.FoilPresenterSongImport',
                                                   'Invalid Foilpresenter song file. No verses found.')))
             self.save_song = False
             return
         for strophe in foilpresenterfolie.strophen.strophe:
-            text = self._child(strophe.text_) if hasattr(strophe, 'text_') else ''
-            verse_name = self._child(strophe.key)
+            text = to_str(strophe.text_) if hasattr(strophe, 'text_') else ''
+            verse_name = to_str(strophe.key)
             children = strophe.getchildren()
             sortnr = False
             for child in children:
                 if child.tag == 'sortnr':
-                    verse_sortnr = self._child(strophe.sortnr)
+                    verse_sortnr = to_str(strophe.sortnr)
                     sortnr = True
                 # In older Version there is no sortnr, but we need one
             if not sortnr:
@@ -484,7 +474,7 @@
         song.song_number = ''
         try:
             for bucheintrag in foilpresenterfolie.buch.bucheintrag:
-                book_name = self._child(bucheintrag.name)
+                book_name = to_str(bucheintrag.name)
                 if book_name:
                     book = self.manager.get_object_filtered(Book, Book.name == book_name)
                     if book is None:
@@ -493,8 +483,8 @@
                         self.manager.save_object(book)
                     song.song_book_id = book.id
                     try:
-                        if self._child(bucheintrag.nummer):
-                            song.song_number = self._child(bucheintrag.nummer)
+                        if to_str(bucheintrag.nummer):
+                            song.song_number = to_str(bucheintrag.nummer)
                     except AttributeError:
                         pass
                     # We only support one song book, so take the first one.
@@ -512,13 +502,13 @@
         try:
             for title_string in foilpresenterfolie.titel.titelstring:
                 if not song.title:
-                    song.title = self._child(title_string)
+                    song.title = to_str(title_string)
                     song.alternate_title = ''
                 else:
-                    song.alternate_title = self._child(title_string)
+                    song.alternate_title = to_str(title_string)
         except AttributeError:
             # Use first line of first verse
-            first_line = self._child(foilpresenterfolie.strophen.strophe.text_)
+            first_line = to_str(foilpresenterfolie.strophen.strophe.text_)
             song.title = first_line.split('\n')[0]
 
     def _process_topics(self, foilpresenterfolie, song):
@@ -530,7 +520,7 @@
         """
         try:
             for name in foilpresenterfolie.kategorien.name:
-                topic_text = self._child(name)
+                topic_text = to_str(name)
                 if topic_text:
                     topic = self.manager.get_object_filtered(Topic, Topic.name == topic_text)
                     if topic is None:
@@ -540,3 +530,15 @@
                     song.topics.append(topic)
         except AttributeError:
             pass
+
+
+def to_str(element):
+    """
+    This returns the text of an element as unicode string.
+
+    :param element: The element
+    """
+    if element is not None:
+        return str(element)
+    return ''
+

=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/songsplugin.py	2016-01-07 13:11:16 +0000
@@ -211,7 +211,8 @@
         if self.media_item:
             self.media_item.on_export_click()
 
-    def about(self):
+    @staticmethod
+    def about():
         """
         Provides information for the plugin manager to display.
 
@@ -296,7 +297,7 @@
             if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
                 self.application.process_events()
                 song_dbs.append(os.path.join(db_dir, sfile))
-                song_count += self._count_songs(os.path.join(db_dir, sfile))
+                song_count += SongsPlugin._count_songs(os.path.join(db_dir, sfile))
         if not song_dbs:
             return
         self.application.process_events()
@@ -343,7 +344,8 @@
         for song in songs:
             self.manager.delete_object(Song, song.id)
 
-    def _count_songs(self, db_file):
+    @staticmethod
+    def _count_songs(db_file):
         """
         Provide a count of the songs in the database
 

=== modified file 'openlp/plugins/songusage/songusageplugin.py'
--- openlp/plugins/songusage/songusageplugin.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songusage/songusageplugin.py	2016-01-07 13:11:16 +0000
@@ -226,8 +226,9 @@
         """
         self.song_usage_detail_form.initialise()
         self.song_usage_detail_form.exec()
-
-    def about(self):
+    
+    @staticmethod
+    def about():
         """
         The plugin about text
 

=== modified file 'tests/functional/openlp_plugins/media/test_mediaplugin.py'
--- tests/functional/openlp_plugins/media/test_mediaplugin.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_plugins/media/test_mediaplugin.py	2016-01-07 13:11:16 +0000
@@ -57,3 +57,11 @@
         mocked_settings.get_files_from_config.assert_called_with(media_plugin)
         mocked_settings.setValue.assert_called_with('media/media files', True)
         mocked_initialise.assert_called_with()
+
+    def test_about_text(self):
+        # GIVEN: The MediaPlugin
+        # WHEN: Retrieving the about text
+        # THEN: about() should return a string object
+        self.assertIsInstance(MediaPlugin.about(), str)
+        # THEN: about() should return a non-empty string
+        self.assertNotEquals(len(MediaPlugin.about()), 0)

=== modified file 'tests/functional/openlp_plugins/songs/test_foilpresenterimport.py'
--- tests/functional/openlp_plugins/songs/test_foilpresenterimport.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_plugins/songs/test_foilpresenterimport.py	2016-01-07 13:11:16 +0000
@@ -39,7 +39,7 @@
     """
     # TODO: The following modules still need tests written for
     #   xml_to_song
-    #   _child
+    #   to_str
     #   _process_authors
     #   _process_cclinumber
     #   _process_comments
@@ -50,7 +50,7 @@
     #   _process_topics
 
     def setUp(self):
-        self.child_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.FoilPresenter._child')
+        self.to_str_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.to_str')
         self.clean_song_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.clean_song')
         self.objectify_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.objectify')
         self.process_authors_patcher = \
@@ -72,7 +72,7 @@
         self.song_xml_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.SongXML')
         self.translate_patcher = patch('openlp.plugins.songs.lib.importers.foilpresenter.translate')
 
-        self.mocked_child = self.child_patcher.start()
+        self.mocked_child = self.to_str_patcher.start()
         self.mocked_clean_song = self.clean_song_patcher.start()
         self.mocked_objectify = self.objectify_patcher.start()
         self.mocked_process_authors = self.process_authors_patcher.start()
@@ -92,7 +92,7 @@
         self.mocked_song_import = MagicMock()
 
     def tearDown(self):
-        self.child_patcher.stop()
+        self.to_str_patcher.stop()
         self.clean_song_patcher.stop()
         self.objectify_patcher.stop()
         self.process_authors_patcher.stop()

=== added directory 'tests/functional/openlp_plugins/songusage'
=== added file 'tests/functional/openlp_plugins/songusage/__init__.py'
--- tests/functional/openlp_plugins/songusage/__init__.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songusage/__init__.py	2016-01-07 13:11:16 +0000
@@ -0,0 +1,24 @@
+# -*- 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                          #
+###############################################################################
+"""
+Tests for the Songusage plugin
+"""

=== added file 'tests/functional/openlp_plugins/songusage/test_songusage.py'
--- tests/functional/openlp_plugins/songusage/test_songusage.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songusage/test_songusage.py	2016-01-07 13:11:16 +0000
@@ -0,0 +1,37 @@
+# -*- 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 Songusage plugin.
+"""
+from unittest import TestCase
+from openlp.plugins.songusage.songusageplugin import SongUsagePlugin
+
+class TestSongUsage(TestCase):
+
+    def test_about_text(self):
+        # GIVEN: The SongUsagePlugin
+        # WHEN: Retrieving the about text
+        # THEN: about() should return a string object
+        self.assertIsInstance(SongUsagePlugin.about(), str)
+        # THEN: about() should return a non-empty string
+        self.assertNotEquals(len(SongUsagePlugin.about()), 0)
+        self.assertNotEquals(len(SongUsagePlugin.about()), 0)


Follow ups