Head 1815

This commit is contained in:
Tim Bentley 2011-12-03 17:02:11 +00:00
commit add75e9c7f
27 changed files with 74 additions and 68 deletions

View File

@ -182,6 +182,7 @@ class Manager(object):
settings.beginGroup(plugin_name) settings.beginGroup(plugin_name)
self.db_url = u'' self.db_url = u''
self.is_dirty = False self.is_dirty = False
self.session = None
db_type = unicode( db_type = unicode(
settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString()) settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString())
if db_type == u'sqlite': if db_type == u'sqlite':

View File

@ -104,7 +104,7 @@ class ListWidgetWithDnD(QtGui.QListWidget):
elif os.path.isdir(localFile): elif os.path.isdir(localFile):
listing = os.listdir(localFile) listing = os.listdir(localFile)
for file in listing: for file in listing:
files.append(os.path.join(localFile,file)) files.append(os.path.join(localFile, file))
Receiver.send_message(u'%s_dnd' % self.mimeDataText,files) Receiver.send_message(u'%s_dnd' % self.mimeDataText, files)
else: else:
event.ignore() event.ignore()

View File

@ -158,6 +158,8 @@ class PluginManager(object):
for plugin in self.plugins: for plugin in self.plugins:
if plugin.status is not PluginStatus.Disabled: if plugin.status is not PluginStatus.Disabled:
plugin.settings_tab = plugin.getSettingsTab(settings_form) plugin.settings_tab = plugin.getSettingsTab(settings_form)
else:
plugin.settings_tab = None
settings_form.plugins = self.plugins settings_form.plugins = self.plugins
def hook_import_menu(self, import_menu): def hook_import_menu(self, import_menu):

View File

@ -65,7 +65,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self.downloadCanceled = False self.downloadCanceled = False
self.downloading = unicode(translate('OpenLP.FirstTimeWizard', self.downloading = unicode(translate('OpenLP.FirstTimeWizard',
'Downloading %s...')) 'Downloading %s...'))
QtCore.QObject.connect(self.cancelButton,QtCore.SIGNAL('clicked()'), QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL('clicked()'),
self.onCancelButtonClicked) self.onCancelButtonClicked)
QtCore.QObject.connect(self.noInternetFinishButton, QtCore.QObject.connect(self.noInternetFinishButton,
QtCore.SIGNAL('clicked()'), self.onNoInternetFinishButtonClicked) QtCore.SIGNAL('clicked()'), self.onNoInternetFinishButtonClicked)

View File

@ -29,7 +29,6 @@ The :mod:`maindisplay` module provides the functionality to display screens
and play multimedia within OpenLP. and play multimedia within OpenLP.
""" """
import logging import logging
import os
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
from PyQt4.phonon import Phonon from PyQt4.phonon import Phonon

View File

