Songs can now have attached audio files, which are stored in services, and imported if the song needs to be imported.

bzr-revno: 1738
Fixes: https://launchpad.net/bugs/739770
This commit is contained in:
Raoul Snyman 2011-09-04 22:00:10 +02:00
commit 3a87f3dff1
26 changed files with 696 additions and 163 deletions

View File

@ -27,3 +27,9 @@
"""
The :mod:`openlp` module contains all the project produced OpenLP functionality
"""
import core
import plugins
__all__ = [u'core', u'plugins']

View File

@ -25,7 +25,12 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
__all__ = ('OpenLP', 'main')
"""
The :mod:`core` module provides all core application functions
All the core functions of the OpenLP application including the GUI, settings,
logging and a plugin framework are contained within the openlp.core module.
"""
import os
import sys
@ -46,16 +51,11 @@ from openlp.core.ui import SplashScreen, ScreenList
from openlp.core.utils import AppLocation, LanguageManager, VersionThread, \
get_application_version, DelayStartThread
__all__ = [u'OpenLP', u'main']
log = logging.getLogger()
"""
The :mod:`core` module provides all core application functions
All the core functions of the OpenLP application including the GUI, settings,
logging and a plugin framework are contained within the openlp.core module.
"""
application_stylesheet = u"""
QMainWindow::separator
{

View File

@ -36,6 +36,13 @@ from PyQt4 import QtCore, QtGui
log = logging.getLogger(__name__)
class MediaType(object):
"""
An enumeration class for types of media.
"""
Audio = 1
Video = 2
def translate(context, text, comment=None,
encoding=QtCore.QCoreApplication.CodecForTr, n=-1,
translate=QtCore.QCoreApplication.translate):
@ -241,9 +248,7 @@ from settingsmanager import SettingsManager
from plugin import PluginStatus, StringContent, Plugin
from pluginmanager import PluginManager
from settingstab import SettingsTab
from serviceitem import ServiceItem
from serviceitem import ServiceItemType
from serviceitem import ItemCapabilities
from serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
from htmlbuilder import build_html, build_lyrics_format_css, \
build_lyrics_outline_css
from toolbar import OpenLPToolbar

View File

@ -82,7 +82,7 @@ def upgrade_db(url, upgrade):
load_changes = True
try:
tables = upgrade.upgrade_setup(metadata)
except SQLAlchemyError, DBAPIError:
except (SQLAlchemyError, DBAPIError):
load_changes = False
metadata_table = Table(u'metadata', metadata,
Column(u'key', types.Unicode(64), primary_key=True),
@ -106,7 +106,7 @@ def upgrade_db(url, upgrade):
getattr(upgrade, u'upgrade_%d' % version) \
(session, metadata, tables)
version_meta.value = unicode(version)
except SQLAlchemyError, DBAPIError:
except (SQLAlchemyError, DBAPIError):
log.exception(u'Could not run database upgrade script '
'"upgrade_%s", upgrade process has been halted.', version)
break
@ -213,7 +213,8 @@ class Manager(object):
return
try:
self.session = init_schema(self.db_url)
except:
except (SQLAlchemyError, DBAPIError):
log.exception(u'Error loading database: %s', self.db_url)
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'OpenLP cannot load your '

View File

@ -111,7 +111,7 @@ class MediaManagerItem(QtGui.QWidget):
self.requiredIcons()
self.setupUi()
self.retranslateUi()
self.auto_select_id = -1
self.autoSelectId = -1
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'%s_service_load' % self.plugin.name),
self.serviceLoad)
@ -506,7 +506,7 @@ class MediaManagerItem(QtGui.QWidget):
if QtCore.QSettings().value(u'advanced/single click preview',
QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \
and self.listView.selectedIndexes() \
and self.auto_select_id == -1:
and self.autoSelectId == -1:
self.onPreviewClick(True)
def onPreviewClick(self, keepFocus=False):
@ -626,7 +626,7 @@ class MediaManagerItem(QtGui.QWidget):
"""
pass
def check_search_result(self):
def checkSearchResult(self):
"""
Checks if the listView is empty and adds a "No Search Results" item.
"""
@ -662,15 +662,15 @@ class MediaManagerItem(QtGui.QWidget):
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
return item_id
def save_auto_select_id(self):
def saveAutoSelectId(self):
"""
Sorts out, what item to select after loading a list.
"""
# The item to select has not been set.
if self.auto_select_id == -1:
if self.autoSelectId == -1:
item = self.listView.currentItem()
if item:
self.auto_select_id = item.data(QtCore.Qt.UserRole).toInt()[0]
self.autoSelectId = item.data(QtCore.Qt.UserRole).toInt()[0]
def search(self, string):
"""

View File

@ -368,3 +368,4 @@ class Plugin(QtCore.QObject):
after this has been set.
"""
self.textStrings[name] = {u'title': title, u'tooltip': tooltip}

View File

@ -222,14 +222,14 @@ class Renderer(object):
if item.is_capable(ItemCapabilities.NoLineBreaks):
line_end = u' '
# Bibles
if item.is_capable(ItemCapabilities.AllowsWordSplit):
if item.is_capable(ItemCapabilities.CanWordSplit):
pages = self._paginate_slide_words(text.split(u'\n'), line_end)
else:
# Clean up line endings.
lines = self._lines_split(text)
pages = self._paginate_slide(lines, line_end)
# Songs and Custom
if item.is_capable(ItemCapabilities.AllowsVirtualSplit) and \
if item.is_capable(ItemCapabilities.CanSoftBreak) and \
len(pages) > 1 and u'[---]' in text:
pages = []
while True:

View File

