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

@ -50,7 +50,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
def setText(self, text):
"""
Set the text for slideTextEdit.
``text``
The text (unicode).
"""
@ -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

@ -38,7 +38,7 @@ log = logging.getLogger(__name__)
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 @@ class PresentationMediaItem(MediaManagerItem):
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 @@ class PresentationMediaItem(MediaManagerItem):
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 @@ 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
@ -277,7 +277,7 @@ class PresentationMediaItem(MediaManagerItem):
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.

View File

@ -630,14 +630,17 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
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 @@ 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)