← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~trb143/openlp/bugs into lp:openlp

 

Tim Bentley has proposed merging lp:~trb143/openlp/bugs into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)


Remove theme for Images.
Sort out silly ness which requires a restart
Add preview when returning from blank screen.
Fix bug in servicemanager where DnD with nothing present crashes.
Clean up Custom Plugin a bit.

Build OpenLyrics Import / Export for service items so song items can be moved across computermabobs.
-- 
https://code.launchpad.net/~trb143/openlp/bugs/+merge/42136
Your team OpenLP Core is subscribed to branch lp:openlp.
=== added file 'documentation/manual/source/pics/songusage.png'
Binary files documentation/manual/source/pics/songusage.png	1970-01-01 00:00:00 +0000 and documentation/manual/source/pics/songusage.png	2010-11-29 16:16:38 +0000 differ
=== added file 'documentation/manual/source/pics/songusagedelete.png'
Binary files documentation/manual/source/pics/songusagedelete.png	1970-01-01 00:00:00 +0000 and documentation/manual/source/pics/songusagedelete.png	2010-11-29 16:16:38 +0000 differ
=== added file 'documentation/manual/source/pics/songusagereport.png'
Binary files documentation/manual/source/pics/songusagereport.png	1970-01-01 00:00:00 +0000 and documentation/manual/source/pics/songusagereport.png	2010-11-29 16:16:38 +0000 differ
=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py	2010-11-16 06:19:23 +0000
+++ openlp/core/lib/db.py	2010-11-29 16:16:38 +0000
@@ -294,4 +294,5 @@
         """
         if self.is_dirty:
             engine = create_engine(self.db_url)
-            engine.execute("vacuum")
+            if self.db_url.startswith(u'sqlite'):
+                engine.execute("vacuum")

=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2010-11-20 18:14:43 +0000
+++ openlp/core/lib/mediamanageritem.py	2010-11-29 16:16:38 +0000
@@ -320,15 +320,9 @@
                     translate('OpenLP.MediaManagerItem',
                         '&Add to selected Service Item'),
                     self.onAddEditClick))
-        if QtCore.QSettings().value(u'advanced/double click live',
-            QtCore.QVariant(False)).toBool():
-            QtCore.QObject.connect(self.listView,
-                QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
-                self.onLiveClick)
-        else:
-            QtCore.QObject.connect(self.listView,
-                QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
-                self.onPreviewClick)
+        QtCore.QObject.connect(self.listView,
+            QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
+            self.onClickPressed)
 
     def initialise(self):
         """
@@ -426,10 +420,20 @@
         raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to '
             u'be defined by the plugin')
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, serviceItem, item=None, xmlVersion=False):
         raise NotImplementedError(u'MediaManagerItem.generateSlideData needs '
             u'to be defined by the plugin')
 
+    def onClickPressed(self):
+        """
+        Allows the list click action to be determined dynamically
+        """
+        if QtCore.QSettings().value(u'advanced/double click live',
+            QtCore.QVariant(False)).toBool():
+            self.onLiveClick()
+        else:
+            self.onPreviewClick()
+
     def onPreviewClick(self):
         """
         Preview an item by building a service item then adding that service
@@ -442,10 +446,10 @@
                     'You must select one or more items to preview.'))
         else:
             log.debug(self.plugin.name + u' Preview requested')
-            service_item = self.buildServiceItem()
-            if service_item:
-                service_item.from_plugin = True
-                self.parent.previewController.addServiceItem(service_item)
+            serviceItem = self.buildServiceItem()
+            if serviceItem:
+                serviceItem.from_plugin = True
+                self.parent.previewController.addServiceItem(serviceItem)
 
     def onLiveClick(self):
         """
@@ -459,10 +463,10 @@
                     'You must select one or more items to send live.'))
         else:
             log.debug(self.plugin.name + u' Live requested')