@ -52,20 +52,21 @@ class ItemCapabilities(object):
"""
Provides an enumeration of a serviceitem's capabilities
"""
AllowsPreview = 1
AllowsEdit = 2
AllowsMaintain = 3
CanPreview = 1
CanEdit = 2
CanMaintain = 3
RequiresMedia = 4
AllowsLoop = 5
AllowsAdditions = 6
CanLoop = 5
CanAppend = 6
NoLineBreaks = 7
OnLoadUpdate = 8
AddIfNewItem = 9
ProvidesOwnDisplay = 10
AllowsDetailedTitleDisplay = 11
AllowsVariableStartTime = 12
AllowsVirtualSplit = 13
AllowsWordSplit = 14
HasDetailedTitleDisplay = 11
HasVariableStartTime = 12
CanSoftBreak = 13
CanWordSplit = 14
HasBackgroundAudio = 15
class ServiceItem(object):
@ -116,6 +117,7 @@ class ServiceItem(object):
self.media_length = 0
self.from_service = False
self.image_border = u'#000000'
self.background_audio = []
self._new_item()
def _new_item(self):
@ -159,7 +161,7 @@ class ServiceItem(object):
"""
The render method is what generates the frames for the screen and
obtains the display information from the renderemanager.
At this point all the slides are build for the given
At this point all the slides are built for the given
display size.
"""
log.debug(u'Render called')
@ -272,7 +274,8 @@ class ServiceItem(object):
u'xml_version': self.xml_version,
u'start_time': self.start_time,
u'end_time': self.end_time,
u'media_length': self.media_length
u'media_length': self.media_length,
u'background_audio': self.background_audio
}
service_data = []
if self.service_item_type == ServiceItemType.Text:
@ -320,6 +323,8 @@ class ServiceItem(object):
self.end_time = header[u'end_time']
if u'media_length' in header:
self.media_length = header[u'media_length']
if u'background_audio' in header:
self.background_audio = header[u'background_audio']
if self.service_item_type == ServiceItemType.Text:
for slide in serviceitem[u'serviceitem'][u'data']:
self._raw_frames.append(slide)
@ -341,7 +346,7 @@ class ServiceItem(object):
if self.is_text():
return self.title
else:
if ItemCapabilities.AllowsDetailedTitleDisplay in self.capabilities:
if ItemCapabilities.HasDetailedTitleDisplay in self.capabilities:
return self._raw_frames[0][u'title']
elif len(self._raw_frames) > 1:
return self.title
@ -359,6 +364,8 @@ class ServiceItem(object):
"""
self._uuid = other._uuid
self.notes = other.notes
if self.is_capable(ItemCapabilities.HasBackgroundAudio):
log.debug(self.background_audio)
def __eq__(self, other):
"""

View File

