← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~j-corwin/openlp/general into lp:openlp

 

Jonathan Corwin has proposed merging lp:~j-corwin/openlp/general into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)
Related bugs:
  Bug #634771 in OpenLP: "OpenLP 1.9.2+bzr1016-0ubuntu1~lucid1 does not start"
  https://bugs.launchpad.net/openlp/+bug/634771
  Bug #646718 in OpenLP: "Songbook, Number will not loaded, Title will not be saved"
  https://bugs.launchpad.net/openlp/+bug/646718
  Bug #696013 in OpenLP: "song import from powerpoint crashes every second time"
  https://bugs.launchpad.net/openlp/+bug/696013
  Bug #696021 in OpenLP: "presentation loader does not work fine in Windows using Powerpoint Viewer 2007"
  https://bugs.launchpad.net/openlp/+bug/696021
  Bug #696637 in OpenLP: "Alert not positioned correctly in single screen"
  https://bugs.launchpad.net/openlp/+bug/696637
  Bug #727732 in OpenLP: "Openlp 1.9.?? crashes on start"
  https://bugs.launchpad.net/openlp/+bug/727732
  Bug #735039 in OpenLP: "Cannot import PowerPoint Presentations with PowerPoint 2010"
  https://bugs.launchpad.net/openlp/+bug/735039

For more details, see:
https://code.launchpad.net/~j-corwin/openlp/general/+merge/61047

Implement a Search and Go-Live option to the Web Remote.

Also made tweaks to the buttons on the slide/service controller on the web remote so they fit on one row on a mobile.
-- 
https://code.launchpad.net/~j-corwin/openlp/general/+merge/61047
Your team OpenLP Core is requested to review the proposed merge of lp:~j-corwin/openlp/general into lp:openlp.
=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2011-05-15 12:11:08 +0000
+++ openlp/core/lib/mediamanageritem.py	2011-05-15 22:21:01 +0000
@@ -102,6 +102,7 @@
         self.remoteTriggered = None
         self.singleServiceItem = True
         self.quickPreviewAllowed = False
+        self.hasSearch = False
         self.pageLayout = QtGui.QVBoxLayout(self)
         self.pageLayout.setSpacing(0)
         self.pageLayout.setMargin(0)
@@ -474,11 +475,23 @@
                 translate('OpenLP.MediaManagerItem',
                     'You must select one or more items to send live.'))
         else:
-            log.debug(u'%s Live requested', self.plugin.name)
-            serviceItem = self.buildServiceItem()
-            if serviceItem:
+            self.goLive()
+
+    def goLive(self, item_id=None):
+        log.debug(u'%s Live requested', self.plugin.name)
+        item = None
+        if item_id:
+            item = self.createItemFromId(item_id)
+        serviceItem = self.buildServiceItem(item)
+        if serviceItem:
+            if not item_id:
                 serviceItem.from_plugin = True
-                self.parent.liveController.addServiceItem(serviceItem)
+            self.parent.liveController.addServiceItem(serviceItem)
+
+    def createItemFromId(self, item_id):
+        item = QtGui.QListWidgetItem()
+        item.setData(QtCore.Qt.UserRole, QtCore.QVariant(item_id))
+        return item
 
     def onAddClick(self):
         """
@@ -586,3 +599,10 @@
         else:
             item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
         return item_id
+
+    def search(self, string):
+        """
+        Performs a plugin specific search for items containing ``string``
+        """
+        raise NotImplementedError(
+            u'Plugin.search needs to be defined by the plugin')

=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py	2011-04-14 18:30:53 +0000
+++ openlp/core/lib/pluginmanager.py	2011-05-15 22:21:01 +0000
@@ -211,3 +211,12 @@
             if plugin.isActive():
                 plugin.finalise()
                 log.info(u'Finalisation Complete for %s ' % plugin.name)
+
+    def get_plugin_by_name(self, name):
+        """
+        Return the plugin which has a name with value ``name``
+        """
+        for plugin in self.plugins:
+            if plugin.name == name:
+                return plugin
+        return None

=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py	2011-04-13 18:53:05 +0000
+++ openlp/plugins/bibles/lib/db.py	2011-05-15 22:21:01 +0000
@@ -323,7 +323,7 @@
         """
         return self.get_all_objects(Book, order_by_ref=Book.id)
 