-            service_item = self.buildServiceItem()
-            if service_item:
-                service_item.from_plugin = True
-                self.parent.liveController.addServiceItem(service_item)
+            serviceItem = self.buildServiceItem()
+            if serviceItem:
+                serviceItem.from_plugin = True
+                self.parent.liveController.addServiceItem(serviceItem)
 
     def onAddClick(self):
         """
@@ -474,22 +478,22 @@
                 translate('OpenLP.MediaManagerItem',
                     'You must select one or more items.'))
         else:
-            # Is it posssible to process multiple list items to generate multiple
-            # service items?
+            # Is it posssible to process multiple list items to generate
+            # multiple service items?
             if self.singleServiceItem or self.remoteTriggered:
                 log.debug(self.plugin.name + u' Add requested')
-                service_item = self.buildServiceItem()
-                if service_item:
-                    service_item.from_plugin = False
-                    self.parent.serviceManager.addServiceItem(service_item,
+                serviceItem = self.buildServiceItem(None, True)
+                if serviceItem:
+                    serviceItem.from_plugin = False
+                    self.parent.serviceManager.addServiceItem(serviceItem,
                         replace=self.remoteTriggered)
             else:
                 items = self.listView.selectedIndexes()
                 for item in items:
-                    service_item = self.buildServiceItem(item)
-                    if service_item:
-                        service_item.from_plugin = False
-                        self.parent.serviceManager.addServiceItem(service_item)
+                    serviceItem = self.buildServiceItem(item, True)
+                    if serviceItem:
+                        serviceItem.from_plugin = False
+                        self.parent.serviceManager.addServiceItem(serviceItem)
 
     def onAddEditClick(self):
         """
@@ -502,16 +506,16 @@
                     'You must select one or more items'))
         else:
             log.debug(self.plugin.name + u' Add requested')
-            service_item = self.parent.serviceManager.getServiceItem()
-            if not service_item:
+            serviceItem = self.parent.serviceManager.getServiceItem()
+            if not serviceItem:
                  QtGui.QMessageBox.information(self,
                     translate('OpenLP.MediaManagerItem',
                         'No Service Item Selected'),
                     translate('OpenLP.MediaManagerItem',
                         'You must select an existing service item to add to.'))
-            elif self.title.lower() == service_item.name.lower():
-                self.generateSlideData(service_item)
-                self.parent.serviceManager.addServiceItem(service_item,
+            elif self.title.lower() == serviceItem.name.lower():
+                self.generateSlideData(serviceItem)
+                self.parent.serviceManager.addServiceItem(serviceItem,
                     replace=True)
             else:
                 # Turn off the remote edit update message indicator
@@ -521,17 +525,17 @@
                     unicode(translate('OpenLP.MediaManagerItem',
                         'You must select a %s service item.')) % self.title)
 
-    def buildServiceItem(self, item=None):
+    def buildServiceItem(self, item=None, xmlVersion=False):
         """
         Common method for generating a service item
         """
-        service_item = ServiceItem(self.parent)
+        serviceItem = ServiceItem(self.parent)
         if self.serviceItemIconName:
-            service_item.add_icon(self.serviceItemIconName)
+            serviceItem.add_icon(self.serviceItemIconName)
         else:
-            service_item.add_icon(self.parent.icon_path)
-        if self.generateSlideData(service_item, item):
-            return service_item
+            serviceItem.add_icon(self.parent.icon_path)
+        if self.generateSlideData(serviceItem, item, xmlVersion):
+            return serviceItem
         else:
             return None
 

=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2010-11-20 18:14:43 +0000
+++ openlp/core/lib/serviceitem.py	2010-11-29 16:16:38 +0000
@@ -101,6 +101,7 @@
         self.search_string = u''
         self.data_string = u''
         self.edit_id = None
+        self.xml_version = None
         self._new_item()
 
     def _new_item(self):
@@ -252,7 +253,8 @@
             u'from_plugin': self.from_plugin,
             u'capabilities': self.capabilities,
             u'search': self.search_string,
-            u'data': self.data_string
+            u'data': self.data_string,
+            u'xml_version': self.xml_version
         }
         service_data = []
         if self.service_item_type == ServiceItemType.Text:
@@ -294,6 +296,8 @@
         if u'search' in header:
             self.search_string = header[u'search']
             self.data_string = header[u'data']
+        if u'xml_version' in header:
+            self.xml_version = header[u'xml_version']
         if self.service_item_type == ServiceItemType.Text:
             for slide in serviceitem[u'serviceitem'][u'data']:
                 self._raw_frames.append(slide)

=== modified file 'openlp/core/ui/advancedtab.py'
--- openlp/core/ui/advancedtab.py	2010-11-20 16:36:54 +0000
+++ openlp/core/ui/advancedtab.py	2010-11-29 16:16:38 +0000
@@ -146,7 +146,7 @@
         self.mediaPluginCheckBox.setText(translate('OpenLP.AdvancedTab',
             'Remember active media manager tab on startup'))
         self.doubleClickLiveCheckBox.setText(translate('OpenLP.AdvancedTab',
-            'Double-click to send items straight to live (requires restart)'))
+            'Double-click to send items straight to live'))
         self.expandServiceItemCheckBox.setText(translate('OpenLP.AdvancedTab',
             'Expand new service items on creation'))
 #        self.sharedDirGroupBox.setTitle(