@ -62,6 +62,10 @@ class MainDisplay(QtGui.QGraphicsView):
self.override = {}
self.retranslateUi()
self.mediaObject = None
if live:
self.audioPlayer = AudioPlayer(self)
else:
self.audioPlayer = None
self.firstTime = True
self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool |
@ -587,61 +591,76 @@ class AudioPlayer(QtCore.QObject):
"""
log.debug(u'AudioPlayer Initialisation started')
QtCore.QObject.__init__(self, parent)
self.message = None
self.currentIndex = -1
self.playlist = []
self.mediaObject = Phonon.MediaObject()
self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory)
Phonon.createPath(self.mediaObject, self.audioObject)
QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'aboutToFinish()'), self.onAboutToFinish)
def setup(self):
"""
Sets up the Audio Player for use
"""
log.debug(u'AudioPlayer Setup')
def close(self):
def __del__(self):
"""
Shutting down so clean up connections
"""
self.onMediaStop()
self.stop()
for path in self.mediaObject.outputPaths():
path.disconnect()
QtCore.QObject.__del__(self)
def onMediaQueue(self, message):
def onAboutToFinish(self):
"""
Set up a video to play from the serviceitem.
Just before the audio player finishes the current track, queue the next
item in the playlist, if there is one.
"""
log.debug(u'AudioPlayer Queue new media message %s' % message)
mfile = os.path.join(message[0].get_frame_path(),
message[0].get_frame_title())
self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile))
self.onMediaPlay()
self.currentIndex += 1
if len(self.playlist) > self.currentIndex:
self.mediaObject.enqueue(self.playlist[self.currentIndex])
def onMediaPlay(self):
def connectVolumeSlider(self, slider):
slider.setAudioOutput(self.audioObject)
def reset(self):
"""
We want to play the play so start it
Reset the audio player, clearing the playlist and the queue.
"""
log.debug(u'AudioPlayer _play called')
self.currentIndex = -1
self.playlist = []
self.stop()
self.mediaObject.clear()
def play(self):
"""
We want to play the file so start it
"""
log.debug(u'AudioPlayer.play() called')
if self.currentIndex == -1:
self.onAboutToFinish()
self.mediaObject.play()
def onMediaPause(self):
def pause(self):
"""
Pause the Audio
"""
log.debug(u'AudioPlayer Media paused by user')
log.debug(u'AudioPlayer.pause() called')
self.mediaObject.pause()
def onMediaStop(self):
def stop(self):
"""
Stop the Audio and clean up
"""
log.debug(u'AudioPlayer Media stopped by user')
self.message = None
log.debug(u'AudioPlayer.stop() called')
self.mediaObject.stop()
self.onMediaFinish()
def onMediaFinish(self):
def addToPlaylist(self, filenames):
"""
Clean up the Object queue
Add another file to the playlist.
``filename``
The file to add to the playlist.
"""
log.debug(u'AudioPlayer Reached end of media playlist')
self.mediaObject.clearQueue()
if not isinstance(filenames, list):
filenames = [filenames]
for filename in filenames:
self.playlist.append(Phonon.MediaSource(filename))

View File

@ -28,6 +28,7 @@ import cgi
import cPickle
import logging
import os
import shutil
import zipfile
log = logging.getLogger(__name__)
@ -471,23 +472,34 @@ class ServiceManager(QtGui.QWidget):
if not self.fileName():
return self.saveFileAs()
path_file_name = unicode(self.fileName())
(path, file_name) = os.path.split(path_file_name)
(basename, extension) = os.path.splitext(file_name)
service_file_name = basename + '.osd'
path, file_name = os.path.split(path_file_name)
basename, extension = os.path.splitext(file_name)
service_file_name = '%s.osd' % basename
log.debug(u'ServiceManager.saveFile - %s' % path_file_name)
SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection,
path)
service = []
write_list = []
audio_files = []
total_size = 0
Receiver.send_message(u'cursor_busy')
# Number of items + 1 to zip it
self.mainwindow.displayProgressBar(len(self.serviceItems) + 1)
for item in self.serviceItems:
self.mainwindow.incrementProgressBar()
service.append({u'serviceitem':
item[u'service_item'].get_service_repr()})
service_item = item[u'service_item'].get_service_repr()
# Get all the audio files, and ready them for embedding in the
# service file.
if len(service_item[u'header'][u'background_audio']) > 0:
for i, filename in \
enumerate(service_item[u'header'][u'background_audio']):
new_file = os.path.join(u'audio', item[u'service_item']._uuid,
os.path.split(filename)[1])
audio_files.append((filename, new_file))
service_item[u'header'][u'background_audio'][i] = new_file
# Add the service item to the service.
service.append({u'serviceitem': service_item})
if not item[u'service_item'].uses_file():
continue
skipMissing = False
@ -541,6 +553,8 @@ class ServiceManager(QtGui.QWidget):
# Finally add all the listed media files.
for path_from in write_list:
zip.write(path_from, path_from.encode(u'utf-8'))
for path_from, path_to in audio_files:
zip.write(path_from, path_to.encode(u'utf-8'))
except IOError:
log.exception(u'Failed to save service to disk')
success = False
@ -595,11 +609,12 @@ class ServiceManager(QtGui.QWidget):
'The content encoding is not UTF-8.'))
continue
osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile))
filename_only = os.path.split(osfile)[1]
zipinfo.filename = filename_only
if not osfile.startswith(u'audio'):
osfile = os.path.split(osfile)[1]
zipinfo.filename = osfile
zip.extract(zipinfo, self.servicePath)
if filename_only.endswith(u'osd'):
p_file = os.path.join(self.servicePath, filename_only)
if osfile.endswith(u'osd'):
p_file = os.path.join(self.servicePath, osfile)
if 'p_file' in locals():
Receiver.send_message(u'cursor_busy')
fileTo = open(p_file, u'r')
@ -630,10 +645,10 @@ class ServiceManager(QtGui.QWidget):
'File is not a valid service.'))
log.exception(u'File contains no service data')
except (IOError, NameError, zipfile.BadZipfile):
log.exception(u'Problem loading service file %s' % fileName)
critical_error_message_box(
message=translate('OpenLP.ServiceManager',
'File could not be opened because it is corrupt.'))
log.exception(u'Problem loading service file %s' % fileName)
except zipfile.BadZipfile:
if os.path.getsize(fileName) == 0:
log.exception(u'Service file is zero sized: %s' % fileName)
@ -682,16 +697,16 @@ class ServiceManager(QtGui.QWidget):
self.maintainAction.setVisible(False)
self.notesAction.setVisible(False)
self.timeAction.setVisible(False)
if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit)\
if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit)\
and serviceItem[u'service_item'].edit_id:
self.editAction.setVisible(True)
if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsMaintain):
.is_capable(ItemCapabilities.CanMaintain):
self.maintainAction.setVisible(True)
if item.parent() is None:
self.notesAction.setVisible(True)
if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsVariableStartTime):
.is_capable(ItemCapabilities.HasVariableStartTime):
self.timeAction.setVisible(True)
self.themeMenu.menuAction().setVisible(False)
# Set up the theme menu.
@ -962,7 +977,7 @@ class ServiceManager(QtGui.QWidget):
(unicode(translate('OpenLP.ServiceManager', 'Notes')),
cgi.escape(unicode(serviceitem.notes))))
if item[u'service_item'] \
.is_capable(ItemCapabilities.AllowsVariableStartTime):
.is_capable(ItemCapabilities.HasVariableStartTime):
tips.append(item[u'service_item'].get_media_time())
treewidgetitem.setToolTip(0, u'<br>'.join(tips))
treewidgetitem.setData(0, QtCore.Qt.UserRole,
@ -998,6 +1013,8 @@ class ServiceManager(QtGui.QWidget):
for file in os.listdir(self.servicePath):
file_path = os.path.join(self.servicePath, file)
delete_file(file_path)
if os.path.exists(os.path.join(self.servicePath, u'audio')):
shutil.rmtree(os.path.join(self.servicePath, u'audio'), False)
def onThemeComboBoxSelected(self, currentIndex):
"""
@ -1196,7 +1213,7 @@ class ServiceManager(QtGui.QWidget):
item += 1
if self.serviceItems and item < len(self.serviceItems) and \
self.serviceItems[item][u'service_item'].is_capable(
ItemCapabilities.AllowsPreview):
ItemCapabilities.CanPreview):
self.mainwindow.previewController.addServiceManagerItem(
self.serviceItems[item][u'service_item'], 0)
self.mainwindow.liveController.previewListWidget.setFocus()
@ -1214,7 +1231,7 @@ class ServiceManager(QtGui.QWidget):
"""
item = self.findServiceItem()[0]
if self.serviceItems[item][u'service_item']\
.is_capable(ItemCapabilities.AllowsEdit):
.is_capable(ItemCapabilities.CanEdit):
Receiver.send_message(u'%s_edit' %
self.serviceItems[item][u'service_item'].name.lower(),
u'L:%s' % self.serviceItems[item][u'service_item'].edit_id)
@ -1297,7 +1314,7 @@ class ServiceManager(QtGui.QWidget):
serviceItem = self.serviceItems[pos]
if (plugin == serviceItem[u'service_item'].name and
serviceItem[u'service_item'].is_capable(
ItemCapabilities.AllowsAdditions)):
ItemCapabilities.CanAppend)):
action = self.dndMenu.exec_(QtGui.QCursor.pos())
# New action required
if action == self.newAction:

View File

@ -256,6 +256,12 @@ class SlideController(QtGui.QWidget):
self.songMenu.setMenu(QtGui.QMenu(
translate('OpenLP.SlideController', 'Go To'), self.toolbar))
self.toolbar.makeWidgetsInvisible([u'Song Menu'])
# Stuff for items with background audio.
self.audioPauseItem = self.toolbar.addToolbarButton(
u'Pause Audio', u':/slides/media_playback_pause.png',
translate('OpenLP.SlideController', 'Pause audio.'),
self.onAudioPauseClicked, True)
self.audioPauseItem.setVisible(False)
# Build the volumeSlider.
self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.volumeSlider.setTickInterval(1)
@ -512,13 +518,13 @@ class SlideController(QtGui.QWidget):
self.playSlidesOnce.setChecked(False)
self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setChecked(False)
self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
if item.is_text():
if QtCore.QSettings().value(
self.parent().songsSettingsSection + u'/display songbar',
QtCore.QVariant(True)).toBool() and len(self.slideList) > 0:
self.toolbar.makeWidgetsVisible([u'Song Menu'])
if item.is_capable(ItemCapabilities.AllowsLoop) and \
if item.is_capable(ItemCapabilities.CanLoop) and \
len(item.get_frames()) > 1:
self.toolbar.makeWidgetsVisible(self.loopList)
if item.is_media():
@ -538,7 +544,7 @@ class SlideController(QtGui.QWidget):
self.toolbar.hide()
self.mediabar.setVisible(False)
self.toolbar.makeWidgetsInvisible(self.songEditList)
if item.is_capable(ItemCapabilities.AllowsEdit) and item.from_plugin:
if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin:
self.toolbar.makeWidgetsVisible(self.songEditList)
elif item.is_media():
self.toolbar.setVisible(False)
@ -576,7 +582,7 @@ class SlideController(QtGui.QWidget):
"""
Replacement item following a remote edit
"""
if item.__eq__(self.serviceItem):
if item == self.serviceItem:
self._processItem(item, self.previewListWidget.currentRow())
def addServiceManagerItem(self, item, slideno):
@ -586,15 +592,17 @@ class SlideController(QtGui.QWidget):
Called by ServiceManager
"""
log.debug(u'addServiceManagerItem live = %s' % self.isLive)
# If no valid slide number is specified we take the first one.
# If no valid slide number is specified we take the first one, but we
# remember the initial value to see if we should reload the song or not
slidenum = slideno
if slideno == -1:
slideno = 0
# If service item is the same as the current on only change slide
if item.__eq__(self.serviceItem):
self.__checkUpdateSelectedSlide(slideno)
slidenum = 0
# If service item is the same as the current one, only change slide
if slideno >= 0 and item == self.serviceItem:
self.__checkUpdateSelectedSlide(slidenum)
self.slideSelected()
return
self._processItem(item, slideno)
else:
self._processItem(item, slidenum)
def _processItem(self, serviceItem, slideno):
"""
@ -618,6 +626,15 @@ class SlideController(QtGui.QWidget):
self.previewListWidget.setColumnWidth(0, width)
if self.isLive:
self.songMenu.menu().clear()
self.display.audioPlayer.reset()
self.setAudioItemsVisibility(False)
self.audioPauseItem.setChecked(False)
if self.serviceItem.is_capable(ItemCapabilities.HasBackgroundAudio):
log.debug(u'Starting to play...')
self.display.audioPlayer.addToPlaylist(
self.serviceItem.background_audio)
self.display.audioPlayer.play()
self.setAudioItemsVisibility(True)
row = 0
text = []
for framenumber, frame in enumerate(self.serviceItem.get_frames()):
@ -1097,6 +1114,17 @@ class SlideController(QtGui.QWidget):
self.playSlidesLoop.setChecked(False)
self.onToggleLoop()
def setAudioItemsVisibility(self, visible):
self.audioPauseItem.setVisible(visible)
def onAudioPauseClicked(self, checked):
if not self.audioPauseItem.isVisible():
return
if checked:
self.display.audioPlayer.pause()
else:
self.display.audioPlayer.play()
def timerEvent(self, event):
"""
If the timer event is for this window select next slide