-    def get_verses(self, reference_list):
+    def get_verses(self, reference_list, show_error=True):
         """
         This is probably the most used function. It retrieves the list of
         verses based on the user's query.
@@ -360,11 +360,12 @@
                 verse_list.extend(verses)
             else:
                 log.debug(u'OpenLP failed to find book %s', book)
-                critical_error_message_box(
-                    translate('BiblesPlugin', 'No Book Found'),
-                    translate('BiblesPlugin', 'No matching book '
-                    'could be found in this Bible. Check that you have '
-                    'spelled the name of the book correctly.'))
+                if show_error:
+                    critical_error_message_box(
+                        translate('BiblesPlugin', 'No Book Found'),
+                        translate('BiblesPlugin', 'No matching book '
+                        'could be found in this Bible. Check that you '
+                        'have spelled the name of the book correctly.'))
         return verse_list
 
     def verse_search(self, text):

=== modified file 'openlp/plugins/bibles/lib/http.py'
--- openlp/plugins/bibles/lib/http.py	2011-03-24 19:04:02 +0000
+++ openlp/plugins/bibles/lib/http.py	2011-05-15 22:21:01 +0000
@@ -425,7 +425,7 @@
             self.create_meta(u'proxy password', self.proxy_password)
         return True
 
-    def get_verses(self, reference_list):
+    def get_verses(self, reference_list, show_error=True):
         """
         A reimplementation of the ``BibleDB.get_verses`` method, this one is
         specifically for web Bibles. It first checks to see if the particular
@@ -453,11 +453,12 @@
             if not db_book:
                 book_details = HTTPBooks.get_book(book)
                 if not book_details:
-                    critical_error_message_box(
-                        translate('BiblesPlugin', 'No Book Found'),
-                        translate('BiblesPlugin', 'No matching '
-                        'book could be found in this Bible. Check that you '
-                        'have spelled the name of the book correctly.'))
+                    if show_error:
+                        critical_error_message_box(
+                            translate('BiblesPlugin', 'No Book Found'),
+                            translate('BiblesPlugin', 'No matching '
+                            'book could be found in this Bible. Check that you '
+                            'have spelled the name of the book correctly.'))
                     return []
                 db_book = self.create_book(book_details[u'name'],
                     book_details[u'abbreviation'],
@@ -480,7 +481,7 @@
                     Receiver.send_message(u'openlp_process_events')
                 Receiver.send_message(u'cursor_normal')
             Receiver.send_message(u'openlp_process_events')
-        return BibleDB.get_verses(self, reference_list)
+        return BibleDB.get_verses(self, reference_list, show_error)
 
     def get_chapter(self, book, chapter):
         """

=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py	2011-03-24 19:04:02 +0000
+++ openlp/plugins/bibles/lib/manager.py	2011-05-15 22:21:01 +0000
@@ -231,7 +231,7 @@
             bible, book, chapter)
         return self.db_cache[bible].get_verse_count(book, chapter)
 
-    def get_verses(self, bible, versetext):
+    def get_verses(self, bible, versetext, show_error=True):
         """
         Parses a scripture reference, fetches the verses from the Bible
         specified, and returns a list of ``Verse`` objects.
@@ -252,32 +252,34 @@
         """
         log.debug(u'BibleManager.get_verses("%s", "%s")', bible, versetext)
         if not bible:
-            Receiver.send_message(u'openlp_information_message', {
-                u'title': translate('BiblesPlugin.BibleManager',
-                'No Bibles Available'),
-                u'message': translate('BiblesPlugin.BibleManager',
-                'There are no Bibles currently installed. Please use the '
-                'Import Wizard to install one or more Bibles.')
-                })
+            if show_error:
+                Receiver.send_message(u'openlp_information_message', {
+                    u'title': translate('BiblesPlugin.BibleManager',
+                    'No Bibles Available'),
+                    u'message': translate('BiblesPlugin.BibleManager',
+                    'There are no Bibles currently installed. Please use the '
+                    'Import Wizard to install one or more Bibles.')
+                    })
             return None
         reflist = parse_reference(versetext)
         if reflist:
-            return self.db_cache[bible].get_verses(reflist)
+            return self.db_cache[bible].get_verses(reflist, show_error)
         else:
-            Receiver.send_message(u'openlp_information_message', {
-                u'title': translate('BiblesPlugin.BibleManager',
-                'Scripture Reference Error'),
-                u'message': translate('BiblesPlugin.BibleManager',
-                'Your scripture reference is either not supported by OpenLP '
-                'or is invalid. Please make sure your reference conforms to '
-                'one of the following patterns:\n\n'
-                'Book Chapter\n'
-                'Book Chapter-Chapter\n'
-                'Book Chapter:Verse-Verse\n'
-                'Book Chapter:Verse-Verse,Verse-Verse\n'
-                'Book Chapter:Verse-Verse,Chapter:Verse-Verse\n'
-                'Book Chapter:Verse-Chapter:Verse')
-                })
+            if show_message:
+                Receiver.send_message(u'openlp_information_message', {
+                    u'title': translate('BiblesPlugin.BibleManager',
+                    'Scripture Reference Error'),
+                    u'message': translate('BiblesPlugin.BibleManager',
+                    'Your scripture reference is either not supported by '
+                    'OpenLP or is invalid. Please make sure your reference '
+                    'conforms to one of the following patterns:\n\n'
+                    'Book Chapter\n'
+                    'Book Chapter-Chapter\n'
+                    'Book Chapter:Verse-Verse\n'
+                    'Book Chapter:Verse-Verse,Verse-Verse\n'
+                    'Book Chapter:Verse-Verse,Chapter:Verse-Verse\n'
+                    'Book Chapter:Verse-Chapter:Verse')
+                    })
             return None
 
     def verse_search(self, bible, second_bible, text):

=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py	2011-05-15 12:11:33 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -61,6 +61,7 @@
         # Place to store the search results for both bibles.
         self.settings = self.parent.settings_tab
         self.quickPreviewAllowed = True
+        self.hasSearch = True
         self.search_results = {}
         self.second_search_results = {}
         self.check_search_result()
@@ -650,6 +651,19 @@
         Displays the search results in the media manager. All data needed for
         further action is saved for/in each row.
         """
+        items = self.buildDisplayResults(bible, second_bible,
+            self.search_results)
+        for bible_verse in items:
+            self.listView.addItem(bible_verse)
+        self.listView.selectAll()
+        self.search_results = {}
+        self.second_search_results = {}
+
+    def buildDisplayResults(self, bible, second_bible, search_results):
+        """
+        Displays the search results in the media manager. All data needed for
+        further action is saved for/in each row.
+        """
         verse_separator = get_reference_match(u'sep_v_display')
         version = self.parent.manager.get_meta_data(bible, u'Version').value
         copyright = self.parent.manager.get_meta_data(bible, u'Copyright').value
@@ -665,7 +679,8 @@
                 second_bible, u'Copyright').value
             second_permissions = self.parent.manager.get_meta_data(
                 second_bible, u'Permissions').value