@ -35,8 +35,7 @@ from datetime import datetime
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \ from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \
PluginManager, Receiver, translate, ImageManager, PluginStatus, \ PluginManager, Receiver, translate, ImageManager, PluginStatus
SettingsManager
from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \ from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \
icon_action, shortcut_action icon_action, shortcut_action
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \ from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
@ -44,7 +43,7 @@ from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
ShortcutListForm, FormattingTagForm ShortcutListForm, FormattingTagForm
from openlp.core.ui.media import MediaController from openlp.core.ui.media import MediaController
from openlp.core.utils import AppLocation, add_actions, LanguageManager, \ from openlp.core.utils import AppLocation, add_actions, LanguageManager, \
get_application_version, delete_file get_application_version
from openlp.core.utils.actions import ActionList, CategoryOrder from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui import ScreenList from openlp.core.ui import ScreenList
@ -504,7 +503,8 @@ class Ui_MainWindow(object):
self.toolsFirstTimeWizard.setText( self.toolsFirstTimeWizard.setText(
translate('OpenLP.MainWindow', 'Re-run First Time Wizard')) translate('OpenLP.MainWindow', 'Re-run First Time Wizard'))
self.toolsFirstTimeWizard.setStatusTip(translate('OpenLP.MainWindow', self.toolsFirstTimeWizard.setStatusTip(translate('OpenLP.MainWindow',
'Re-run the First Time Wizard, importing songs, Bibles and themes.')) 'Re-run the First Time Wizard, importing songs, Bibles and '
'themes.'))
self.updateThemeImages.setText( self.updateThemeImages.setText(
translate('OpenLP.MainWindow', 'Update Theme Images')) translate('OpenLP.MainWindow', 'Update Theme Images'))
self.updateThemeImages.setStatusTip( self.updateThemeImages.setStatusTip(

View File

@ -26,13 +26,12 @@
############################################################################### ###############################################################################
import logging import logging
import os
import sys, os,time from PyQt4 import QtCore, QtGui
from PyQt4 import QtCore, QtGui, QtWebKit
from openlp.core.lib import OpenLPToolbar, Receiver, translate from openlp.core.lib import OpenLPToolbar, Receiver, translate
from openlp.core.lib.mediaplayer import MediaPlayer from openlp.core.lib.mediaplayer import MediaPlayer
from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import MediaState, MediaInfo, MediaType from openlp.core.ui.media import MediaState, MediaInfo, MediaType
from openlp.core.utils import AppLocation from openlp.core.utils import AppLocation
@ -136,8 +135,9 @@ class MediaController(object):
savedPlayers = playerSettings.split(u',') savedPlayers = playerSettings.split(u',')
invalidMediaPlayers = [mediaPlayer for mediaPlayer in savedPlayers \ invalidMediaPlayers = [mediaPlayer for mediaPlayer in savedPlayers \
if not mediaPlayer in self.mediaPlayers] if not mediaPlayer in self.mediaPlayers]
if len(invalidMediaPlayers)>0: if len(invalidMediaPlayers) > 0:
[savedPlayers.remove(invalidPlayer) for invalidPlayer in invalidMediaPlayers] for invalidPlayer in invalidMediaPlayers:
savedPlayers.remove(invalidPlayer)
newPlayerSetting = u','.join(savedPlayers) newPlayerSetting = u','.join(savedPlayers)
QtCore.QSettings().setValue(u'media/players', QtCore.QSettings().setValue(u'media/players',
QtCore.QVariant(newPlayerSetting)) QtCore.QVariant(newPlayerSetting))
@ -346,7 +346,7 @@ class MediaController(object):
def check_file_type(self, controller, display): def check_file_type(self, controller, display):
""" """
Used to choose the right media Player type from the prioritized Player list Select the correct media Player type from the prioritized Player list
""" """
playerSettings = str(QtCore.QSettings().value(u'media/players', playerSettings = str(QtCore.QSettings().value(u'media/players',
QtCore.QVariant(u'webkit')).toString()) QtCore.QVariant(u'webkit')).toString())
@ -356,12 +356,14 @@ class MediaController(object):
if self.overridenPlayer != '': if self.overridenPlayer != '':
usedPlayers = [self.overridenPlayer] usedPlayers = [self.overridenPlayer]
if controller.media_info.file_info.isFile(): if controller.media_info.file_info.isFile():
suffix = u'*.%s' % controller.media_info.file_info.suffix().toLower() suffix = u'*.%s' % \
controller.media_info.file_info.suffix().toLower()
for title in usedPlayers: for title in usedPlayers:
player = self.mediaPlayers[title] player = self.mediaPlayers[title]
if suffix in player.video_extensions_list: if suffix in player.video_extensions_list:
if not controller.media_info.is_background or \ if not controller.media_info.is_background or \
controller.media_info.is_background and player.canBackground: controller.media_info.is_background and \
player.canBackground:
self.resize(controller, display, player) self.resize(controller, display, player)
if player.load(display): if player.load(display):
self.curDisplayMediaPlayer[display] = player self.curDisplayMediaPlayer[display] = player
@ -399,7 +401,8 @@ class MediaController(object):
return False return False
if status: if status:
display.frame.evaluateJavaScript(u'show_blank("desktop");') display.frame.evaluateJavaScript(u'show_blank("desktop");')
self.curDisplayMediaPlayer[display].set_visible(display, True) self.curDisplayMediaPlayer[display].set_visible(display,
True)
if controller.isLive: if controller.isLive:
if controller.hideMenu.defaultAction().isChecked(): if controller.hideMenu.defaultAction().isChecked():
controller.hideMenu.defaultAction().trigger() controller.hideMenu.defaultAction().trigger()

View File

@ -29,7 +29,6 @@ import logging
import mimetypes import mimetypes
from datetime import datetime from datetime import datetime
from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon from PyQt4.phonon import Phonon
from openlp.core.lib import Receiver from openlp.core.lib import Receiver

View File

@ -27,9 +27,6 @@
import logging import logging
from PyQt4 import QtCore, QtGui, QtWebKit
from openlp.core.lib import OpenLPToolbar, translate
from openlp.core.lib.mediaplayer import MediaPlayer from openlp.core.lib.mediaplayer import MediaPlayer
from openlp.core.ui.media import MediaState from openlp.core.ui.media import MediaState

View File

@ -117,6 +117,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
self.pluginListWidget.currentItem().text().split(u'(')[0][:-1] self.pluginListWidget.currentItem().text().split(u'(')[0][:-1]
self.activePlugin = None self.activePlugin = None
for plugin in self.parent().pluginManager.plugins: for plugin in self.parent().pluginManager.plugins:
if plugin.status != PluginStatus.Disabled:
if plugin.nameStrings[u'singular'] == plugin_name_singular: if plugin.nameStrings[u'singular'] == plugin_name_singular:
self.activePlugin = plugin self.activePlugin = plugin
break break
@ -126,7 +127,7 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
self._clearDetails() self._clearDetails()
def onStatusComboBoxChanged(self, status): def onStatusComboBoxChanged(self, status):
if self.programaticChange: if self.programaticChange or status == PluginStatus.Disabled:
return return
if status == 0: if status == 0:
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')

View File

@ -27,12 +27,10 @@
import os import os
import logging import logging
import time
import copy import copy
from collections import deque from collections import deque
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon
from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \
translate, build_icon, ServiceItem, build_html, PluginManager, ServiceItem translate, build_icon, ServiceItem, build_html, PluginManager, ServiceItem
@ -68,7 +66,8 @@ class Controller(QtGui.QWidget):
created from within other plugins created from within other plugins
This function is needed to catch the current controller This function is needed to catch the current controller
""" """
sender = self.sender().objectName() if self.sender().objectName() else self.sender().text() sender = self.sender().objectName() if self.sender().objectName() \
else self.sender().text()
controller = self controller = self
Receiver.send_message('%s' % sender, [controller, args]) Receiver.send_message('%s' % sender, [controller, args])
@ -574,7 +573,6 @@ class SlideController(Controller):
self.keypress_queue.append(u'previous') self.keypress_queue.append(u'previous')
self._process_queue() self._process_queue()
def serviceNext(self): def serviceNext(self):
""" """
Live event to select the next service item from the service manager. Live event to select the next service item from the service manager.
@ -596,7 +594,6 @@ class SlideController(Controller):
Receiver.send_message('servicemanager_next_item') Receiver.send_message('servicemanager_next_item')
self.keypress_loop = False self.keypress_loop = False
def screenSizeChanged(self): def screenSizeChanged(self):
""" """
Settings dialog has changed the screen size of adjust output and Settings dialog has changed the screen size of adjust output and
@ -640,11 +637,12 @@ class SlideController(Controller):
self.previewFrame.height()): self.previewFrame.height()):
# We have to take the height as limit. # We have to take the height as limit.
max_height = self.previewFrame.height() - self.grid.margin() * 2 max_height = self.previewFrame.height() - self.grid.margin() * 2
self.slidePreview.setFixedSize(QtCore.QSize(max_height * self.ratio, self.slidePreview.setFixedSize(QtCore.QSize(
max_height)) max_height * self.ratio, max_height))
self.previewDisplay.setFixedSize(QtCore.QSize(max_height * self.ratio, self.previewDisplay.setFixedSize(QtCore.QSize(
max_height)) max_height * self.ratio, max_height))
self.previewDisplay.screen = {u'size':self.previewDisplay.geometry()} self.previewDisplay.screen = {
u'size': self.previewDisplay.geometry()}
else: else:
# We have to take the width as limit. # We have to take the width as limit.
max_width = self.previewFrame.width() - self.grid.margin() * 2 max_width = self.previewFrame.width() - self.grid.margin() * 2
@ -652,7 +650,8 @@ class SlideController(Controller):
max_width / self.ratio)) max_width / self.ratio))
self.previewDisplay.setFixedSize(QtCore.QSize(max_width, self.previewDisplay.setFixedSize(QtCore.QSize(max_width,
max_width / self.ratio)) max_width / self.ratio))
self.previewDisplay.screen = {u'size':self.previewDisplay.geometry()} self.previewDisplay.screen = {
u'size': self.previewDisplay.geometry()}
# Make sure that the frames have the correct size. # Make sure that the frames have the correct size.
self.previewListWidget.setColumnWidth(0, self.previewListWidget.setColumnWidth(0,
self.previewListWidget.viewport().size().width()) self.previewListWidget.viewport().size().width())