View File

@ -67,7 +67,7 @@ class BibleMediaItem(MediaManagerItem):
self.hasSearch = True
self.search_results = {}
self.second_search_results = {}
self.check_search_result()
self.checkSearchResult()
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles)
@ -651,7 +651,7 @@ class BibleMediaItem(MediaManagerItem):
elif self.search_results:
self.displayResults(bible, second_bible)
self.advancedSearchButton.setEnabled(True)
self.check_search_result()
self.checkSearchResult()
Receiver.send_message(u'cursor_normal')
Receiver.send_message(u'openlp_process_events')
@ -715,7 +715,7 @@ class BibleMediaItem(MediaManagerItem):
elif self.search_results:
self.displayResults(bible, second_bible)
self.quickSearchButton.setEnabled(True)
self.check_search_result()
self.checkSearchResult()
Receiver.send_message(u'cursor_normal')
Receiver.send_message(u'openlp_process_events')
@ -863,9 +863,9 @@ class BibleMediaItem(MediaManagerItem):
not second_bible:
# Split the line but do not replace line breaks in renderer.
service_item.add_capability(ItemCapabilities.NoLineBreaks)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.AllowsWordSplit)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanWordSplit)
# Service Item: Title
service_item.title = u', '.join(raw_title)
# Service Item: Theme

View File

@ -135,7 +135,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
self.customSlide.credits = unicode(self.creditEdit.text())
self.customSlide.theme_name = unicode(self.themeComboBox.currentText())
success = self.manager.save_object(self.customSlide)
self.mediaitem.auto_select_id = self.customSlide.id
self.mediaitem.autoSelectId = self.customSlide.id
return success
def onUpButtonClicked(self):

View File

@ -132,7 +132,7 @@ class CustomMediaItem(MediaManagerItem):
def loadList(self, custom_slides):
# Sort out what custom we want to select after loading the list.
self.save_auto_select_id()
self.saveAutoSelectId()
self.listView.clear()
# Sort the customs by its title considering language specific
# characters. lower() is needed for windows!
@ -144,9 +144,9 @@ class CustomMediaItem(MediaManagerItem):
QtCore.Qt.UserRole, QtCore.QVariant(custom_slide.id))
self.listView.addItem(custom_name)
# Auto-select the custom.
if custom_slide.id == self.auto_select_id:
if custom_slide.id == self.autoSelectId:
self.listView.setCurrentItem(custom_name)
self.auto_select_id = -1
self.autoSelectId = -1
# 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.
@ -180,7 +180,7 @@ class CustomMediaItem(MediaManagerItem):
self.remoteTriggered = remote_type
self.edit_custom_form.loadCustom(custom_id, (remote_type == u'P'))
self.edit_custom_form.exec_()
self.auto_select_id = -1
self.autoSelectId = -1
self.onSearchTextButtonClick()
def onEditClick(self):
@ -192,7 +192,7 @@ class CustomMediaItem(MediaManagerItem):
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.edit_custom_form.loadCustom(item_id, False)
self.edit_custom_form.exec_()
self.auto_select_id = -1
self.autoSelectId = -1
self.onSearchTextButtonClick()
def onDeleteClick(self):
@ -227,10 +227,10 @@ class CustomMediaItem(MediaManagerItem):
slide = None
theme = None
item_id = self._getIdOfItemToGenerate(item, self.remoteCustom)
service_item.add_capability(ItemCapabilities.AllowsEdit)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.AllowsVirtualSplit)
service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanSoftBreak)
customSlide = self.plugin.manager.get_object(CustomSlide, item_id)
title = customSlide.title
credit = customSlide.credits
@ -273,7 +273,7 @@ class CustomMediaItem(MediaManagerItem):
CustomSlide.theme_name.like(u'%' + self.whitespace.sub(u' ',
search_keywords) + u'%'), order_by_ref=CustomSlide.title)
self.loadList(search_results)
self.check_search_result()
self.checkSearchResult()
def onSearchTextEditChanged(self, text):
"""

View File

@ -149,10 +149,10 @@ class ImageMediaItem(MediaManagerItem):
if not items:
return False
service_item.title = unicode(self.plugin.nameStrings[u'plural'])
service_item.add_capability(ItemCapabilities.AllowsMaintain)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.AllowsAdditions)
service_item.add_capability(ItemCapabilities.CanMaintain)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
# force a nonexistent theme
service_item.theme = -1
missing_items = []

View File

@ -31,11 +31,11 @@ import os
import locale
from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon
from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
SettingsManager, translate, check_item_selected, Receiver
SettingsManager, translate, check_item_selected, Receiver, MediaType
from openlp.core.lib.ui import UiStrings, critical_error_message_box
from PyQt4.phonon import Phonon
log = logging.getLogger(__name__)
@ -48,9 +48,9 @@ class MediaMediaItem(MediaManagerItem):
log.info(u'%s MediaMediaItem loaded', __name__)
def __init__(self, parent, plugin, icon):
self.IconPath = u'images/image'
self.iconPath = u'images/image'
self.background = False
self.PreviewFunction = CLAPPERBOARD
self.previewFunction = CLAPPERBOARD
MediaManagerItem.__init__(self, parent, plugin, icon)
self.singleServiceItem = False
self.hasSearch = True
@ -139,8 +139,8 @@ class MediaMediaItem(MediaManagerItem):
# File is no longer present
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Missing Media File'),
unicode(translate('MediaPlugin.MediaItem',
'The file %s no longer exists.')) % filename)
unicode(translate('MediaPlugin.MediaItem',
'The file %s no longer exists.')) % filename)
return False
self.mediaObject.stop()
self.mediaObject.clearQueue()
@ -156,13 +156,16 @@ class MediaMediaItem(MediaManagerItem):
or self.mediaObject.currentSource().type() \
== Phonon.MediaSource.Invalid:
self.mediaObject.stop()
critical_error_message_box(UiStrings().UnsupportedFile,
UiStrings().UnsupportedFile)
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'File Too Big'),
translate('MediaPlugin.MediaItem', 'The file you are '
'trying to load is too big. Please reduce it to less '
'than 50MiB.'))
return False
self.mediaObject.stop()
service_item.media_length = self.mediaObject.totalTime() / 1000
service_item.add_capability(
ItemCapabilities.AllowsVariableStartTime)
ItemCapabilities.HasVariableStartTime)
service_item.title = unicode(self.plugin.nameStrings[u'singular'])
service_item.add_capability(ItemCapabilities.RequiresMedia)
# force a non-existent theme
@ -217,6 +220,19 @@ class MediaMediaItem(MediaManagerItem):
item_name.setToolTip(track)
self.listView.addItem(item_name)
def getList(self, type=MediaType.Audio):
media = SettingsManager.load_list(self.settingsSection, u'media')
media.sort(cmp=locale.strcoll,
key=lambda filename: os.path.split(unicode(filename))[1].lower())
ext = []
if type == MediaType.Audio:
ext = self.plugin.audio_extensions_list
else:
ext = self.plugin.video_extensions_list
ext = map(lambda x: x[1:], ext)
media = filter(lambda x: os.path.splitext(x)[1] in ext, media)
return media
def createPhonon(self):
log.debug(u'CreatePhonon')
if not self.mediaObject:

View File

@ -248,7 +248,7 @@ class PresentationMediaItem(MediaManagerItem):
service_item.title = unicode(self.displayTypeComboBox.currentText())
service_item.shortname = unicode(self.displayTypeComboBox.currentText())
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
service_item.add_capability(ItemCapabilities.AllowsDetailedTitleDisplay)
service_item.add_capability(ItemCapabilities.HasDetailedTitleDisplay)
shortname = service_item.shortname
if shortname:
for bitem in items:

View File

@ -52,6 +52,7 @@ them separate from the functionality, so that it is easier to recreate the GUI
from the .ui files later if necessary.
"""
from mediafilesform import MediaFilesForm
from authorsform import AuthorsForm
from topicsform import TopicsForm
from songbookform import SongBookForm