-        for count, verse in enumerate(self.search_results):
+        items = []
+        for count, verse in enumerate(search_results):
             data = {
                 'book': QtCore.QVariant(verse.book.name),
                 'chapter': QtCore.QVariant(verse.chapter),
@@ -697,10 +712,8 @@
                     verse.chapter, verse_separator, verse.verse, version)
             bible_verse = QtGui.QListWidgetItem(bible_text)
             bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(data))
-            self.listView.addItem(bible_verse)
-        self.listView.selectAll()
-        self.search_results = {}
-        self.second_search_results = {}
+            items.append(bible_verse)
+        return items
 
     def generateSlideData(self, service_item, item=None, xmlVersion=False):
         """
@@ -708,7 +721,10 @@
         service item's title.
         """
         log.debug(u'generating slide data')
-        items = self.listView.selectedIndexes()
+        if item:
+            items = item
+        else:
+            items = self.listView.selectedItems()
         if len(items) == 0:
             return False
         bible_text = u''
@@ -717,8 +733,7 @@
         raw_slides = []
         raw_title = []
         verses = VerseReferenceList()
-        for item in items:
-            bitem = self.listView.item(item.row())
+        for bitem in items:
             book = self._decodeQtObject(bitem, 'book')
             chapter = int(self._decodeQtObject(bitem, 'chapter'))
             verse = int(self._decodeQtObject(bitem, 'verse'))
@@ -752,11 +767,11 @@
             else:
                 bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
             if not old_item:
-                start_item = item
-            elif self.checkTitle(item, old_item):
+                start_item = bitem
+            elif self.checkTitle(bitem, old_item):
                 raw_title.append(self.formatTitle(start_item, old_item))
-                start_item = item
-            old_item = item
+                start_item = bitem
+            old_item = bitem
             old_chapter = chapter
         # Add footer
         service_item.raw_footer.append(verses.format_verses())
@@ -764,7 +779,7 @@
             verses.add_version(second_version, second_copyright,
                 second_permissions)
         service_item.raw_footer.append(verses.format_versions())
-        raw_title.append(self.formatTitle(start_item, item))
+        raw_title.append(self.formatTitle(start_item, bitem))
         # If there are no more items we check whether we have to add bible_text.
         if bible_text:
             raw_slides.append(bible_text.lstrip())