View File

@ -28,7 +28,6 @@
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.lib.ui import create_accept_reject_button_box
class Ui_ThemeLayoutDialog(object): class Ui_ThemeLayoutDialog(object):

View File

@ -29,9 +29,6 @@ from PyQt4 import QtGui, QtCore
from themelayoutdialog import Ui_ThemeLayoutDialog from themelayoutdialog import Ui_ThemeLayoutDialog
from openlp.core.lib import translate
from openlp.core.lib.ui import UiStrings, critical_error_message_box
class ThemeLayoutForm(QtGui.QDialog, Ui_ThemeLayoutDialog): class ThemeLayoutForm(QtGui.QDialog, Ui_ThemeLayoutDialog):
""" """
The exception dialog The exception dialog

View File

@ -72,7 +72,8 @@ class VerseReferenceList(object):
continue continue
prev = index - 1 prev = index - 1
if self.verse_list[prev][u'version'] != verse[u'version']: if self.verse_list[prev][u'version'] != verse[u'version']:
result = u'%s (%s)' % (result, self.verse_list[prev][u'version']) result = u'%s (%s)' % (result,
self.verse_list[prev][u'version'])
result = result + u', ' result = result + u', '
if self.verse_list[prev][u'book'] != verse[u'book']: if self.verse_list[prev][u'book'] != verse[u'book']:
result = u'%s%s %s:' % (result, verse[u'book'], result = u'%s%s %s:' % (result, verse[u'book'],

View File

@ -30,7 +30,8 @@ import logging
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver, translate from openlp.core.lib import Receiver, translate
from openlp.core.lib.ui import critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import critical_error_message_box, \
find_and_set_in_combo_box
from openlp.plugins.custom.lib import CustomXMLBuilder, CustomXMLParser from openlp.plugins.custom.lib import CustomXMLBuilder, CustomXMLParser
from openlp.plugins.custom.lib.db import CustomSlide from openlp.plugins.custom.lib.db import CustomSlide
from editcustomdialog import Ui_CustomEditDialog from editcustomdialog import Ui_CustomEditDialog

View File

@ -30,7 +30,6 @@ import os
import locale import locale
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon
from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
SettingsManager, translate, check_item_selected, Receiver, MediaType, \ SettingsManager, translate, check_item_selected, Receiver, MediaType, \
@ -139,7 +138,8 @@ class MediaMediaItem(MediaManagerItem):
# Add the Media widget to the page layout # Add the Media widget to the page layout
self.pageLayout.addWidget(self.mediaWidget) self.pageLayout.addWidget(self.mediaWidget)
QtCore.QObject.connect(self.displayTypeComboBox, QtCore.QObject.connect(self.displayTypeComboBox,
QtCore.SIGNAL(u'currentIndexChanged (int)'), self.overridePlayerChanged) QtCore.SIGNAL(u'currentIndexChanged (int)'),
self.overridePlayerChanged)
def overridePlayerChanged(self, index): def overridePlayerChanged(self, index):
Receiver.send_message(u'media_override_player', \ Receiver.send_message(u'media_override_player', \

View File

@ -28,7 +28,7 @@
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import SettingsTab, translate, Receiver from openlp.core.lib import SettingsTab, translate, Receiver
from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.ui import UiStrings
class MediaTab(SettingsTab): class MediaTab(SettingsTab):
""" """

View File

@ -26,7 +26,6 @@
############################################################################### ###############################################################################
import logging import logging
import os
from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib import Plugin, StringContent, build_icon, translate
from openlp.plugins.media.lib import MediaMediaItem, MediaTab from openlp.plugins.media.lib import MediaMediaItem, MediaTab

View File

@ -285,7 +285,8 @@ class PresentationMediaItem(MediaManagerItem):
critical_error_message_box( critical_error_message_box(
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'Missing Presentation'), 'Missing Presentation'),
unicode(translate('PresentationPlugin.MediaItem', unicode(translate(
'PresentationPlugin.MediaItem',
'The Presentation %s is incomplete,' 'The Presentation %s is incomplete,'
' please reload.')) % filename) ' please reload.')) % filename)
return False return False

View File

@ -330,8 +330,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.topicsListView.addItem(topic_name) self.topicsListView.addItem(topic_name)
self.audioListWidget.clear() self.audioListWidget.clear()
for media in self.song.media_files: for media in self.song.media_files:
media_file = QtGui.QListWidgetItem(os.path.split(media.file_name)[1]) media_file = QtGui.QListWidgetItem(
media_file.setData(QtCore.Qt.UserRole, QtCore.QVariant(media.file_name)) os.path.split(media.file_name)[1])
media_file.setData(QtCore.Qt.UserRole,
QtCore.QVariant(media.file_name))
self.audioListWidget.addItem(media_file) self.audioListWidget.addItem(media_file)
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# Hide or show the preview button. # Hide or show the preview button.
@ -720,7 +722,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
""" """
if self.mediaForm.exec_(): if self.mediaForm.exec_():
for filename in self.mediaForm.getSelectedFiles(): for filename in self.mediaForm.getSelectedFiles():
item = QtGui.QListWidgetItem(os.path.split(unicode(filename))[1]) item = QtGui.QListWidgetItem(
os.path.split(unicode(filename))[1])
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, filename)
self.audioListWidget.addItem(item) self.audioListWidget.addItem(item)
@ -875,7 +878,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
os.remove(audio) os.remove(audio)
except: except:
log.exception('Could not remove file: %s', audio) log.exception('Could not remove file: %s', audio)
pass
if not files: if not files:
try: try:
os.rmdir(save_path) os.rmdir(save_path)

View File

@ -39,7 +39,7 @@ from openlp.core.lib import translate
from openlp.core.lib.db import BaseModel from openlp.core.lib.db import BaseModel
from openlp.core.ui.wizard import WizardStrings from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song from openlp.plugins.songs.lib import clean_song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile from openlp.plugins.songs.lib.db import Author, Book, MediaFile, Song, Topic
from songimport import SongImport from songimport import SongImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@ -31,7 +31,6 @@ backend for the Songs plugin
from sqlalchemy import Column, Table, types from sqlalchemy import Column, Table, types
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
from migrate import changeset
from migrate.changeset.constraint import ForeignKeyConstraint from migrate.changeset.constraint import ForeignKeyConstraint
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song

View File

@ -64,6 +64,9 @@ class SongsPlugin(Plugin):
self.icon_path = u':/plugins/plugin_songs.png' self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path) self.icon = build_icon(self.icon_path)
def checkPreConditions(self):
return self.manager.session is not None
def initialise(self): def initialise(self):
log.info(u'Songs Initialising') log.info(u'Songs Initialising')
Plugin.initialise(self) Plugin.initialise(self)