=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py	2010-11-20 16:36:54 +0000
+++ openlp/core/ui/maindisplay.py	2010-11-29 16:16:38 +0000
@@ -100,7 +100,7 @@
         self.screens = screens
         self.isLive = live
         self.alertTab = None
-        self.hide_mode = None
+        self.hideMode = None
         self.setWindowTitle(u'OpenLP Display')
         self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
         self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
@@ -381,8 +381,8 @@
         if self.isLive:
             self.setVisible(True)
         # if was hidden keep it hidden
-        if self.hide_mode and self.isLive:
-            self.hideDisplay(self.hide_mode)
+        if self.hideMode and self.isLive:
+            self.hideDisplay(self.hideMode)
         preview = QtGui.QImage(self.screen[u'size'].width(),
             self.screen[u'size'].height(),
             QtGui.QImage.Format_ARGB32_Premultiplied)
@@ -412,8 +412,8 @@
         if serviceItem.foot_text and serviceItem.foot_text:
             self.footer(serviceItem.foot_text)
         # if was hidden keep it hidden
-        if self.hide_mode and self.isLive:
-            self.hideDisplay(self.hide_mode)
+        if self.hideMode and self.isLive:
+            self.hideDisplay(self.hideMode)
 
     def footer(self, text):
         """
@@ -444,7 +444,7 @@
                 self.setVisible(True)
             if self.phononActive:
                 self.webView.setVisible(True)
-        self.hide_mode = mode
+        self.hideMode = mode
 
     def showDisplay(self):
         """
@@ -459,9 +459,9 @@
         if self.phononActive:
             self.webView.setVisible(False)
             self.videoPlay()
+        self.hideMode = None
         # Trigger actions when display is active again
         Receiver.send_message(u'maindisplay_active')
-        self.hide_mode = None
 
 class AudioPlayer(QtCore.QObject):
     """

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2010-11-20 18:14:43 +0000
+++ openlp/core/ui/servicemanager.py	2010-11-29 16:16:38 +0000
@@ -789,6 +789,8 @@
         self.serviceName = name[len(name) - 1]
         self.parent.addRecentFile(filename)
         self.parent.serviceChanged(True, self.serviceName)
+        # Refresh Plugin lists
+        Receiver.send_message(u'plugin_list_refresh')
 
     def validateItem(self, serviceItem):
         """
@@ -1028,6 +1030,9 @@
             # ServiceManager started the drag and drop
             if plugin == u'ServiceManager':
                 startpos, startCount = self.findServiceItem()
+                # If no items selected
+                if startpos == -1:
+                    return
                 if item is None:
                     endpos = len(self.serviceItems)
                 else:

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2010-11-20 18:14:43 +0000
+++ openlp/core/ui/slidecontroller.py	2010-11-29 16:16:38 +0000
@@ -331,10 +331,8 @@
         QtCore.QObject.connect(self.PreviewListWidget,
             QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected)
         if not self.isLive:
