This commit is contained in:
rimach 2010-12-02 22:58:23 +01:00
commit d7f944b49a
37 changed files with 473 additions and 106 deletions

View File

@ -14,6 +14,8 @@ Contents:
introduction
glossary
dualmonitors
mediamanager
songs
Indices and tables
==================

View File

@ -0,0 +1,26 @@
=============
Media Manager
=============
Once you get your system set up for OpenLP you will be ready to add content to
your setup. This will all happen through the **Media Manager**. The
`Media Manager` contains all the bibles, songs, presentations, media, and
everything else that you will project through OpenLP.
Enabling the Plugins
--------------------
You may need to enable the plugins that came with OpenLP. As you can see below
this is what the `Media Manager` looks like with all the plugins enabled.
.. image:: pics/mediamanager.png
To enable the plugins navigate to :menuselection:`Settings --> Plugins` or
press `F7`. You will then want to click on the plugin to the left that you want
to enable and select **active** from the drop down box to the right.
.. image:: pics/plugins.png
Now you should be ready to add content to OpenLP check out the section of this
guide on the individual plugins.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

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

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

0
documentation/manual/source/pics/vistapersonalize.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

0
documentation/manual/source/pics/winsevendisplay.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,100 @@
=====
Songs
=====
Managing your songs in OpenLP is a relatively simple process. There are also
converters provided to get data from other formats into OpenLP.
Song Importer
=============
If you are using an earlier version of OpenLP or come from another software
package, you may be able to convert your existing database to work in OpenLP
2.0. To access the Song Importer :menuselection:`File --> Import --> Song`.
You will then see the Song Importer window, then click :guilabel:`Next`.
.. image:: pics/songimporter.png
After choosing :guilabel:`Next` you can then select from the various types of
software that OpenLP will convert songs from.
.. image:: pics/songimporterchoices.png
Then click on the file folder icon to choose the file of the song database you
want to import. See the following sections for information on the different
formats that OpenLP will import.
Importing from OpenLP Version 1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Converting from OpenLP Version 1 is a pretty simple process. You will first
need to locate your version 1 database file.
Windows XP::
C:\Documents and Settings\All Users\Application Data\openlp.org\Data\songs.olp
Windows Vista / Windows 7::
C:\ProgramData\openlp.org\Data\songs.olp
After clicking :guilabel:`Next` your conversion should be complete.
.. image:: pics/finishedimport.png
Then press :guilabel:`Finish` and you should now be ready to use your OpenLP
version one songs.
Importing from OpenSong
^^^^^^^^^^^^^^^^^^^^^^^
Converting from OpenSong you will need to locate your songs database. In the
later versions of OpenSong you are asked to define the location of this. The
songs will be located in a folder named :guilabel:`Songs`. This folder should
contain files with all your songs in them without a file extension. (file.xxx).
When you have located this folder you will then need to select the songs from
the folder.
.. image:: pics/selectsongs.png
On most operating systems to select all the songs, first select the first song
in the lest then press shift and select the last song in the list. After this
press :guilabel:`Next` and you should see that your import has been successful.
.. image:: pics/finishedimport.png
Press :guilabel:`Finish` and you will now be ready to use your songs imported
from OpenSong.
Importing from CCLI Song Select
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To import from CCLI Song Select you must be a CCLI Subscriber and also a
subscriber of the Song Select service. For info on that go to:
http://www.ccli.com
The first step for importing from CCLI Song Select is to log into your account.
Then search for your desired song. For this example we will be adding the song
"Amazing Grace".
.. image:: pics/songselectsongsearch.png
For the song you are searching for select `lyrics` This should take you to a
page displaying the lyrics and copyright info for your song.
.. image:: pics/songselectlyrics.png
Next, hover over the :guilabel:`Lyrics` menu from the upper right corner. Then
choose either the .txt or .usr file. You will then be asked to chose a download
location if your browser does not automatically select that for you. Select
this file from the OpenLP import window and then click :guilabel:`Next` You can
also select multiple songs for import at once on most operating systems by
selecting the first item in the list then holding shift select the last item in
the list. When finished you should see that your import has completed.
.. image:: pics/finishedimport.png
Press :guilabel:`Finish` and you will now be ready to use your songs imported
from CCLI SongSelect.

View File

@ -294,4 +294,5 @@ class Manager(object):
"""
if self.is_dirty:
engine = create_engine(self.db_url)
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)
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.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

@ -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

@ -152,6 +152,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def newSong(self):
log.debug(u'New Song')
self.initialise()
self.SongTabWidget.setCurrentIndex(0)
self.TitleEditItem.setText(u'')
self.AlternativeEdit.setText(u'')
@ -170,8 +171,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
# it's a new song to preview is not possible
self.previewButton.setVisible(False)
def loadSong(self, id, preview):
def loadSong(self, id, preview=False):
"""
Loads a song.
``id``
The song id (int).
``preview``
Should be ``True`` if the song is also previewed (boolean).
"""
log.debug(u'Load Song')
self.initialise()
self.SongTabWidget.setCurrentIndex(0)
self.loadAuthors()
self.loadTopics()
@ -594,6 +605,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
"""
Save and Preview button pressed.
The Song is valid so as the plugin to add it to preview to see.
``button``
A button (QPushButton).
"""
log.debug(u'onPreview')
if unicode(button.objectName()) == u'previewButton' and \
@ -631,13 +645,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
database.
``preview``
Should be True if song is also previewed.
Should be ``True`` if the song is also previewed (boolean).
"""
self.song.title = unicode(self.TitleEditItem.text())
self.song.alternate_title = unicode(self.AlternativeEdit.text())
self.song.copyright = unicode(self.CopyrightEditItem.text())
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 +665,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''
@ -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)