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.

bzr-revno: 1126
This commit is contained in:
Tim Bentley 2010-12-01 19:55:05 +00:00
commit bbdae1395c
22 changed files with 330 additions and 105 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -294,4 +294,5 @@ class Manager(object):
"""
if self.is_dirty:
engine = create_engine(self.db_url)
engine.execute("vacuum")
if self.db_url.startswith(u'sqlite'):
engine.execute("vacuum")

View File

@ -320,15 +320,9 @@ class MediaManagerItem(QtGui.QWidget):
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 @@ class MediaManagerItem(QtGui.QWidget):
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 @@ class MediaManagerItem(QtGui.QWidget):
'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 @@ class MediaManagerItem(QtGui.QWidget):
'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 @@ class MediaManagerItem(QtGui.QWidget):
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 @@ class MediaManagerItem(QtGui.QWidget):
'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 @@ class MediaManagerItem(QtGui.QWidget):
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

View File

@ -101,6 +101,7 @@ class ServiceItem(object):
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 @@ class ServiceItem(object):
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 @@ class ServiceItem(object):
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)

View File

@ -146,7 +146,7 @@ class AdvancedTab(SettingsTab):
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(

View File

@ -100,7 +100,7 @@ class MainDisplay(DisplayWidget):
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 @@ class MainDisplay(DisplayWidget):
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 @@ class MainDisplay(DisplayWidget):
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 @@ class MainDisplay(DisplayWidget):
self.setVisible(True)
if self.phononActive:
self.webView.setVisible(True)
self.hide_mode = mode
self.hideMode = mode
def showDisplay(self):
"""
@ -459,9 +459,9 @@ class MainDisplay(DisplayWidget):
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):
"""

View File

@ -789,6 +789,8 @@ class ServiceManager(QtGui.QWidget):
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 @@ class ServiceManager(QtGui.QWidget):
# 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:

View File

@ -331,10 +331,8 @@ class SlideController(QtGui.QWidget):
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 @@ class SlideController(QtGui.QWidget):
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 @@ class SlideController(QtGui.QWidget):
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 @@ class SlideController(QtGui.QWidget):
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

View File

@ -545,7 +545,7 @@ class BibleMediaItem(MediaManagerItem):
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 @@ class BibleMediaItem(MediaManagerItem):
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.

View File

@ -46,7 +46,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
Constructor
"""
QtGui.QDialog.__init__(self, parent)
#self.parent = parent
self.setupUi(self)
# Connecting signals and slots
self.previewButton = QtGui.QPushButton()
@ -124,8 +123,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
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 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
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')

View File

@ -67,7 +67,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
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')

View File

@ -43,6 +43,7 @@ import logging
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 @@ class CustomXMLBuilder(object):
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 @@ class CustomXMLBuilder(object):
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 @@ class CustomXMLBuilder(object):
``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 @@ class CustomXMLBuilder(object):
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 @@ class CustomXMLBuilder(object):
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 @@ class CustomXMLParser(object):
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.
"""

View File

@ -74,9 +74,9 @@ class CustomMediaItem(MediaManagerItem):
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 @@ class CustomMediaItem(MediaManagerItem):
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

View File

@ -154,7 +154,7 @@ class ImageMediaItem(MediaManagerItem):
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 @@ class ImageMediaItem(MediaManagerItem):
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())

View File

@ -116,7 +116,7 @@ class MediaMediaItem(MediaManagerItem):
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:

View File

@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem):
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

View File

@ -636,8 +636,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
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 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
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()

View File

@ -62,6 +62,36 @@ class VerseType(object):
elif verse_type == VerseType.Other:
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):
"""
@ -92,7 +122,6 @@ class VerseType(object):
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

View File

@ -32,7 +32,7 @@ from openlp.core.lib import MediaManagerItem, BaseListWithDnD, Receiver, \
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
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 @@ class SongMediaItem(MediaManagerItem):
]
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 @@ class SongMediaItem(MediaManagerItem):
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))

View File

@ -39,8 +39,11 @@ The basic XML is of the format::
"""
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 @@ class SongXMLBuilder(object):
``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,153 @@ class LyricsXML(object):
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
"""
song_xml_parser = SongXMLParser(song.lyrics)
verse_list = song_xml_parser.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 verse_list:
verse_tag = u'%s%s' % (
verse[0][u'type'][0].lower(), verse[0][u'label'])
element = \
self._add_text_to_element(u'verse', lyrics, None, verse_tag)
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)
if song.verse_order == u'None':
song.verse_order = u''
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)