View File

@ -30,7 +30,6 @@ backend for the SongsUsage plugin
""" """
from sqlalchemy import Column, Table, types from sqlalchemy import Column, Table, types
from migrate import changeset
__version__ = 1 __version__ = 1

View File

@ -54,6 +54,9 @@ class SongUsagePlugin(Plugin):
self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png') self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
self.songUsageActive = False self.songUsageActive = False
def checkPreConditions(self):
return self.manager.session is not None
def addToolsMenuItem(self, tools_menu): def addToolsMenuItem(self, tools_menu):
""" """
Give the SongUsage plugin the opportunity to add items to the Give the SongUsage plugin the opportunity to add items to the

View File

@ -10,8 +10,9 @@ Homepage: http://openlp.org/
Package: openlp Package: openlp
Architecture: all Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-qt4, Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-qt4,
python-qt4-phonon, python-sqlalchemy, python-chardet, python-beautifulsoup, python-qt4-phonon, python-qt4-gl, python-sqlalchemy, python-chardet,
python-lxml, python-sqlite, python-enchant, python-mako, python-migrate python-beautifulsoup, python-lxml, python-sqlite, python-enchant,
python-mako, python-migrate
Conflicts: python-openlp Conflicts: python-openlp
Description: Church lyrics projection application Description: Church lyrics projection application
OpenLP is free church presentation software, or lyrics projection software, OpenLP is free church presentation software, or lyrics projection software,