@@ -787,9 +802,9 @@
         [service_item.add_from_text(slide[:30], slide) for slide in raw_slides]
         return True
 
-    def formatTitle(self, start_item, old_item):
+    def formatTitle(self, start_bitem, old_bitem):
         """
-        This methode is called, when we have to change the title, because
+        This method is called, when we have to change the title, because
         we are at the end of a verse range. E. g. if we want to add
         Genesis 1:1-6 as well as Daniel 2:14.
 
@@ -801,10 +816,8 @@
         """
         verse_separator = get_reference_match(u'sep_v_display')
         range_separator = get_reference_match(u'sep_r_display')
-        old_bitem = self.listView.item(old_item.row())
         old_chapter = self._decodeQtObject(old_bitem, 'chapter')
         old_verse = self._decodeQtObject(old_bitem, 'verse')
-        start_bitem = self.listView.item(start_item.row())
         start_book = self._decodeQtObject(start_bitem, 'book')
         start_chapter = self._decodeQtObject(start_bitem, 'chapter')
         start_verse = self._decodeQtObject(start_bitem, 'verse')
@@ -825,9 +838,9 @@
                 range_separator + old_chapter + verse_separator + old_verse
         return u'%s %s (%s)' % (start_book, verse_range, bibles)
 
-    def checkTitle(self, item, old_item):
+    def checkTitle(self, bitem, old_bitem):
         """
-        This methode checks if we are at the end of an verse range. If that is
+        This method checks if we are at the end of an verse range. If that is
         the case, we return True, otherwise False. E. g. if we added
         Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
 
@@ -838,13 +851,11 @@
             The item we were previously dealing with.
         """
         # Get all the necessary meta data.
-        bitem = self.listView.item(item.row())
         book = self._decodeQtObject(bitem, 'book')
         chapter = int(self._decodeQtObject(bitem, 'chapter'))
         verse = int(self._decodeQtObject(bitem, 'verse'))
         bible = self._decodeQtObject(bitem, 'bible')
         second_bible = self._decodeQtObject(bitem, 'second_bible')
-        old_bitem = self.listView.item(old_item.row())
         old_book = self._decodeQtObject(old_bitem, 'book')
         old_chapter = int(self._decodeQtObject(old_bitem, 'chapter'))
         old_verse = int(self._decodeQtObject(old_bitem, 'verse'))
@@ -896,3 +907,26 @@
         if self.settings.display_style == DisplayStyle.Square:
             return u'{su}[%s]{/su}' % verse_text
         return u'{su}%s{/su}' % verse_text
+
+    def search(self, string):
+        """
+        Search for some Bible verses (by reference).
+        """
+        bible = unicode(self.quickVersionComboBox.currentText())
+        search_results = self.parent.manager.get_verses(bible, string, False)
+        results = []
+        if search_results:
+            versetext = u''
+            for verse in search_results:
+                if versetext:
+                    versetext += u' '
+                versetext += verse.text
+            results.append([string, versetext])
+        return results
+
+    def createItemFromId(self, item_id):
+        item = QtGui.QListWidgetItem()
+        bible = unicode(self.quickVersionComboBox.currentText())
+        search_results = self.parent.manager.get_verses(bible, item_id, False)
+        items = self.buildDisplayResults(bible, u'', search_results)
+        return items

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2011-04-16 11:42:35 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -27,6 +27,7 @@
 import logging
 
 from PyQt4 import QtCore, QtGui
+from sqlalchemy.sql import or_, func
 
 from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
     check_item_selected
@@ -47,6 +48,7 @@
         MediaManagerItem.__init__(self, parent, self, icon)
         self.singleServiceItem = False
         self.quickPreviewAllowed = True
+        self.hasSearch = True
         # Holds information about whether the edit is remotly triggered and
         # which Custom is required.
         self.remoteCustom = -1
@@ -161,4 +163,16 @@
         else:
             raw_footer.append(u'')
         service_item.raw_footer = raw_footer
-        return True
\ No newline at end of file
+        return True
+
+    def search(self, string):
+        search_results = self.manager.get_all_objects(CustomSlide,
+            or_(func.lower(CustomSlide.title).like(u'%' +
+            string.lower() + u'%'),
+            func.lower(CustomSlide.text).like(u'%' +
+            string.lower() + u'%')),
+            order_by_ref=CustomSlide.title)
+        results = []
+        for custom in search_results:
+            results.append([custom.id, custom.title])
+        return results

=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py	2011-04-30 17:36:13 +0000
+++ openlp/plugins/images/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -47,6 +47,7 @@
         self.IconPath = u'images/image'
         MediaManagerItem.__init__(self, parent, self, icon)
         self.quickPreviewAllowed = True