-            if QtCore.QSettings().value(u'advanced/double click live',
-                QtCore.QVariant(False)).toBool():
-                QtCore.QObject.connect(self.PreviewListWidget,
-                    QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLive)
+            QtCore.QObject.connect(self.PreviewListWidget,
+                QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLiveClick)
         if isLive:
             QtCore.QObject.connect(Receiver.get_receiver(),
                 QtCore.SIGNAL(u'slidecontroller_live_spin_delay'),
@@ -391,6 +389,8 @@
         if self.isLive:
             QtCore.QObject.connect(self.volumeSlider,
                 QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume)
+            QtCore.QObject.connect(Receiver.get_receiver(),
+                QtCore.SIGNAL(u'maindisplay_active'), self.updatePreview)
 
     def screenSizeChanged(self):
         """
@@ -823,16 +823,15 @@
             row)
 
     def updatePreview(self):
+        log.debug(u'updatePreview %s ' %self.screens.current[u'primary'])
         if not self.screens.current[u'primary']:
             # Grab now, but try again in a couple of seconds if slide change
             # is slow
             QtCore.QTimer.singleShot(0.5, self.grabMainDisplay)
             QtCore.QTimer.singleShot(2.5, self.grabMainDisplay)
         else:
-            label = self.PreviewListWidget.cellWidget(
-                self.PreviewListWidget.currentRow(), 1)
-            if label:
-                self.SlidePreview.setPixmap(label.pixmap())
+            self.SlidePreview.setPixmap(
+                QtGui.QPixmap.fromImage(self.display.preview()))
 
     def grabMainDisplay(self):
         winid = QtGui.QApplication.desktop().winId()
@@ -944,6 +943,14 @@
         Receiver.send_message(u'%s_edit' % self.serviceItem.name.lower(),
             u'P:%s' % self.serviceItem.edit_id)
 
+    def onGoLiveClick(self):
+        """
+        triggered by clicking the Preview slide items
+        """
+        if QtCore.QSettings().value(u'advanced/double click live',
+            QtCore.QVariant(False)).toBool():
+            self.onGoLive()
+
     def onGoLive(self):
         """
         If preview copy slide item to live

=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py	2010-10-21 15:31:22 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -545,7 +545,7 @@
                 self.dual_search_results = self.parent.manager.get_verses(
                     dual_bible, text)
         else:
-            # We are doing a ' Text Search'.
+            # We are doing a 'Text Search'.
             bibles = self.parent.manager.get_bibles()
             self.search_results = self.parent.manager.verse_search(bible, text)
             if dual_bible and self.search_results:
@@ -651,7 +651,7 @@
             obj = obj.toPyObject()
         return unicode(obj)
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         """
         Generates and formats the slides for the service item as well as the
         service item's title.

=== modified file 'openlp/plugins/custom/forms/editcustomform.py'
--- openlp/plugins/custom/forms/editcustomform.py	2010-10-29 16:08:31 +0000
+++ openlp/plugins/custom/forms/editcustomform.py	2010-11-29 16:16:38 +0000
@@ -46,7 +46,6 @@
         Constructor
         """
         QtGui.QDialog.__init__(self, parent)
-        #self.parent = parent
         self.setupUi(self)
         # Connecting signals and slots
         self.previewButton = QtGui.QPushButton()
@@ -124,8 +123,9 @@
                 self.slideListView.addItem(slide[1])
             theme = self.customSlide.theme_name
             id = self.themeComboBox.findText(theme, QtCore.Qt.MatchExactly)
+            # No theme match
             if id == -1:
-                id = 0 # Not Found
+                id = 0
             self.themeComboBox.setCurrentIndex(id)
         else:
             self.themeComboBox.setCurrentIndex(0)
@@ -264,7 +264,7 @@
             self.titleEdit.setFocus()
             return False, translate('CustomPlugin.EditCustomForm',
                 'You need to type in a title.')
-        # We must have one slide.
+        # We must have at least one slide.
         if self.slideListView.count() == 0:
             return False, translate('CustomPlugin.EditCustomForm',
                 'You need to add at least one slide')

=== modified file 'openlp/plugins/custom/forms/editcustomslideform.py'
--- openlp/plugins/custom/forms/editcustomslideform.py	2010-10-21 15:31:22 +0000
+++ openlp/plugins/custom/forms/editcustomslideform.py	2010-11-29 16:16:38 +0000
@@ -50,7 +50,7 @@
     def setText(self, text):
         """
         Set the text for slideTextEdit.
-        
+
         ``text``
             The text (unicode).
         """
@@ -67,7 +67,7 @@
 
     def onSplitButtonPressed(self):
         """
-        Splits a slide in two slides.
+        Adds a slide split at the cursor.
         """
         if self.slideTextEdit.textCursor().columnNumber() != 0:
             self.slideTextEdit.insertPlainText(u'\n')

=== modified file 'openlp/plugins/custom/lib/customxmlhandler.py'
--- openlp/plugins/custom/lib/customxmlhandler.py	2010-09-14 18:18:47 +0000
+++ openlp/plugins/custom/lib/customxmlhandler.py	2010-11-29 16:16:38 +0000
@@ -43,6 +43,7 @@
 
 from xml.dom.minidom import Document
 from xml.etree.ElementTree import ElementTree, XML, dump
+from lxml import etree, objectify
 from xml.parsers.expat import ExpatError
 
 log = logging.getLogger(__name__)
@@ -55,14 +56,14 @@
 
     def __init__(self):
         """
-        Set up the song builder.
+        Set up the custom builder.
         """
         # Create the minidom document
         self.custom_xml = Document()
 
     def new_document(self):
         """
-        Create a new song XML document.
+        Create a new custom XML document.
         """
         # Create the <song> base element
         self.song = self.custom_xml.createElement(u'song')
@@ -72,7 +73,7 @@
     def add_lyrics_to_song(self):
         """
         Set up and add a ``<lyrics>`` tag which contains the lyrics of the
-        song.
+        custom item.
         """
         # Create the main <lyrics> element
         self.lyrics = self.custom_xml.createElement(u'lyrics')
@@ -93,7 +94,6 @@
         ``content``
             The actual text of the verse to be stored.
         """
-        #log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
         verse = self.custom_xml.createElement(u'verse')
         verse.setAttribute(u'type', type)
         verse.setAttribute(u'label', number)
@@ -102,7 +102,7 @@
         cds = self.custom_xml.createCDATASection(content)
         verse.appendChild(cds)
 
-    def dump_xml(self):
+    def _dump_xml(self):
         """
         Debugging aid to dump XML so that we can see what we have.
         """
@@ -110,29 +110,30 @@
 
     def extract_xml(self):
         """
-        Extract our newly created XML song.
+        Extract our newly created XML custom.
         """
         return self.custom_xml.toxml(u'utf-8')
 
 
 class CustomXMLParser(object):
     """
-    A class to read in and parse a song's XML.
+    A class to read in and parse a custom's XML.
     """
     log.info(u'CustomXMLParser Loaded')
 
     def __init__(self, xml):
         """
-        Set up our song XML parser.
+        Set up our custom XML parser.
 
         ``xml``
-            The XML of the song to be parsed.
+            The XML of the custom to be parsed.
         """
         self.custom_xml = None
+        if xml[:5] == u'<?xml':
+            xml = xml[38:]
         try:
-            self.custom_xml = ElementTree(
-                element=XML(unicode(xml).encode('unicode-escape')))
-        except ExpatError:
+            self.custom_xml = objectify.fromstring(xml)
+        except etree.XMLSyntaxError:
             log.exception(u'Invalid xml %s', xml)
 
     def get_verses(self):
@@ -146,11 +147,10 @@
             if element.tag == u'verse':
                 if element.text is None:
                     element.text = u''
-                verse_list.append([element.attrib,
-                    unicode(element.text).decode('unicode-escape')])
+                verse_list.append([element.attrib, unicode(element.text)])
         return verse_list
 
-    def dump_xml(self):
+    def _dump_xml(self):
         """
         Debugging aid to dump XML so that we can see what we have.
         """

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2010-11-20 18:14:43 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -74,9 +74,9 @@
     def initialise(self):
         self.loadCustomListView(self.manager.get_all_objects(
             CustomSlide, order_by_ref=CustomSlide.title))
-        #Called to redisplay the song list screen edith from a search
-        #or from the exit of the Song edit dialog.  If remote editing is active
-        #Trigger it and clean up so it will not update again.
+        # 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.
         if self.remoteTriggered == u'L':
             self.onAddClick()
         if self.remoteTriggered == u'P':
@@ -144,7 +144,7 @@
             for row in row_list:
                 self.listView.takeItem(row)
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         raw_slides = []
         raw_footer = []
         slide = None

=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py	2010-10-23 17:37:10 +0000
+++ openlp/plugins/images/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -154,7 +154,7 @@
             item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
             self.listView.addItem(item_name)
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         items = self.listView.selectedIndexes()
         if items:
             service_item.title = unicode(
@@ -163,6 +163,8 @@
             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
             for item in items:
                 bitem = self.listView.item(item.row())
                 filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())

=== modified file 'openlp/plugins/media/lib/mediaitem.py'
--- openlp/plugins/media/lib/mediaitem.py	2010-11-21 20:45:22 +0000
+++ openlp/plugins/media/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -116,7 +116,7 @@
             self.parent.liveController.display.video(filename, 0, True)
         self.resetButton.setVisible(True)
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         if item is None:
             item = self.listView.currentItem()
             if item is None:

=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py	2010-09-27 18:15:55 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -38,7 +38,7 @@
 class PresentationListView(BaseListWithDnD):
     """
     Class for the list of Presentations
-    
+
     We have to explicitly create separate classes for each plugin
     in order for DnD to the Service manager to work correctly.
     """
@@ -67,7 +67,7 @@
         self.message_listener = MessageListener(self)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'mediaitem_presentation_rebuild'), self.rebuild)
-        
+
     def retranslateUi(self):
         """
         The name of the plugin media displayed in UI
@@ -159,7 +159,7 @@
         if self.DisplayTypeComboBox.count() > 1:
             self.DisplayTypeComboBox.insertItem(0, self.Automatic)
             self.DisplayTypeComboBox.setCurrentIndex(0)
-        if QtCore.QSettings().value(self.settingsSection + u'/override app', 
+        if QtCore.QSettings().value(self.settingsSection + u'/override app',
             QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked:
             self.PresentationWidget.show()
         else:
@@ -238,7 +238,7 @@
             SettingsManager.set_list(self.settingsSection,
                 self.settingsSection, self.getFileList())
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         """
         Load the relevant information for displaying the presentation
         in the slidecontroller. In the case of powerpoints, an image
@@ -277,7 +277,7 @@
     def findControllerByType(self, filename):
         """
         Determine the default application controller to use for the selected
-        file type. This is used if "Automatic" is set as the preferred 
+        file type. This is used if "Automatic" is set as the preferred
         controller. Find the first (alphabetic) enabled controller which
         "supports" the extension. If none found, then look for a controller
         which "alsosupports" it instead.

=== modified file 'openlp/plugins/songs/forms/editsongform.py'
--- openlp/plugins/songs/forms/editsongform.py	2010-11-03 18:18:44 +0000
+++ openlp/plugins/songs/forms/editsongform.py	2010-11-29 16:16:38 +0000
@@ -630,14 +630,17 @@
         Get all the data from the widgets on the form, and then save it to the
         database.
 
-        ``preview`` 
+        ``preview``
             Should be True if song is also previewed.
         """
         self.song.title = unicode(self.TitleEditItem.text())
         self.song.alternate_title = unicode(self.AlternativeEdit.text())
         self.song.copyright = unicode(self.CopyrightEditItem.text())
-        self.song.search_title = self.song.title + u'@' + \
-            self.song.alternate_title
+        if self.song.alternate_title:
+            self.song.search_title = self.song.title + u'@' + \
+                self.song.alternate_title
+        else:
+            self.song.search_title = self.song.title
         self.song.comments = unicode(self.CommentsEdit.toPlainText())
         self.song.verse_order = unicode(self.VerseOrderEdit.text())
         self.song.ccli_number = unicode(self.CCLNumberEdit.text())
@@ -648,6 +651,11 @@
                 Book.name == book_name)
         else:
             self.song.book = None
+        theme_name = unicode(self.ThemeSelectionComboItem.currentText())
+        if theme_name:
+            self.song.theme_name = theme_name
+        else:
+            self.song.theme_name = None
         if self._validate_song():
             self.processLyrics()
             self.processTitle()

=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py	2010-09-14 18:18:47 +0000
+++ openlp/plugins/songs/lib/__init__.py	2010-11-29 16:16:38 +0000
@@ -63,6 +63,36 @@
             return translate('SongsPlugin.VerseType', 'Other')
 
     @staticmethod
+    def expand_string(verse_type):
+        """
+        Return the VerseType for a given string
+
+        ``verse_type``
+            The string to return a VerseType for
+        """
+        verse_type = verse_type.lower()
+        if verse_type == unicode(VerseType.to_string(VerseType.Verse)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Verse')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.Chorus)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Chorus')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.Bridge)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Bridge')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.PreChorus)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'PreChorus')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.Intro)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Intro')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.Ending)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Ending')
+        elif verse_type == \
+            unicode(VerseType.to_string(VerseType.Other)).lower()[0]:
+            return translate('SongsPlugin.VerseType', 'Other')
+
+    @staticmethod
     def from_string(verse_type):
         """
         Return the VerseType for a given string
@@ -92,7 +122,6 @@
             unicode(VerseType.to_string(VerseType.Other)).lower():
             return VerseType.Other
 
-
-from xml import LyricsXML, SongXMLBuilder, SongXMLParser
+from xml import LyricsXML, SongXMLBuilder, SongXMLParser, OpenLyricsParser
 from songstab import SongsTab
 from mediaitem import SongMediaItem

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2010-11-24 17:40:28 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2010-11-29 16:16:38 +0000
@@ -32,7 +32,7 @@
     ItemCapabilities, translate, check_item_selected
 from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
     SongImportForm
-from openlp.plugins.songs.lib import SongXMLParser
+from openlp.plugins.songs.lib import SongXMLParser, OpenLyricsParser
 from openlp.plugins.songs.lib.db import Author, Song
 
 log = logging.getLogger(__name__)
@@ -53,8 +53,8 @@
         self.ListViewWithDnD_class = SongListView
         MediaManagerItem.__init__(self, parent, self, icon)
         self.edit_song_form = EditSongForm(self, self.parent.manager)
+        self.openLyrics = OpenLyricsParser(self.parent.manager)
         self.singleServiceItem = False
-        #self.edit_song_form = EditSongForm(self.parent.manager, self)
         self.song_maintenance_form = SongMaintenanceForm(
             self.parent.manager, self)
         # Holds information about whether the edit is remotly triggered and
@@ -114,6 +114,8 @@
         self.SearchButtonLayout.addWidget(self.ClearTextButton)
         self.pageLayout.addLayout(self.SearchButtonLayout)
         # Signals and slots
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'plugin_list_refresh'), self.onSearchTextButtonClick)
         QtCore.QObject.connect(self.SearchTextEdit,
             QtCore.SIGNAL(u'returnPressed()'), self.onSearchTextButtonClick)
         QtCore.QObject.connect(self.SearchTextButton,
@@ -141,7 +143,7 @@
         self.updateServiceOnEdit = QtCore.QSettings().value(
             self.settingsSection + u'/update service on edit',
             QtCore.QVariant(u'False')).toBool()
-        self.AddSongFromServide = QtCore.QSettings().value(
+        self.addSongFromService = QtCore.QSettings().value(
             self.settingsSection + u'/add song from service',
             QtCore.QVariant(u'True')).toBool()
 
@@ -328,7 +330,7 @@
                 self.parent.manager.delete_object(Song, item_id)
             self.onSearchTextButtonClick()
 
-    def generateSlideData(self, service_item, item=None):
+    def generateSlideData(self, service_item, item=None, xmlVersion=False):
         log.debug(u'generateSlideData (%s:%s)' % (service_item, item))
         raw_footer = []
         author_list = u''
@@ -355,7 +357,7 @@
         if song.lyrics.startswith(u'<?xml version='):
             songXML = SongXMLParser(song.lyrics)
             verseList = songXML.get_verses()
-            #no verse list or only 1 space (in error)
+            # no verse list or only 1 space (in error)
             if not song.verse_order or not song.verse_order.strip():
                 for verse in verseList:
                     verseTag = u'%s:%s' % (
@@ -397,6 +399,7 @@
         ]
         service_item.data_string = {u'title':song.search_title,
             u'authors':author_list}
+        service_item.xml_version = self.openLyrics.song_to_xml(song)
         return True
 
     def serviceLoad(self, item):
@@ -406,21 +409,31 @@
         log.debug(u'serviceLoad')
         if item.data_string:
             search_results = self.parent.manager.get_all_objects(Song,
-                Song.search_title.like(u'%' +
-                    item.data_string[u'title'].split(u'@')[0] + u'%'),
+                Song.search_title ==
+                    item.data_string[u'title'].split(u'@')[0].lower() ,
                 Song.search_title.asc())
             author_list = item.data_string[u'authors'].split(u', ')
             editId = 0
-            uuid = 0
+            uuid = item._uuid
             if search_results:
                 for song in search_results:
                     count = 0
                     for author in song.authors:
                         if author.display_name in author_list:
                             count += 1
+                    # All Authors the same
                     if count == len(author_list):
                         editId = song.id
-                        uuid = item._uuid
+                    else:
+                        # Authors different
+                        if self.addSongFromService:
+                            editId = self.openLyrics. \
+                                xml_to_song(item.xml_version)
+            else:
+                # Title does not match
+                if self.addSongFromService:
+                    editId = self.openLyrics.xml_to_song(item.xml_version)
+            # Update service with correct song id
             if editId != 0:
                 Receiver.send_message(u'service_item_update',
                     u'%s:%s' %(editId, uuid))

=== modified file 'openlp/plugins/songs/lib/xml.py'
--- openlp/plugins/songs/lib/xml.py	2010-09-14 18:18:47 +0000
+++ openlp/plugins/songs/lib/xml.py	2010-11-29 16:16:38 +0000
@@ -39,8 +39,11 @@
 """
 
 import logging
+import re
 
 from lxml import etree, objectify
+from openlp.plugins.songs.lib import VerseType
+from openlp.plugins.songs.lib.db import Author, Song
 
 log = logging.getLogger(__name__)
 
@@ -77,7 +80,6 @@
         ``content``
             The actual text of the verse to be stored.
         """
-        # log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
         verse = etree.Element(u'verse', type = unicode(type),
             label = unicode(number))
         verse.text = etree.CDATA(content)
@@ -239,3 +241,150 @@
         song_output = u'<?xml version="1.0" encoding="UTF-8"?>' + \
             u'<song version="1.0">%s</song>' % lyrics_output
         return song_output
+
+
+class OpenLyricsParser(object):
+    """
+    This class represents the converter for Song to/from OpenLyrics XML.
+    """
+    def __init__(self, manager):
+        self.manager = manager
+
+    def song_to_xml(self, song):
+        """
+        Convert the song to OpenLyrics Format
+        """
+        songXML = SongXMLParser(song.lyrics)
+        verseList = songXML.get_verses()
+        song_xml = objectify.fromstring(
+            u'<song version="0.7" createdIn="OpenLP 2.0"/>')
+        properties = etree.SubElement(song_xml, u'properties')
+        titles = etree.SubElement(properties, u'titles')
+        self._add_text_to_element(u'title', titles, song.title)
+        if song.alternate_title:
+            self._add_text_to_element(u'title', titles, song.alternate_title)
+        if song.theme_name:
+            themes = etree.SubElement(properties, u'themes')
+            self._add_text_to_element(u'theme', themes, song.theme_name)
+        self._add_text_to_element(u'copyright', properties, song.copyright)
+        self._add_text_to_element(u'verseOrder', properties, song.verse_order)
+        if song.ccli_number:
+            self._add_text_to_element(u'ccliNo', properties, song.ccli_number)
+        authors = etree.SubElement(properties, u'authors')
+        for author in song.authors:
+            self._add_text_to_element(u'author', authors, author.display_name)
+        lyrics = etree.SubElement(song_xml, u'lyrics')
+        for verse in verseList:
+            verseTag = u'%s%s' % (
+                verse[0][u'type'][0].lower(), verse[0][u'label'])
+            element = self._add_text_to_element(u'verse', lyrics, None, verseTag)
+            element = self._add_text_to_element(u'lines', element)
+            for line in unicode(verse[1]).split(u'\n'):
+                self._add_text_to_element(u'line', element, line)
+        return self._extract_xml(song_xml)
+
+    def xml_to_song(self, xml):
+        """
+        Create a Song from OpenLyrics format xml
+        """
+        # No xml get out of here
+        if not xml:
+            return 0
+        song = Song()
+        if xml[:5] == u'<?xml':
+            xml = xml[38:]
+        song_xml = objectify.fromstring(xml)
+        properties = song_xml.properties
+        song.copyright = unicode(properties.copyright.text)
+        song.verse_order = unicode(properties.verseOrder.text)
+        song.topics = []
+        song.book = None
+        theme_name = None
+        try:
+            song.ccli_number = unicode(properties.ccliNo.text)
+        except:
+            song.ccli_number = u''
+        try:
+            theme_name = unicode(properties.themes.theme)
+        except:
+            pass
+        if theme_name:
+            song.theme_name = theme_name
+        else:
+            song.theme_name = u''
+        # Process Titles
+        for title in properties.titles.title:
+            if not song.title:
+                song.title = unicode(title.text)
+                song.search_title = unicode(song.title)
+                song.alternate_title = u''
+            else:
+                song.alternate_title = unicode(title.text)
+                song.search_title += u'@' + song.alternate_title
+        song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
+            unicode(song.search_title)).lower()
+        # Process Lyrics
+        sxml = SongXMLBuilder()
+        search_text = u''
+        for lyrics in song_xml.lyrics:
+            for verse in song_xml.lyrics.verse:
+                text = u''
+                for line in verse.lines.line:
+                    line = unicode(line)
+                    if not text:
+                        text = line
+                    else:
+                        text += u'\n' + line
+                type = VerseType.expand_string(verse.attrib[u'name'][0])
+                sxml.add_verse_to_lyrics(type, verse.attrib[u'name'][1], text)
+                search_text = search_text + text
+        song.search_lyrics = search_text.lower()
+        song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
+        song.comments = u''
+        song.song_number = u''
+        # Process Authors
+        for author in properties.authors.author:
+            self._process_author(author.text, song)
+        self.manager.save_object(song)
+        return song.id
+
+    def _add_text_to_element(self, tag, parent, text=None, label=None):
+        if label:
+            element = etree.Element(tag, name = unicode(label))
+        else:
+            element = etree.Element(tag)
+        if text:
+            element.text = unicode(text)
+        parent.append(element)
+        return element
+
+    def _dump_xml(self, xml):
+        """
+        Debugging aid to dump XML so that we can see what we have.
+        """
+        return etree.tostring(xml, encoding=u'UTF-8',
+            xml_declaration=True, pretty_print=True)
+
+    def _extract_xml(self, xml):
+        """
+        Extract our newly created XML song.
+        """
+        return etree.tostring(xml, encoding=u'UTF-8',
+            xml_declaration=True)
+
+    def _process_author(self, name, song):
+        """
+        Find or create an Author from display_name.
+        """
+        name = unicode(name)
+        author = self.manager.get_object_filtered(Author,
+            Author.display_name == name)
+        if author:
+            # should only be one! so take the first
+            song.authors.append(author)
+        else:
+            # Need a new author
+            new_author = Author.populate(first_name=name.rsplit(u' ', 1)[0],
+                        last_name=name.rsplit(u' ', 1)[1], display_name=name)
+            self.manager.save_object(new_author)
+            song.authors.append(new_author)