View File

@ -28,7 +28,8 @@
from PyQt4 import QtCore, QtGui
from openlp.core.lib import build_icon, translate
from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box
from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box, \
create_up_down_push_button_set
from openlp.plugins.songs.lib.ui import SongStrings
class Ui_EditSongDialog(object):
@ -36,9 +37,11 @@ class Ui_EditSongDialog(object):
editSongDialog.setObjectName(u'editSongDialog')
editSongDialog.resize(650, 400)
editSongDialog.setWindowIcon(
build_icon(u':/icon/openlp.org-icon-32.bmp'))
build_icon(u':/icon/openlp-logo-16x16.png'))
editSongDialog.setModal(True)
self.dialogLayout = QtGui.QVBoxLayout(editSongDialog)
self.dialogLayout.setSpacing(8)
self.dialogLayout.setContentsMargins(8, 8, 8, 8)
self.dialogLayout.setObjectName(u'dialogLayout')
self.songTabWidget = QtGui.QTabWidget(editSongDialog)
self.songTabWidget.setObjectName(u'songTabWidget')
@ -246,6 +249,36 @@ class Ui_EditSongDialog(object):
self.commentsLayout.addWidget(self.commentsEdit)
self.themeTabLayout.addWidget(self.commentsGroupBox)
self.songTabWidget.addTab(self.themeTab, u'')
# audio tab
self.audioTab = QtGui.QWidget()
self.audioTab.setObjectName(u'audioTab')
self.audioLayout = QtGui.QHBoxLayout(self.audioTab)
self.audioLayout.setObjectName(u'audioLayout')
self.audioListWidget = QtGui.QListWidget(self.audioTab)
self.audioListWidget.setObjectName(u'audioListWidget')
self.audioLayout.addWidget(self.audioListWidget)
self.audioButtonsLayout = QtGui.QVBoxLayout()
self.audioButtonsLayout.setObjectName(u'audioButtonsLayout')
self.audioAddFromFileButton = QtGui.QPushButton(self.audioTab)
self.audioAddFromFileButton.setObjectName(u'audioAddFromFileButton')
self.audioButtonsLayout.addWidget(self.audioAddFromFileButton)
self.audioAddFromMediaButton = QtGui.QPushButton(self.audioTab)
self.audioAddFromMediaButton.setObjectName(u'audioAddFromMediaButton')
self.audioButtonsLayout.addWidget(self.audioAddFromMediaButton)
self.audioRemoveButton = QtGui.QPushButton(self.audioTab)
self.audioRemoveButton.setObjectName(u'audioRemoveButton')
self.audioButtonsLayout.addWidget(self.audioRemoveButton)
self.audioRemoveAllButton = QtGui.QPushButton(self.audioTab)
self.audioRemoveAllButton.setObjectName(u'audioRemoveAllButton')
self.audioButtonsLayout.addWidget(self.audioRemoveAllButton)
self.audioButtonsLayout.addStretch(1)
self.upButton, self.downButton = \
create_up_down_push_button_set(self)
self.audioButtonsLayout.addWidget(self.upButton)
self.audioButtonsLayout.addWidget(self.downButton)
self.audioLayout.addLayout(self.audioButtonsLayout)
self.songTabWidget.addTab(self.audioTab, u'')
# Last few bits
self.dialogLayout.addWidget(self.songTabWidget)
self.buttonBox = create_accept_reject_button_box(editSongDialog)
self.dialogLayout.addWidget(self.buttonBox)
@ -305,6 +338,17 @@ class Ui_EditSongDialog(object):
self.songTabWidget.indexOf(self.themeTab),
translate('SongsPlugin.EditSongForm',
'Theme, Copyright Info && Comments'))
self.songTabWidget.setTabText(
self.songTabWidget.indexOf(self.audioTab),
translate('SongsPlugin.EditSongForm', 'Linked Audio'))
self.audioAddFromFileButton.setText(
translate('SongsPlugin.EditSongForm', 'Add &File(s)'))
self.audioAddFromMediaButton.setText(
translate('SongsPlugin.EditSongForm', 'Add &Media'))
self.audioRemoveButton.setText(
translate('SongsPlugin.EditSongForm', '&Remove'))
self.audioRemoveAllButton.setText(
translate('SongsPlugin.EditSongForm', 'Remove &All'))
def editSongDialogComboBox(parent, name):
"""

View File

@ -27,15 +27,18 @@
import logging
import re
import os
import shutil
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver, translate
from openlp.core.lib import PluginStatus, Receiver, MediaType, translate
from openlp.core.lib.ui import UiStrings, add_widget_completer, \
critical_error_message_box, find_and_set_in_combo_box
from openlp.plugins.songs.forms import EditVerseForm
from openlp.core.utils import AppLocation
from openlp.plugins.songs.forms import EditVerseForm, MediaFilesForm
from openlp.plugins.songs.lib import SongXML, VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
from editsongdialog import Ui_EditSongDialog
@ -93,6 +96,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.mediaitem.plugin.renderer.themeManager.onAddTheme)
QtCore.QObject.connect(self.maintenanceButton,
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked)
QtCore.QObject.connect(self.audioAddFromFileButton,
QtCore.SIGNAL(u'clicked()'), self.onAudioAddFromFileButtonClicked)
QtCore.QObject.connect(self.audioAddFromMediaButton,
QtCore.SIGNAL(u'clicked()'), self.onAudioAddFromMediaButtonClicked)
QtCore.QObject.connect(self.audioRemoveButton,
QtCore.SIGNAL(u'clicked()'), self.onAudioRemoveButtonClicked)
QtCore.QObject.connect(self.audioRemoveAllButton,
QtCore.SIGNAL(u'clicked()'), self.onAudioRemoveAllButtonClicked)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'theme_update_list'), self.loadThemes)
self.previewButton = QtGui.QPushButton()
@ -104,12 +115,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
QtCore.SIGNAL(u'clicked(QAbstractButton*)'), self.onPreview)
# Create other objects and forms
self.manager = manager
self.verse_form = EditVerseForm(self)
self.verseForm = EditVerseForm(self)
self.mediaForm = MediaFilesForm(self)
self.initialise()
self.authorsListView.setSortingEnabled(False)
self.authorsListView.setAlternatingRowColors(True)
self.topicsListView.setSortingEnabled(False)
self.topicsListView.setAlternatingRowColors(True)
self.audioListWidget.setAlternatingRowColors(True)
self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE)
self.whitespace = re.compile(r'\W+', re.UNICODE)
@ -161,6 +174,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.themes.append(theme)
add_widget_completer(self.themes, self.themeComboBox)
def loadMediaFiles(self):
self.audioAddFromMediaButton.setVisible(False)
for plugin in self.parent().pluginManager.plugins:
if plugin.name == u'media' and \
plugin.status == PluginStatus.Active:
self.audioAddFromMediaButton.setVisible(True)
self.mediaForm.populateFiles(
plugin.getMediaManagerItem().getList(MediaType.Audio))
break
def newSong(self):
log.debug(u'New Song')
self.song = None
@ -176,11 +199,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.setRowCount(0)
self.authorsListView.clear()
self.topicsListView.clear()
self.audioListWidget.clear()
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
self.songBookNumberEdit.setText(u'')
self.loadAuthors()
self.loadTopics()
self.loadBooks()
self.loadMediaFiles()
self.themeComboBox.setCurrentIndex(0)
# it's a new song to preview is not possible
self.previewButton.setVisible(False)
@ -201,6 +226,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.loadAuthors()
self.loadTopics()
self.loadBooks()
self.loadMediaFiles()
self.song = self.manager.get_object(Song, id)
self.titleEdit.setText(self.song.title)
if self.song.alternate_title:
@ -303,6 +329,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
topic_name = QtGui.QListWidgetItem(unicode(topic.name))
topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id))
self.topicsListView.addItem(topic_name)
self.audioListWidget.clear()
for media in self.song.media_files:
media_file = QtGui.QListWidgetItem(os.path.split(media.file_name)[1])
media_file.setData(QtCore.Qt.UserRole, QtCore.QVariant(media.file_name))
self.audioListWidget.addItem(media_file)
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# Hide or show the preview button.
self.previewButton.setVisible(preview)
@ -436,9 +467,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseDeleteButton.setEnabled(True)
def onVerseAddButtonClicked(self):
self.verse_form.setVerse(u'', True)
if self.verse_form.exec_():
after_text, verse_tag, verse_num = self.verse_form.getVerse()
self.verseForm.setVerse(u'', True)
if self.verseForm.exec_():
after_text, verse_tag, verse_num = self.verseForm.getVerse()
verse_def = u'%s%s' % (verse_tag, verse_num)
item = QtGui.QTableWidgetItem(after_text)
item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def))
@ -454,20 +485,21 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
if item:
tempText = item.text()
verseId = unicode(item.data(QtCore.Qt.UserRole).toString())
self.verse_form.setVerse(tempText, True, verseId)
if self.verse_form.exec_():
after_text, verse_tag, verse_num = self.verse_form.getVerse()
self.verseForm.setVerse(tempText, True, verseId)
if self.verseForm.exec_():
after_text, verse_tag, verse_num = self.verseForm.getVerse()
verse_def = u'%s%s' % (verse_tag, verse_num)
item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def))
item.setText(after_text)
# number of lines has change so repaint the list moving the data
# number of lines has changed, repaint the list moving the data
if len(tempText.split(u'\n')) != len(after_text.split(u'\n')):
tempList = {}
tempId = {}
for row in range(0, self.verseListWidget.rowCount()):
tempList[row] = self.verseListWidget.item(row, 0).text()
tempId[row] = self.verseListWidget.item(row, 0).\
data(QtCore.Qt.UserRole)
tempList[row] = self.verseListWidget.item(row, 0)\
.text()
tempId[row] = self.verseListWidget.item(row, 0)\
.data(QtCore.Qt.UserRole)
self.verseListWidget.clear()
for row in range (0, len(tempList)):
item = QtGui.QTableWidgetItem(tempList[row], 0)
@ -486,12 +518,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_list += u'---[%s:%s]---\n' % (verse_tag, verse_num)
verse_list += item.text()
verse_list += u'\n'
self.verse_form.setVerse(verse_list)
self.verseForm.setVerse(verse_list)
else:
self.verse_form.setVerse(u'')
if not self.verse_form.exec_():
self.verseForm.setVerse(u'')
if not self.verseForm.exec_():
return
verse_list = self.verse_form.getVerseAll()
verse_list = self.verseForm.getVerseAll()
verse_list = unicode(verse_list.replace(u'\r\n', u'\n'))
self.verseListWidget.clear()
self.verseListWidget.setRowCount(0)
@ -670,6 +702,66 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.saveSong(True)
Receiver.send_message(u'songs_preview')
def onAudioAddFromFileButtonClicked(self):
"""
Loads file(s) from the filesystem.
"""
filters = u'%s (*)' % UiStrings().AllFiles
filenames = QtGui.QFileDialog.getOpenFileNames(self,
translate('SongsPlugin.EditSongForm', 'Open File(s)'),
QtCore.QString(), filters)
for filename in filenames:
item = QtGui.QListWidgetItem(os.path.split(unicode(filename))[1])
item.setData(QtCore.Qt.UserRole, filename)
self.audioListWidget.addItem(item)
def onAudioAddFromMediaButtonClicked(self):
"""
Loads file(s) from the media plugin.
"""
if self.mediaForm.exec_():
for filename in self.mediaForm.getSelectedFiles():
item = QtGui.QListWidgetItem(os.path.split(unicode(filename))[1])
item.setData(QtCore.Qt.UserRole, filename)
self.audioListWidget.addItem(item)
def onAudioRemoveButtonClicked(self):
"""
Removes a file from the list.
"""
row = self.audioListWidget.currentRow()
if row == -1:
return
self.audioListWidget.takeItem(row)
def onAudioRemoveAllButtonClicked(self):
"""
Removes all files from the list.
"""
self.audioListWidget.clear()
def onUpButtonClicked(self):
"""
Moves a file up when the user clicks the up button on the audio tab.
"""
row = self.audioListWidget.currentRow()
if row <= 0:
return
item = self.audioListWidget.takeItem(row)
self.audioListWidget.insertItem(row - 1, item)
self.audioListWidget.setCurrentRow(row - 1)
def onDownButtonClicked(self):
"""
Moves a file down when the user clicks the up button on the audio tab.
"""
row = self.audioListWidget.currentRow()
if row == -1 or row > self.audioListWidget.count() - 1:
return
item = self.audioListWidget.takeItem(row)
self.audioListWidget.insertItem(row + 1, item)
self.audioListWidget.setCurrentRow(row + 1)
def clearCaches(self):
"""
Free up autocompletion memory on dialog exit
@ -744,18 +836,55 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.theme_name = None
self._processLyrics()
self.song.authors = []
for row in range(self.authorsListView.count()):
for row in xrange(self.authorsListView.count()):
item = self.authorsListView.item(row)
authorId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.authors.append(self.manager.get_object(Author, authorId))
self.song.topics = []
for row in range(self.topicsListView.count()):
for row in xrange(self.topicsListView.count()):
item = self.topicsListView.item(row)
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.topics.append(self.manager.get_object(Topic, topicId))
# Save the song here because we need a valid id for the audio files.
clean_song(self.manager, self.song)
self.manager.save_object(self.song)
self.mediaitem.auto_select_id = self.song.id
audio_files = map(lambda a: a.file_name, self.song.media_files)
log.debug(audio_files)
save_path = os.path.join(
AppLocation.get_section_data_path(self.mediaitem.plugin.name),
'audio', str(self.song.id))
if not os.path.exists(save_path):
os.makedirs(save_path)
self.song.media_files = []
files = []
for row in xrange(self.audioListWidget.count()):
item = self.audioListWidget.item(row)
filename = unicode(item.data(QtCore.Qt.UserRole).toString())
if not filename.startswith(save_path):
oldfile, filename = filename, os.path.join(save_path,
os.path.split(filename)[1])
shutil.copyfile(oldfile, filename)
files.append(filename)
media_file = MediaFile()
media_file.file_name = filename
media_file.type = u'audio'
media_file.weight = row
self.song.media_files.append(media_file)
for audio in audio_files:
if audio not in files:
try:
os.remove(audio)
except:
log.exception('Could not remove file: %s', audio)
pass
if not files:
try:
os.rmdir(save_path)
except OSError:
log.exception(u'Could not remove directory: %s', save_path)
clean_song(self.manager, self.song)
self.manager.save_object(self.song)
self.mediaitem.autoSelectId = self.song.id
def _processLyrics(self):
"""
@ -783,3 +912,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
except:
log.exception(u'Problem processing song Lyrics \n%s',
sxml.dump_xml())

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, build_icon
class Ui_MediaFilesDialog(object):
def setupUi(self, mediaFilesDialog):
mediaFilesDialog.setObjectName(u'mediaFilesDialog')
mediaFilesDialog.setWindowModality(QtCore.Qt.ApplicationModal)
mediaFilesDialog.resize(400, 300)
mediaFilesDialog.setModal(True)
mediaFilesDialog.setWindowIcon(
build_icon(u':/icon/openlp-logo-16x16.png'))
self.filesVerticalLayout = QtGui.QVBoxLayout(mediaFilesDialog)
self.filesVerticalLayout.setSpacing(8)
self.filesVerticalLayout.setMargin(8)
self.filesVerticalLayout.setObjectName(u'filesVerticalLayout')
self.selectLabel = QtGui.QLabel(mediaFilesDialog)
self.selectLabel.setWordWrap(True)
self.selectLabel.setObjectName(u'selectLabel')
self.filesVerticalLayout.addWidget(self.selectLabel)
self.fileListWidget = QtGui.QListWidget(mediaFilesDialog)
self.fileListWidget.setAlternatingRowColors(True)
self.fileListWidget.setSelectionMode(
QtGui.QAbstractItemView.ExtendedSelection)
self.fileListWidget.setObjectName(u'fileListWidget')
self.filesVerticalLayout.addWidget(self.fileListWidget)
self.buttonBox = QtGui.QDialogButtonBox(mediaFilesDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(
QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(u'buttonBox')
self.filesVerticalLayout.addWidget(self.buttonBox)
self.retranslateUi(mediaFilesDialog)
QtCore.QObject.connect(self.buttonBox,
QtCore.SIGNAL(u'accepted()'), mediaFilesDialog.accept)
QtCore.QObject.connect(self.buttonBox,
QtCore.SIGNAL(u'rejected()'), mediaFilesDialog.reject)
QtCore.QMetaObject.connectSlotsByName(mediaFilesDialog)
def retranslateUi(self, mediaFilesDialog):
mediaFilesDialog.setWindowTitle(
translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)'))
self.selectLabel.setText(
translate('SongsPlugin.MediaFilesForm', u'Select one or more '
'audio files from the list below, and click OK to import them '
'into this song.'))

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
import os
from PyQt4 import QtCore, QtGui
from mediafilesdialog import Ui_MediaFilesDialog
log = logging.getLogger(__name__)
class MediaFilesForm(QtGui.QDialog, Ui_MediaFilesDialog):
"""
Class to show a list of files from the
"""
log.info(u'%s MediaFilesForm loaded', __name__)
def __init__(self, parent):
QtGui.QDialog.__init__(self)
self.setupUi(self)
def populateFiles(self, files):
self.fileListWidget.clear()
for file in files:
item = QtGui.QListWidgetItem(os.path.split(file)[1])
item.setData(QtCore.Qt.UserRole, file)
self.fileListWidget.addItem(item)
def getSelectedFiles(self):
return map(lambda x: unicode(x.data(QtCore.Qt.UserRole).toString()),
self.fileListWidget.selectedItems())

View File

@ -232,7 +232,8 @@ def init_schema(url):
'authors': relation(Author, backref='songs',
secondary=authors_songs_table, lazy=False),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs'),
'media_files': relation(MediaFile, backref='songs',
order_by=media_files_table.c.weight),
'topics': relation(Topic, backref='songs',
secondary=songs_topics_table)
})

View File

@ -28,6 +28,8 @@
import logging
import locale
import re
import os
import shutil
from PyQt4 import QtCore, QtGui
from sqlalchemy.sql import or_
@ -37,11 +39,12 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import UiStrings, context_menu_action, \
context_menu_separator
from openlp.core.utils import AppLocation
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
SongImportForm, SongExportForm
from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \
clean_string
from openlp.plugins.songs.lib.db import Author, Song
from openlp.plugins.songs.lib.db import Author, Song, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__)
@ -79,6 +82,22 @@ class SongMediaItem(MediaManagerItem):
self.quickPreviewAllowed = True
self.hasSearch = True
def _updateBackgroundAudio(self, song, item):
song.media_files = []
for i, bga in enumerate(item.background_audio):
dest_file = os.path.join(
AppLocation.get_section_data_path(self.plugin.name),
u'audio', str(song.id), os.path.split(bga)[1])
if not os.path.exists(os.path.split(dest_file)[0]):
os.makedirs(os.path.split(dest_file)[0])
shutil.copyfile(os.path.join(
AppLocation.get_section_data_path(
u'servicemanager'), bga),
dest_file)
song.media_files.append(MediaFile.populate(
weight=i, file_name=dest_file))
self.plugin.manager.save_object(song, True)
def addEndHeaderBar(self):
self.addToolbarSeparator()
## Song Maintenance Button ##
@ -210,7 +229,7 @@ class SongMediaItem(MediaManagerItem):
search_results = self.plugin.manager.get_all_objects(Song,
Song.theme_name.like(u'%' + search_keywords + u'%'))
self.displayResultsSong(search_results)
self.check_search_result()
self.checkSearchResult()
def searchEntire(self, search_keywords):
return self.plugin.manager.get_all_objects(Song,
@ -244,7 +263,7 @@ class SongMediaItem(MediaManagerItem):
def displayResultsSong(self, searchresults):
log.debug(u'display results Song')
self.save_auto_select_id()
self.saveAutoSelectId()
self.listView.clear()
# Sort the songs by its title considering language specific characters.
# lower() is needed for windows!
@ -258,9 +277,9 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id))
self.listView.addItem(song_name)
# Auto-select the item if name has been set
if song.id == self.auto_select_id:
if song.id == self.autoSelectId:
self.listView.setCurrentItem(song_name)
self.auto_select_id = -1
self.autoSelectId = -1
def displayResultsAuthor(self, searchresults):
log.debug(u'display results Author')
@ -312,7 +331,7 @@ class SongMediaItem(MediaManagerItem):
self.edit_song_form.exec_()
self.onClearTextButtonClick()
self.onSelectionChange()
self.auto_select_id = -1
self.autoSelectId = -1
def onSongMaintenanceClick(self):
self.song_maintenance_form.exec_()
@ -335,9 +354,9 @@ class SongMediaItem(MediaManagerItem):
if valid:
self.remoteSong = song_id
self.remoteTriggered = remote_type
self.edit_song_form.loadSong(song_id, (remote_type == u'P'))
self.edit_song_form.loadSong(song_id, remote_type == u'P')
self.edit_song_form.exec_()
self.auto_select_id = -1
self.autoSelectId = -1
self.onSongListLoad()
def onEditClick(self):
@ -350,7 +369,7 @@ class SongMediaItem(MediaManagerItem):
item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0]
self.edit_song_form.loadSong(item_id, False)
self.edit_song_form.exec_()
self.auto_select_id = -1
self.autoSelectId = -1
self.onSongListLoad()
self.editItem = None
@ -395,12 +414,12 @@ class SongMediaItem(MediaManagerItem):
def generateSlideData(self, service_item, item=None, xmlVersion=False):
log.debug(u'generateSlideData (%s:%s)' % (service_item, item))
item_id = self._getIdOfItemToGenerate(item, self.remoteSong)
service_item.add_capability(ItemCapabilities.AllowsEdit)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
service_item.add_capability(ItemCapabilities.AddIfNewItem)
service_item.add_capability(ItemCapabilities.AllowsVirtualSplit)
service_item.add_capability(ItemCapabilities.CanSoftBreak)
song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name
service_item.edit_id = item_id
@ -471,6 +490,10 @@ class SongMediaItem(MediaManagerItem):
service_item.data_string = {u'title': song.search_title,
u'authors': u', '.join(author_list)}
service_item.xml_version = self.openLyrics.song_to_xml(song)
# Add the audio file to the service item.
if len(song.media_files) > 0:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
service_item.background_audio = [m.file_name for m in song.media_files]
return True
def serviceLoad(self, item):
@ -510,8 +533,15 @@ class SongMediaItem(MediaManagerItem):
add_song = False
editId = song.id
break
# If there's any backing tracks, copy them over.
if len(item.background_audio) > 0:
self._updateBackgroundAudio(song, item)
if add_song and self.addSongFromService:
editId = self.openLyrics.xml_to_song(item.xml_version)
song = self.openLyrics.xml_to_song(item.xml_version)
# If there's any backing tracks, copy them over.
if len(item.background_audio) > 0:
self._updateBackgroundAudio(song, item)
editId = song.id
self.onSearchTextButtonClick()
# Update service with correct song id.
if editId:

View File

@ -343,7 +343,7 @@ class OpenLyrics(object):
self._process_topics(properties, song)
clean_song(self.manager, song)
self.manager.save_object(song)
return song.id
return song
def _add_text_to_element(self, tag, parent, text=None, label=None):
if label:

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MediaFilesDialog</class>
<widget class="QDialog" name="MediaFilesDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Select Media File(s)</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="filesVerticalLayout">
<property name="spacing">
<number>8</number>
</property>
<property name="margin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="selectLabel">
<property name="text">
<string>Select one or more audio files from the list below, and click OK to import them into this song.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="fileListView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images/openlp-2.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MediaFilesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MediaFilesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>