+        self.hasSearch = True
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'live_theme_changed'), self.liveThemeChanged)
 
@@ -130,51 +131,51 @@
             self.parent.formparent.finishedProgressBar()
 
     def generateSlideData(self, service_item, item=None, xmlVersion=False):
-        items = self.listView.selectedIndexes()
-        if items:
-            service_item.title = unicode(self.plugin.nameStrings[u'plural'])
-            service_item.add_capability(ItemCapabilities.AllowsMaintain)
-            service_item.add_capability(ItemCapabilities.AllowsPreview)
-            service_item.add_capability(ItemCapabilities.AllowsLoop)
-            service_item.add_capability(ItemCapabilities.AllowsAdditions)
-            # force a nonexistent theme
-            service_item.theme = -1
-            missing_items = []
-            missing_items_filenames = []
-            for item in items:
-                bitem = self.listView.item(item.row())
-                filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
-                if not os.path.exists(filename):
-                    missing_items.append(item)
-                    missing_items_filenames.append(filename)
-            for item in missing_items:
-                items.remove(item)
-            # We cannot continue, as all images do not exist.
+        if item:
+            items = [item]
+        else:
+            items = self.listView.selectedItems()
             if not items:
-                critical_error_message_box(
-                    translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
-                    unicode(translate('ImagePlugin.MediaItem',
-                    'The following image(s) no longer exist: %s')) %
-                    u'\n'.join(missing_items_filenames))
                 return False
-            # We have missing as well as existing images. We ask what to do.
-            elif missing_items and QtGui.QMessageBox.question(self,
+        service_item.title = unicode(self.plugin.nameStrings[u'plural'])
+        service_item.add_capability(ItemCapabilities.AllowsMaintain)
+        service_item.add_capability(ItemCapabilities.AllowsPreview)
+        service_item.add_capability(ItemCapabilities.AllowsLoop)
+        service_item.add_capability(ItemCapabilities.AllowsAdditions)
+        # force a nonexistent theme
+        service_item.theme = -1
+        missing_items = []
+        missing_items_filenames = []
+        for bitem in items:
+            filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
+            if not os.path.exists(filename):
+                missing_items.append(item)
+                missing_items_filenames.append(filename)
+        for item in missing_items:
+            items.remove(item)
+        # We cannot continue, as all images do not exist.
+        if not items:
+            critical_error_message_box(
                 translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
-                unicode(translate('ImagePlugin.MediaItem', 'The following '
-                'image(s) no longer exist: %s\nDo you want to add the other '
-                'images anyway?')) % u'\n'.join(missing_items_filenames),
-                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No |
-                QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
-                return False
-            # Continue with the existing images.
-            for item in items:
-                bitem = self.listView.item(item.row())
-                filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
-                (path, name) = os.path.split(filename)
-                service_item.add_from_image(filename, name)
-            return True
-        else:
-            return False
+                unicode(translate('ImagePlugin.MediaItem',
+                'The following image(s) no longer exist: %s')) %
+                u'\n'.join(missing_items_filenames))
+            return False
+        # We have missing as well as existing images. We ask what to do.
+        elif missing_items and QtGui.QMessageBox.question(self,
+            translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
+            unicode(translate('ImagePlugin.MediaItem', 'The following '
+            'image(s) no longer exist: %s\nDo you want to add the other '
+            'images anyway?')) % u'\n'.join(missing_items_filenames),
+            QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No |
+            QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No:
+            return False
+        # Continue with the existing images.
+        for bitem in items:
+            filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
+            (path, name) = os.path.split(filename)
+            service_item.add_from_image(filename, name)
+        return True
 
     def onResetClick(self):
         """
@@ -208,3 +209,14 @@
                     unicode(translate('ImagePlugin.MediaItem',
                     'There was a problem replacing your background, '
                     'the image file "%s" no longer exists.')) % filename)
+
+    def search(self, string):
+        list = SettingsManager.load_list(self.settingsSection,
+            self.settingsSection)
+        results = []
+        string = string.lower()
+        for file in list:
+            filename = os.path.split(unicode(file))[1]
+            if filename.lower().find(string) > -1:
+                results.append([file, filename])
+        return results

=== modified file 'openlp/plugins/media/lib/mediaitem.py'
--- openlp/plugins/media/lib/mediaitem.py	2011-05-01 07:21:55 +0000
+++ openlp/plugins/media/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -50,6 +50,7 @@
             u':/media/media_video.png').toImage()
         MediaManagerItem.__init__(self, parent, self, icon)
         self.singleServiceItem = False
+        self.hasSearch = True
         self.mediaObject = None
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'video_background_replaced'),
@@ -212,3 +213,14 @@
         log.debug(u'CreatePhonon')
         if not self.mediaObject:
             self.mediaObject = Phonon.MediaObject(self)
+
+    def search(self, string):
+        list = SettingsManager.load_list(self.settingsSection,
+            self.settingsSection)
+        results = []
+        string = string.lower()
+        for file in list:
+            filename = os.path.split(unicode(file))[1]
+            if filename.lower().find(string) > -1:
+                results.append([file, filename])
+        return results

=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py	2011-04-30 17:36:28 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -53,6 +53,7 @@
         self.Automatic = u''
         MediaManagerItem.__init__(self, parent, self, icon)
         self.message_listener = MessageListener(self)
+        self.hasSearch = True
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'mediaitem_presentation_rebuild'), self.rebuild)
 
@@ -231,17 +232,19 @@
         in the slidecontroller. In the case of powerpoints, an image
         for each slide
         """
-        items = self.listView.selectedIndexes()
-        if len(items) > 1:
-            return False
+        if item:
+            items = [item]
+        else:
+            items = self.listView.selectedItems()
+            if len(items) > 1:
+                return False
         service_item.title = unicode(self.displayTypeComboBox.currentText())
         service_item.shortname = unicode(self.displayTypeComboBox.currentText())
         service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
         service_item.add_capability(ItemCapabilities.AllowsDetailedTitleDisplay)
         shortname = service_item.shortname
         if shortname:
-            for item in items:
-                bitem = self.listView.item(item.row())
+            for bitem in items:
                 filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
                 if os.path.exists(filename):
                     if shortname == self.Automatic:
@@ -303,3 +306,12 @@
                 if filetype in self.controllers[controller].alsosupports:
                     return controller
         return None
+
+    def search(self, string):
+        list = SettingsManager.load_list(self.settingsSection, u'presentations')
+        results = []
+        string = string.lower()
+        for file in list:
+            if file.lower().find(string) > -1:
+                results.append([file, file])
+        return results

=== modified file 'openlp/plugins/remotes/html/index.html'
--- openlp/plugins/remotes/html/index.html	2011-05-07 08:18:02 +0000
+++ openlp/plugins/remotes/html/index.html	2011-05-15 22:21:01 +0000
@@ -43,6 +43,7 @@
       <a href="#service-manager" data-role="button" data-icon="arrow-r" data-iconpos="right">Service Manager</a>
       <a href="#slide-controller" data-role="button" data-icon="arrow-r" data-iconpos="right">Slide Controller</a>
       <a href="#alerts" data-role="button" data-icon="arrow-r" data-iconpos="right">Alerts</a>
+      <a href="#search" data-role="button" data-icon="arrow-r" data-iconpos="right">Search</a>
     </div>
   </div>
 </div>
@@ -50,6 +51,7 @@
   <div data-role="header">
     <a href="#" data-rel="back" data-icon="arrow-l">Back</a>
     <h1>Service Manager</h1>
+    <a href="#" id="service-refresh" data-role="button" data-icon="refresh">Refresh</a>
   </div>
   <div data-role="content">
     <ul data-role="listview" data-inset="true">
@@ -57,9 +59,8 @@
   </div>
   <div data-role="footer" data-theme="b" class="ui-bar">
     <a href="#" id="service-blank" data-role="button" data-icon="blank">Blank</a>
-    <a href="#" id="service-unblank" data-role="button" data-icon="unblank">Unblank</a>
-    <a href="#" id="service-refresh" data-role="button" data-icon="refresh">Refresh</a>
-    <a href="#" id="service-previous" data-role="button" data-icon="arrow-l">Previous</a>
+    <a href="#" id="service-unblank" data-role="button" data-icon="unblank">Show</a>
+    <a href="#" id="service-previous" data-role="button" data-icon="arrow-l">Prev</a>
     <a href="#" id="service-next" data-role="button" data-icon="arrow-r" data-iconpos="right">Next</a>
   </div>
 </div>
@@ -67,6 +68,7 @@
   <div data-role="header">
     <a href="#" data-rel="back" data-icon="arrow-l">Back</a>
     <h1>Slide Controller</h1>
+    <a href="#" id="controller-refresh" data-role="button" data-icon="refresh">Refresh</a>
   </div>
   <div data-role="content">
     <ul data-role="listview" data-inset="true">
@@ -74,9 +76,8 @@
   </div>
   <div data-role="footer" data-theme="b" class="ui-bar">
     <a href="#" id="controller-blank" data-role="button" data-icon="blank">Blank</a>
-    <a href="#" id="controller-unblank" data-role="button" data-icon="unblank">Unblank</a>
-    <a href="#" id="controller-refresh" data-role="button" data-icon="refresh">Refresh</a>
-    <a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">Previous</a>
+    <a href="#" id="controller-unblank" data-role="button" data-icon="unblank">Show</a>
+    <a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">Prev</a>
     <a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">Next</a>
   </div>
 </div>
@@ -93,5 +94,23 @@
     <a href="#" id="alert-submit" data-role="button">Show Alert</a>
   </div>
 </div>
+<div data-role="page" id="search">
+  <div data-role="header">
+    <a href="#" data-rel="back" data-icon="arrow-l">Back</a>
+    <h1>Search</h1>
+  </div>
+  <div data-role="content">
+    <div data-role="fieldcontain">
+      <label for="search-plugin">Search:</label>
+	  <select name="search-plugin" id="search-plugin" data-native-menu="false"></select>
+    </div>
+    <div data-role="fieldcontain">
+      <label for="search-text">Text:</label>
+      <input type="search" name="search-text" id="search-text" value="" />
+    </div>
+    <a href="#" id="search-submit" data-role="button">Search</a>
+    <ul data-role="listview" data-inset="true">
+  </div>
+</div>
 </body>
 </html>

=== modified file 'openlp/plugins/remotes/html/openlp.js'
--- openlp/plugins/remotes/html/openlp.js	2011-05-08 19:26:32 +0000
+++ openlp/plugins/remotes/html/openlp.js	2011-05-15 22:21:01 +0000
@@ -39,6 +39,19 @@
     }
     return $(targ);
   },
+  searchPlugins: function (event) {
+    $.getJSON(
+      "/api/plugin/search",
+      function (data, status) {
+        var select = $("#search-plugin");
+        select.html("");
+        $.each(data.results.items, function (idx, value) {
+          select.append("<option value='" + value + "'>" + value + "</option>");
+        });
+        select.selectmenu("refresh");
+      }
+    );
+  },
   loadService: function (event) {
     $.getJSON(
       "/api/service/list",
@@ -189,7 +202,43 @@
       }
     );
     return false;
+  },
+  search: function (event) {
+    var text = JSON.stringify({"request": {"text": $("#search-text").val()}});
+    $.getJSON(
+      "/api/" + $("#search-plugin").val() + "/search",
+      {"data": text},
+      function (data, status) {
+        var ul = $("#search > div[data-role=content] > ul[data-role=listview]");
+        ul.html("");
+        if (data.results.items.length == 0) {
+          var li = $("<li data-icon=\"false\">").text('No results');
+          ul.append(li);
+        } 
+        else {
+            $.each(data.results.items, function (idx, value) {
+              var li = $("<li data-icon=\"false\">").append(
+                $("<a href=\"#\">").attr("value", value[0]).text(value[1]));
+              li.children("a").click(OpenLP.goLive);
+              ul.append(li);
+            });
+        }
+        ul.listview("refresh");
+      }
+    );
+    return false;
+  },
+  goLive: function (event) {
+    var slide = OpenLP.getElement(event);
+    var id = slide.attr("value");
+    var text = JSON.stringify({"request": {"id": id}});
+    $.getJSON(
+      "/api/" + $("#search-plugin").val() + "/live",
+      {"data": text})
+    $.mobile.changePage("slide-controller");
+    return false;
   }
+
 }
 // Service Manager
 $("#service-manager").live("pagebeforeshow", OpenLP.loadService);
@@ -207,7 +256,10 @@
 $("#controller-unblank").live("click", OpenLP.unblankDisplay);
 // Alerts
 $("#alert-submit").live("click", OpenLP.showAlert);
+// Search
+$("#search-submit").live("click", OpenLP.search);
 // Poll the server twice a second to get any updates.
+OpenLP.searchPlugins();
 $.ajaxSetup({ cache: false });
 setInterval("OpenLP.pollServer();", 500);
 OpenLP.pollServer();

=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py	2011-05-11 23:16:53 +0000
+++ openlp/plugins/remotes/lib/httpserver.py	2011-05-15 22:21:01 +0000
@@ -250,7 +250,10 @@
             (r'^/api/controller/(live|preview)/(.*)$', self.controller),
             (r'^/api/service/(.*)$', self.service),
             (r'^/api/display/(hide|show)$', self.display),
-            (r'^/api/alert$', self.alert)
+            (r'^/api/alert$', self.alert),
+            (r'^/api/plugin/(search)$', self.plugin),
+            (r'^/api/(.*)/search$', self.search),
+            (r'^/api/(.*)/live$', self.go_live)
         ]
         QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
             self.ready_read)
@@ -443,6 +446,49 @@
         return HttpResponse(json.dumps({u'results': {u'success': True}}),
             {u'Content-Type': u'application/json'})
 
+    def plugin(self, action):
+        """
+        Return plugin related actions
+
+        ``action`` - The action to perform
+            if 'search' return a list of plugin names which support search
+        """
+        if action == u'search':
+            searches = []
+            for plugin in self.parent.parent.pluginManager.plugins:
+                media_item = plugin.mediaItem
+                if media_item and media_item.hasSearch:
+                    searches.append(plugin.name)
+            return HttpResponse(
+                json.dumps({u'results': {u'items': searches}}),
+                {u'Content-Type': u'application/json'})
+
+    def search(self, type):
+        """
+        Return a list of items that match the search text
+
+        ``type``
+        The plugin name to search in.
+        """
+        text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
+        plugin = self.parent.parent.pluginManager.get_plugin_by_name(type)
+        media_item = plugin.mediaItem
+        if media_item and media_item.hasSearch:
+            results = media_item.search(text)
+            return HttpResponse(
+                json.dumps({u'results': {u'items': results}}),
+                {u'Content-Type': u'application/json'})
+
+    def go_live(self, type):
+        """
+        Go live on an item of type ``type``.
+        """
+        id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
+        plugin = self.parent.parent.pluginManager.get_plugin_by_name(type)
+        media_item = plugin.mediaItem
+        if media_item:
+            media_item.goLive(id)
+
     def send_response(self, response):
         http = u'HTTP/1.1 %s\r\n' % response.code
         for header, value in response.headers.iteritems():

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2011-05-15 12:11:08 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2011-05-15 22:21:01 +0000
@@ -74,6 +74,7 @@
         self.editItem = None
         self.whitespace = re.compile(r'\W+', re.UNICODE)
         self.quickPreviewAllowed = True
+        self.hasSearch = True
 
     def addEndHeaderBar(self):
         self.addToolbarSeparator()
@@ -171,11 +172,7 @@
         search_type = self.searchTextEdit.currentSearchType()
         if search_type == SongSearch.Entire:
             log.debug(u'Entire Song Search')
-            search_results = self.parent.manager.get_all_objects(Song,
-                or_(Song.search_title.like(u'%' + self.whitespace.sub(u' ',
-                search_keywords.lower()) + u'%'),
-                Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'),
-                Song.comments.like(u'%' + search_keywords.lower() + u'%')))
+            search_results = self.searchEntire(search_keywords)
             self.displayResultsSong(search_results)
         elif search_type == SongSearch.Titles:
             log.debug(u'Titles Search')
@@ -201,6 +198,13 @@
             self.displayResultsSong(search_results)
         self.check_search_result()
 
+    def searchEntire(self, search_keywords):
+        return self.parent.manager.get_all_objects(Song,
+            or_(Song.search_title.like(u'%' + self.whitespace.sub(u' ',
+            search_keywords.lower()) + u'%'),
+            Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'),
+            Song.comments.like(u'%' + search_keywords.lower() + u'%')))
+
     def onSongListLoad(self):
         """
         Handle the exit from the edit dialog and trigger remote updates
@@ -217,7 +221,8 @@
         # Push edits to the service manager to update items
         if self.editItem and self.updateServiceOnEdit and \
             not self.remoteTriggered:
-            item = self.buildServiceItem(self.editItem)
+            item_id = _getIdOfItemToGenerate(self.editItem)
+            item = self.buildServiceItem(item_id)
             self.parent.serviceManager.replaceServiceItem(item)
         self.onRemoteEditClear()
         self.onSearchTextButtonClick()
@@ -475,3 +480,13 @@
         """
         return locale.strcoll(unicode(song_1.title.lower()),
              unicode(song_2.title.lower()))
+
+    def search(self, string):
+        """
+        Search for some songs
+        """
+        search_results = self.searchEntire(string)
+        results = []
+        for song in search_results:
+            results.append([song.id, song.title])
+        return results

=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py	2011-05-07 11:06:43 +0000
+++ openlp/plugins/songs/songsplugin.py	2011-05-15 22:21:01 +0000
@@ -268,4 +268,3 @@
         action_list.remove_action(self.songExportItem, UiStrings().Export)
         action_list.remove_action(self.toolsReindexItem, UiStrings().Tools)
         Plugin.finalise(self)
-


Follow ups