This commit is contained in:
Andreas Preikschat 2011-09-18 12:28:53 +02:00
commit 2dad85f4c5
91 changed files with 10521 additions and 7548 deletions

View File

@ -25,254 +25,15 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import os
import sys
import logging
# Import uuid now, to avoid the rare bug described in the support system: # Import uuid now, to avoid the rare bug described in the support system:
# http://support.openlp.org/issues/102 # http://support.openlp.org/issues/102
# If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be # If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be
# removed. # removed.
import uuid import uuid
from optparse import OptionParser
from traceback import format_exception
from PyQt4 import QtCore, QtGui from openlp.core import main
from openlp.core.lib import Receiver, check_directory_exists
from openlp.core.lib.ui import UiStrings
from openlp.core.resources import qInitResources
from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.exceptionform import ExceptionForm
from openlp.core.ui import SplashScreen, ScreenList
from openlp.core.utils import AppLocation, LanguageManager, VersionThread, \
get_application_version, DelayStartThread
log = logging.getLogger()
application_stylesheet = u"""
QMainWindow::separator
{
border: none;
}
QDockWidget::title
{
border: 1px solid palette(dark);
padding-left: 5px;
padding-top: 2px;
margin: 1px 0;
}
QToolBar
{
border: none;
margin: 0;
padding: 0;
}
"""
class OpenLP(QtGui.QApplication):
"""
The core application class. This class inherits from Qt's QApplication
class in order to provide the core of the application.
"""
args = []
def exec_(self):
"""
Override exec method to allow the shared memory to be released on exit
"""
QtGui.QApplication.exec_()
self.sharedMemory.detach()
def run(self, args):
"""
Run the OpenLP application.
"""
# On Windows, the args passed into the constructor are
# ignored. Not very handy, so set the ones we want to use.
self.args.extend(args)
# provide a listener for widgets to reqest a screen update.
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_process_events'), self.processEvents)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cursor_busy'), self.setBusyCursor)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor)
# Decide how many screens we have and their size
screens = ScreenList(self.desktop())
# First time checks in settings
has_run_wizard = QtCore.QSettings().value(
u'general/has run wizard', QtCore.QVariant(False)).toBool()
if not has_run_wizard:
if FirstTimeForm(screens).exec_() == QtGui.QDialog.Accepted:
QtCore.QSettings().setValue(u'general/has run wizard',
QtCore.QVariant(True))
if os.name == u'nt':
self.setStyleSheet(application_stylesheet)
show_splash = QtCore.QSettings().value(
u'general/show splash', QtCore.QVariant(True)).toBool()
if show_splash:
self.splash = SplashScreen()
self.splash.show()
# make sure Qt really display the splash screen
self.processEvents()
# start the main app window
self.mainWindow = MainWindow(self.clipboard(), self.args)
self.mainWindow.show()
if show_splash:
# now kill the splashscreen
self.splash.finish(self.mainWindow)
log.debug(u'Splashscreen closed')
# make sure Qt really display the splash screen
self.processEvents()
self.mainWindow.repaint()
self.processEvents()
if not has_run_wizard:
self.mainWindow.firstTime()
update_check = QtCore.QSettings().value(
u'general/update check', QtCore.QVariant(True)).toBool()
if update_check:
VersionThread(self.mainWindow).start()
Receiver.send_message(u'maindisplay_blank_check')
self.mainWindow.appStartup()
DelayStartThread(self.mainWindow).start()
return self.exec_()
def isAlreadyRunning(self):
"""
Look to see if OpenLP is already running and ask if a 2nd copy
is to be started.
"""
self.sharedMemory = QtCore.QSharedMemory('OpenLP')
if self.sharedMemory.attach():
status = QtGui.QMessageBox.critical(None,
UiStrings().Error, UiStrings().OpenLPStart,
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No))
if status == QtGui.QMessageBox.No:
return True
return False
else:
self.sharedMemory.create(1)
return False
def hookException(self, exctype, value, traceback):
if not hasattr(self, u'mainWindow'):
log.exception(''.join(format_exception(exctype, value, traceback)))
return
if not hasattr(self, u'exceptionForm'):
self.exceptionForm = ExceptionForm(self.mainWindow)
self.exceptionForm.exceptionTextEdit.setPlainText(
''.join(format_exception(exctype, value, traceback)))
self.setNormalCursor()
self.exceptionForm.exec_()
def setBusyCursor(self):
"""
Sets the Busy Cursor for the Application
"""
self.setOverrideCursor(QtCore.Qt.BusyCursor)
self.processEvents()
def setNormalCursor(self):
"""
Sets the Normal Cursor for the Application
"""
self.restoreOverrideCursor()
def event(self, event):
"""
Enables direct file opening on OS X
"""
if event.type() == QtCore.QEvent.FileOpen:
file_name = event.file()
log.debug(u'Got open file event for %s!', file_name)
self.args.insert(0, unicode(file_name))
return True
else:
return QtGui.QApplication.event(self, event)
def main():
"""
The main function which parses command line options and then runs
the PyQt4 Application.
"""
# Set up command line options.
usage = 'Usage: %prog [options] [qt-options]'
parser = OptionParser(usage=usage)
parser.add_option('-e', '--no-error-form', dest='no_error_form',
action='store_true', help='Disable the error notification form.')
parser.add_option('-l', '--log-level', dest='loglevel',
default='warning', metavar='LEVEL', help='Set logging to LEVEL '
'level. Valid values are "debug", "info", "warning".')
parser.add_option('-p', '--portable', dest='portable',
action='store_true', help='Specify if this should be run as a '
'portable app, off a USB flash drive (not implemented).')
parser.add_option('-d', '--dev-version', dest='dev_version',
action='store_true', help='Ignore the version file and pull the '
'version directly from Bazaar')
parser.add_option('-s', '--style', dest='style',
help='Set the Qt4 style (passed directly to Qt4).')
# Set up logging
log_path = AppLocation.get_directory(AppLocation.CacheDir)
check_directory_exists(log_path)
filename = os.path.join(log_path, u'openlp.log')
logfile = logging.FileHandler(filename, u'w')
logfile.setFormatter(logging.Formatter(
u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
log.addHandler(logfile)
logging.addLevelName(15, u'Timer')
# Parse command line options and deal with them.
(options, args) = parser.parse_args()
qt_args = []
if options.loglevel.lower() in ['d', 'debug']:
log.setLevel(logging.DEBUG)
print 'Logging to:', filename
elif options.loglevel.lower() in ['w', 'warning']:
log.setLevel(logging.WARNING)
else:
log.setLevel(logging.INFO)
if options.style:
qt_args.extend(['-style', options.style])
# Throw the rest of the arguments at Qt, just in case.
qt_args.extend(args)
# Initialise the resources
qInitResources()
# Now create and actually run the application.
app = OpenLP(qt_args)
# Instance check
if app.isAlreadyRunning():
sys.exit()
app.setOrganizationName(u'OpenLP')
app.setOrganizationDomain(u'openlp.org')
app.setApplicationName(u'OpenLP')
app.setApplicationVersion(get_application_version()[u'version'])
# First time checks in settings
if not QtCore.QSettings().value(u'general/has run wizard',
QtCore.QVariant(False)).toBool():
if not FirstTimeLanguageForm().exec_():
# if cancel then stop processing
sys.exit()
if sys.platform == u'darwin':
OpenLP.addLibraryPath(QtGui.QApplication.applicationDirPath()
+ "/qt4_plugins")
# i18n Set Language
language = LanguageManager.get_language()
app_translator, default_translator = \
LanguageManager.get_translator(language)
if not app_translator.isEmpty():
app.installTranslator(app_translator)
if not default_translator.isEmpty():
app.installTranslator(default_translator)
else:
log.debug(u'Could not find default_translator.')
if not options.no_error_form:
sys.excepthook = app.hookException
sys.exit(app.run(qt_args))
if __name__ == u'__main__': if __name__ == u'__main__':
""" """

View File

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

View File

@ -24,9 +24,268 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
""" """
The :mod:`core` module provides all core application functions The :mod:`core` module provides all core application functions
All the core functions of the OpenLP application including the GUI, settings, All the core functions of the OpenLP application including the GUI, settings,
logging and a plugin framework are contained within the openlp.core module. logging and a plugin framework are contained within the openlp.core module.
""" """
import os
import sys
import logging
from optparse import OptionParser
from traceback import format_exception
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver, check_directory_exists
from openlp.core.lib.ui import UiStrings
from openlp.core.resources import qInitResources
from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.exceptionform import ExceptionForm
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()
application_stylesheet = u"""
QMainWindow::separator
{
border: none;
}
QDockWidget::title
{
border: 1px solid palette(dark);
padding-left: 5px;
padding-top: 2px;
margin: 1px 0;
}
QToolBar
{
border: none;
margin: 0;
padding: 0;
}
"""
class OpenLP(QtGui.QApplication):
"""
The core application class. This class inherits from Qt's QApplication
class in order to provide the core of the application.
"""
args = []
def exec_(self):
"""
Override exec method to allow the shared memory to be released on exit
"""
QtGui.QApplication.exec_()
self.sharedMemory.detach()
def run(self, args, testing=False):
"""
Run the OpenLP application.
"""
# On Windows, the args passed into the constructor are
# ignored. Not very handy, so set the ones we want to use.
self.args.extend(args)
# provide a listener for widgets to reqest a screen update.
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_process_events'), self.processEvents)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cursor_busy'), self.setBusyCursor)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor)
# Decide how many screens we have and their size
screens = ScreenList(self.desktop())
# First time checks in settings
has_run_wizard = QtCore.QSettings().value(
u'general/has run wizard', QtCore.QVariant(False)).toBool()
if not has_run_wizard:
if FirstTimeForm(screens).exec_() == QtGui.QDialog.Accepted:
QtCore.QSettings().setValue(u'general/has run wizard',
QtCore.QVariant(True))
if os.name == u'nt':
self.setStyleSheet(application_stylesheet)
show_splash = QtCore.QSettings().value(
u'general/show splash', QtCore.QVariant(True)).toBool()
if show_splash:
self.splash = SplashScreen()
self.splash.show()
# make sure Qt really display the splash screen
self.processEvents()
# start the main app window
self.mainWindow = MainWindow(self.clipboard(), self.args)
self.mainWindow.show()
if show_splash:
# now kill the splashscreen
self.splash.finish(self.mainWindow)
log.debug(u'Splashscreen closed')
# make sure Qt really display the splash screen
self.processEvents()
self.mainWindow.repaint()
self.processEvents()
if not has_run_wizard:
self.mainWindow.firstTime()
update_check = QtCore.QSettings().value(
u'general/update check', QtCore.QVariant(True)).toBool()
if update_check:
VersionThread(self.mainWindow).start()
Receiver.send_message(u'maindisplay_blank_check')
self.mainWindow.appStartup()
DelayStartThread(self.mainWindow).start()
# Skip exec_() for gui tests
if not testing:
return self.exec_()
def isAlreadyRunning(self):
"""
Look to see if OpenLP is already running and ask if a 2nd copy
is to be started.
"""
self.sharedMemory = QtCore.QSharedMemory('OpenLP')
if self.sharedMemory.attach():
status = QtGui.QMessageBox.critical(None,
UiStrings().Error, UiStrings().OpenLPStart,
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No))
if status == QtGui.QMessageBox.No:
return True
return False
else:
self.sharedMemory.create(1)
return False
def hookException(self, exctype, value, traceback):
if not hasattr(self, u'mainWindow'):
log.exception(''.join(format_exception(exctype, value, traceback)))
return
if not hasattr(self, u'exceptionForm'):
self.exceptionForm = ExceptionForm(self.mainWindow)
self.exceptionForm.exceptionTextEdit.setPlainText(
''.join(format_exception(exctype, value, traceback)))
self.setNormalCursor()
self.exceptionForm.exec_()
def setBusyCursor(self):
"""
Sets the Busy Cursor for the Application
"""
self.setOverrideCursor(QtCore.Qt.BusyCursor)
self.processEvents()
def setNormalCursor(self):
"""
Sets the Normal Cursor for the Application
"""
self.restoreOverrideCursor()
def event(self, event):
"""
Enables direct file opening on OS X
"""
if event.type() == QtCore.QEvent.FileOpen:
file_name = event.file()
log.debug(u'Got open file event for %s!', file_name)
self.args.insert(0, unicode(file_name))
return True
else:
return QtGui.QApplication.event(self, event)
def main(args=None):
"""
The main function which parses command line options and then runs
the PyQt4 Application.
"""
# Set up command line options.
usage = 'Usage: %prog [options] [qt-options]'
parser = OptionParser(usage=usage)
parser.add_option('-e', '--no-error-form', dest='no_error_form',
action='store_true', help='Disable the error notification form.')
parser.add_option('-l', '--log-level', dest='loglevel',
default='warning', metavar='LEVEL', help='Set logging to LEVEL '
'level. Valid values are "debug", "info", "warning".')
parser.add_option('-p', '--portable', dest='portable',
action='store_true', help='Specify if this should be run as a '
'portable app, off a USB flash drive (not implemented).')
parser.add_option('-d', '--dev-version', dest='dev_version',
action='store_true', help='Ignore the version file and pull the '
'version directly from Bazaar')
parser.add_option('-s', '--style', dest='style',
help='Set the Qt4 style (passed directly to Qt4).')
parser.add_option('--testing', dest='testing',
action='store_true', help='Run by testing framework')
# Set up logging
log_path = AppLocation.get_directory(AppLocation.CacheDir)
check_directory_exists(log_path)
filename = os.path.join(log_path, u'openlp.log')
logfile = logging.FileHandler(filename, u'w')
logfile.setFormatter(logging.Formatter(
u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
log.addHandler(logfile)
# Parse command line options and deal with them.
# Use args supplied programatically if possible.
(options, args) = parser.parse_args(args) if args else parser.parse_args()
qt_args = []
if options.loglevel.lower() in ['d', 'debug']:
log.setLevel(logging.DEBUG)
print 'Logging to:', filename
elif options.loglevel.lower() in ['w', 'warning']:
log.setLevel(logging.WARNING)
else:
log.setLevel(logging.INFO)
if options.style:
qt_args.extend(['-style', options.style])
# Throw the rest of the arguments at Qt, just in case.
qt_args.extend(args)
# Initialise the resources
qInitResources()
# Now create and actually run the application.
app = OpenLP(qt_args)
app.setOrganizationName(u'OpenLP')
app.setOrganizationDomain(u'openlp.org')
app.setApplicationName(u'OpenLP')
app.setApplicationVersion(get_application_version()[u'version'])
# Instance check
if not options.testing:
# Instance check
if app.isAlreadyRunning():
sys.exit()
# First time checks in settings
if not QtCore.QSettings().value(u'general/has run wizard',
QtCore.QVariant(False)).toBool():
if not FirstTimeLanguageForm().exec_():
# if cancel then stop processing
sys.exit()
# i18n Set Language
language = LanguageManager.get_language()
app_translator, default_translator = \
LanguageManager.get_translator(language)
if not app_translator.isEmpty():
app.installTranslator(app_translator)
if not default_translator.isEmpty():
app.installTranslator(default_translator)
else:
log.debug(u'Could not find default_translator.')
if not options.no_error_form:
sys.excepthook = app.hookException
# Do not run method app.exec_() when running gui tests
if options.testing:
app.run(qt_args, testing=True)
# For gui tests we need access to window intances and their components
return app
else:
sys.exit(app.run(qt_args))

View File

@ -36,6 +36,13 @@ from PyQt4 import QtCore, QtGui
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class MediaType(object):
"""
An enumeration class for types of media.
"""
Audio = 1
Video = 2
def translate(context, text, comment=None, def translate(context, text, comment=None,
encoding=QtCore.QCoreApplication.CodecForTr, n=-1, encoding=QtCore.QCoreApplication.CodecForTr, n=-1,
translate=QtCore.QCoreApplication.translate): translate=QtCore.QCoreApplication.translate):
@ -137,7 +144,7 @@ def image_to_byte(image):
# convert to base64 encoding so does not get missed! # convert to base64 encoding so does not get missed!
return byte_array.toBase64() return byte_array.toBase64()
def resize_image(image_path, width, height, background=QtCore.Qt.black): def resize_image(image_path, width, height, background):
""" """
Resize an image to fit on the current screen. Resize an image to fit on the current screen.
@ -241,9 +248,7 @@ from settingsmanager import SettingsManager
from plugin import PluginStatus, StringContent, Plugin from plugin import PluginStatus, StringContent, Plugin
from pluginmanager import PluginManager from pluginmanager import PluginManager
from settingstab import SettingsTab from settingstab import SettingsTab
from serviceitem import ServiceItem from serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
from serviceitem import ServiceItemType
from serviceitem import ItemCapabilities
from htmlbuilder import build_html, build_lyrics_format_css, \ from htmlbuilder import build_html, build_lyrics_format_css, \
build_lyrics_outline_css build_lyrics_outline_css
from toolbar import OpenLPToolbar from toolbar import OpenLPToolbar

View File

@ -31,11 +31,13 @@ import logging
import os import os
from PyQt4 import QtCore from PyQt4 import QtCore
from sqlalchemy import create_engine, MetaData from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.exc import InvalidRequestError from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker, mapper
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.utils import AppLocation, delete_file from openlp.core.utils import AppLocation, delete_file
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -59,6 +61,64 @@ def init_db(url, auto_flush=True, auto_commit=False):
autocommit=auto_commit, bind=engine)) autocommit=auto_commit, bind=engine))
return session, metadata return session, metadata
def upgrade_db(url, upgrade):
"""
Upgrade a database.
``url``
The url of the database to upgrade.
``upgrade``
The python module that contains the upgrade instructions.
"""
session, metadata = init_db(url)
class Metadata(BaseModel):
"""
Provides a class for the metadata table.
"""
pass
load_changes = True
try:
tables = upgrade.upgrade_setup(metadata)
except (SQLAlchemyError, DBAPIError):
load_changes = False
metadata_table = Table(u'metadata', metadata,
Column(u'key', types.Unicode(64), primary_key=True),
Column(u'value', types.UnicodeText(), default=None)
)
metadata_table.create(checkfirst=True)
mapper(Metadata, metadata_table)
version_meta = session.query(Metadata).get(u'version')
if version_meta is None:
version_meta = Metadata.populate(key=u'version', value=u'0')
version = 0
else:
version = int(version_meta.value)
if version > upgrade.__version__:
return version, upgrade.__version__
version += 1
if load_changes:
while hasattr(upgrade, u'upgrade_%d' % version):
log.debug(u'Running upgrade_%d', version)
try:
getattr(upgrade, u'upgrade_%d' % version) \
(session, metadata, tables)
version_meta.value = unicode(version)
except (SQLAlchemyError, DBAPIError):
log.exception(u'Could not run database upgrade script '
'"upgrade_%s", upgrade process has been halted.', version)
break
version += 1
else:
version_meta = Metadata.populate(key=u'version',
value=int(upgrade.__version__))
session.add(version_meta)
session.commit()
return int(version_meta.value), upgrade.__version__
def delete_database(plugin_name, db_file_name=None): def delete_database(plugin_name, db_file_name=None):
""" """
Remove a database file from the system. Remove a database file from the system.
@ -79,6 +139,7 @@ def delete_database(plugin_name, db_file_name=None):
AppLocation.get_section_data_path(plugin_name), plugin_name) AppLocation.get_section_data_path(plugin_name), plugin_name)
return delete_file(db_file_path) return delete_file(db_file_path)
class BaseModel(object): class BaseModel(object):
""" """
BaseModel provides a base object with a set of generic functions BaseModel provides a base object with a set of generic functions
@ -93,12 +154,12 @@ class BaseModel(object):
instance.__setattr__(key, value) instance.__setattr__(key, value)
return instance return instance
class Manager(object): class Manager(object):
""" """
Provide generic object persistence management Provide generic object persistence management
""" """
def __init__(self, plugin_name, init_schema, db_file_name=None): def __init__(self, plugin_name, init_schema, db_file_name=None,
upgrade_mod=None):
""" """
Runs the initialisation process that includes creating the connection Runs the initialisation process that includes creating the connection
to the database and the tables if they don't exist. to the database and the tables if they don't exist.
@ -109,6 +170,9 @@ class Manager(object):
``init_schema`` ``init_schema``
The init_schema function for this database The init_schema function for this database
``upgrade_schema``
The upgrade_schema function for this database
``db_file_name`` ``db_file_name``
The file name to use for this database. Defaults to None resulting The file name to use for this database. Defaults to None resulting
in the plugin_name being used. in the plugin_name being used.
@ -134,7 +198,28 @@ class Manager(object):
unicode(settings.value(u'db hostname').toString()), unicode(settings.value(u'db hostname').toString()),
unicode(settings.value(u'db database').toString())) unicode(settings.value(u'db database').toString()))
settings.endGroup() settings.endGroup()
self.session = init_schema(self.db_url) if upgrade_mod:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
if db_ver > up_ver:
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'The database being '
'loaded was created in a more recent version of '
'OpenLP. The database is version %d, while OpenLP '
'expects version %d. The database will not be loaded.'
'\n\nDatabase: %s')) % \
(db_ver, up_ver, self.db_url)
)
return
try:
self.session = init_schema(self.db_url)
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 '
'database.\n\nDatabase: %s')) % self.db_url
)
def save_object(self, object_instance, commit=True): def save_object(self, object_instance, commit=True):
""" """

View File

@ -36,7 +36,7 @@ import Queue
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib import resize_image, image_to_byte from openlp.core.lib import resize_image, image_to_byte, Receiver
from openlp.core.ui import ScreenList from openlp.core.ui import ScreenList
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -100,12 +100,14 @@ class Image(object):
variables ``image`` and ``image_bytes`` to ``None`` and add the image object variables ``image`` and ``image_bytes`` to ``None`` and add the image object
to the queue of images to process. to the queue of images to process.
""" """
def __init__(self, name='', path=''): def __init__(self, name, path, source, background):
self.name = name self.name = name
self.path = path self.path = path
self.image = None self.image = None
self.image_bytes = None self.image_bytes = None
self.priority = Priority.Normal self.priority = Priority.Normal
self.source = source
self.background = background
class PriorityQueue(Queue.PriorityQueue): class PriorityQueue(Queue.PriorityQueue):
@ -151,6 +153,8 @@ class ImageManager(QtCore.QObject):
self._cache = {} self._cache = {}
self._imageThread = ImageThread(self) self._imageThread = ImageThread(self)
self._conversion_queue = PriorityQueue() self._conversion_queue = PriorityQueue()
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'config_updated'), self.process_updates)
def update_display(self): def update_display(self):
""" """
@ -162,12 +166,42 @@ class ImageManager(QtCore.QObject):
self.height = current_screen[u'size'].height() self.height = current_screen[u'size'].height()
# Mark the images as dirty for a rebuild by setting the image and byte # Mark the images as dirty for a rebuild by setting the image and byte
# stream to None. # stream to None.
self._conversion_queue = PriorityQueue()
for key, image in self._cache.iteritems(): for key, image in self._cache.iteritems():
image.priority = Priority.Normal self._reset_image(image)
image.image = None
image.image_bytes = None def update_images(self, image_type, background):
self._conversion_queue.put((image.priority, image)) """
Border has changed so update all the images affected.
"""
log.debug(u'update_images')
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
for key, image in self._cache.iteritems():
if image.source == image_type:
image.background = background
self._reset_image(image)
def update_image(self, name, image_type, background):
"""
Border has changed so update the image affected.
"""
log.debug(u'update_images')
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
for key, image in self._cache.iteritems():
if image.source == image_type and image.name == name:
image.background = background
self._reset_image(image)
def _reset_image(self, image):
image.image = None
image.image_bytes = None
self._conversion_queue.modify_priority(image, Priority.Normal)
def process_updates(self):
"""
Flush the queue to updated any data to update
"""
# We want only one thread. # We want only one thread.
if not self._imageThread.isRunning(): if not self._imageThread.isRunning():
self._imageThread.start() self._imageThread.start()
@ -215,13 +249,13 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.remove(self._cache[name]) self._conversion_queue.remove(self._cache[name])
del self._cache[name] del self._cache[name]
def add_image(self, name, path): def add_image(self, name, path, source, background):
""" """
Add image to cache if it is not already there. Add image to cache if it is not already there.
""" """
log.debug(u'add_image %s:%s' % (name, path)) log.debug(u'add_image %s:%s' % (name, path))
if not name in self._cache: if not name in self._cache:
image = Image(name, path) image = Image(name, path, source, background)
self._cache[name] = image self._cache[name] = image
self._conversion_queue.put((image.priority, image)) self._conversion_queue.put((image.priority, image))
else: else:
@ -247,7 +281,8 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[1] image = self._conversion_queue.get()[1]
# Generate the QImage for the image. # Generate the QImage for the image.
if image.image is None: if image.image is None:
image.image = resize_image(image.path, self.width, self.height) image.image = resize_image(image.path, self.width, self.height,
image.background)
# Set the priority to Lowest and stop here as we need to process # Set the priority to Lowest and stop here as we need to process
# more important images first. # more important images first.
if image.priority == Priority.Normal: if image.priority == Priority.Normal:

View File

@ -111,7 +111,7 @@ class MediaManagerItem(QtGui.QWidget):
self.requiredIcons() self.requiredIcons()
self.setupUi() self.setupUi()
self.retranslateUi() self.retranslateUi()
self.auto_select_id = -1 self.autoSelectId = -1
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'%s_service_load' % self.plugin.name), QtCore.SIGNAL(u'%s_service_load' % self.plugin.name),
self.serviceLoad) self.serviceLoad)
@ -376,18 +376,23 @@ class MediaManagerItem(QtGui.QWidget):
The files to be loaded The files to be loaded
""" """
names = [] names = []
fullList = []
for count in range(0, self.listView.count()): for count in range(0, self.listView.count()):
names.append(self.listView.item(count).text()) names.append(unicode(self.listView.item(count).text()))
newFiles = [] fullList.append(unicode(self.listView.item(count).
data(QtCore.Qt.UserRole).toString()))
duplicatesFound = False duplicatesFound = False
filesAdded = False
for file in files: for file in files:
filename = os.path.split(unicode(file))[1] filename = os.path.split(unicode(file))[1]
if filename in names: if filename in names:
duplicatesFound = True duplicatesFound = True
else: else:
newFiles.append(file) filesAdded = True
if newFiles: fullList.append(file)
self.loadList(newFiles) if fullList and filesAdded:
self.listView.clear()
self.loadList(fullList)
lastDir = os.path.split(unicode(files[0]))[0] lastDir = os.path.split(unicode(files[0]))[0]
SettingsManager.set_last_dir(self.settingsSection, lastDir) SettingsManager.set_last_dir(self.settingsSection, lastDir)
SettingsManager.set_list(self.settingsSection, SettingsManager.set_list(self.settingsSection,
@ -396,7 +401,7 @@ class MediaManagerItem(QtGui.QWidget):
critical_error_message_box( critical_error_message_box(
UiStrings().Duplicate, UiStrings().Duplicate,
unicode(translate('OpenLP.MediaManagerItem', unicode(translate('OpenLP.MediaManagerItem',
'Duplicate files found on import and ignored.'))) 'Duplicate files were found on import and were ignored.')))
def contextMenu(self, point): def contextMenu(self, point):
item = self.listView.itemAt(point) item = self.listView.itemAt(point)
@ -485,7 +490,8 @@ class MediaManagerItem(QtGui.QWidget):
""" """
pass pass
def generateSlideData(self, serviceItem, item=None, xmlVersion=False): def generateSlideData(self, serviceItem, item=None, xmlVersion=False,
remote=False):
raise NotImplementedError(u'MediaManagerItem.generateSlideData needs ' raise NotImplementedError(u'MediaManagerItem.generateSlideData needs '
u'to be defined by the plugin') u'to be defined by the plugin')
@ -506,7 +512,7 @@ class MediaManagerItem(QtGui.QWidget):
if QtCore.QSettings().value(u'advanced/single click preview', if QtCore.QSettings().value(u'advanced/single click preview',
QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \ QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \
and self.listView.selectedIndexes() \ and self.listView.selectedIndexes() \
and self.auto_select_id == -1: and self.autoSelectId == -1:
self.onPreviewClick(True) self.onPreviewClick(True)
def onPreviewClick(self, keepFocus=False): def onPreviewClick(self, keepFocus=False):
@ -539,12 +545,12 @@ class MediaManagerItem(QtGui.QWidget):
else: else:
self.goLive() self.goLive()
def goLive(self, item_id=None): def goLive(self, item_id=None, remote=False):
log.debug(u'%s Live requested', self.plugin.name) log.debug(u'%s Live requested', self.plugin.name)
item = None item = None
if item_id: if item_id:
item = self.createItemFromId(item_id) item = self.createItemFromId(item_id)
serviceItem = self.buildServiceItem(item) serviceItem = self.buildServiceItem(item, remote=remote)
if serviceItem: if serviceItem:
if not item_id: if not item_id:
serviceItem.from_plugin = True serviceItem.from_plugin = True
@ -574,8 +580,8 @@ class MediaManagerItem(QtGui.QWidget):
for item in items: for item in items:
self.addToService(item) self.addToService(item)
def addToService(self, item=None, replace=None): def addToService(self, item=None, replace=None, remote=False):
serviceItem = self.buildServiceItem(item, True) serviceItem = self.buildServiceItem(item, True, remote=remote)
if serviceItem: if serviceItem:
serviceItem.from_plugin = False serviceItem.from_plugin = False
self.plugin.serviceManager.addServiceItem(serviceItem, self.plugin.serviceManager.addServiceItem(serviceItem,
@ -608,13 +614,13 @@ class MediaManagerItem(QtGui.QWidget):
unicode(translate('OpenLP.MediaManagerItem', unicode(translate('OpenLP.MediaManagerItem',
'You must select a %s service item.')) % self.title) 'You must select a %s service item.')) % self.title)
def buildServiceItem(self, item=None, xmlVersion=False): def buildServiceItem(self, item=None, xmlVersion=False, remote=False):
""" """
Common method for generating a service item Common method for generating a service item
""" """
serviceItem = ServiceItem(self.plugin) serviceItem = ServiceItem(self.plugin)
serviceItem.add_icon(self.plugin.icon_path) serviceItem.add_icon(self.plugin.icon_path)
if self.generateSlideData(serviceItem, item, xmlVersion): if self.generateSlideData(serviceItem, item, xmlVersion, remote):
return serviceItem return serviceItem
else: else:
return None return None
@ -626,7 +632,7 @@ class MediaManagerItem(QtGui.QWidget):
""" """
pass pass
def check_search_result(self): def checkSearchResult(self):
""" """
Checks if the listView is empty and adds a "No Search Results" item. Checks if the listView is empty and adds a "No Search Results" item.
""" """
@ -662,15 +668,15 @@ class MediaManagerItem(QtGui.QWidget):
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
return item_id return item_id
def save_auto_select_id(self): def saveAutoSelectId(self):
""" """
Sorts out, what item to select after loading a list. Sorts out, what item to select after loading a list.
""" """
# The item to select has not been set. # The item to select has not been set.
if self.auto_select_id == -1: if self.autoSelectId == -1:
item = self.listView.currentItem() item = self.listView.currentItem()
if item: 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): def search(self, string):
""" """

View File

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

View File

@ -27,7 +27,7 @@
import logging import logging
from PyQt4 import QtCore, QtWebKit from PyQt4 import QtGui, QtCore, QtWebKit
from openlp.core.lib import ServiceItem, expand_tags, \ from openlp.core.lib import ServiceItem, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \ build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
@ -166,7 +166,8 @@ class Renderer(object):
# if No file do not update cache # if No file do not update cache
if self.theme_data.background_filename: if self.theme_data.background_filename:
self.imageManager.add_image(self.theme_data.theme_name, self.imageManager.add_image(self.theme_data.theme_name,
self.theme_data.background_filename) self.theme_data.background_filename, u'theme',
QtGui.QColor(self.theme_data.background_border_color))
return self._rect, self._rect_footer return self._rect, self._rect_footer
def generate_preview(self, theme_data, force_page=False): def generate_preview(self, theme_data, force_page=False):
@ -221,21 +222,56 @@ class Renderer(object):
if item.is_capable(ItemCapabilities.NoLineBreaks): if item.is_capable(ItemCapabilities.NoLineBreaks):
line_end = u' ' line_end = u' '
# Bibles # Bibles
if item.is_capable(ItemCapabilities.AllowsWordSplit): if item.is_capable(ItemCapabilities.CanWordSplit):
pages = self._paginate_slide_words(text.split(u'\n'), line_end) pages = self._paginate_slide_words(text.split(u'\n'), line_end)
else: else:
# Clean up line endings. # Clean up line endings.
lines = self._lines_split(text) lines = self._lines_split(text)
pages = self._paginate_slide(lines, line_end) pages = self._paginate_slide(lines, line_end)
if len(pages) > 1: # Songs and Custom
# Songs and Custom if item.is_capable(ItemCapabilities.CanSoftBreak) and \
if item.is_capable(ItemCapabilities.AllowsVirtualSplit): len(pages) > 1 and u'[---]' in text:
# Do not forget the line breaks! pages = []
slides = text.split(u'[---]') while True:
pages = [] slides = text.split(u'\n[---]\n', 2)
for slide in slides: # If there are (at least) two occurrences of [---] we use
lines = slide.strip(u'\n').split(u'\n') # the first two slides (and neglect the last for now).
if len(slides) == 3:
html_text = expand_tags(u'\n'.join(slides[:2]))
# We check both slides to determine if the virtual break is
# needed (there is only one virtual break).
else:
html_text = expand_tags(u'\n'.join(slides))
html_text = html_text.replace(u'\n', u'<br>')
if self._text_fits_on_slide(html_text):
# The first two virtual slides fit (as a whole) on one
# slide. Replace the first occurrence of [---].
text = text.replace(u'\n[---]', u'', 1)
else:
# The first virtual slide fits, which means we have to
# render the first virtual slide.
text_contains_break = u'[---]' in text
if text_contains_break:
text_to_render, text = text.split(u'\n[---]\n', 1)
else:
text_to_render = text
text = u''
lines = text_to_render.strip(u'\n').split(u'\n')
slides = self._paginate_slide(lines, line_end)
if len(slides) > 1 and text:
# Add all slides apart from the last one the list.
pages.extend(slides[:-1])
if text_contains_break:
text = slides[-1] + u'\n[---]\n' + text
else:
text = slides[-1] + u'\n'+ text
text = text.replace(u'<br>', u'\n')
else:
pages.extend(slides)
if u'[---]' not in text:
lines = text.strip(u'\n').split(u'\n')
pages.extend(self._paginate_slide(lines, line_end)) pages.extend(self._paginate_slide(lines, line_end))
break
new_pages = [] new_pages = []
for page in pages: for page in pages:
while page.endswith(u'<br>'): while page.endswith(u'<br>'):
@ -341,7 +377,7 @@ class Renderer(object):
separator = u'<br>' separator = u'<br>'
html_lines = map(expand_tags, lines) html_lines = map(expand_tags, lines)
# Text too long so go to next page. # Text too long so go to next page.
if self._text_fits_on_slide(separator.join(html_lines)): if not self._text_fits_on_slide(separator.join(html_lines)):
html_text, previous_raw = self._binary_chop(formatted, html_text, previous_raw = self._binary_chop(formatted,
previous_html, previous_raw, html_lines, lines, separator, u'') previous_html, previous_raw, html_lines, lines, separator, u'')
else: else:
@ -374,18 +410,18 @@ class Renderer(object):
line = line.strip() line = line.strip()
html_line = expand_tags(line) html_line = expand_tags(line)
# Text too long so go to next page. # Text too long so go to next page.
if self._text_fits_on_slide(previous_html + html_line): if not self._text_fits_on_slide(previous_html + html_line):
# Check if there was a verse before the current one and append # Check if there was a verse before the current one and append
# it, when it fits on the page. # it, when it fits on the page.
if previous_html: if previous_html:
if not self._text_fits_on_slide(previous_html): if self._text_fits_on_slide(previous_html):
formatted.append(previous_raw) formatted.append(previous_raw)
previous_html = u'' previous_html = u''
previous_raw = u'' previous_raw = u''
# Now check if the current verse will fit, if it does # Now check if the current verse will fit, if it does
# not we have to start to process the verse word by # not we have to start to process the verse word by
# word. # word.
if not self._text_fits_on_slide(html_line): if self._text_fits_on_slide(html_line):
previous_html = html_line + line_end previous_html = html_line + line_end
previous_raw = line + line_end previous_raw = line + line_end
continue continue
@ -442,7 +478,7 @@ class Renderer(object):
highest_index = len(html_list) - 1 highest_index = len(html_list) - 1
index = int(highest_index / 2) index = int(highest_index / 2)
while True: while True:
if self._text_fits_on_slide( if not self._text_fits_on_slide(
previous_html + separator.join(html_list[:index + 1]).strip()): previous_html + separator.join(html_list[:index + 1]).strip()):
# We know that it does not fit, so change/calculate the # We know that it does not fit, so change/calculate the
# new index and highest_index accordingly. # new index and highest_index accordingly.
@ -465,8 +501,8 @@ class Renderer(object):
else: else:
continue continue
# Check if the remaining elements fit on the slide. # Check if the remaining elements fit on the slide.
if not self._text_fits_on_slide( if self._text_fits_on_slide(
separator.join(html_list[index + 1:]).strip()): separator.join(html_list[index + 1:]).strip()):
previous_html = separator.join( previous_html = separator.join(
html_list[index + 1:]).strip() + line_end html_list[index + 1:]).strip() + line_end
previous_raw = separator.join( previous_raw = separator.join(
@ -488,11 +524,11 @@ class Renderer(object):
returned, otherwise ``False``. returned, otherwise ``False``.
``text`` ``text``
The text to check. It can contain HTML tags. The text to check. It may contain HTML tags.
""" """
self.web_frame.evaluateJavaScript(u'show_text("%s")' % self.web_frame.evaluateJavaScript(u'show_text("%s")' %
text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
return self.web_frame.contentsSize().height() > self.page_height return self.web_frame.contentsSize().height() <= self.page_height
def _words_split(self, line): def _words_split(self, line):
""" """

View File

@ -52,20 +52,21 @@ class ItemCapabilities(object):
""" """
Provides an enumeration of a serviceitem's capabilities Provides an enumeration of a serviceitem's capabilities
""" """
AllowsPreview = 1 CanPreview = 1
AllowsEdit = 2 CanEdit = 2
AllowsMaintain = 3 CanMaintain = 3
RequiresMedia = 4 RequiresMedia = 4
AllowsLoop = 5 CanLoop = 5
AllowsAdditions = 6 CanAppend = 6
NoLineBreaks = 7 NoLineBreaks = 7
OnLoadUpdate = 8 OnLoadUpdate = 8
AddIfNewItem = 9 AddIfNewItem = 9
ProvidesOwnDisplay = 10 ProvidesOwnDisplay = 10
AllowsDetailedTitleDisplay = 11 HasDetailedTitleDisplay = 11
AllowsVariableStartTime = 12 HasVariableStartTime = 12
AllowsVirtualSplit = 13 CanSoftBreak = 13
AllowsWordSplit = 14 CanWordSplit = 14
HasBackgroundAudio = 15
class ServiceItem(object): class ServiceItem(object):
@ -115,6 +116,8 @@ class ServiceItem(object):
self.end_time = 0 self.end_time = 0
self.media_length = 0 self.media_length = 0
self.from_service = False self.from_service = False
self.image_border = u'#000000'
self.background_audio = []
self._new_item() self._new_item()
def _new_item(self): def _new_item(self):
@ -158,7 +161,7 @@ class ServiceItem(object):
""" """
The render method is what generates the frames for the screen and The render method is what generates the frames for the screen and
obtains the display information from the renderemanager. 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. display size.
""" """
log.debug(u'Render called') log.debug(u'Render called')
@ -195,7 +198,7 @@ class ServiceItem(object):
self.foot_text = \ self.foot_text = \
u'<br>'.join([footer for footer in self.raw_footer if footer]) u'<br>'.join([footer for footer in self.raw_footer if footer])
def add_from_image(self, path, title): def add_from_image(self, path, title, background=None):
""" """
Add an image slide to the service item. Add an image slide to the service item.
@ -205,9 +208,12 @@ class ServiceItem(object):
``title`` ``title``
A title for the slide in the service item. A title for the slide in the service item.
""" """
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path}) self._raw_frames.append({u'title': title, u'path': path})
self.renderer.imageManager.add_image(title, path) self.renderer.imageManager.add_image(title, path, u'image',
self.image_border)
self._new_item() self._new_item()
def add_from_text(self, title, raw_slide, verse_tag=None): def add_from_text(self, title, raw_slide, verse_tag=None):
@ -268,7 +274,8 @@ class ServiceItem(object):
u'xml_version': self.xml_version, u'xml_version': self.xml_version,
u'start_time': self.start_time, u'start_time': self.start_time,
u'end_time': self.end_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 = [] service_data = []
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
@ -316,6 +323,8 @@ class ServiceItem(object):
self.end_time = header[u'end_time'] self.end_time = header[u'end_time']
if u'media_length' in header: if u'media_length' in header:
self.media_length = header[u'media_length'] 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: if self.service_item_type == ServiceItemType.Text:
for slide in serviceitem[u'serviceitem'][u'data']: for slide in serviceitem[u'serviceitem'][u'data']:
self._raw_frames.append(slide) self._raw_frames.append(slide)
@ -337,7 +346,7 @@ class ServiceItem(object):
if self.is_text(): if self.is_text():
return self.title return self.title
else: else:
if ItemCapabilities.AllowsDetailedTitleDisplay in self.capabilities: if ItemCapabilities.HasDetailedTitleDisplay in self.capabilities:
return self._raw_frames[0][u'title'] return self._raw_frames[0][u'title']
elif len(self._raw_frames) > 1: elif len(self._raw_frames) > 1:
return self.title return self.title
@ -355,6 +364,8 @@ class ServiceItem(object):
""" """
self._uuid = other._uuid self._uuid = other._uuid
self.notes = other.notes self.notes = other.notes
if self.is_capable(ItemCapabilities.HasBackgroundAudio):
log.debug(self.background_audio)
def __eq__(self, other): def __eq__(self, other):
""" """

View File

@ -44,6 +44,7 @@ BLANK_THEME_XML = \
<name> </name> <name> </name>
<background type="image"> <background type="image">
<filename></filename> <filename></filename>
<borderColor>#000000</borderColor>
</background> </background>
<background type="gradient"> <background type="gradient">
<startColor>#000000</startColor> <startColor>#000000</startColor>
@ -282,7 +283,7 @@ class ThemeXML(object):
# Create direction element # Create direction element
self.child_element(background, u'direction', unicode(direction)) self.child_element(background, u'direction', unicode(direction))
def add_background_image(self, filename): def add_background_image(self, filename, borderColor):
""" """
Add a image background. Add a image background.
@ -294,6 +295,8 @@ class ThemeXML(object):
self.theme.appendChild(background) self.theme.appendChild(background)
# Create Filename element # Create Filename element
self.child_element(background, u'filename', filename) self.child_element(background, u'filename', filename)
# Create endColor element
self.child_element(background, u'borderColor', unicode(borderColor))
def add_font(self, name, color, size, override, fonttype=u'main', def add_font(self, name, color, size, override, fonttype=u'main',
bold=u'False', italics=u'False', line_adjustment=0, bold=u'False', italics=u'False', line_adjustment=0,
@ -597,7 +600,7 @@ class ThemeXML(object):
self.background_direction) self.background_direction)
else: else:
filename = os.path.split(self.background_filename)[1] filename = os.path.split(self.background_filename)[1]
self.add_background_image(filename) self.add_background_image(filename, self.background_border_color)
self.add_font(self.font_main_name, self.add_font(self.font_main_name,
self.font_main_color, self.font_main_color,
self.font_main_size, self.font_main_size,

View File

@ -116,7 +116,7 @@ class Ui_AboutDialog(object):
u'Scott "sguerrieri" Guerrieri', u'Scott "sguerrieri" Guerrieri',
u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan', u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan',
u'Armin "orangeshirt" K\xf6hler', u'Joshua "milleja46" Miller', u'Armin "orangeshirt" K\xf6hler', u'Joshua "milleja46" Miller',
u'Stevan "StevanP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru', u'Stevan "ElderP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru',
u'Christian "crichter" Richter', u'Philip "Phill" Ridout', u'Christian "crichter" Richter', u'Philip "Phill" Ridout',
u'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith', u'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith',
u'Maikel Stuivenberg', u'Frode "frodus" Woldsund'] u'Maikel Stuivenberg', u'Frode "frodus" Woldsund']
@ -125,7 +125,7 @@ class Ui_AboutDialog(object):
packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)', packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)',
u'Tim "TRB143" Bentley (Fedora)', u'Tim "TRB143" Bentley (Fedora)',
u'Matthias "matthub" Hub (Mac OS X)', u'Matthias "matthub" Hub (Mac OS X)',
u'Stevan "StevanP" Pettit (Windows)', u'Stevan "ElderP" Pettit (Windows)',
u'Raoul "superfly" Snyman (Ubuntu)'] u'Raoul "superfly" Snyman (Ubuntu)']
translators = { translators = {
u'af': [u'Johan "nuvolari" Mynhardt'], u'af': [u'Johan "nuvolari" Mynhardt'],

View File

@ -28,7 +28,9 @@
import io import io
import logging import logging
import os import os
import sys
import urllib import urllib
import urllib2
from tempfile import gettempdir from tempfile import gettempdir
from ConfigParser import SafeConfigParser from ConfigParser import SafeConfigParser
@ -60,8 +62,13 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
files = self.webAccess.read() files = self.webAccess.read()
self.config.readfp(io.BytesIO(files)) self.config.readfp(io.BytesIO(files))
self.updateScreenListCombo() self.updateScreenListCombo()
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()'),
self.onCancelButtonClicked)
QtCore.QObject.connect(self.noInternetFinishButton,
QtCore.SIGNAL('clicked()'), self.onNoInternetFinishButtonClicked)
QtCore.QObject.connect(self, QtCore.QObject.connect(self,
QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
@ -80,6 +87,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
""" """
self.restart() self.restart()
check_directory_exists(os.path.join(gettempdir(), u'openlp')) check_directory_exists(os.path.join(gettempdir(), u'openlp'))
self.noInternetFinishButton.setVisible(False)
# Check if this is a re-run of the wizard.
self.hasRunWizard = QtCore.QSettings().value(
u'general/has run wizard', QtCore.QVariant(False)).toBool()
# Sort out internet access for downloads # Sort out internet access for downloads
if self.webAccess: if self.webAccess:
songs = self.config.get(u'songs', u'languages') songs = self.config.get(u'songs', u'languages')
@ -120,7 +131,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
title = self.config.get(u'theme_%s' % theme, u'title') title = self.config.get(u'theme_%s' % theme, u'title')
filename = self.config.get(u'theme_%s' % theme, u'filename') filename = self.config.get(u'theme_%s' % theme, u'filename')
screenshot = self.config.get(u'theme_%s' % theme, u'screenshot') screenshot = self.config.get(u'theme_%s' % theme, u'screenshot')
urllib.urlretrieve(u'%s/%s' % (self.web, screenshot), urllib.urlretrieve(u'%s%s' % (self.web, screenshot),
os.path.join(gettempdir(), u'openlp', screenshot)) os.path.join(gettempdir(), u'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.themesListWidget) item = QtGui.QListWidgetItem(title, self.themesListWidget)
item.setData(QtCore.Qt.UserRole, item.setData(QtCore.Qt.UserRole,
@ -135,6 +146,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
""" """
Determine the next page in the Wizard to go to. Determine the next page in the Wizard to go to.
""" """
Receiver.send_message(u'openlp_process_events')
if self.currentId() == FirstTimePage.Plugins: if self.currentId() == FirstTimePage.Plugins:
if not self.webAccess: if not self.webAccess:
return FirstTimePage.NoInternet return FirstTimePage.NoInternet
@ -151,16 +163,24 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
""" """
Detects Page changes and updates as approprate. Detects Page changes and updates as approprate.
""" """
if pageId == FirstTimePage.Defaults: # Keep track of the page we are at. Pressing "Cancel" causes pageId
# to be a -1.
if pageId != -1:
self.lastId = pageId
if pageId == FirstTimePage.Plugins:
# Set the no internet page text.
if self.hasRunWizard:
self.noInternetLabel.setText(self.noInternetText)
else:
self.noInternetLabel.setText(self.noInternetText +
self.cancelWizardText)
elif pageId == FirstTimePage.Defaults:
self.themeComboBox.clear() self.themeComboBox.clear()
for iter in xrange(self.themesListWidget.count()): for iter in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(iter) item = self.themesListWidget.item(iter)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
self.themeComboBox.addItem(item.text()) self.themeComboBox.addItem(item.text())
# Check if this is a re-run of the wizard. if self.hasRunWizard:
self.has_run_wizard = QtCore.QSettings().value(
u'general/has run wizard', QtCore.QVariant(False)).toBool()
if self.has_run_wizard:
# Add any existing themes to list. # Add any existing themes to list.
for theme in self.parent().themeManagerContents.getThemes(): for theme in self.parent().themeManagerContents.getThemes():
index = self.themeComboBox.findText(theme) index = self.themeComboBox.findText(theme)
@ -172,12 +192,21 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
# Pre-select the current default theme. # Pre-select the current default theme.
index = self.themeComboBox.findText(default_theme) index = self.themeComboBox.findText(default_theme)
self.themeComboBox.setCurrentIndex(index) self.themeComboBox.setCurrentIndex(index)
elif pageId == FirstTimePage.NoInternet:
self.backButton.setVisible(False)
self.nextButton.setVisible(False)
self.noInternetFinishButton.setVisible(True)
if self.hasRunWizard:
self.cancelButton.setVisible(False)
elif pageId == FirstTimePage.Progress: elif pageId == FirstTimePage.Progress:
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
self._preWizard() self._preWizard()
Receiver.send_message(u'openlp_process_events')
self._performWizard() self._performWizard()
Receiver.send_message(u'openlp_process_events')
self._postWizard() self._postWizard()
Receiver.send_message(u'cursor_normal') Receiver.send_message(u'cursor_normal')
Receiver.send_message(u'openlp_process_events')
def updateScreenListCombo(self): def updateScreenListCombo(self):
""" """
@ -188,6 +217,53 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self.displayComboBox.addItems(self.screens.get_screen_list()) self.displayComboBox.addItems(self.screens.get_screen_list())
self.displayComboBox.setCurrentIndex(self.displayComboBox.count() - 1) self.displayComboBox.setCurrentIndex(self.displayComboBox.count() - 1)
def onCancelButtonClicked(self):
"""
Process the pressing of the cancel button.
"""
if self.lastId == FirstTimePage.NoInternet or \
(self.lastId <= FirstTimePage.Plugins and \
not self.hasRunWizard):
QtCore.QCoreApplication.exit()
sys.exit()
self.downloadCanceled = True
Receiver.send_message(u'cursor_normal')
def onNoInternetFinishButtonClicked(self):
"""
Process the pressing of the "Finish" button on the No Internet page.
"""
Receiver.send_message(u'cursor_busy')
self._performWizard()
Receiver.send_message(u'openlp_process_events')
Receiver.send_message(u'cursor_normal')
QtCore.QSettings().setValue(u'general/has run wizard',
QtCore.QVariant(True))
self.close()
def urlGetFile(self, url, fpath):
""""
Download a file given a URL. The file is retrieved in chunks, giving
the ability to cancel the download at any point.
"""
block_count = 0
block_size = 4096
urlfile = urllib2.urlopen(url)
filesize = urlfile.headers["Content-Length"]
filename = open(fpath, "wb")
# Download until finished or canceled.
while not self.downloadCanceled:
data = urlfile.read(block_size)
if not data:
break
filename.write(data)
block_count += 1
self._downloadProgress(block_count, block_size, filesize)
filename.close()
# Delete file if canceled, it may be a partial file.
if self.downloadCanceled:
os.remove(fpath)
def _getFileSize(self, url): def _getFileSize(self, url):
site = urllib.urlopen(url) site = urllib.urlopen(url)
meta = site.info() meta = site.info()
@ -197,7 +273,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
increment = (count * block_size) - self.previous_size increment = (count * block_size) - self.previous_size
self._incrementProgressBar(None, increment) self._incrementProgressBar(None, increment)
self.previous_size = count * block_size self.previous_size = count * block_size
def _incrementProgressBar(self, status_text, increment=1): def _incrementProgressBar(self, status_text, increment=1):
""" """
Update the wizard progress page. Update the wizard progress page.
@ -219,6 +295,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
Prepare the UI for the process. Prepare the UI for the process.
""" """
self.max_progress = 0 self.max_progress = 0
self.finishButton.setVisible(False)
Receiver.send_message(u'openlp_process_events')
# Loop through the songs list and increase for each selected item # Loop through the songs list and increase for each selected item
for i in xrange(self.songsListWidget.count()): for i in xrange(self.songsListWidget.count()):
item = self.songsListWidget.item(i) item = self.songsListWidget.item(i)
@ -242,7 +320,6 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
filename = item.data(QtCore.Qt.UserRole).toString() filename = item.data(QtCore.Qt.UserRole).toString()
size = self._getFileSize(u'%s%s' % (self.web, filename)) size = self._getFileSize(u'%s%s' % (self.web, filename))
self.max_progress += size self.max_progress += size
self.finishButton.setVisible(False)
if self.max_progress: if self.max_progress:
# Add on 2 for plugins status setting plus a "finished" point. # Add on 2 for plugins status setting plus a "finished" point.
self.max_progress = self.max_progress + 2 self.max_progress = self.max_progress + 2
@ -266,7 +343,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
""" """
if self.max_progress: if self.max_progress:
self.progressBar.setValue(self.progressBar.maximum()) self.progressBar.setValue(self.progressBar.maximum())
if self.has_run_wizard: if self.hasRunWizard:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
'Download complete.' 'Download complete.'
' Click the finish button to return to OpenLP.')) ' Click the finish button to return to OpenLP.'))
@ -275,7 +352,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
'Download complete.' 'Download complete.'
' Click the finish button to start OpenLP.')) ' Click the finish button to start OpenLP.'))
else: else:
if self.has_run_wizard: if self.hasRunWizard:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
'Click the finish button to return to OpenLP.')) 'Click the finish button to return to OpenLP.'))
else: else:
@ -304,42 +381,42 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._setPluginStatus(self.customCheckBox, u'custom/status') self._setPluginStatus(self.customCheckBox, u'custom/status')
self._setPluginStatus(self.songUsageCheckBox, u'songusage/status') self._setPluginStatus(self.songUsageCheckBox, u'songusage/status')
self._setPluginStatus(self.alertCheckBox, u'alerts/status') self._setPluginStatus(self.alertCheckBox, u'alerts/status')
# Build directories for downloads if self.webAccess:
songs_destination = os.path.join(unicode(gettempdir()), u'openlp') # Build directories for downloads
bibles_destination = AppLocation.get_section_data_path(u'bibles') songs_destination = os.path.join(unicode(gettempdir()), u'openlp')
themes_destination = AppLocation.get_section_data_path(u'themes') bibles_destination = AppLocation.get_section_data_path(u'bibles')
# Download songs themes_destination = AppLocation.get_section_data_path(u'themes')
for i in xrange(self.songsListWidget.count()): # Download songs
item = self.songsListWidget.item(i) for i in xrange(self.songsListWidget.count()):
if item.checkState() == QtCore.Qt.Checked: item = self.songsListWidget.item(i)
filename = item.data(QtCore.Qt.UserRole).toString() if item.checkState() == QtCore.Qt.Checked:
self._incrementProgressBar(self.downloading % filename, 0) filename = item.data(QtCore.Qt.UserRole).toString()
self.previous_size = 0 self._incrementProgressBar(self.downloading % filename, 0)
destination = os.path.join(songs_destination, unicode(filename)) self.previous_size = 0
urllib.urlretrieve(u'%s%s' % (self.web, filename), destination, destination = os.path.join(songs_destination,
self._downloadProgress) unicode(filename))
# Download Bibles self.urlGetFile(u'%s%s' % (self.web, filename), destination)
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) # Download Bibles
while bibles_iterator.value(): bibles_iterator = QtGui.QTreeWidgetItemIterator(
item = bibles_iterator.value() self.biblesTreeWidget)
if item.parent() and item.checkState(0) == QtCore.Qt.Checked: while bibles_iterator.value():
bible = unicode(item.data(0, QtCore.Qt.UserRole).toString()) item = bibles_iterator.value()
self._incrementProgressBar(self.downloading % bible, 0) if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
self.previous_size = 0 bible = unicode(item.data(0, QtCore.Qt.UserRole).toString())
urllib.urlretrieve(u'%s%s' % (self.web, bible), self._incrementProgressBar(self.downloading % bible, 0)
os.path.join(bibles_destination, bible), self.previous_size = 0
self._downloadProgress) self.urlGetFile(u'%s%s' % (self.web, bible),
bibles_iterator += 1 os.path.join(bibles_destination, bible))
# Download themes bibles_iterator += 1
for i in xrange(self.themesListWidget.count()): # Download themes
item = self.themesListWidget.item(i) for i in xrange(self.themesListWidget.count()):
if item.checkState() == QtCore.Qt.Checked: item = self.themesListWidget.item(i)
theme = unicode(item.data(QtCore.Qt.UserRole).toString()) if item.checkState() == QtCore.Qt.Checked:
self._incrementProgressBar(self.downloading % theme, 0) theme = unicode(item.data(QtCore.Qt.UserRole).toString())
self.previous_size = 0 self._incrementProgressBar(self.downloading % theme, 0)
urllib.urlretrieve(u'%s%s' % (self.web, theme), self.previous_size = 0
os.path.join(themes_destination, theme), self.urlGetFile(u'%s%s' % (self.web, theme),
self._downloadProgress) os.path.join(themes_destination, theme))
# Set Default Display # Set Default Display
if self.displayComboBox.currentIndex() != -1: if self.displayComboBox.currentIndex() != -1:
QtCore.QSettings().setValue(u'General/monitor', QtCore.QSettings().setValue(u'General/monitor',

View File

@ -51,8 +51,10 @@ class Ui_FirstTimeWizard(object):
FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages | FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage) QtGui.QWizard.NoBackButtonOnLastPage |
QtGui.QWizard.HaveCustomButton1)
self.finishButton = self.button(QtGui.QWizard.FinishButton) self.finishButton = self.button(QtGui.QWizard.FinishButton)
self.noInternetFinishButton = self.button(QtGui.QWizard.CustomButton1)
self.cancelButton = self.button(QtGui.QWizard.CancelButton) self.cancelButton = self.button(QtGui.QWizard.CancelButton)
self.nextButton = self.button(QtGui.QWizard.NextButton) self.nextButton = self.button(QtGui.QWizard.NextButton)
self.backButton = self.button(QtGui.QWizard.BackButton) self.backButton = self.button(QtGui.QWizard.BackButton)
@ -189,9 +191,7 @@ class Ui_FirstTimeWizard(object):
self.progressBar.setObjectName(u'progressBar') self.progressBar.setObjectName(u'progressBar')
self.progressLayout.addWidget(self.progressBar) self.progressLayout.addWidget(self.progressBar)
FirstTimeWizard.setPage(FirstTimePage.Progress, self.progressPage) FirstTimeWizard.setPage(FirstTimePage.Progress, self.progressPage)
self.retranslateUi(FirstTimeWizard) self.retranslateUi(FirstTimeWizard)
QtCore.QMetaObject.connectSlotsByName(FirstTimeWizard)
def retranslateUi(self, FirstTimeWizard): def retranslateUi(self, FirstTimeWizard):
FirstTimeWizard.setWindowTitle(translate( FirstTimeWizard.setWindowTitle(translate(
@ -230,14 +230,17 @@ class Ui_FirstTimeWizard(object):
self.noInternetPage.setSubTitle(translate( self.noInternetPage.setSubTitle(translate(
'OpenLP.FirstTimeWizard', 'OpenLP.FirstTimeWizard',
'Unable to detect an Internet connection.')) 'Unable to detect an Internet connection.'))
self.noInternetLabel.setText(translate('OpenLP.FirstTimeWizard', self.noInternetText = translate('OpenLP.FirstTimeWizard',
'No Internet connection was found. The First Time Wizard needs an ' 'No Internet connection was found. The First Time Wizard needs an '
'Internet connection in order to be able to download sample ' 'Internet connection in order to be able to download sample '
'songs, Bibles and themes.\n\nTo re-run the First Time Wizard and ' 'songs, Bibles and themes. Press the Finish button now to start '
'import this sample data at a later stage, press the cancel ' 'OpenLP with initial settings and no sample data.\n\nTo re-run the '
'button now, check your Internet connection, and restart OpenLP.' 'First Time Wizard and import this sample data at a later time, '
'\n\nTo cancel the First Time Wizard completely, press the finish ' 'check your Internet connection and re-run this wizard by '
'button now.')) 'selecting "Tools/Re-run First Time Wizard" from OpenLP.')
self.cancelWizardText = translate('OpenLP.FirstTimeWizard',
'\n\nTo cancel the First Time Wizard completely (and not start '
'OpenLP), press the Cancel button now.')
self.songsPage.setTitle(translate('OpenLP.FirstTimeWizard', self.songsPage.setTitle(translate('OpenLP.FirstTimeWizard',
'Sample Songs')) 'Sample Songs'))
self.songsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', self.songsPage.setSubTitle(translate('OpenLP.FirstTimeWizard',
@ -260,3 +263,5 @@ class Ui_FirstTimeWizard(object):
'Select default theme:')) 'Select default theme:'))
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
'Starting configuration process...')) 'Starting configuration process...'))
FirstTimeWizard.setButtonText(QtGui.QWizard.CustomButton1,
translate('OpenLP.FirstTimeWizard', 'Finish'))

View File

@ -170,6 +170,15 @@ class GeneralTab(SettingsTab):
self.customHeightValueEdit.setMaximum(9999) self.customHeightValueEdit.setMaximum(9999)
self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3) self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3)
self.rightLayout.addWidget(self.displayGroupBox) self.rightLayout.addWidget(self.displayGroupBox)
# Background audio
self.audioGroupBox = QtGui.QGroupBox(self.rightColumn)
self.audioGroupBox.setObjectName(u'audioGroupBox')
self.audioLayout = QtGui.QVBoxLayout(self.audioGroupBox)
self.audioLayout.setObjectName(u'audioLayout')
self.startPausedCheckBox = QtGui.QCheckBox(self.audioGroupBox)
self.startPausedCheckBox.setObjectName(u'startPausedCheckBox')
self.audioLayout.addWidget(self.startPausedCheckBox)
self.rightLayout.addWidget(self.audioGroupBox)
self.rightLayout.addStretch() self.rightLayout.addStretch()
# Signals and slots # Signals and slots
QtCore.QObject.connect(self.overrideCheckBox, QtCore.QObject.connect(self.overrideCheckBox,
@ -243,6 +252,10 @@ class GeneralTab(SettingsTab):
self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y')) self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y'))
self.customHeightLabel.setText(translate('OpenLP.GeneralTab', 'Height')) self.customHeightLabel.setText(translate('OpenLP.GeneralTab', 'Height'))
self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width')) self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width'))
self.audioGroupBox.setTitle(
translate('OpenLP.GeneralTab', 'Background Audio'))
self.startPausedCheckBox.setText(
translate('OpenLP.GeneralTab', 'Start background audio paused'))
def load(self): def load(self):
""" """
@ -290,6 +303,8 @@ class GeneralTab(SettingsTab):
QtCore.QVariant(self.screens.current[u'size'].height())).toInt()[0]) QtCore.QVariant(self.screens.current[u'size'].height())).toInt()[0])
self.customWidthValueEdit.setValue(settings.value(u'width', self.customWidthValueEdit.setValue(settings.value(u'width',
QtCore.QVariant(self.screens.current[u'size'].width())).toInt()[0]) QtCore.QVariant(self.screens.current[u'size'].width())).toInt()[0])
self.startPausedCheckBox.setChecked(settings.value(
u'audio start paused', QtCore.QVariant(True)).toBool())
settings.endGroup() settings.endGroup()
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
@ -341,6 +356,8 @@ class GeneralTab(SettingsTab):
QtCore.QVariant(self.customWidthValueEdit.value())) QtCore.QVariant(self.customWidthValueEdit.value()))
settings.setValue(u'override position', settings.setValue(u'override position',
QtCore.QVariant(self.overrideCheckBox.isChecked())) QtCore.QVariant(self.overrideCheckBox.isChecked()))
settings.setValue(u'audio start paused',
QtCore.QVariant(self.startPausedCheckBox.isChecked()))
settings.endGroup() settings.endGroup()
# On save update the screens as well # On save update the screens as well
self.postSetUp(True) self.postSetUp(True)

View File

@ -62,6 +62,10 @@ class MainDisplay(QtGui.QGraphicsView):
self.override = {} self.override = {}
self.retranslateUi() self.retranslateUi()
self.mediaObject = None self.mediaObject = None
if live:
self.audioPlayer = AudioPlayer(self)
else:
self.audioPlayer = None
self.firstTime = True self.firstTime = True
self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool |
@ -228,11 +232,11 @@ class MainDisplay(QtGui.QGraphicsView):
shrinkItem.setVisible(False) shrinkItem.setVisible(False)
self.setGeometry(self.screen[u'size']) self.setGeometry(self.screen[u'size'])
def directImage(self, name, path): def directImage(self, name, path, background):
""" """
API for replacement backgrounds so Images are added directly to cache API for replacement backgrounds so Images are added directly to cache
""" """
self.imageManager.add_image(name, path) self.imageManager.add_image(name, path, u'image', background)
if hasattr(self, u'serviceItem'): if hasattr(self, u'serviceItem'):
self.override[u'image'] = name self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name self.override[u'theme'] = self.serviceItem.themedata.theme_name
@ -587,61 +591,76 @@ class AudioPlayer(QtCore.QObject):
""" """
log.debug(u'AudioPlayer Initialisation started') log.debug(u'AudioPlayer Initialisation started')
QtCore.QObject.__init__(self, parent) QtCore.QObject.__init__(self, parent)
self.message = None self.currentIndex = -1
self.playlist = []
self.mediaObject = Phonon.MediaObject() self.mediaObject = Phonon.MediaObject()
self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory) self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory)
Phonon.createPath(self.mediaObject, self.audioObject) Phonon.createPath(self.mediaObject, self.audioObject)
QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'aboutToFinish()'), self.onAboutToFinish)
def setup(self): def __del__(self):
"""
Sets up the Audio Player for use
"""
log.debug(u'AudioPlayer Setup')
def close(self):
""" """
Shutting down so clean up connections Shutting down so clean up connections
""" """
self.onMediaStop() self.stop()
for path in self.mediaObject.outputPaths(): for path in self.mediaObject.outputPaths():
path.disconnect() 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) self.currentIndex += 1
mfile = os.path.join(message[0].get_frame_path(), if len(self.playlist) > self.currentIndex:
message[0].get_frame_title()) self.mediaObject.enqueue(self.playlist[self.currentIndex])
self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile))
self.onMediaPlay()
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() self.mediaObject.play()
def onMediaPause(self): def pause(self):
""" """
Pause the Audio Pause the Audio
""" """
log.debug(u'AudioPlayer Media paused by user') log.debug(u'AudioPlayer.pause() called')
self.mediaObject.pause() self.mediaObject.pause()
def onMediaStop(self): def stop(self):
""" """
Stop the Audio and clean up Stop the Audio and clean up
""" """
log.debug(u'AudioPlayer Media stopped by user') log.debug(u'AudioPlayer.stop() called')
self.message = None
self.mediaObject.stop() 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') if not isinstance(filenames, list):
self.mediaObject.clearQueue() filenames = [filenames]
for filename in filenames:
self.playlist.append(Phonon.MediaSource(filename))

View File

@ -30,11 +30,13 @@ import os
import sys import sys
import shutil import shutil
from tempfile import gettempdir from tempfile import gettempdir
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, \
@ -214,7 +216,7 @@ class Ui_MainWindow(object):
self.mediaManagerDock.isVisible(), UiStrings().View) self.mediaManagerDock.isVisible(), UiStrings().View)
self.viewThemeManagerItem = shortcut_action(mainWindow, self.viewThemeManagerItem = shortcut_action(mainWindow,
u'viewThemeManagerItem', [QtGui.QKeySequence(u'F10')], u'viewThemeManagerItem', [QtGui.QKeySequence(u'F10')],
self.toggleThemeManager, u':/system/system_thememanager.png', self.toggleThemeManager, u':/system/system_thememanager.png',
self.themeManagerDock.isVisible(), UiStrings().View) self.themeManagerDock.isVisible(), UiStrings().View)
self.viewServiceManagerItem = shortcut_action(mainWindow, self.viewServiceManagerItem = shortcut_action(mainWindow,
u'viewServiceManagerItem', [QtGui.QKeySequence(u'F9')], u'viewServiceManagerItem', [QtGui.QKeySequence(u'F9')],
@ -284,6 +286,10 @@ class Ui_MainWindow(object):
self.settingsConfigureItem = icon_action(mainWindow, self.settingsConfigureItem = icon_action(mainWindow,
u'settingsConfigureItem', u':/system/system_settings.png', u'settingsConfigureItem', u':/system/system_settings.png',
category=UiStrings().Settings) category=UiStrings().Settings)
self.settingsImportItem = base_action(mainWindow,
u'settingsImportItem', category=UiStrings().Settings)
self.settingsExportItem = base_action(mainWindow,
u'settingsExportItem', category=UiStrings().Settings)
action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu) action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu)
self.aboutItem = shortcut_action(mainWindow, u'aboutItem', self.aboutItem = shortcut_action(mainWindow, u'aboutItem',
[QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked, [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked,
@ -301,10 +307,10 @@ class Ui_MainWindow(object):
u':/system/system_online_help.png', category=UiStrings().Help) u':/system/system_online_help.png', category=UiStrings().Help)
self.webSiteItem = base_action( self.webSiteItem = base_action(
mainWindow, u'webSiteItem', category=UiStrings().Help) mainWindow, u'webSiteItem', category=UiStrings().Help)
add_actions(self.fileImportMenu, add_actions(self.fileImportMenu, (self.settingsImportItem, None,
(self.importThemeItem, self.importLanguageItem)) self.importThemeItem, self.importLanguageItem))
add_actions(self.fileExportMenu, add_actions(self.fileExportMenu, (self.settingsExportItem, None,
(self.exportThemeItem, self.exportLanguageItem)) self.exportThemeItem, self.exportLanguageItem))
add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem, add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem,
self.fileSaveItem, self.fileSaveAsItem, self.fileSaveItem, self.fileSaveAsItem,
self.recentFilesMenu.menuAction(), None, self.recentFilesMenu.menuAction(), None,
@ -357,6 +363,7 @@ class Ui_MainWindow(object):
self.importLanguageItem.setVisible(False) self.importLanguageItem.setVisible(False)
self.exportLanguageItem.setVisible(False) self.exportLanguageItem.setVisible(False)
self.setLockPanel(panelLocked) self.setLockPanel(panelLocked)
self.settingsImported = False
def retranslateUi(self, mainWindow): def retranslateUi(self, mainWindow):
""" """
@ -420,6 +427,15 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Configure &Formatting Tags...')) translate('OpenLP.MainWindow', 'Configure &Formatting Tags...'))
self.settingsConfigureItem.setText( self.settingsConfigureItem.setText(
translate('OpenLP.MainWindow', '&Configure OpenLP...')) translate('OpenLP.MainWindow', '&Configure OpenLP...'))
self.settingsExportItem.setStatusTip(translate('OpenLP.MainWindow',
'Export OpenLP settings to a specified *.config file'))
self.settingsExportItem.setText(
translate('OpenLP.MainWindow', 'Settings'))
self.settingsImportItem.setStatusTip(translate('OpenLP.MainWindow',
'Import OpenLP settings from a specified *.config file previously '
'exported on this or another machine'))
self.settingsImportItem.setText(
translate('OpenLP.MainWindow', 'Settings'))
self.viewMediaManagerItem.setText( self.viewMediaManagerItem.setText(
translate('OpenLP.MainWindow', '&Media Manager')) translate('OpenLP.MainWindow', '&Media Manager'))
self.viewMediaManagerItem.setToolTip( self.viewMediaManagerItem.setToolTip(
@ -523,8 +539,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# (not for use by plugins) # (not for use by plugins)
self.uiSettingsSection = u'user interface' self.uiSettingsSection = u'user interface'
self.generalSettingsSection = u'general' self.generalSettingsSection = u'general'
self.serviceSettingsSection = u'servicemanager' self.advancedlSettingsSection = u'advanced'
self.shortcutsSettingsSection = u'shortcuts'
self.servicemanagerSettingsSection = u'servicemanager'
self.songsSettingsSection = u'songs' self.songsSettingsSection = u'songs'
self.themesSettingsSection = u'themes'
self.displayTagsSection = u'displayTags'
self.headerSection = u'SettingsImport'
self.serviceNotSaved = False self.serviceNotSaved = False
self.aboutForm = AboutForm(self) self.aboutForm = AboutForm(self)
self.settingsForm = SettingsForm(self, self) self.settingsForm = SettingsForm(self, self)
@ -573,6 +594,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked) QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked)
QtCore.QObject.connect(self.settingsShortcutsItem, QtCore.QObject.connect(self.settingsShortcutsItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsShortcutsItemClicked) QtCore.SIGNAL(u'triggered()'), self.onSettingsShortcutsItemClicked)
QtCore.QObject.connect(self.settingsImportItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsImportItemClicked)
QtCore.QObject.connect(self.settingsExportItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsExportItemClicked)
# i18n set signals for languages # i18n set signals for languages
self.languageGroup.triggered.connect(LanguageManager.set_language) self.languageGroup.triggered.connect(LanguageManager.set_language)
QtCore.QObject.connect(self.modeDefaultItem, QtCore.QObject.connect(self.modeDefaultItem,
@ -751,22 +776,25 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
return return
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
screens = ScreenList.get_instance() screens = ScreenList.get_instance()
if FirstTimeForm(screens, self).exec_() == QtGui.QDialog.Accepted: FirstTimeForm(screens, self).exec_()
self.firstTime() self.firstTime()
for plugin in self.pluginManager.plugins: for plugin in self.pluginManager.plugins:
self.activePlugin = plugin self.activePlugin = plugin
oldStatus = self.activePlugin.status oldStatus = self.activePlugin.status
self.activePlugin.setStatus() self.activePlugin.setStatus()
if oldStatus != self.activePlugin.status: if oldStatus != self.activePlugin.status:
if self.activePlugin.status == PluginStatus.Active: if self.activePlugin.status == PluginStatus.Active:
self.activePlugin.toggleStatus(PluginStatus.Active) self.activePlugin.toggleStatus(PluginStatus.Active)
self.activePlugin.appStartup() self.activePlugin.appStartup()
else: else:
self.activePlugin.toggleStatus(PluginStatus.Inactive) self.activePlugin.toggleStatus(PluginStatus.Inactive)
self.themeManagerContents.configUpdated() self.themeManagerContents.configUpdated()
self.themeManagerContents.loadThemes(True) self.themeManagerContents.loadThemes(True)
Receiver.send_message(u'theme_update_global', Receiver.send_message(u'theme_update_global',
self.themeManagerContents.global_theme) self.themeManagerContents.global_theme)
# Check if any Bibles downloaded. If there are, they will be
# processed.
Receiver.send_message(u'bibles_load_list', True)
def blankCheck(self): def blankCheck(self):
""" """
@ -868,6 +896,171 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if self.shortcutForm.exec_(): if self.shortcutForm.exec_():
self.shortcutForm.save() self.shortcutForm.save()
def onSettingsImportItemClicked(self):
"""
Import settings from an export INI file
"""
answer = QtGui.QMessageBox.critical(self,
translate('OpenLP.MainWindow', 'Import settings?'),
translate('OpenLP.MainWindow',
'Are you sure you want to import settings?\n\n'
'Importing settings will make permanent changes to your current '
'OpenLP configuration.\n\n'
'Importing incorrect settings may cause erratic behaviour or '
'OpenLP to terminate abnormally.'),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer == QtGui.QMessageBox.No:
return
import_file_name = unicode(QtGui.QFileDialog.getOpenFileName(self,
translate('OpenLP.MainWindow', 'Open File'),
'',
translate('OpenLP.MainWindow',
'OpenLP Export Settings Files (*.conf)')))
if not import_file_name:
return
setting_sections = []
# Add main sections.
setting_sections.extend([self.generalSettingsSection])
setting_sections.extend([self.advancedlSettingsSection])
setting_sections.extend([self.uiSettingsSection])
setting_sections.extend([self.shortcutsSettingsSection])
setting_sections.extend([self.servicemanagerSettingsSection])
setting_sections.extend([self.themesSettingsSection])
setting_sections.extend([self.displayTagsSection])
setting_sections.extend([self.headerSection])
# Add plugin sections.
for plugin in self.pluginManager.plugins:
setting_sections.extend([plugin.name])
settings = QtCore.QSettings()
import_settings = QtCore.QSettings(import_file_name,
QtCore.QSettings.IniFormat)
import_keys = import_settings.allKeys()
for section_key in import_keys:
# We need to handle the really bad files.
try:
section, key = section_key.split(u'/')
except ValueError:
section = u'unknown'
key = u''
# Switch General back to lowercase.
if section == u'General':
section = u'general'
section_key = section + "/" + key
# Make sure it's a valid section for us.
if not section in setting_sections:
QtGui.QMessageBox.critical(self,
translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow',
'The file you selected does appear to be a valid OpenLP '
'settings file.\n\n'
'Section [%s] is not valid \n\n'
'Processing has terminated and no changed have been made.'
% section),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Ok))
return
# We have a good file, import it.
for section_key in import_keys:
value = import_settings.value(section_key)
settings.setValue(u'%s' % (section_key) ,
QtCore.QVariant(value))
now = datetime.now()
settings.beginGroup(self.headerSection)
settings.setValue( u'file_imported' , QtCore.QVariant(import_file_name))
settings.setValue(u'file_date_imported',
now.strftime("%Y-%m-%d %H:%M"))
settings.endGroup()
settings.sync()
# We must do an immediate restart or current configuration will
# overwrite what was just imported when application terminates
# normally. We need to exit without saving configuration.
QtGui.QMessageBox.information(self,
translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow',
'OpenLP will now close. Imported settings will '
'be applied the next time you start OpenLP.'),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Ok))
self.settingsImported = True
self.cleanUp()
QtCore.QCoreApplication.exit()
def onSettingsExportItemClicked(self):
"""
Export settings to a .conf file in INI format
"""
export_file_name = unicode(QtGui.QFileDialog.getSaveFileName(self,
translate('OpenLP.MainWindow', 'Export Settings File'), '',
translate('OpenLP.MainWindow',
'OpenLP Export Settings File (*.conf)')))
if not export_file_name:
return
# Make sure it's a .conf file.
if not export_file_name.endswith(u'conf'):
export_file_name = export_file_name + u'.conf'
temp_file = os.path.join(unicode(gettempdir()),
u'openlp', u'exportConf.tmp')
self.saveSettings()
setting_sections = []
# Add main sections.
setting_sections.extend([self.generalSettingsSection])
setting_sections.extend([self.advancedlSettingsSection])
setting_sections.extend([self.uiSettingsSection])
setting_sections.extend([self.shortcutsSettingsSection])
setting_sections.extend([self.servicemanagerSettingsSection])
setting_sections.extend([self.themesSettingsSection])
setting_sections.extend([self.displayTagsSection])
# Add plugin sections.
for plugin in self.pluginManager.plugins:
setting_sections.extend([plugin.name])
# Delete old files if found.
if os.path.exists(temp_file):
os.remove(temp_file)
if os.path.exists(export_file_name):
os.remove(export_file_name)
settings = QtCore.QSettings()
settings.remove(self.headerSection)
# Get the settings.
keys = settings.allKeys()
export_settings = QtCore.QSettings(temp_file,
QtCore.QSettings.IniFormat)
# Add a header section.
# This is to insure it's our conf file for import.
now = datetime.now()
application_version = get_application_version()
# Write INI format using Qsettings.
# Write our header.
export_settings.beginGroup(self.headerSection)
export_settings.setValue(u'Make_Changes', u'At_Own_RISK')
export_settings.setValue(u'type', u'OpenLP_settings_export')
export_settings.setValue(u'file_date_created',
now.strftime("%Y-%m-%d %H:%M"))
export_settings.setValue(u'version', application_version[u'full'])
export_settings.endGroup()
# Write all the sections and keys.
for section_key in keys:
section, key = section_key.split(u'/')
key_value = settings.value(section_key)
export_settings.setValue(section_key, key_value)
export_settings.sync()
# Temp CONF file has been written. Blanks in keys are now '%20'.
# Read the temp file and output the user's CONF file with blanks to
# make it more readable.
temp_conf = open(temp_file, u'r')
export_conf = open(export_file_name, u'w')
for file_record in temp_conf:
# Get rid of any invalid entries.
if file_record.find(u'@Invalid()') == -1:
file_record = file_record.replace(u'%20', u' ')
export_conf.write(file_record)
temp_conf.close()
export_conf.close()
os.remove(temp_file)
return
def onModeDefaultItemClicked(self): def onModeDefaultItemClicked(self):
""" """
Put OpenLP into "Default" view mode. Put OpenLP into "Default" view mode.
@ -920,6 +1113,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
""" """
Hook to close the main window and display windows on exit Hook to close the main window and display windows on exit
""" """
# If we just did a settings import, close without saving changes.
if self.settingsImported:
event.accept()
if self.serviceManagerContents.isModified(): if self.serviceManagerContents.isModified():
ret = self.serviceManagerContents.saveModifiedService() ret = self.serviceManagerContents.saveModifiedService()
if ret == QtGui.QMessageBox.Save: if ret == QtGui.QMessageBox.Save:
@ -1095,6 +1291,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
""" """
log.debug(u'Loading QSettings') log.debug(u'Loading QSettings')
settings = QtCore.QSettings() settings = QtCore.QSettings()
# Remove obsolete entries.
settings.remove(u'custom slide')
settings.remove(u'service')
settings.beginGroup(self.generalSettingsSection) settings.beginGroup(self.generalSettingsSection)
self.recentFiles = settings.value(u'recent files').toStringList() self.recentFiles = settings.value(u'recent files').toStringList()
settings.endGroup() settings.endGroup()
@ -1117,6 +1316,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
""" """
Save the main window settings. Save the main window settings.
""" """
# Exit if we just did a settings import.
if self.settingsImported:
return
log.debug(u'Saving QSettings') log.debug(u'Saving QSettings')
settings = QtCore.QSettings() settings = QtCore.QSettings()
settings.beginGroup(self.generalSettingsSection) settings.beginGroup(self.generalSettingsSection)

View File

@ -31,7 +31,7 @@ import os
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from lxml import html from lxml import html
from openlp.core.lib import translate, get_text_file_string from openlp.core.lib import translate, get_text_file_string, Receiver
from openlp.core.lib.ui import UiStrings from openlp.core.lib.ui import UiStrings
from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize
from openlp.core.utils import AppLocation from openlp.core.utils import AppLocation
@ -327,12 +327,14 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
""" """
Copies the display text to the clipboard as plain text Copies the display text to the clipboard as plain text
""" """
self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toPlainText()) self.mainWindow.clipboard.setText(self.document.toPlainText())
def copyHtmlText(self): def copyHtmlText(self):
""" """
Copies the display text to the clipboard as Html Copies the display text to the clipboard as Html
""" """
self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toHtml()) self.mainWindow.clipboard.setText(self.document.toHtml())
def printServiceOrder(self): def printServiceOrder(self):
@ -341,6 +343,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
""" """
if not self.printDialog.exec_(): if not self.printDialog.exec_():
return return
self.update_song_usage()
# Print the document. # Print the document.
self.document.print_(self.printer) self.document.print_(self.printer)
@ -397,3 +400,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
settings.setValue(u'print notes', settings.setValue(u'print notes',
QtCore.QVariant(self.notesCheckBox.isChecked())) QtCore.QVariant(self.notesCheckBox.isChecked()))
settings.endGroup() settings.endGroup()
def update_song_usage(self):
for index, item in enumerate(self.serviceManager.serviceItems):
# Trigger Audit requests
Receiver.send_message(u'print_service_started',
[item[u'service_item']])

View File

@ -28,6 +28,7 @@ import cgi
import cPickle import cPickle
import logging import logging
import os import os
import shutil
import zipfile import zipfile
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -290,7 +291,7 @@ class ServiceManager(QtGui.QWidget):
QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate) QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate)
# Last little bits of setting up # Last little bits of setting up
self.service_theme = unicode(QtCore.QSettings().value( self.service_theme = unicode(QtCore.QSettings().value(
self.mainwindow.serviceSettingsSection + u'/service theme', self.mainwindow.servicemanagerSettingsSection + u'/service theme',
QtCore.QVariant(u'')).toString()) QtCore.QVariant(u'')).toString())
self.servicePath = AppLocation.get_section_data_path(u'servicemanager') self.servicePath = AppLocation.get_section_data_path(u'servicemanager')
# build the drag and drop context menu # build the drag and drop context menu
@ -371,7 +372,7 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.setServiceModified(self.isModified(), self.mainwindow.setServiceModified(self.isModified(),
self.shortFileName()) self.shortFileName())
QtCore.QSettings(). \ QtCore.QSettings(). \
setValue(u'service/last file',QtCore.QVariant(fileName)) setValue(u'servicemanager/last file',QtCore.QVariant(fileName))
def fileName(self): def fileName(self):
""" """
@ -429,14 +430,15 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow, self.mainwindow,
translate('OpenLP.ServiceManager', 'Open File'), translate('OpenLP.ServiceManager', 'Open File'),
SettingsManager.get_last_dir( SettingsManager.get_last_dir(
self.mainwindow.serviceSettingsSection), self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager', translate('OpenLP.ServiceManager',
'OpenLP Service Files (*.osz)'))) 'OpenLP Service Files (*.osz)')))
if not fileName: if not fileName:
return False return False
else: else:
fileName = loadFile fileName = loadFile
SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection,
split_filename(fileName)[0]) split_filename(fileName)[0])
self.loadFile(fileName) self.loadFile(fileName)
@ -461,7 +463,7 @@ class ServiceManager(QtGui.QWidget):
self.setFileName(u'') self.setFileName(u'')
self.setModified(False) self.setModified(False)
QtCore.QSettings(). \ QtCore.QSettings(). \
setValue(u'service/last file',QtCore.QVariant(u'')) setValue(u'servicemanager/last file',QtCore.QVariant(u''))
def saveFile(self): def saveFile(self):
""" """
@ -470,22 +472,34 @@ class ServiceManager(QtGui.QWidget):
if not self.fileName(): if not self.fileName():
return self.saveFileAs() return self.saveFileAs()
path_file_name = unicode(self.fileName()) path_file_name = unicode(self.fileName())
(path, file_name) = os.path.split(path_file_name) path, file_name = os.path.split(path_file_name)
(basename, extension) = os.path.splitext(file_name) basename, extension = os.path.splitext(file_name)
service_file_name = basename + '.osd' service_file_name = '%s.osd' % basename
log.debug(u'ServiceManager.saveFile - %s' % path_file_name) log.debug(u'ServiceManager.saveFile - %s' % path_file_name)
SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection,
path) path)
service = [] service = []
write_list = [] write_list = []
audio_files = []
total_size = 0 total_size = 0
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
# Number of items + 1 to zip it # Number of items + 1 to zip it
self.mainwindow.displayProgressBar(len(self.serviceItems) + 1) self.mainwindow.displayProgressBar(len(self.serviceItems) + 1)
for item in self.serviceItems: for item in self.serviceItems:
self.mainwindow.incrementProgressBar() self.mainwindow.incrementProgressBar()
service.append({u'serviceitem': service_item = item[u'service_item'].get_service_repr()
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(): if not item[u'service_item'].uses_file():
continue continue
skipMissing = False skipMissing = False
@ -539,6 +553,8 @@ class ServiceManager(QtGui.QWidget):
# Finally add all the listed media files. # Finally add all the listed media files.
for path_from in write_list: for path_from in write_list:
zip.write(path_from, path_from.encode(u'utf-8')) 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: except IOError:
log.exception(u'Failed to save service to disk') log.exception(u'Failed to save service to disk')
success = False success = False
@ -562,7 +578,7 @@ class ServiceManager(QtGui.QWidget):
fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow, fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow,
UiStrings().SaveService, UiStrings().SaveService,
SettingsManager.get_last_dir( SettingsManager.get_last_dir(
self.mainwindow.serviceSettingsSection), self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)')))
if not fileName: if not fileName:
return False return False
@ -584,8 +600,8 @@ class ServiceManager(QtGui.QWidget):
fileTo = None fileTo = None
try: try:
zip = zipfile.ZipFile(fileName) zip = zipfile.ZipFile(fileName)
for file in zip.namelist(): for zipinfo in zip.infolist():
ucsfile = file_is_unicode(file) ucsfile = file_is_unicode(zipinfo.filename)
if not ucsfile: if not ucsfile:
critical_error_message_box( critical_error_message_box(
message=translate('OpenLP.ServiceManager', message=translate('OpenLP.ServiceManager',
@ -593,14 +609,12 @@ class ServiceManager(QtGui.QWidget):
'The content encoding is not UTF-8.')) 'The content encoding is not UTF-8.'))
continue continue
osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile))
filePath = os.path.join(self.servicePath, if not osfile.startswith(u'audio'):
os.path.split(osfile)[1]) osfile = os.path.split(osfile)[1]
fileTo = open(filePath, u'wb') zipinfo.filename = osfile
fileTo.write(zip.read(file)) zip.extract(zipinfo, self.servicePath)
fileTo.flush() if osfile.endswith(u'osd'):
fileTo.close() p_file = os.path.join(self.servicePath, osfile)
if filePath.endswith(u'osd'):
p_file = filePath
if 'p_file' in locals(): if 'p_file' in locals():
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
fileTo = open(p_file, u'r') fileTo = open(p_file, u'r')
@ -624,17 +638,17 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.addRecentFile(fileName) self.mainwindow.addRecentFile(fileName)
self.setModified(False) self.setModified(False)
QtCore.QSettings().setValue( QtCore.QSettings().setValue(
'service/last file', QtCore.QVariant(fileName)) 'servicemanager/last file', QtCore.QVariant(fileName))
else: else:
critical_error_message_box( critical_error_message_box(
message=translate('OpenLP.ServiceManager', message=translate('OpenLP.ServiceManager',
'File is not a valid service.')) 'File is not a valid service.'))
log.exception(u'File contains no service data') log.exception(u'File contains no service data')
except (IOError, NameError, zipfile.BadZipfile): except (IOError, NameError, zipfile.BadZipfile):
log.exception(u'Problem loading service file %s' % fileName)
critical_error_message_box( critical_error_message_box(
message=translate('OpenLP.ServiceManager', message=translate('OpenLP.ServiceManager',
'File could not be opened because it is corrupt.')) 'File could not be opened because it is corrupt.'))
log.exception(u'Problem loading service file %s' % fileName)
except zipfile.BadZipfile: except zipfile.BadZipfile:
if os.path.getsize(fileName) == 0: if os.path.getsize(fileName) == 0:
log.exception(u'Service file is zero sized: %s' % fileName) log.exception(u'Service file is zero sized: %s' % fileName)
@ -666,7 +680,7 @@ class ServiceManager(QtGui.QWidget):
present. present.
""" """
fileName = QtCore.QSettings(). \ fileName = QtCore.QSettings(). \
value(u'service/last file',QtCore.QVariant(u'')).toString() value(u'servicemanager/last file',QtCore.QVariant(u'')).toString()
if fileName: if fileName:
self.loadFile(fileName) self.loadFile(fileName)
@ -683,16 +697,16 @@ class ServiceManager(QtGui.QWidget):
self.maintainAction.setVisible(False) self.maintainAction.setVisible(False)
self.notesAction.setVisible(False) self.notesAction.setVisible(False)
self.timeAction.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: and serviceItem[u'service_item'].edit_id:
self.editAction.setVisible(True) self.editAction.setVisible(True)
if serviceItem[u'service_item']\ if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsMaintain): .is_capable(ItemCapabilities.CanMaintain):
self.maintainAction.setVisible(True) self.maintainAction.setVisible(True)
if item.parent() is None: if item.parent() is None:
self.notesAction.setVisible(True) self.notesAction.setVisible(True)
if serviceItem[u'service_item']\ if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsVariableStartTime): .is_capable(ItemCapabilities.HasVariableStartTime):
self.timeAction.setVisible(True) self.timeAction.setVisible(True)
self.themeMenu.menuAction().setVisible(False) self.themeMenu.menuAction().setVisible(False)
# Set up the theme menu. # Set up the theme menu.
@ -963,7 +977,7 @@ class ServiceManager(QtGui.QWidget):
(unicode(translate('OpenLP.ServiceManager', 'Notes')), (unicode(translate('OpenLP.ServiceManager', 'Notes')),
cgi.escape(unicode(serviceitem.notes)))) cgi.escape(unicode(serviceitem.notes))))
if item[u'service_item'] \ if item[u'service_item'] \
.is_capable(ItemCapabilities.AllowsVariableStartTime): .is_capable(ItemCapabilities.HasVariableStartTime):
tips.append(item[u'service_item'].get_media_time()) tips.append(item[u'service_item'].get_media_time())
treewidgetitem.setToolTip(0, u'<br>'.join(tips)) treewidgetitem.setToolTip(0, u'<br>'.join(tips))
treewidgetitem.setData(0, QtCore.Qt.UserRole, treewidgetitem.setData(0, QtCore.Qt.UserRole,
@ -999,6 +1013,8 @@ class ServiceManager(QtGui.QWidget):
for file in os.listdir(self.servicePath): for file in os.listdir(self.servicePath):
file_path = os.path.join(self.servicePath, file) file_path = os.path.join(self.servicePath, file)
delete_file(file_path) 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): def onThemeComboBoxSelected(self, currentIndex):
""" """
@ -1008,7 +1024,8 @@ class ServiceManager(QtGui.QWidget):
self.service_theme = unicode(self.themeComboBox.currentText()) self.service_theme = unicode(self.themeComboBox.currentText())
self.mainwindow.renderer.set_service_theme(self.service_theme) self.mainwindow.renderer.set_service_theme(self.service_theme)
QtCore.QSettings().setValue( QtCore.QSettings().setValue(
self.mainwindow.serviceSettingsSection + u'/service theme', self.mainwindow.servicemanagerSettingsSection +
u'/service theme',
QtCore.QVariant(self.service_theme)) QtCore.QVariant(self.service_theme))
self.regenerateServiceItems() self.regenerateServiceItems()
@ -1196,7 +1213,7 @@ class ServiceManager(QtGui.QWidget):
item += 1 item += 1
if self.serviceItems and item < len(self.serviceItems) and \ if self.serviceItems and item < len(self.serviceItems) and \
self.serviceItems[item][u'service_item'].is_capable( self.serviceItems[item][u'service_item'].is_capable(
ItemCapabilities.AllowsPreview): ItemCapabilities.CanPreview):
self.mainwindow.previewController.addServiceManagerItem( self.mainwindow.previewController.addServiceManagerItem(
self.serviceItems[item][u'service_item'], 0) self.serviceItems[item][u'service_item'], 0)
self.mainwindow.liveController.previewListWidget.setFocus() self.mainwindow.liveController.previewListWidget.setFocus()
@ -1214,7 +1231,7 @@ class ServiceManager(QtGui.QWidget):
""" """
item = self.findServiceItem()[0] item = self.findServiceItem()[0]
if self.serviceItems[item][u'service_item']\ if self.serviceItems[item][u'service_item']\
.is_capable(ItemCapabilities.AllowsEdit): .is_capable(ItemCapabilities.CanEdit):
Receiver.send_message(u'%s_edit' % Receiver.send_message(u'%s_edit' %
self.serviceItems[item][u'service_item'].name.lower(), self.serviceItems[item][u'service_item'].name.lower(),
u'L:%s' % self.serviceItems[item][u'service_item'].edit_id) u'L:%s' % self.serviceItems[item][u'service_item'].edit_id)
@ -1297,7 +1314,7 @@ class ServiceManager(QtGui.QWidget):
serviceItem = self.serviceItems[pos] serviceItem = self.serviceItems[pos]
if (plugin == serviceItem[u'service_item'].name and if (plugin == serviceItem[u'service_item'].name and
serviceItem[u'service_item'].is_capable( serviceItem[u'service_item'].is_capable(
ItemCapabilities.AllowsAdditions)): ItemCapabilities.CanAppend)):
action = self.dndMenu.exec_(QtGui.QCursor.pos()) action = self.dndMenu.exec_(QtGui.QCursor.pos())
# New action required # New action required
if action == self.newAction: if action == self.newAction:

View File

@ -256,6 +256,12 @@ class SlideController(QtGui.QWidget):
self.songMenu.setMenu(QtGui.QMenu( self.songMenu.setMenu(QtGui.QMenu(
translate('OpenLP.SlideController', 'Go To'), self.toolbar)) translate('OpenLP.SlideController', 'Go To'), self.toolbar))
self.toolbar.makeWidgetsInvisible([u'Song Menu']) 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. # Build the volumeSlider.
self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal) self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.volumeSlider.setTickInterval(1) self.volumeSlider.setTickInterval(1)
@ -512,13 +518,13 @@ class SlideController(QtGui.QWidget):
self.playSlidesOnce.setChecked(False) self.playSlidesOnce.setChecked(False)
self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png'))
self.playSlidesLoop.setChecked(False) 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 item.is_text():
if QtCore.QSettings().value( if QtCore.QSettings().value(
self.parent().songsSettingsSection + u'/display songbar', self.parent().songsSettingsSection + u'/display songbar',
QtCore.QVariant(True)).toBool() and len(self.slideList) > 0: QtCore.QVariant(True)).toBool() and len(self.slideList) > 0:
self.toolbar.makeWidgetsVisible([u'Song Menu']) 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: len(item.get_frames()) > 1:
self.toolbar.makeWidgetsVisible(self.loopList) self.toolbar.makeWidgetsVisible(self.loopList)
if item.is_media(): if item.is_media():
@ -538,7 +544,7 @@ class SlideController(QtGui.QWidget):
self.toolbar.hide() self.toolbar.hide()
self.mediabar.setVisible(False) self.mediabar.setVisible(False)
self.toolbar.makeWidgetsInvisible(self.songEditList) 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) self.toolbar.makeWidgetsVisible(self.songEditList)
elif item.is_media(): elif item.is_media():
self.toolbar.setVisible(False) self.toolbar.setVisible(False)
@ -576,7 +582,7 @@ class SlideController(QtGui.QWidget):
""" """
Replacement item following a remote edit Replacement item following a remote edit
""" """
if item.__eq__(self.serviceItem): if item == self.serviceItem:
self._processItem(item, self.previewListWidget.currentRow()) self._processItem(item, self.previewListWidget.currentRow())
def addServiceManagerItem(self, item, slideno): def addServiceManagerItem(self, item, slideno):
@ -586,15 +592,17 @@ class SlideController(QtGui.QWidget):
Called by ServiceManager Called by ServiceManager
""" """
log.debug(u'addServiceManagerItem live = %s' % self.isLive) 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: if slideno == -1:
slideno = 0 slidenum = 0
# If service item is the same as the current on only change slide # If service item is the same as the current one, only change slide
if item.__eq__(self.serviceItem): if slideno >= 0 and item == self.serviceItem:
self.__checkUpdateSelectedSlide(slideno) self.__checkUpdateSelectedSlide(slidenum)
self.slideSelected() self.slideSelected()
return else:
self._processItem(item, slideno) self._processItem(item, slidenum)
def _processItem(self, serviceItem, slideno): def _processItem(self, serviceItem, slideno):
""" """
@ -618,6 +626,22 @@ class SlideController(QtGui.QWidget):
self.previewListWidget.setColumnWidth(0, width) self.previewListWidget.setColumnWidth(0, width)
if self.isLive: if self.isLive:
self.songMenu.menu().clear() 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)
if QtCore.QSettings().value(
self.parent().generalSettingsSection + \
u'/audio start paused',
QtCore.QVariant(True)).toBool():
self.audioPauseItem.setChecked(True)
self.display.audioPlayer.pause()
else:
self.display.audioPlayer.play()
self.setAudioItemsVisibility(True)
row = 0 row = 0
text = [] text = []
for framenumber, frame in enumerate(self.serviceItem.get_frames()): for framenumber, frame in enumerate(self.serviceItem.get_frames()):
@ -767,6 +791,8 @@ class SlideController(QtGui.QWidget):
self.onBlankDisplay(True) self.onBlankDisplay(True)
else: else:
Receiver.send_message(u'maindisplay_show') Receiver.send_message(u'maindisplay_show')
else:
Receiver.send_message(u'maindisplay_hide', HideMode.Screen)
def onSlideBlank(self): def onSlideBlank(self):
""" """
@ -1097,6 +1123,17 @@ class SlideController(QtGui.QWidget):
self.playSlidesLoop.setChecked(False) self.playSlidesLoop.setChecked(False)
self.onToggleLoop() 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): def timerEvent(self, event):
""" """
If the timer event is for this window select next slide If the timer event is for this window select next slide

View File

@ -66,6 +66,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.onGradientComboBoxCurrentIndexChanged) self.onGradientComboBoxCurrentIndexChanged)
QtCore.QObject.connect(self.colorButton, QtCore.QObject.connect(self.colorButton,
QtCore.SIGNAL(u'clicked()'), self.onColorButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onColorButtonClicked)
QtCore.QObject.connect(self.imageColorButton,
QtCore.SIGNAL(u'clicked()'), self.onImageColorButtonClicked)
QtCore.QObject.connect(self.gradientStartButton, QtCore.QObject.connect(self.gradientStartButton,
QtCore.SIGNAL(u'clicked()'), self.onGradientStartButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onGradientStartButtonClicked)
QtCore.QObject.connect(self.gradientEndButton, QtCore.QObject.connect(self.gradientEndButton,
@ -330,6 +332,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.theme.background_end_color) self.theme.background_end_color)
self.setField(u'background_type', QtCore.QVariant(1)) self.setField(u'background_type', QtCore.QVariant(1))
else: else:
self.imageColorButton.setStyleSheet(u'background-color: %s' %
self.theme.background_border_color)
self.imageFileEdit.setText(self.theme.background_filename) self.imageFileEdit.setText(self.theme.background_filename)
self.setField(u'background_type', QtCore.QVariant(2)) self.setField(u'background_type', QtCore.QVariant(2))
if self.theme.background_direction == \ if self.theme.background_direction == \
@ -464,6 +468,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self._colorButton(self.theme.background_color) self._colorButton(self.theme.background_color)
self.setBackgroundPageValues() self.setBackgroundPageValues()
def onImageColorButtonClicked(self):
"""
Background / Gradient 1 Color button pushed.
"""
self.theme.background_border_color = \
self._colorButton(self.theme.background_border_color)
self.setBackgroundPageValues()
def onGradientStartButtonClicked(self): def onGradientStartButtonClicked(self):
""" """
Gradient 2 Color button pushed. Gradient 2 Color button pushed.
@ -564,7 +576,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
def accept(self): def accept(self):
""" """
Lets save the them as Finish has been pressed Lets save the theme as Finish has been pressed
""" """
# Save the theme name # Save the theme name
self.theme.theme_name = unicode(self.field(u'name').toString()) self.theme.theme_name = unicode(self.field(u'name').toString())

View File

@ -610,6 +610,11 @@ class ThemeManager(QtGui.QWidget):
and to trigger the reload of the theme list and to trigger the reload of the theme list
""" """
self._writeTheme(theme, imageFrom, imageTo) self._writeTheme(theme, imageFrom, imageTo)
if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.update_image(theme.theme_name,
u'theme', QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.process_updates()
self.loadThemes() self.loadThemes()
def _writeTheme(self, theme, imageFrom, imageTo): def _writeTheme(self, theme, imageFrom, imageTo):

View File

@ -105,6 +105,11 @@ class Ui_ThemeWizard(object):
self.imageLayout = QtGui.QFormLayout(self.imageWidget) self.imageLayout = QtGui.QFormLayout(self.imageWidget)
self.imageLayout.setMargin(0) self.imageLayout.setMargin(0)
self.imageLayout.setObjectName(u'ImageLayout') self.imageLayout.setObjectName(u'ImageLayout')
self.imageColorLabel = QtGui.QLabel(self.colorWidget)
self.imageColorLabel.setObjectName(u'ImageColorLabel')
self.imageColorButton = QtGui.QPushButton(self.colorWidget)
self.imageColorButton.setObjectName(u'ImageColorButton')
self.imageLayout.addRow(self.imageColorLabel, self.imageColorButton)
self.imageLabel = QtGui.QLabel(self.imageWidget) self.imageLabel = QtGui.QLabel(self.imageWidget)
self.imageLabel.setObjectName(u'ImageLabel') self.imageLabel.setObjectName(u'ImageLabel')
self.imageFileLayout = QtGui.QHBoxLayout() self.imageFileLayout = QtGui.QHBoxLayout()
@ -118,7 +123,7 @@ class Ui_ThemeWizard(object):
build_icon(u':/general/general_open.png')) build_icon(u':/general/general_open.png'))
self.imageFileLayout.addWidget(self.imageBrowseButton) self.imageFileLayout.addWidget(self.imageBrowseButton)
self.imageLayout.addRow(self.imageLabel, self.imageFileLayout) self.imageLayout.addRow(self.imageLabel, self.imageFileLayout)
self.imageLayout.setItem(1, QtGui.QFormLayout.LabelRole, self.spacer) self.imageLayout.setItem(2, QtGui.QFormLayout.LabelRole, self.spacer)
self.backgroundStack.addWidget(self.imageWidget) self.backgroundStack.addWidget(self.imageWidget)
self.backgroundLayout.addLayout(self.backgroundStack) self.backgroundLayout.addLayout(self.backgroundStack)
themeWizard.addPage(self.backgroundPage) themeWizard.addPage(self.backgroundPage)
@ -443,6 +448,8 @@ class Ui_ThemeWizard(object):
translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right')) translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right'))
self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom, self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom,
translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right')) translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right'))
self.imageColorLabel.setText(
translate(u'OpenLP.ThemeWizard', 'Background color:'))
self.imageLabel.setText(u'%s:' % UiStrings().Image) self.imageLabel.setText(u'%s:' % UiStrings().Image)
self.mainAreaPage.setTitle( self.mainAreaPage.setTitle(
translate('OpenLP.ThemeWizard', 'Main Area Font Details')) translate('OpenLP.ThemeWizard', 'Main Area Font Details'))

View File

@ -218,7 +218,7 @@ class BSExtract(object):
send_error_message(u'parse') send_error_message(u'parse')
return None return None
content = content.find(u'div').findAll(u'div') content = content.find(u'div').findAll(u'div')
verse_number = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse') verse_number = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*')
verses = {} verses = {}
for verse in content: for verse in content:
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')

View File

@ -67,7 +67,7 @@ class BibleMediaItem(MediaManagerItem):
self.hasSearch = True self.hasSearch = True
self.search_results = {} self.search_results = {}
self.second_search_results = {} self.second_search_results = {}
self.check_search_result() self.checkSearchResult()
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles) QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles)
@ -391,10 +391,13 @@ class BibleMediaItem(MediaManagerItem):
elif len(bibles): elif len(bibles):
self.initialiseAdvancedBible(bibles[0]) self.initialiseAdvancedBible(bibles[0])
def reloadBibles(self): def reloadBibles(self, process=False):
log.debug(u'Reloading Bibles') log.debug(u'Reloading Bibles')
self.plugin.manager.reload_bibles() self.plugin.manager.reload_bibles()
self.loadBibles() self.loadBibles()
# If called from first time wizard re-run, process any new bibles.
if process:
self.plugin.appStartup()
self.updateAutoCompleter() self.updateAutoCompleter()
def initialiseAdvancedBible(self, bible): def initialiseAdvancedBible(self, bible):
@ -648,7 +651,7 @@ class BibleMediaItem(MediaManagerItem):
elif self.search_results: elif self.search_results:
self.displayResults(bible, second_bible) self.displayResults(bible, second_bible)
self.advancedSearchButton.setEnabled(True) self.advancedSearchButton.setEnabled(True)
self.check_search_result() self.checkSearchResult()
Receiver.send_message(u'cursor_normal') Receiver.send_message(u'cursor_normal')
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
@ -712,7 +715,7 @@ class BibleMediaItem(MediaManagerItem):
elif self.search_results: elif self.search_results:
self.displayResults(bible, second_bible) self.displayResults(bible, second_bible)
self.quickSearchButton.setEnabled(True) self.quickSearchButton.setEnabled(True)
self.check_search_result() self.checkSearchResult()
Receiver.send_message(u'cursor_normal') Receiver.send_message(u'cursor_normal')
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
@ -785,7 +788,8 @@ class BibleMediaItem(MediaManagerItem):
items.append(bible_verse) items.append(bible_verse)
return items return items
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
""" """
Generates and formats the slides for the service item as well as the Generates and formats the slides for the service item as well as the
service item's title. service item's title.
@ -860,9 +864,9 @@ class BibleMediaItem(MediaManagerItem):
not second_bible: not second_bible:
# Split the line but do not replace line breaks in renderer. # Split the line but do not replace line breaks in renderer.
service_item.add_capability(ItemCapabilities.NoLineBreaks) service_item.add_capability(ItemCapabilities.NoLineBreaks)
service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop) service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.AllowsWordSplit) service_item.add_capability(ItemCapabilities.CanWordSplit)
# Service Item: Title # Service Item: Title
service_item.title = u', '.join(raw_title) service_item.title = u', '.join(raw_title)
# Service Item: Theme # Service Item: Theme

View File

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

View File

@ -132,7 +132,7 @@ class CustomMediaItem(MediaManagerItem):
def loadList(self, custom_slides): def loadList(self, custom_slides):
# Sort out what custom we want to select after loading the list. # Sort out what custom we want to select after loading the list.
self.save_auto_select_id() self.saveAutoSelectId()
self.listView.clear() self.listView.clear()
# Sort the customs by its title considering language specific # Sort the customs by its title considering language specific
# characters. lower() is needed for windows! # characters. lower() is needed for windows!
@ -144,9 +144,9 @@ class CustomMediaItem(MediaManagerItem):
QtCore.Qt.UserRole, QtCore.QVariant(custom_slide.id)) QtCore.Qt.UserRole, QtCore.QVariant(custom_slide.id))
self.listView.addItem(custom_name) self.listView.addItem(custom_name)
# Auto-select the custom. # Auto-select the custom.
if custom_slide.id == self.auto_select_id: if custom_slide.id == self.autoSelectId:
self.listView.setCurrentItem(custom_name) self.listView.setCurrentItem(custom_name)
self.auto_select_id = -1 self.autoSelectId = -1
# Called to redisplay the custom list screen edith from a search # Called to redisplay the custom list screen edith from a search
# or from the exit of the Custom edit dialog. If remote editing is # 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. # active trigger it and clean up so it will not update again.
@ -180,7 +180,7 @@ class CustomMediaItem(MediaManagerItem):
self.remoteTriggered = remote_type self.remoteTriggered = remote_type
self.edit_custom_form.loadCustom(custom_id, (remote_type == u'P')) self.edit_custom_form.loadCustom(custom_id, (remote_type == u'P'))
self.edit_custom_form.exec_() self.edit_custom_form.exec_()
self.auto_select_id = -1 self.autoSelectId = -1
self.onSearchTextButtonClick() self.onSearchTextButtonClick()
def onEditClick(self): def onEditClick(self):
@ -192,7 +192,7 @@ class CustomMediaItem(MediaManagerItem):
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.edit_custom_form.loadCustom(item_id, False) self.edit_custom_form.loadCustom(item_id, False)
self.edit_custom_form.exec_() self.edit_custom_form.exec_()
self.auto_select_id = -1 self.autoSelectId = -1
self.onSearchTextButtonClick() self.onSearchTextButtonClick()
def onDeleteClick(self): def onDeleteClick(self):
@ -217,21 +217,21 @@ class CustomMediaItem(MediaManagerItem):
for item in self.listView.selectedIndexes()] for item in self.listView.selectedIndexes()]
for id in id_list: for id in id_list:
self.plugin.manager.delete_object(CustomSlide, id) self.plugin.manager.delete_object(CustomSlide, id)
for row in row_list: self.onSearchTextButtonClick()
self.listView.takeItem(row)
def onFocus(self): def onFocus(self):
self.searchTextEdit.setFocus() self.searchTextEdit.setFocus()
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
raw_footer = [] raw_footer = []
slide = None slide = None
theme = None theme = None
item_id = self._getIdOfItemToGenerate(item, self.remoteCustom) item_id = self._getIdOfItemToGenerate(item, self.remoteCustom)
service_item.add_capability(ItemCapabilities.AllowsEdit) service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop) service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.AllowsVirtualSplit) service_item.add_capability(ItemCapabilities.CanSoftBreak)
customSlide = self.plugin.manager.get_object(CustomSlide, item_id) customSlide = self.plugin.manager.get_object(CustomSlide, item_id)
title = customSlide.title title = customSlide.title
credit = customSlide.credits credit = customSlide.credits
@ -274,7 +274,7 @@ class CustomMediaItem(MediaManagerItem):
CustomSlide.theme_name.like(u'%' + self.whitespace.sub(u' ', CustomSlide.theme_name.like(u'%' + self.whitespace.sub(u' ',
search_keywords) + u'%'), order_by_ref=CustomSlide.title) search_keywords) + u'%'), order_by_ref=CustomSlide.title)
self.loadList(search_results) self.loadList(search_results)
self.check_search_result() self.checkSearchResult()
def onSearchTextEditChanged(self, text): def onSearchTextEditChanged(self, text):
""" """

View File

@ -25,10 +25,13 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
from PyQt4 import QtCore, QtGui
import logging import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
from openlp.plugins.images.lib import ImageMediaItem Receiver
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -36,10 +39,13 @@ class ImagePlugin(Plugin):
log.info(u'Image Plugin loaded') log.info(u'Image Plugin loaded')
def __init__(self, plugin_helpers): def __init__(self, plugin_helpers):
Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem) Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem,
ImageTab)
self.weight = -7 self.weight = -7
self.icon_path = u':/plugins/plugin_images.png' self.icon_path = u':/plugins/plugin_images.png'
self.icon = build_icon(self.icon_path) self.icon = build_icon(self.icon_path)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'image_updated'), self.image_updated)
def about(self): def about(self):
about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>' about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>'
@ -81,3 +87,13 @@ class ImagePlugin(Plugin):
'Add the selected image to the service.') 'Add the selected image to the service.')
} }
self.setPluginUiTextStrings(tooltips) self.setPluginUiTextStrings(tooltips)
def image_updated(self):
"""
Triggered by saving and changing the image border. Sets the images in
image manager to require updates. Actual update is triggered by the
last part of saving the config.
"""
background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000')))
self.liveController.imageManager.update_images(u'image', background)

View File

@ -26,3 +26,4 @@
############################################################################### ###############################################################################
from mediaitem import ImageMediaItem from mediaitem import ImageMediaItem
from imagetab import ImageTab

View File

@ -0,0 +1,101 @@
# -*- 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 SettingsTab, translate, Receiver
from openlp.core.lib.ui import UiStrings, create_valign_combo
class ImageTab(SettingsTab):
"""
ImageTab is the images settings tab in the settings dialog.
"""
def __init__(self, parent, name, visible_title, icon_path):
SettingsTab.__init__(self, parent, name, visible_title, icon_path)
def setupUi(self):
self.setObjectName(u'ImagesTab')
SettingsTab.setupUi(self)
self.bgColorGroupBox = QtGui.QGroupBox(self.leftColumn)
self.bgColorGroupBox.setObjectName(u'FontGroupBox')
self.formLayout = QtGui.QFormLayout(self.bgColorGroupBox)
self.formLayout.setObjectName(u'FormLayout')
self.colorLayout = QtGui.QHBoxLayout()
self.backgroundColorLabel = QtGui.QLabel(self.bgColorGroupBox)
self.backgroundColorLabel.setObjectName(u'BackgroundColorLabel')
self.colorLayout.addWidget(self.backgroundColorLabel)
self.backgroundColorButton = QtGui.QPushButton(self.bgColorGroupBox)
self.backgroundColorButton.setObjectName(u'BackgroundColorButton')
self.colorLayout.addWidget(self.backgroundColorButton)
self.formLayout.addRow(self.colorLayout)
self.informationLabel = QtGui.QLabel(self.bgColorGroupBox)
self.informationLabel.setObjectName(u'InformationLabel')
self.formLayout.addRow(self.informationLabel)
self.leftLayout.addWidget(self.bgColorGroupBox)
self.leftLayout.addStretch()
self.rightColumn.setSizePolicy(
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.rightLayout.addStretch()
# Signals and slots
QtCore.QObject.connect(self.backgroundColorButton,
QtCore.SIGNAL(u'pressed()'), self.onbackgroundColorButtonClicked)
def retranslateUi(self):
self.bgColorGroupBox.setTitle(
translate('ImagesPlugin.ImageTab', 'Background Color'))
self.backgroundColorLabel.setText(
translate('ImagesPlugin.ImageTab', 'Default Color:'))
self.informationLabel.setText(
translate('ImagesPlugin.ImageTab', 'Provides border where image '
'is not the correct dimensions for the screen when resized.'))
def onbackgroundColorButtonClicked(self):
new_color = QtGui.QColorDialog.getColor(
QtGui.QColor(self.bg_color), self)
if new_color.isValid():
self.bg_color = new_color.name()
self.backgroundColorButton.setStyleSheet(
u'background-color: %s' % self.bg_color)
def load(self):
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
self.bg_color = unicode(settings.value(
u'background color', QtCore.QVariant(u'#000000')).toString())
self.initial_color = self.bg_color
settings.endGroup()
self.backgroundColorButton.setStyleSheet(
u'background-color: %s' % self.bg_color)
def save(self):
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
settings.setValue(u'background color', QtCore.QVariant(self.bg_color))
settings.endGroup()
if self.initial_color != self.bg_color:
Receiver.send_message(u'image_updated')

View File

@ -99,6 +99,8 @@ class ImageMediaItem(MediaManagerItem):
""" """
Remove an image item from the list Remove an image item from the list
""" """
# Turn off auto preview triggers.
self.listView.blockSignals(True)
if check_item_selected(self.listView, translate('ImagePlugin.MediaItem', if check_item_selected(self.listView, translate('ImagePlugin.MediaItem',
'You must select an image to delete.')): 'You must select an image to delete.')):
row_list = [item.row() for item in self.listView.selectedIndexes()] row_list = [item.row() for item in self.listView.selectedIndexes()]
@ -111,6 +113,7 @@ class ImageMediaItem(MediaManagerItem):
self.listView.takeItem(row) self.listView.takeItem(row)
SettingsManager.set_list(self.settingsSection, SettingsManager.set_list(self.settingsSection,
u'images', self.getFileList()) u'images', self.getFileList())
self.listView.blockSignals(False)
def loadList(self, images, initialLoad=False): def loadList(self, images, initialLoad=False):
if not initialLoad: if not initialLoad:
@ -139,7 +142,10 @@ class ImageMediaItem(MediaManagerItem):
if not initialLoad: if not initialLoad:
self.plugin.formparent.finishedProgressBar() self.plugin.formparent.finishedProgressBar()
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000')))
if item: if item:
items = [item] items = [item]
else: else:
@ -147,10 +153,10 @@ class ImageMediaItem(MediaManagerItem):
if not items: if not items:
return False return False
service_item.title = unicode(self.plugin.nameStrings[u'plural']) service_item.title = unicode(self.plugin.nameStrings[u'plural'])
service_item.add_capability(ItemCapabilities.AllowsMaintain) service_item.add_capability(ItemCapabilities.CanMaintain)
service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop) service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.AllowsAdditions) service_item.add_capability(ItemCapabilities.CanAppend)
# force a nonexistent theme # force a nonexistent theme
service_item.theme = -1 service_item.theme = -1
missing_items = [] missing_items = []
@ -164,11 +170,12 @@ class ImageMediaItem(MediaManagerItem):
items.remove(item) items.remove(item)
# We cannot continue, as all images do not exist. # We cannot continue, as all images do not exist.
if not items: if not items:
critical_error_message_box( if not remote:
translate('ImagePlugin.MediaItem', 'Missing Image(s)'), critical_error_message_box(
unicode(translate('ImagePlugin.MediaItem', translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
'The following image(s) no longer exist: %s')) % unicode(translate('ImagePlugin.MediaItem',
u'\n'.join(missing_items_filenames)) 'The following image(s) no longer exist: %s')) %
u'\n'.join(missing_items_filenames))
return False return False
# We have missing as well as existing images. We ask what to do. # We have missing as well as existing images. We ask what to do.
elif missing_items and QtGui.QMessageBox.question(self, elif missing_items and QtGui.QMessageBox.question(self,
@ -183,7 +190,7 @@ class ImageMediaItem(MediaManagerItem):
for bitem in items: for bitem in items:
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
(path, name) = os.path.split(filename) (path, name) = os.path.split(filename)
service_item.add_from_image(filename, name) service_item.add_from_image(filename, name, background)
return True return True
def onResetClick(self): def onResetClick(self):
@ -206,13 +213,16 @@ class ImageMediaItem(MediaManagerItem):
if check_item_selected(self.listView, if check_item_selected(self.listView,
translate('ImagePlugin.MediaItem', translate('ImagePlugin.MediaItem',
'You must select an image to replace the background with.')): 'You must select an image to replace the background with.')):
background = QtGui.QColor(QtCore.QSettings().value(
self.settingsSection + u'/background color',
QtCore.QVariant(u'#000000')))
item = self.listView.selectedIndexes()[0] item = self.listView.selectedIndexes()[0]
bitem = self.listView.item(item.row()) bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename): if os.path.exists(filename):
(path, name) = os.path.split(filename) (path, name) = os.path.split(filename)
if self.plugin.liveController.display.directImage(name, if self.plugin.liveController.display.directImage(name,
filename): filename, background):
self.resetAction.setVisible(True) self.resetAction.setVisible(True)
else: else:
critical_error_message_box(UiStrings().LiveBGError, critical_error_message_box(UiStrings().LiveBGError,

View File

@ -31,11 +31,11 @@ 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 SettingsManager, translate, check_item_selected, Receiver, MediaType
from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.ui import UiStrings, critical_error_message_box
from PyQt4.phonon import Phonon
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -48,9 +48,9 @@ class MediaMediaItem(MediaManagerItem):
log.info(u'%s MediaMediaItem loaded', __name__) log.info(u'%s MediaMediaItem loaded', __name__)
def __init__(self, parent, plugin, icon): def __init__(self, parent, plugin, icon):
self.IconPath = u'images/image' self.iconPath = u'images/image'
self.background = False self.background = False
self.PreviewFunction = CLAPPERBOARD self.previewFunction = CLAPPERBOARD
MediaManagerItem.__init__(self, parent, plugin, icon) MediaManagerItem.__init__(self, parent, plugin, icon)
self.singleServiceItem = False self.singleServiceItem = False
self.hasSearch = True self.hasSearch = True
@ -129,18 +129,20 @@ class MediaMediaItem(MediaManagerItem):
'There was a problem replacing your background, ' 'There was a problem replacing your background, '
'the media file "%s" no longer exists.')) % filename) 'the media file "%s" no longer exists.')) % filename)
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
if item is None: if item is None:
item = self.listView.currentItem() item = self.listView.currentItem()
if item is None: if item is None:
return False return False
filename = unicode(item.data(QtCore.Qt.UserRole).toString()) filename = unicode(item.data(QtCore.Qt.UserRole).toString())
if not os.path.exists(filename): if not os.path.exists(filename):
# File is no longer present if not remote:
critical_error_message_box( # File is no longer present
translate('MediaPlugin.MediaItem', 'Missing Media File'), critical_error_message_box(
unicode(translate('MediaPlugin.MediaItem', translate('MediaPlugin.MediaItem', 'Missing Media File'),
'The file %s no longer exists.')) % filename) unicode(translate('MediaPlugin.MediaItem',
'The file %s no longer exists.')) % filename)
return False return False
self.mediaObject.stop() self.mediaObject.stop()
self.mediaObject.clearQueue() self.mediaObject.clearQueue()
@ -156,13 +158,16 @@ class MediaMediaItem(MediaManagerItem):
or self.mediaObject.currentSource().type() \ or self.mediaObject.currentSource().type() \
== Phonon.MediaSource.Invalid: == Phonon.MediaSource.Invalid:
self.mediaObject.stop() self.mediaObject.stop()
critical_error_message_box(UiStrings().UnsupportedFile, critical_error_message_box(
UiStrings().UnsupportedFile) 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 return False
self.mediaObject.stop() self.mediaObject.stop()
service_item.media_length = self.mediaObject.totalTime() / 1000 service_item.media_length = self.mediaObject.totalTime() / 1000
service_item.add_capability( service_item.add_capability(
ItemCapabilities.AllowsVariableStartTime) ItemCapabilities.HasVariableStartTime)
service_item.title = unicode(self.plugin.nameStrings[u'singular']) service_item.title = unicode(self.plugin.nameStrings[u'singular'])
service_item.add_capability(ItemCapabilities.RequiresMedia) service_item.add_capability(ItemCapabilities.RequiresMedia)
# force a non-existent theme # force a non-existent theme
@ -217,6 +222,19 @@ class MediaMediaItem(MediaManagerItem):
item_name.setToolTip(track) item_name.setToolTip(track)
self.listView.addItem(item_name) 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): def createPhonon(self):
log.debug(u'CreatePhonon') log.debug(u'CreatePhonon')
if not self.mediaObject: if not self.mediaObject:

View File

@ -233,7 +233,8 @@ class PresentationMediaItem(MediaManagerItem):
SettingsManager.set_list(self.settingsSection, SettingsManager.set_list(self.settingsSection,
u'presentations', self.getFileList()) u'presentations', self.getFileList())
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
""" """
Load the relevant information for displaying the presentation Load the relevant information for displaying the presentation
in the slidecontroller. In the case of powerpoints, an image in the slidecontroller. In the case of powerpoints, an image
@ -248,7 +249,7 @@ class PresentationMediaItem(MediaManagerItem):
service_item.title = unicode(self.displayTypeComboBox.currentText()) service_item.title = unicode(self.displayTypeComboBox.currentText())
service_item.shortname = unicode(self.displayTypeComboBox.currentText()) service_item.shortname = unicode(self.displayTypeComboBox.currentText())
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
service_item.add_capability(ItemCapabilities.AllowsDetailedTitleDisplay) service_item.add_capability(ItemCapabilities.HasDetailedTitleDisplay)
shortname = service_item.shortname shortname = service_item.shortname
if shortname: if shortname:
for bitem in items: for bitem in items:
@ -275,12 +276,13 @@ class PresentationMediaItem(MediaManagerItem):
return True return True
else: else:
# File is no longer present # File is no longer present
critical_error_message_box( if not remote:
translate('PresentationPlugin.MediaItem', critical_error_message_box(
'Missing Presentation'), translate('PresentationPlugin.MediaItem',
unicode(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
'The Presentation %s is incomplete,' unicode(translate('PresentationPlugin.MediaItem',
' please reload.')) % filename) 'The Presentation %s is incomplete,'
' please reload.')) % filename)
return False return False
else: else:
# File is no longer present # File is no longer present

View File

@ -528,7 +528,7 @@ class HttpConnection(object):
id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem: if plugin.status == PluginStatus.Active and plugin.mediaItem:
plugin.mediaItem.goLive(id) plugin.mediaItem.goLive(id, remote=True)
def add_to_service(self, type): def add_to_service(self, type):
""" """
@ -538,7 +538,7 @@ class HttpConnection(object):
plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
if plugin.status == PluginStatus.Active and plugin.mediaItem: if plugin.status == PluginStatus.Active and plugin.mediaItem:
item_id = plugin.mediaItem.createItemFromId(id) item_id = plugin.mediaItem.createItemFromId(id)
plugin.mediaItem.addToService(item_id) plugin.mediaItem.addToService(item_id, remote=True)
def send_response(self, response): def send_response(self, response):
http = u'HTTP/1.1 %s\r\n' % response.code http = u'HTTP/1.1 %s\r\n' % response.code

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 the .ui files later if necessary.
""" """
from mediafilesform import MediaFilesForm
from authorsform import AuthorsForm from authorsform import AuthorsForm
from topicsform import TopicsForm from topicsform import TopicsForm
from songbookform import SongBookForm from songbookform import SongBookForm

View File

@ -28,7 +28,8 @@
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import build_icon, translate 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 from openlp.plugins.songs.lib.ui import SongStrings
class Ui_EditSongDialog(object): class Ui_EditSongDialog(object):
@ -36,9 +37,11 @@ class Ui_EditSongDialog(object):
editSongDialog.setObjectName(u'editSongDialog') editSongDialog.setObjectName(u'editSongDialog')
editSongDialog.resize(650, 400) editSongDialog.resize(650, 400)
editSongDialog.setWindowIcon( editSongDialog.setWindowIcon(
build_icon(u':/icon/openlp.org-icon-32.bmp')) build_icon(u':/icon/openlp-logo-16x16.png'))
editSongDialog.setModal(True) editSongDialog.setModal(True)
self.dialogLayout = QtGui.QVBoxLayout(editSongDialog) self.dialogLayout = QtGui.QVBoxLayout(editSongDialog)
self.dialogLayout.setSpacing(8)
self.dialogLayout.setContentsMargins(8, 8, 8, 8)
self.dialogLayout.setObjectName(u'dialogLayout') self.dialogLayout.setObjectName(u'dialogLayout')
self.songTabWidget = QtGui.QTabWidget(editSongDialog) self.songTabWidget = QtGui.QTabWidget(editSongDialog)
self.songTabWidget.setObjectName(u'songTabWidget') self.songTabWidget.setObjectName(u'songTabWidget')
@ -246,6 +249,36 @@ class Ui_EditSongDialog(object):
self.commentsLayout.addWidget(self.commentsEdit) self.commentsLayout.addWidget(self.commentsEdit)
self.themeTabLayout.addWidget(self.commentsGroupBox) self.themeTabLayout.addWidget(self.commentsGroupBox)
self.songTabWidget.addTab(self.themeTab, u'') 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.dialogLayout.addWidget(self.songTabWidget)
self.buttonBox = create_accept_reject_button_box(editSongDialog) self.buttonBox = create_accept_reject_button_box(editSongDialog)
self.dialogLayout.addWidget(self.buttonBox) self.dialogLayout.addWidget(self.buttonBox)
@ -305,6 +338,17 @@ class Ui_EditSongDialog(object):
self.songTabWidget.indexOf(self.themeTab), self.songTabWidget.indexOf(self.themeTab),
translate('SongsPlugin.EditSongForm', translate('SongsPlugin.EditSongForm',
'Theme, Copyright Info && Comments')) '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): def editSongDialogComboBox(parent, name):
""" """

View File

@ -27,15 +27,18 @@
import logging import logging
import re import re
import os
import shutil
from PyQt4 import QtCore, QtGui 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, \ from openlp.core.lib.ui import UiStrings, add_widget_completer, \
critical_error_message_box, find_and_set_in_combo_box 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 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 openlp.plugins.songs.lib.ui import SongStrings
from editsongdialog import Ui_EditSongDialog from editsongdialog import Ui_EditSongDialog
@ -93,6 +96,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.mediaitem.plugin.renderer.themeManager.onAddTheme) self.mediaitem.plugin.renderer.themeManager.onAddTheme)
QtCore.QObject.connect(self.maintenanceButton, QtCore.QObject.connect(self.maintenanceButton,
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked) 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.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'theme_update_list'), self.loadThemes) QtCore.SIGNAL(u'theme_update_list'), self.loadThemes)
self.previewButton = QtGui.QPushButton() self.previewButton = QtGui.QPushButton()
@ -104,12 +115,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
QtCore.SIGNAL(u'clicked(QAbstractButton*)'), self.onPreview) QtCore.SIGNAL(u'clicked(QAbstractButton*)'), self.onPreview)
# Create other objects and forms # Create other objects and forms
self.manager = manager self.manager = manager
self.verse_form = EditVerseForm(self) self.verseForm = EditVerseForm(self)
self.mediaForm = MediaFilesForm(self)
self.initialise() self.initialise()
self.authorsListView.setSortingEnabled(False) self.authorsListView.setSortingEnabled(False)
self.authorsListView.setAlternatingRowColors(True) self.authorsListView.setAlternatingRowColors(True)
self.topicsListView.setSortingEnabled(False) self.topicsListView.setSortingEnabled(False)
self.topicsListView.setAlternatingRowColors(True) self.topicsListView.setAlternatingRowColors(True)
self.audioListWidget.setAlternatingRowColors(True)
self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE) self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE)
self.whitespace = re.compile(r'\W+', re.UNICODE) self.whitespace = re.compile(r'\W+', re.UNICODE)
@ -161,6 +174,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.themes.append(theme) self.themes.append(theme)
add_widget_completer(self.themes, self.themeComboBox) 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): def newSong(self):
log.debug(u'New Song') log.debug(u'New Song')
self.song = None self.song = None
@ -176,11 +199,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.setRowCount(0) self.verseListWidget.setRowCount(0)
self.authorsListView.clear() self.authorsListView.clear()
self.topicsListView.clear() self.topicsListView.clear()
self.audioListWidget.clear()
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
self.songBookNumberEdit.setText(u'') self.songBookNumberEdit.setText(u'')
self.loadAuthors() self.loadAuthors()
self.loadTopics() self.loadTopics()
self.loadBooks() self.loadBooks()
self.loadMediaFiles()
self.themeComboBox.setCurrentIndex(0) self.themeComboBox.setCurrentIndex(0)
# it's a new song to preview is not possible # it's a new song to preview is not possible
self.previewButton.setVisible(False) self.previewButton.setVisible(False)
@ -201,6 +226,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.loadAuthors() self.loadAuthors()
self.loadTopics() self.loadTopics()
self.loadBooks() self.loadBooks()
self.loadMediaFiles()
self.song = self.manager.get_object(Song, id) self.song = self.manager.get_object(Song, id)
self.titleEdit.setText(self.song.title) self.titleEdit.setText(self.song.title)
if self.song.alternate_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 = QtGui.QListWidgetItem(unicode(topic.name))
topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id)) topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id))
self.topicsListView.addItem(topic_name) 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) self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# Hide or show the preview button. # Hide or show the preview button.
self.previewButton.setVisible(preview) self.previewButton.setVisible(preview)
@ -436,9 +467,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseDeleteButton.setEnabled(True) self.verseDeleteButton.setEnabled(True)
def onVerseAddButtonClicked(self): def onVerseAddButtonClicked(self):
self.verse_form.setVerse(u'', True) self.verseForm.setVerse(u'', True)
if self.verse_form.exec_(): if self.verseForm.exec_():
after_text, verse_tag, verse_num = self.verse_form.getVerse() after_text, verse_tag, verse_num = self.verseForm.getVerse()
verse_def = u'%s%s' % (verse_tag, verse_num) verse_def = u'%s%s' % (verse_tag, verse_num)
item = QtGui.QTableWidgetItem(after_text) item = QtGui.QTableWidgetItem(after_text)
item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def)) item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def))
@ -454,20 +485,21 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
if item: if item:
tempText = item.text() tempText = item.text()
verseId = unicode(item.data(QtCore.Qt.UserRole).toString()) verseId = unicode(item.data(QtCore.Qt.UserRole).toString())
self.verse_form.setVerse(tempText, True, verseId) self.verseForm.setVerse(tempText, True, verseId)
if self.verse_form.exec_(): if self.verseForm.exec_():
after_text, verse_tag, verse_num = self.verse_form.getVerse() after_text, verse_tag, verse_num = self.verseForm.getVerse()
verse_def = u'%s%s' % (verse_tag, verse_num) verse_def = u'%s%s' % (verse_tag, verse_num)
item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def)) item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def))
item.setText(after_text) 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')): if len(tempText.split(u'\n')) != len(after_text.split(u'\n')):
tempList = {} tempList = {}
tempId = {} tempId = {}
for row in range(0, self.verseListWidget.rowCount()): for row in range(0, self.verseListWidget.rowCount()):
tempList[row] = self.verseListWidget.item(row, 0).text() tempList[row] = self.verseListWidget.item(row, 0)\
tempId[row] = self.verseListWidget.item(row, 0).\ .text()
data(QtCore.Qt.UserRole) tempId[row] = self.verseListWidget.item(row, 0)\
.data(QtCore.Qt.UserRole)
self.verseListWidget.clear() self.verseListWidget.clear()
for row in range (0, len(tempList)): for row in range (0, len(tempList)):
item = QtGui.QTableWidgetItem(tempList[row], 0) 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 += u'---[%s:%s]---\n' % (verse_tag, verse_num)
verse_list += item.text() verse_list += item.text()
verse_list += u'\n' verse_list += u'\n'
self.verse_form.setVerse(verse_list) self.verseForm.setVerse(verse_list)
else: else:
self.verse_form.setVerse(u'') self.verseForm.setVerse(u'')
if not self.verse_form.exec_(): if not self.verseForm.exec_():
return return
verse_list = self.verse_form.getVerseAll() verse_list = self.verseForm.getVerseAll()
verse_list = unicode(verse_list.replace(u'\r\n', u'\n')) verse_list = unicode(verse_list.replace(u'\r\n', u'\n'))
self.verseListWidget.clear() self.verseListWidget.clear()
self.verseListWidget.setRowCount(0) self.verseListWidget.setRowCount(0)
@ -670,6 +702,66 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.saveSong(True) self.saveSong(True)
Receiver.send_message(u'songs_preview') 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): def clearCaches(self):
""" """
Free up autocompletion memory on dialog exit Free up autocompletion memory on dialog exit
@ -744,18 +836,55 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.theme_name = None self.song.theme_name = None
self._processLyrics() self._processLyrics()
self.song.authors = [] self.song.authors = []
for row in range(self.authorsListView.count()): for row in xrange(self.authorsListView.count()):
item = self.authorsListView.item(row) item = self.authorsListView.item(row)
authorId = (item.data(QtCore.Qt.UserRole)).toInt()[0] authorId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.authors.append(self.manager.get_object(Author, authorId)) self.song.authors.append(self.manager.get_object(Author, authorId))
self.song.topics = [] self.song.topics = []
for row in range(self.topicsListView.count()): for row in xrange(self.topicsListView.count()):
item = self.topicsListView.item(row) item = self.topicsListView.item(row)
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0] topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.topics.append(self.manager.get_object(Topic, topicId)) 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) clean_song(self.manager, self.song)
self.manager.save_object(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): def _processLyrics(self):
""" """
@ -783,3 +912,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
except: except:
log.exception(u'Problem processing song Lyrics \n%s', log.exception(u'Problem processing song Lyrics \n%s',
sxml.dump_xml()) 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

@ -170,8 +170,8 @@ class SongExportForm(OpenLPWizard):
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')) translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
self.informationLabel.setText( self.informationLabel.setText(
translate('SongsPlugin.ExportWizardForm', 'This wizard will help to' translate('SongsPlugin.ExportWizardForm', 'This wizard will help to'
' export your songs to the open and free OpenLyrics worship song ' ' export your songs to the open and free <strong>OpenLyrics'
'format.')) '</strong> worship song format.'))
self.availableSongsPage.setTitle( self.availableSongsPage.setTitle(
translate('SongsPlugin.ExportWizardForm', 'Select Songs')) translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
self.availableSongsPage.setSubTitle( self.availableSongsPage.setSubTitle(
@ -285,7 +285,9 @@ class SongExportForm(OpenLPWizard):
self, songs, unicode(self.directoryLineEdit.text())) self, songs, unicode(self.directoryLineEdit.text()))
if exporter.do_export(): if exporter.do_export():
self.progressLabel.setText( self.progressLabel.setText(
translate('SongsPlugin.SongExportForm', 'Finished export.')) translate('SongsPlugin.SongExportForm', 'Finished export. To '
'import these files use the <strong>OpenLyrics</strong> '
'importer.'))
else: else:
self.progressLabel.setText( self.progressLabel.setText(
translate('SongsPlugin.SongExportForm', translate('SongsPlugin.SongExportForm',

View File

@ -686,7 +686,7 @@ class SongImportForm(OpenLPWizard):
def performWizard(self): def performWizard(self):
""" """
Perform the actual import. This method pulls in the correct importer Perform the actual import. This method pulls in the correct importer
class, and then runs the ``do_import`` method of the importer to do class, and then runs the ``doImport`` method of the importer to do
the actual importing. the actual importing.
""" """
source_format = self.formatComboBox.currentIndex() source_format = self.formatComboBox.currentIndex()
@ -759,8 +759,8 @@ class SongImportForm(OpenLPWizard):
importer = self.plugin.importSongs(SongFormat.FoilPresenter, importer = self.plugin.importSongs(SongFormat.FoilPresenter,
filenames=self.getListOfFiles(self.foilPresenterFileListWidget) filenames=self.getListOfFiles(self.foilPresenterFileListWidget)
) )
importer.do_import() importer.doImport()
if importer.error_log: if importer.errorLog:
self.progressLabel.setText(translate( self.progressLabel.setText(translate(
'SongsPlugin.SongImportForm', 'Your song import failed.')) 'SongsPlugin.SongImportForm', 'Your song import failed.'))
else: else:

View File

@ -184,10 +184,11 @@ class VerseType(object):
verse_index = VerseType.from_translated_string(verse_name) verse_index = VerseType.from_translated_string(verse_name)
if verse_index is None: if verse_index is None:
verse_index = VerseType.from_string(verse_name) verse_index = VerseType.from_string(verse_name)
if verse_index is None: elif len(verse_name) == 1:
verse_index = VerseType.from_translated_tag(verse_name) if verse_index is None:
if verse_index is None: verse_index = VerseType.from_translated_tag(verse_name)
verse_index = VerseType.from_tag(verse_name) if verse_index is None:
verse_index = VerseType.from_tag(verse_name)
return verse_index return verse_index
def retrieve_windows_encoding(recommendation=None): def retrieve_windows_encoding(recommendation=None):

View File

@ -55,13 +55,13 @@ class CCLIFileImport(SongImport):
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
""" """
Import either a ``.usr`` or a ``.txt`` SongSelect file. Import either a ``.usr`` or a ``.txt`` SongSelect file.
""" """
log.debug(u'Starting CCLI File Import') log.debug(u'Starting CCLI File Import')
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for filename in self.import_source: for filename in self.importSource:
filename = unicode(filename) filename = unicode(filename)
log.debug(u'Importing CCLI File: %s', filename) log.debug(u'Importing CCLI File: %s', filename)
lines = [] lines = []
@ -80,23 +80,23 @@ class CCLIFileImport(SongImport):
ext = os.path.splitext(filename)[1] ext = os.path.splitext(filename)[1]
if ext.lower() == u'.usr': if ext.lower() == u'.usr':
log.info(u'SongSelect .usr format file found: %s', filename) log.info(u'SongSelect .usr format file found: %s', filename)
if not self.do_import_usr_file(lines): if not self.doImportUsrFile(lines):
self.log_error(filename) self.logError(filename)
elif ext.lower() == u'.txt': elif ext.lower() == u'.txt':
log.info(u'SongSelect .txt format file found: %s', filename) log.info(u'SongSelect .txt format file found: %s', filename)
if not self.do_import_txt_file(lines): if not self.doImportTxtFile(lines):
self.log_error(filename) self.logError(filename)
else: else:
self.log_error(filename, self.logError(filename,
translate('SongsPlugin.CCLIFileImport', translate('SongsPlugin.CCLIFileImport',
'The file does not have a valid extension.')) 'The file does not have a valid extension.'))
log.info(u'Extension %s is not valid', filename) log.info(u'Extension %s is not valid', filename)
if self.stop_import_flag: if self.stopImportFlag:
return return
def do_import_usr_file(self, textList): def doImportUsrFile(self, textList):
""" """
The :func:`do_import_usr_file` method provides OpenLP with the ability The :func:`doImport_usr_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *USR* file format. to import CCLI SongSelect songs in *USR* file format.
``textList`` ``textList``
@ -165,7 +165,7 @@ class CCLIFileImport(SongImport):
elif line.startswith(u'Themes='): elif line.startswith(u'Themes='):
song_topics = line[7:].strip() song_topics = line[7:].strip()
elif line.startswith(u'[S A'): elif line.startswith(u'[S A'):
self.ccli_number = line[4:-3].strip() self.ccliNumber = line[4:-3].strip()
elif line.startswith(u'Fields='): elif line.startswith(u'Fields='):
# Fields contain single line indicating verse, chorus, etc, # Fields contain single line indicating verse, chorus, etc,
# /t delimited, same as with words field. store seperately # /t delimited, same as with words field. store seperately
@ -204,7 +204,7 @@ class CCLIFileImport(SongImport):
verse_type = VerseType.Tags[VerseType.Other] verse_type = VerseType.Tags[VerseType.Other]
verse_text = verse_lines[1] verse_text = verse_lines[1]
if len(verse_text) > 0: if len(verse_text) > 0:
self.add_verse(verse_text, verse_type) self.addVerse(verse_text, verse_type)
check_first_verse_line = False check_first_verse_line = False
# Handle multiple authors # Handle multiple authors
author_list = song_author.split(u'/') author_list = song_author.split(u'/')
@ -213,15 +213,15 @@ class CCLIFileImport(SongImport):
for author in author_list: for author in author_list:
separated = author.split(u',') separated = author.split(u',')
if len(separated) > 1: if len(separated) > 1:
self.add_author(u' '.join(reversed(separated))) self.addAuthor(u' '.join(reversed(separated)))
else: else:
self.add_author(author) self.addAuthor(author)
self.topics = [topic.strip() for topic in song_topics.split(u'/t')] self.topics = [topic.strip() for topic in song_topics.split(u'/t')]
return self.finish() return self.finish()
def do_import_txt_file(self, textList): def doImportTxtFile(self, textList):
""" """
The :func:`do_import_txt_file` method provides OpenLP with the ability The :func:`doImport_txt_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *TXT* file format. to import CCLI SongSelect songs in *TXT* file format.
``textList`` ``textList``
@ -264,7 +264,7 @@ class CCLIFileImport(SongImport):
continue continue
elif verse_start: elif verse_start:
if verse_text: if verse_text:
self.add_verse(verse_text, verse_type) self.addVerse(verse_text, verse_type)
verse_text = u'' verse_text = u''
verse_start = False verse_start = False
else: else:
@ -278,7 +278,7 @@ class CCLIFileImport(SongImport):
if clean_line.startswith(u'CCLI'): if clean_line.startswith(u'CCLI'):
line_number += 1 line_number += 1
ccli_parts = clean_line.split(' ') ccli_parts = clean_line.split(' ')
self.ccli_number = ccli_parts[len(ccli_parts) - 1] self.ccliNumber = ccli_parts[len(ccli_parts) - 1]
elif not verse_start: elif not verse_start:
# We have the verse descriptor # We have the verse descriptor
verse_desc_parts = clean_line.split(u' ') verse_desc_parts = clean_line.split(u' ')
@ -333,5 +333,5 @@ class CCLIFileImport(SongImport):
if len(author_list) < 2: if len(author_list) < 2:
author_list = song_author.split(u'|') author_list = song_author.split(u'|')
# Clean spaces before and after author names. # Clean spaces before and after author names.
[self.add_author(author_name.strip()) for author_name in author_list] [self.addAuthor(author_name.strip()) for author_name in author_list]
return self.finish() return self.finish()

View File

@ -31,6 +31,7 @@ the Songs plugin
from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation from sqlalchemy.orm import mapper, relation
from sqlalchemy.sql.expression import func
from openlp.core.lib.db import BaseModel, init_db from openlp.core.lib.db import BaseModel, init_db
@ -70,7 +71,6 @@ class Topic(BaseModel):
""" """
pass pass
def init_schema(url): def init_schema(url):
""" """
Setup the songs database connection and initialise the database schema. Setup the songs database connection and initialise the database schema.
@ -111,10 +111,6 @@ def init_schema(url):
* file_name * file_name
* type * type
**media_files_songs Table**
* media_file_id
* song_id
**song_books Table** **song_books Table**
The *song_books* table holds a list of books that a congregation gets The *song_books* table holds a list of books that a congregation gets
their songs from, or old hymnals now no longer used. This table has the their songs from, or old hymnals now no longer used. This table has the
@ -162,7 +158,7 @@ def init_schema(url):
# Definition of the "authors" table # Definition of the "authors" table
authors_table = Table(u'authors', metadata, authors_table = Table(u'authors', metadata,
Column(u'id', types.Integer, primary_key=True), Column(u'id', types.Integer(), primary_key=True),
Column(u'first_name', types.Unicode(128)), Column(u'first_name', types.Unicode(128)),
Column(u'last_name', types.Unicode(128)), Column(u'last_name', types.Unicode(128)),
Column(u'display_name', types.Unicode(255), index=True, nullable=False) Column(u'display_name', types.Unicode(255), index=True, nullable=False)
@ -170,22 +166,25 @@ def init_schema(url):
# Definition of the "media_files" table # Definition of the "media_files" table
media_files_table = Table(u'media_files', metadata, media_files_table = Table(u'media_files', metadata,
Column(u'id', types.Integer, primary_key=True), Column(u'id', types.Integer(), primary_key=True),
Column(u'song_id', types.Integer(), ForeignKey(u'songs.id'),
default=None),
Column(u'file_name', types.Unicode(255), nullable=False), Column(u'file_name', types.Unicode(255), nullable=False),
Column(u'type', types.Unicode(64), nullable=False, default=u'audio') Column(u'type', types.Unicode(64), nullable=False, default=u'audio'),
Column(u'weight', types.Integer(), default=0)
) )
# Definition of the "song_books" table # Definition of the "song_books" table
song_books_table = Table(u'song_books', metadata, song_books_table = Table(u'song_books', metadata,
Column(u'id', types.Integer, primary_key=True), Column(u'id', types.Integer(), primary_key=True),
Column(u'name', types.Unicode(128), nullable=False), Column(u'name', types.Unicode(128), nullable=False),
Column(u'publisher', types.Unicode(128)) Column(u'publisher', types.Unicode(128))
) )
# Definition of the "songs" table # Definition of the "songs" table
songs_table = Table(u'songs', metadata, songs_table = Table(u'songs', metadata,
Column(u'id', types.Integer, primary_key=True), Column(u'id', types.Integer(), primary_key=True),
Column(u'song_book_id', types.Integer, Column(u'song_book_id', types.Integer(),
ForeignKey(u'song_books.id'), default=None), ForeignKey(u'song_books.id'), default=None),
Column(u'title', types.Unicode(255), nullable=False), Column(u'title', types.Unicode(255), nullable=False),
Column(u'alternate_title', types.Unicode(255)), Column(u'alternate_title', types.Unicode(255)),
@ -197,36 +196,31 @@ def init_schema(url):
Column(u'song_number', types.Unicode(64)), Column(u'song_number', types.Unicode(64)),
Column(u'theme_name', types.Unicode(128)), Column(u'theme_name', types.Unicode(128)),
Column(u'search_title', types.Unicode(255), index=True, nullable=False), Column(u'search_title', types.Unicode(255), index=True, nullable=False),
Column(u'search_lyrics', types.UnicodeText, nullable=False) Column(u'search_lyrics', types.UnicodeText, nullable=False),
Column(u'create_date', types.DateTime(), default=func.now()),
Column(u'last_modified', types.DateTime(), default=func.now(),
onupdate=func.now())
) )
# Definition of the "topics" table # Definition of the "topics" table
topics_table = Table(u'topics', metadata, topics_table = Table(u'topics', metadata,
Column(u'id', types.Integer, primary_key=True), Column(u'id', types.Integer(), primary_key=True),
Column(u'name', types.Unicode(128), index=True, nullable=False) Column(u'name', types.Unicode(128), index=True, nullable=False)
) )
# Definition of the "authors_songs" table # Definition of the "authors_songs" table
authors_songs_table = Table(u'authors_songs', metadata, authors_songs_table = Table(u'authors_songs', metadata,
Column(u'author_id', types.Integer, Column(u'author_id', types.Integer(),
ForeignKey(u'authors.id'), primary_key=True), ForeignKey(u'authors.id'), primary_key=True),
Column(u'song_id', types.Integer, Column(u'song_id', types.Integer(),
ForeignKey(u'songs.id'), primary_key=True)
)
# Definition of the "media_files_songs" table
media_files_songs_table = Table(u'media_files_songs', metadata,
Column(u'media_file_id', types.Integer,
ForeignKey(u'media_files.id'), primary_key=True),
Column(u'song_id', types.Integer,
ForeignKey(u'songs.id'), primary_key=True) ForeignKey(u'songs.id'), primary_key=True)
) )
# Definition of the "songs_topics" table # Definition of the "songs_topics" table
songs_topics_table = Table(u'songs_topics', metadata, songs_topics_table = Table(u'songs_topics', metadata,
Column(u'song_id', types.Integer, Column(u'song_id', types.Integer(),
ForeignKey(u'songs.id'), primary_key=True), ForeignKey(u'songs.id'), primary_key=True),
Column(u'topic_id', types.Integer, Column(u'topic_id', types.Integer(),
ForeignKey(u'topics.id'), primary_key=True) ForeignKey(u'topics.id'), primary_key=True)
) )
@ -239,7 +233,7 @@ def init_schema(url):
secondary=authors_songs_table, lazy=False), secondary=authors_songs_table, lazy=False),
'book': relation(Book, backref='songs'), 'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs', 'media_files': relation(MediaFile, backref='songs',
secondary=media_files_songs_table), order_by=media_files_table.c.weight),
'topics': relation(Topic, backref='songs', 'topics': relation(Topic, backref='songs',
secondary=songs_topics_table) secondary=songs_topics_table)
}) })

View File

@ -49,50 +49,50 @@ class EasiSlidesImport(SongImport):
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.commit = True self.commit = True
def do_import(self): def doImport(self):
""" """
Import either each of the files in self.import_sources - each element of Import either each of the files in self.importSources - each element of
which can be either a single opensong file, or a zipfile containing which can be either a single opensong file, or a zipfile containing
multiple opensong files. If `self.commit` is set False, the multiple opensong files. If `self.commit` is set False, the
import will not be committed to the database (useful for test scripts). import will not be committed to the database (useful for test scripts).
""" """
log.info(u'Importing EasiSlides XML file %s', self.import_source) log.info(u'Importing EasiSlides XML file %s', self.importSource)
parser = etree.XMLParser(remove_blank_text=True) parser = etree.XMLParser(remove_blank_text=True)
parsed_file = etree.parse(self.import_source, parser) parsed_file = etree.parse(self.importSource, parser)
xml = unicode(etree.tostring(parsed_file)) xml = unicode(etree.tostring(parsed_file))
song_xml = objectify.fromstring(xml) song_xml = objectify.fromstring(xml)
self.import_wizard.progressBar.setMaximum(len(song_xml.Item)) self.importWizard.progressBar.setMaximum(len(song_xml.Item))
for song in song_xml.Item: for song in song_xml.Item:
if self.stop_import_flag: if self.stopImportFlag:
return return
self._parse_song(song) self._parseSong(song)
def _parse_song(self, song): def _parseSong(self, song):
self._success = True self._success = True
self._add_unicode_attribute(u'title', song.Title1, True) self._addUnicodeAttribute(u'title', song.Title1, True)
if hasattr(song, u'Title2'): if hasattr(song, u'Title2'):
self._add_unicode_attribute(u'alternate_title', song.Title2) self._addUnicodeAttribute(u'alternateTitle', song.Title2)
if hasattr(song, u'SongNumber'): if hasattr(song, u'SongNumber'):
self._add_unicode_attribute(u'song_number', song.SongNumber) self._addUnicodeAttribute(u'songNumber', song.SongNumber)
if self.song_number == u'0': if self.songNumber == u'0':
self.song_number = u'' self.songNumber = u''
self._add_authors(song) self._addAuthors(song)
if hasattr(song, u'Copyright'): if hasattr(song, u'Copyright'):
self._add_copyright(song.Copyright) self._addCopyright(song.Copyright)
if hasattr(song, u'LicenceAdmin1'): if hasattr(song, u'LicenceAdmin1'):
self._add_copyright(song.LicenceAdmin1) self._addCopyright(song.LicenceAdmin1)
if hasattr(song, u'LicenceAdmin2'): if hasattr(song, u'LicenceAdmin2'):
self._add_copyright(song.LicenceAdmin2) self._addCopyright(song.LicenceAdmin2)
if hasattr(song, u'BookReference'): if hasattr(song, u'BookReference'):
self._add_unicode_attribute(u'song_book_name', song.BookReference) self._addUnicodeAttribute(u'songBookName', song.BookReference)
self._parse_and_add_lyrics(song) self._parseAndAddLyrics(song)
if self._success: if self._success:
if not self.finish(): if not self.finish():
self.log_error(song.Title1 if song.Title1 else u'') self.logError(song.Title1 if song.Title1 else u'')
else: else:
self.set_defaults() self.setDefaults()
def _add_unicode_attribute(self, self_attribute, import_attribute, def _addUnicodeAttribute(self, self_attribute, import_attribute,
mandatory=False): mandatory=False):
""" """
Add imported values to the song model converting them to unicode at the Add imported values to the song model converting them to unicode at the
@ -119,7 +119,7 @@ class EasiSlidesImport(SongImport):
if mandatory: if mandatory:
self._success = False self._success = False
def _add_authors(self, song): def _addAuthors(self, song):
try: try:
authors = unicode(song.Writer).split(u',') authors = unicode(song.Writer).split(u',')
self.authors = \ self.authors = \
@ -130,7 +130,7 @@ class EasiSlidesImport(SongImport):
except AttributeError: except AttributeError:
pass pass
def _add_copyright(self, element): def _addCopyright(self, element):
""" """
Add a piece of copyright to the total copyright information for the Add a piece of copyright to the total copyright information for the
song. song.
@ -139,14 +139,14 @@ class EasiSlidesImport(SongImport):
The imported variable to get the data from. The imported variable to get the data from.
""" """
try: try:
self.add_copyright(unicode(element).strip()) self.addCopyright(unicode(element).strip())
except UnicodeDecodeError: except UnicodeDecodeError:
log.exception(u'Unicode error on decoding copyright: %s' % element) log.exception(u'Unicode error on decoding copyright: %s' % element)
self._success = False self._success = False
except AttributeError: except AttributeError:
pass pass
def _parse_and_add_lyrics(self, song): def _parseAndAddLyrics(self, song):
try: try:
lyrics = unicode(song.Contents).strip() lyrics = unicode(song.Contents).strip()
except UnicodeDecodeError: except UnicodeDecodeError:
@ -295,7 +295,7 @@ class EasiSlidesImport(SongImport):
else: else:
continue continue
if tag in versetags: if tag in versetags:
self.verse_order_list.append(tag) self.verseOrderList.append(tag)
else: else:
log.info(u'Got order item %s, which is not in versetags,' log.info(u'Got order item %s, which is not in versetags,'
u'dropping item from presentation order', tag) u'dropping item from presentation order', tag)

View File

@ -49,15 +49,15 @@ def strip_rtf(blob, encoding):
control = False control = False
clear_text = [] clear_text = []
control_word = [] control_word = []
# workaround for \tx bug: remove one pair of curly braces # workaround for \tx bug: remove one pair of curly braces
# if \tx is encountered # if \tx is encountered
match = RTF_STRIPPING_REGEX.search(blob) match = RTF_STRIPPING_REGEX.search(blob)
if match: if match:
# start and end indices of match are curly braces - filter them out # start and end indices of match are curly braces - filter them out
blob = ''.join([blob[i] for i in xrange(len(blob)) blob = ''.join([blob[i] for i in xrange(len(blob))
if i != match.start() and i !=match.end()]) if i != match.start() and i !=match.end()])
for c in blob: for c in blob:
if control: if control:
# for delimiters, set control to False # for delimiters, set control to False
@ -155,24 +155,24 @@ class EasyWorshipSongImport(SongImport):
def __init__(self, manager, **kwargs): def __init__(self, manager, **kwargs):
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
# Open the DB and MB files if they exist # Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB') import_source_mb = self.importSource.replace('.DB', '.MB')
if not os.path.isfile(self.import_source): if not os.path.isfile(self.importSource):
return return
if not os.path.isfile(import_source_mb): if not os.path.isfile(import_source_mb):
return return
db_size = os.path.getsize(self.import_source) db_size = os.path.getsize(self.importSource)
if db_size < 0x800: if db_size < 0x800:
return return
db_file = open(self.import_source, 'rb') db_file = open(self.importSource, 'rb')
self.memo_file = open(import_source_mb, 'rb') self.memoFile = open(import_source_mb, 'rb')
# Don't accept files that are clearly not paradox files # Don't accept files that are clearly not paradox files
record_size, header_size, block_size, first_block, num_fields \ record_size, header_size, block_size, first_block, num_fields \
= struct.unpack('<hhxb8xh17xh', db_file.read(35)) = struct.unpack('<hhxb8xh17xh', db_file.read(35))
if header_size != 0x800 or block_size < 1 or block_size > 4: if header_size != 0x800 or block_size < 1 or block_size > 4:
db_file.close() db_file.close()
self.memo_file.close() self.memoFile.close()
return return
# Take a stab at how text is encoded # Take a stab at how text is encoded
self.encoding = u'cp1252' self.encoding = u'cp1252'
@ -204,7 +204,7 @@ class EasyWorshipSongImport(SongImport):
# There does not appear to be a _reliable_ way of getting the number # There does not appear to be a _reliable_ way of getting the number
# of songs/records, so let's use file blocks for measuring progress. # of songs/records, so let's use file blocks for measuring progress.
total_blocks = (db_size - header_size) / (block_size * 1024) total_blocks = (db_size - header_size) / (block_size * 1024)
self.import_wizard.progressBar.setMaximum(total_blocks) self.importWizard.progressBar.setMaximum(total_blocks)
# Read the field description information # Read the field description information
db_file.seek(120) db_file.seek(120)
field_info = db_file.read(num_fields * 2) field_info = db_file.read(num_fields * 2)
@ -218,16 +218,16 @@ class EasyWorshipSongImport(SongImport):
field_info, i * 2) field_info, i * 2)
field_descs.append(FieldDescEntry(field_name, field_type, field_descs.append(FieldDescEntry(field_name, field_type,
field_size)) field_size))
self.set_record_struct(field_descs) self.setRecordStruct(field_descs)
# Pick out the field description indexes we will need # Pick out the field description indexes we will need
try: try:
success = True success = True
fi_title = self.find_field(u'Title') fi_title = self.findField(u'Title')
fi_author = self.find_field(u'Author') fi_author = self.findField(u'Author')
fi_copy = self.find_field(u'Copyright') fi_copy = self.findField(u'Copyright')
fi_admin = self.find_field(u'Administrator') fi_admin = self.findField(u'Administrator')
fi_words = self.find_field(u'Words') fi_words = self.findField(u'Words')
fi_ccli = self.find_field(u'Song Number') fi_ccli = self.findField(u'Song Number')
except IndexError: except IndexError:
# This is the wrong table # This is the wrong table
success = False success = False
@ -239,18 +239,18 @@ class EasyWorshipSongImport(SongImport):
rec_count = (rec_count + record_size) / record_size rec_count = (rec_count + record_size) / record_size
# Loop through each record within the current block # Loop through each record within the current block
for i in range(rec_count): for i in range(rec_count):
if self.stop_import_flag: if self.stopImportFlag:
break break
raw_record = db_file.read(record_size) raw_record = db_file.read(record_size)
self.fields = self.record_struct.unpack(raw_record) self.fields = self.record_struct.unpack(raw_record)
self.set_defaults() self.setDefaults()
self.title = self.get_field(fi_title) self.title = self.getField(fi_title)
# Get remaining fields. # Get remaining fields.
copy = self.get_field(fi_copy) copy = self.getField(fi_copy)
admin = self.get_field(fi_admin) admin = self.getField(fi_admin)
ccli = self.get_field(fi_ccli) ccli = self.getField(fi_ccli)
authors = self.get_field(fi_author) authors = self.getField(fi_author)
words = self.get_field(fi_words) words = self.getField(fi_words)
# Set the SongImport object members. # Set the SongImport object members.
if copy: if copy:
self.copyright = copy self.copyright = copy
@ -261,7 +261,7 @@ class EasyWorshipSongImport(SongImport):
unicode(translate('SongsPlugin.EasyWorshipSongImport', unicode(translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s')) % admin 'Administered by %s')) % admin
if ccli: if ccli:
self.ccli_number = ccli self.ccliNumber = ccli
if authors: if authors:
# Split up the authors # Split up the authors
author_list = authors.split(u'/') author_list = authors.split(u'/')
@ -270,7 +270,7 @@ class EasyWorshipSongImport(SongImport):
if len(author_list) < 2: if len(author_list) < 2:
author_list = authors.split(u',') author_list = authors.split(u',')
for author_name in author_list: for author_name in author_list:
self.add_author(author_name.strip()) self.addAuthor(author_name.strip())
if words: if words:
# Format the lyrics # Format the lyrics
words = strip_rtf(words, self.encoding) words = strip_rtf(words, self.encoding)
@ -281,9 +281,9 @@ class EasyWorshipSongImport(SongImport):
continue continue
verse_split = verse.split(u'\n', 1) verse_split = verse.split(u'\n', 1)
first_line_is_tag = False first_line_is_tag = False
# EW tags: verse, chorus, pre-chorus, bridge, tag, # EW tags: verse, chorus, pre-chorus, bridge, tag,
# intro, ending, slide # intro, ending, slide
for type in VerseType.Names+[u'tag', u'slide']: for type in VerseType.Names+[u'tag', u'slide']:
type = type.lower() type = type.lower()
ew_tag = verse_split[0].strip().lower() ew_tag = verse_split[0].strip().lower()
if ew_tag.startswith(type): if ew_tag.startswith(type):
@ -293,7 +293,7 @@ class EasyWorshipSongImport(SongImport):
first_line_is_tag = True first_line_is_tag = True
number_found = False number_found = False
# check if tag is followed by number and/or note # check if tag is followed by number and/or note
if len(ew_tag) > len(type): if len(ew_tag) > len(type):
match = NUMBER_REGEX.search(ew_tag) match = NUMBER_REGEX.search(ew_tag)
if match: if match:
number = match.group() number = match.group()
@ -305,23 +305,24 @@ class EasyWorshipSongImport(SongImport):
if not number_found: if not number_found:
verse_type += u'1' verse_type += u'1'
break break
self.add_verse( self.addVerse(
verse_split[-1].strip() if first_line_is_tag else verse, verse_split[-1].strip() \
if first_line_is_tag else verse,
verse_type) verse_type)
if len(self.comments) > 5: if len(self.comments) > 5:
self.comments += unicode( self.comments += unicode(
translate('SongsPlugin.EasyWorshipSongImport', translate('SongsPlugin.EasyWorshipSongImport',
'\n[above are Song Tags with notes imported from \ '\n[above are Song Tags with notes imported from \
EasyWorship]')) EasyWorship]'))
if self.stop_import_flag: if self.stopImportFlag:
break break
if not self.finish(): if not self.finish():
self.log_error(self.import_source) self.logError(self.importSource)
db_file.close() db_file.close()
self.memo_file.close() self.memoFile.close()
def find_field(self, field_name): def find_field(self, field_name):
return [i for i, x in enumerate(self.field_descs) return [i for i, x in enumerate(self.fieldDescs)
if x.name == field_name][0] if x.name == field_name][0]
def set_record_struct(self, field_descs): def set_record_struct(self, field_descs):
@ -351,12 +352,12 @@ class EasyWorshipSongImport(SongImport):
fsl.append('Q') fsl.append('Q')
else: else:
fsl.append('%ds' % field_desc.size) fsl.append('%ds' % field_desc.size)
self.record_struct = struct.Struct(''.join(fsl)) self.recordStruct = struct.Struct(''.join(fsl))
self.field_descs = field_descs self.fieldDescs = field_descs
def get_field(self, field_desc_index): def get_field(self, field_desc_index):
field = self.fields[field_desc_index] field = self.fields[field_desc_index]
field_desc = self.field_descs[field_desc_index] field_desc = self.fieldDescs[field_desc_index]
# Return None in case of 'blank' entries # Return None in case of 'blank' entries
if isinstance(field, str): if isinstance(field, str):
if len(field.rstrip('\0')) == 0: if len(field.rstrip('\0')) == 0:
@ -382,18 +383,18 @@ class EasyWorshipSongImport(SongImport):
struct.unpack_from('<II', field, len(field)-10) struct.unpack_from('<II', field, len(field)-10)
sub_block = block_start & 0xff sub_block = block_start & 0xff
block_start &= ~0xff block_start &= ~0xff
self.memo_file.seek(block_start) self.memoFile.seek(block_start)
memo_block_type, = struct.unpack('b', self.memo_file.read(1)) memo_block_type, = struct.unpack('b', self.memoFile.read(1))
if memo_block_type == 2: if memo_block_type == 2:
self.memo_file.seek(8, os.SEEK_CUR) self.memoFile.seek(8, os.SEEK_CUR)
elif memo_block_type == 3: elif memo_block_type == 3:
if sub_block > 63: if sub_block > 63:
return u'' return u''
self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR) self.memoFile.seek(11 + (5 * sub_block), os.SEEK_CUR)
sub_block_start, = struct.unpack('B', self.memo_file.read(1)) sub_block_start, = struct.unpack('B', self.memoFile.read(1))
self.memo_file.seek(block_start + (sub_block_start * 16)) self.memoFile.seek(block_start + (sub_block_start * 16))
else: else:
return u'' return u''
return self.memo_file.read(blob_size) return self.memoFile.read(blob_size)
else: else:
return 0 return 0

View File

@ -115,23 +115,23 @@ class FoilPresenterImport(SongImport):
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.FoilPresenter = FoilPresenter(self.manager) self.FoilPresenter = FoilPresenter(self.manager)
def do_import(self): def doImport(self):
""" """
Imports the songs. Imports the songs.
""" """
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
parser = etree.XMLParser(remove_blank_text=True) parser = etree.XMLParser(remove_blank_text=True)
for file_path in self.import_source: for file_path in self.importSource:
if self.stop_import_flag: if self.stopImportFlag:
return return
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType % os.path.basename(file_path))
try: try:
parsed_file = etree.parse(file_path, parser) parsed_file = etree.parse(file_path, parser)
xml = unicode(etree.tostring(parsed_file)) xml = unicode(etree.tostring(parsed_file))
self.FoilPresenter.xml_to_song(xml) self.FoilPresenter.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
self.log_error(file_path, SongStrings.XMLSyntaxError) self.logError(file_path, SongStrings.XMLSyntaxError)
log.exception(u'XML syntax error in file %s' % file_path) log.exception(u'XML syntax error in file %s' % file_path)

View File

@ -28,6 +28,8 @@
import logging import logging
import locale import locale
import re import re
import os
import shutil
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from sqlalchemy.sql import or_ 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.searchedit import SearchEdit
from openlp.core.lib.ui import UiStrings, context_menu_action, \ from openlp.core.lib.ui import UiStrings, context_menu_action, \
context_menu_separator context_menu_separator
from openlp.core.utils import AppLocation
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
SongImportForm, SongExportForm SongImportForm, SongExportForm
from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \ from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \
clean_string 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 from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -66,11 +69,11 @@ class SongMediaItem(MediaManagerItem):
def __init__(self, parent, plugin, icon): def __init__(self, parent, plugin, icon):
self.IconPath = u'songs/song' self.IconPath = u'songs/song'
MediaManagerItem.__init__(self, parent, plugin, icon) MediaManagerItem.__init__(self, parent, plugin, icon)
self.edit_song_form = EditSongForm(self, self.plugin.formparent, self.editSongForm = EditSongForm(self, self.plugin.formparent,
self.plugin.manager) self.plugin.manager)
self.openLyrics = OpenLyrics(self.plugin.manager) self.openLyrics = OpenLyrics(self.plugin.manager)
self.singleServiceItem = False self.singleServiceItem = False
self.song_maintenance_form = SongMaintenanceForm( self.songMaintenanceForm = SongMaintenanceForm(
self.plugin.manager, self) self.plugin.manager, self)
# Holds information about whether the edit is remotly triggered and # Holds information about whether the edit is remotly triggered and
# which Song is required. # which Song is required.
@ -79,6 +82,22 @@ class SongMediaItem(MediaManagerItem):
self.quickPreviewAllowed = True self.quickPreviewAllowed = True
self.hasSearch = 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): def addEndHeaderBar(self):
self.addToolbarSeparator() self.addToolbarSeparator()
## Song Maintenance Button ## ## Song Maintenance Button ##
@ -210,7 +229,7 @@ class SongMediaItem(MediaManagerItem):
search_results = self.plugin.manager.get_all_objects(Song, search_results = self.plugin.manager.get_all_objects(Song,
Song.theme_name.like(u'%' + search_keywords + u'%')) Song.theme_name.like(u'%' + search_keywords + u'%'))
self.displayResultsSong(search_results) self.displayResultsSong(search_results)
self.check_search_result() self.checkSearchResult()
def searchEntire(self, search_keywords): def searchEntire(self, search_keywords):
return self.plugin.manager.get_all_objects(Song, return self.plugin.manager.get_all_objects(Song,
@ -244,7 +263,7 @@ class SongMediaItem(MediaManagerItem):
def displayResultsSong(self, searchresults): def displayResultsSong(self, searchresults):
log.debug(u'display results Song') log.debug(u'display results Song')
self.save_auto_select_id() self.saveAutoSelectId()
self.listView.clear() self.listView.clear()
# Sort the songs by its title considering language specific characters. # Sort the songs by its title considering language specific characters.
# lower() is needed for windows! # lower() is needed for windows!
@ -258,9 +277,9 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id))
self.listView.addItem(song_name) self.listView.addItem(song_name)
# Auto-select the item if name has been set # 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.listView.setCurrentItem(song_name)
self.auto_select_id = -1 self.autoSelectId = -1
def displayResultsAuthor(self, searchresults): def displayResultsAuthor(self, searchresults):
log.debug(u'display results Author') log.debug(u'display results Author')
@ -297,25 +316,26 @@ class SongMediaItem(MediaManagerItem):
self.onClearTextButtonClick() self.onClearTextButtonClick()
def onImportClick(self): def onImportClick(self):
if not hasattr(self, u'import_wizard'): if not hasattr(self, u'importWizard'):
self.import_wizard = SongImportForm(self, self.plugin) self.importWizard = SongImportForm(self, self.plugin)
if self.import_wizard.exec_() == QtGui.QDialog.Accepted: if self.importWizard.exec_() == QtGui.QDialog.Accepted:
Receiver.send_message(u'songs_load_list') Receiver.send_message(u'songs_load_list')
def onExportClick(self): def onExportClick(self):
export_wizard = SongExportForm(self, self.plugin) if not hasattr(self, u'exportWizard'):
export_wizard.exec_() self.exportWizard = SongExportForm(self, self.plugin)
self.exportWizard.exec_()
def onNewClick(self): def onNewClick(self):
log.debug(u'onNewClick') log.debug(u'onNewClick')
self.edit_song_form.newSong() self.editSongForm.newSong()
self.edit_song_form.exec_() self.editSongForm.exec_()
self.onClearTextButtonClick() self.onClearTextButtonClick()
self.onSelectionChange() self.onSelectionChange()
self.auto_select_id = -1 self.autoSelectId = -1
def onSongMaintenanceClick(self): def onSongMaintenanceClick(self):
self.song_maintenance_form.exec_() self.songMaintenanceForm.exec_()
def onRemoteEditClear(self): def onRemoteEditClear(self):
log.debug(u'onRemoteEditClear') log.debug(u'onRemoteEditClear')
@ -335,9 +355,9 @@ class SongMediaItem(MediaManagerItem):
if valid: if valid:
self.remoteSong = song_id self.remoteSong = song_id
self.remoteTriggered = remote_type self.remoteTriggered = remote_type
self.edit_song_form.loadSong(song_id, (remote_type == u'P')) self.editSongForm.loadSong(song_id, remote_type == u'P')
self.edit_song_form.exec_() self.editSongForm.exec_()
self.auto_select_id = -1 self.autoSelectId = -1
self.onSongListLoad() self.onSongListLoad()
def onEditClick(self): def onEditClick(self):
@ -348,9 +368,9 @@ class SongMediaItem(MediaManagerItem):
if check_item_selected(self.listView, UiStrings().SelectEdit): if check_item_selected(self.listView, UiStrings().SelectEdit):
self.editItem = self.listView.currentItem() self.editItem = self.listView.currentItem()
item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0] item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0]
self.edit_song_form.loadSong(item_id, False) self.editSongForm.loadSong(item_id, False)
self.edit_song_form.exec_() self.editSongForm.exec_()
self.auto_select_id = -1 self.autoSelectId = -1
self.onSongListLoad() self.onSongListLoad()
self.editItem = None self.editItem = None
@ -371,6 +391,20 @@ class SongMediaItem(MediaManagerItem):
return return
for item in items: for item in items:
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
media_files = self.plugin.manager.get_all_objects(MediaFile,
MediaFile.song_id == item_id)
for media_file in media_files:
try:
os.remove(media_file.file_name)
except:
log.exception('Could not remove file: %s', audio)
try:
save_path = os.path.join(AppLocation.get_section_data_path(
self.plugin.name), 'audio', str(item_id))
if os.path.exists(save_path):
os.rmdir(save_path)
except OSError:
log.exception(u'Could not remove directory: %s', save_path)
self.plugin.manager.delete_object(Song, item_id) self.plugin.manager.delete_object(Song, item_id)
self.onSearchTextButtonClick() self.onSearchTextButtonClick()
@ -392,15 +426,16 @@ class SongMediaItem(MediaManagerItem):
self.plugin.manager.save_object(new_song) self.plugin.manager.save_object(new_song)
self.onSongListLoad() self.onSongListLoad()
def generateSlideData(self, service_item, item=None, xmlVersion=False): def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False):
log.debug(u'generateSlideData (%s:%s)' % (service_item, item)) log.debug(u'generateSlideData (%s:%s)' % (service_item, item))
item_id = self._getIdOfItemToGenerate(item, self.remoteSong) item_id = self._getIdOfItemToGenerate(item, self.remoteSong)
service_item.add_capability(ItemCapabilities.AllowsEdit) service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop) service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.OnLoadUpdate)
service_item.add_capability(ItemCapabilities.AddIfNewItem) 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) song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name service_item.theme = song.theme_name
service_item.edit_id = item_id service_item.edit_id = item_id
@ -471,6 +506,10 @@ class SongMediaItem(MediaManagerItem):
service_item.data_string = {u'title': song.search_title, service_item.data_string = {u'title': song.search_title,
u'authors': u', '.join(author_list)} u'authors': u', '.join(author_list)}
service_item.xml_version = self.openLyrics.song_to_xml(song) 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 return True
def serviceLoad(self, item): def serviceLoad(self, item):
@ -510,8 +549,15 @@ class SongMediaItem(MediaManagerItem):
add_song = False add_song = False
editId = song.id editId = song.id
break 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: 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() self.onSearchTextButtonClick()
# Update service with correct song id. # Update service with correct song id.
if editId: if editId:

View File

@ -32,6 +32,8 @@ openlp.org 1.x song databases into the current installation database.
import logging import logging
from chardet.universaldetector import UniversalDetector from chardet.universaldetector import UniversalDetector
import sqlite import sqlite
import sys
import os
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.plugins.songs.lib import retrieve_windows_encoding from openlp.plugins.songs.lib import retrieve_windows_encoding
@ -44,7 +46,7 @@ class OpenLP1SongImport(SongImport):
The :class:`OpenLP1SongImport` class provides OpenLP with the ability to The :class:`OpenLP1SongImport` class provides OpenLP with the ability to
import song databases from installations of openlp.org 1.x. import song databases from installations of openlp.org 1.x.
""" """
last_encoding = u'windows-1252' lastEncoding = u'windows-1252'
def __init__(self, manager, **kwargs): def __init__(self, manager, **kwargs):
""" """
@ -57,23 +59,23 @@ class OpenLP1SongImport(SongImport):
The database providing the data to import. The database providing the data to import.
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.available_themes = \ self.availableThemes = \
kwargs[u'plugin'].formparent.themeManagerContents.getThemes() kwargs[u'plugin'].formparent.themeManagerContents.getThemes()
def do_import(self): def doImport(self):
""" """
Run the import for an openlp.org 1.x song database. Run the import for an openlp.org 1.x song database.
""" """
if not self.import_source.endswith(u'.olp'): if not self.importSource.endswith(u'.olp'):
self.log_error(self.import_source, self.logError(self.importSource,
translate('SongsPlugin.OpenLP1SongImport', translate('SongsPlugin.OpenLP1SongImport',
'Not a valid openlp.org 1.x song database.')) 'Not a valid openlp.org 1.x song database.'))
return return
encoding = self.get_encoding() encoding = self.getEncoding()
if not encoding: if not encoding:
return return
# Connect to the database. # Connect to the database.
connection = sqlite.connect(self.import_source, mode=0444, connection = sqlite.connect(self.importSource, mode=0444,
encoding=(encoding, 'replace')) encoding=(encoding, 'replace'))
cursor = connection.cursor() cursor = connection.cursor()
# Determine if we're using a new or an old DB. # Determine if we're using a new or an old DB.
@ -94,64 +96,66 @@ class OpenLP1SongImport(SongImport):
cursor.execute(u'SELECT settingsid, settingsname FROM settings') cursor.execute(u'SELECT settingsid, settingsname FROM settings')
themes = {} themes = {}
for theme_id, theme_name in cursor.fetchall(): for theme_id, theme_name in cursor.fetchall():
if theme_name in self.available_themes: if theme_name in self.availableThemes:
themes[theme_id] = theme_name themes[theme_id] = theme_name
# Import the songs. # Import the songs.
cursor.execute(u'-- types int, unicode, unicode, unicode, int') cursor.execute(u'-- types int, unicode, unicode, unicode, int')
cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, ' cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, '
u'copyrightinfo, settingsid FROM songs') u'copyrightinfo, settingsid FROM songs')
songs = cursor.fetchall() songs = cursor.fetchall()
self.import_wizard.progressBar.setMaximum(len(songs)) self.importWizard.progressBar.setMaximum(len(songs))
for song in songs: for song in songs:
self.set_defaults() self.setDefaults()
if self.stop_import_flag: if self.stopImportFlag:
break break
song_id = song[0] song_id = song[0]
self.title = song[1] self.title = song[1]
lyrics = song[2].replace(u'\r\n', u'\n') lyrics = song[2].replace(u'\r\n', u'\n')
self.add_copyright(song[3]) self.addCopyright(song[3])
if themes.has_key(song[4]): if themes.has_key(song[4]):
self.theme_name = themes[song[4]] self.themeName = themes[song[4]]
verses = lyrics.split(u'\n\n') verses = lyrics.split(u'\n\n')
for verse in verses: for verse in verses:
if verse.strip(): if verse.strip():
self.add_verse(verse.strip()) self.addVerse(verse.strip())
cursor.execute(u'-- types int') cursor.execute(u'-- types int')
cursor.execute(u'SELECT authorid FROM songauthors ' cursor.execute(u'SELECT authorid FROM songauthors '
u'WHERE songid = %s' % song_id) u'WHERE songid = %s' % song_id)
author_ids = cursor.fetchall() author_ids = cursor.fetchall()
for author_id in author_ids: for author_id in author_ids:
if self.stop_import_flag: if self.stopImportFlag:
break break
for author in authors: for author in authors:
if author[0] == author_id[0]: if author[0] == author_id[0]:
self.parse_author(author[1]) self.parseAuthor(author[1])
break break
if self.stop_import_flag: if self.stopImportFlag:
break break
if new_db: if new_db:
cursor.execute(u'-- types int') cursor.execute(u'-- types int, int')
cursor.execute(u'SELECT trackid FROM songtracks ' cursor.execute(u'SELECT trackid, listindex '
u'FROM songtracks '
u'WHERE songid = %s ORDER BY listindex' % song_id) u'WHERE songid = %s ORDER BY listindex' % song_id)
track_ids = cursor.fetchall() track_ids = cursor.fetchall()
for track_id in track_ids: for track_id, listindex in track_ids:
if self.stop_import_flag: if self.stopImportFlag:
break break
for track in tracks: for track in tracks:
if track[0] == track_id[0]: if track[0] == track_id:
self.add_media_file(track[1]) media_file = self.expandMediaFile(track[1])
self.addMediaFile(media_file, listindex)
break break
if self.stop_import_flag: if self.stopImportFlag:
break break
if not self.finish(): if not self.finish():
self.log_error(self.import_source) self.logError(self.importSource)
def get_encoding(self): def getEncoding(self):
""" """
Detect character encoding of an openlp.org 1.x song database. Detect character encoding of an openlp.org 1.x song database.
""" """
# Connect to the database. # Connect to the database.
connection = sqlite.connect(self.import_source, mode=0444) connection = sqlite.connect(self.importSource, mode=0444)
cursor = connection.cursor() cursor = connection.cursor()
detector = UniversalDetector() detector = UniversalDetector()
@ -186,3 +190,22 @@ class OpenLP1SongImport(SongImport):
return detector.result[u'encoding'] return detector.result[u'encoding']
detector.close() detector.close()
return retrieve_windows_encoding(detector.result[u'encoding']) return retrieve_windows_encoding(detector.result[u'encoding'])
def expandMediaFile(self, filename):
"""
When you're on Windows, this function expands the file name to include
the path to OpenLP's application data directory. If you are not on
Windows, it returns the original file name.
``filename``
The filename to expand.
"""
if sys.platform != u'win32' and \
not os.environ.get(u'ALLUSERSPROFILE') and \
not os.environ.get(u'APPDATA'):
return filename
common_app_data = os.path.join(os.environ[u'ALLUSERSPROFILE'],
os.path.split(os.environ[u'APPDATA'])[1])
if not common_app_data:
return filename
return os.path.join(common_app_data, u'openlp.org', 'Audio', filename)

View File

@ -95,22 +95,22 @@ class OpenLPSongImport(SongImport):
The database providing the data to import. The database providing the data to import.
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.source_session = None self.sourceSession = None
def do_import(self): def doImport(self):
""" """
Run the import for an OpenLP version 2 song database. Run the import for an OpenLP version 2 song database.
""" """
if not self.import_source.endswith(u'.sqlite'): if not self.importSource.endswith(u'.sqlite'):
self.log_error(self.import_source, self.logError(self.importSource,
translate('SongsPlugin.OpenLPSongImport', translate('SongsPlugin.OpenLPSongImport',
'Not a valid OpenLP 2.0 song database.')) 'Not a valid OpenLP 2.0 song database.'))
return return
self.import_source = u'sqlite:///%s' % self.import_source self.importSource = u'sqlite:///%s' % self.importSource
engine = create_engine(self.import_source) engine = create_engine(self.importSource)
source_meta = MetaData() source_meta = MetaData()
source_meta.reflect(engine) source_meta.reflect(engine)
self.source_session = scoped_session(sessionmaker(bind=engine)) self.sourceSession = scoped_session(sessionmaker(bind=engine))
if u'media_files' in source_meta.tables.keys(): if u'media_files' in source_meta.tables.keys():
has_media_files = True has_media_files = True
else: else:
@ -156,9 +156,9 @@ class OpenLPSongImport(SongImport):
except UnmappedClassError: except UnmappedClassError:
mapper(OldTopic, source_topics_table) mapper(OldTopic, source_topics_table)
source_songs = self.source_session.query(OldSong).all() source_songs = self.sourceSession.query(OldSong).all()
if self.import_wizard: if self.importWizard:
self.import_wizard.progressBar.setMaximum(len(source_songs)) self.importWizard.progressBar.setMaximum(len(source_songs))
for song in source_songs: for song in source_songs:
new_song = Song() new_song = Song()
new_song.title = song.title new_song.title = song.title
@ -201,22 +201,22 @@ class OpenLPSongImport(SongImport):
if existing_topic is None: if existing_topic is None:
existing_topic = Topic.populate(name=topic.name) existing_topic = Topic.populate(name=topic.name)
new_song.topics.append(existing_topic) new_song.topics.append(existing_topic)
# if has_media_files: if has_media_files:
# if song.media_files: if song.media_files:
# for media_file in song.media_files: for media_file in song.media_files:
# existing_media_file = \ existing_media_file = \
# self.manager.get_object_filtered(MediaFile, self.manager.get_object_filtered(MediaFile,
# MediaFile.file_name == media_file.file_name) MediaFile.file_name == media_file.file_name)
# if existing_media_file: if existing_media_file:
# new_song.media_files.append(existing_media_file) new_song.media_files.append(existing_media_file)
# else: else:
# new_song.media_files.append(MediaFile.populate( new_song.media_files.append(MediaFile.populate(
# file_name=media_file.file_name)) file_name=media_file.file_name))
clean_song(self.manager, new_song) clean_song(self.manager, new_song)
self.manager.save_object(new_song) self.manager.save_object(new_song)
if self.import_wizard: if self.importWizard:
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
WizardStrings.ImportingType % new_song.title) WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag: if self.stopImportFlag:
break break
engine.dispose() engine.dispose()

View File

@ -59,58 +59,58 @@ class OooImport(SongImport):
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.document = None self.document = None
self.process_started = False self.processStarted = False
def do_import(self): def doImport(self):
if not isinstance(self.import_source, list): if not isinstance(self.importSource, list):
return return
try: try:
self.start_ooo() self.start_ooo()
except NoConnectException as exc: except NoConnectException as exc:
self.log_error( self.logError(
self.import_source[0], self.importSource[0],
translate('SongsPlugin.SongImport', translate('SongsPlugin.SongImport',
'Cannot access OpenOffice or LibreOffice')) 'Cannot access OpenOffice or LibreOffice'))
log.error(exc) log.error(exc)
return return
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for filename in self.import_source: for filename in self.importSource:
if self.stop_import_flag: if self.stopImportFlag:
break break
filename = unicode(filename) filename = unicode(filename)
if os.path.isfile(filename): if os.path.isfile(filename):
self.open_ooo_file(filename) self.openOooFile(filename)
if self.document: if self.document:
self.process_ooo_document() self.processOooDocument()
self.close_ooo_file() self.closeOooFile()
else: else:
self.log_error(self.filepath, self.logError(self.filepath,
translate('SongsPlugin.SongImport', translate('SongsPlugin.SongImport',
'Unable to open file')) 'Unable to open file'))
else: else:
self.log_error(self.filepath, self.logError(self.filepath,
translate('SongsPlugin.SongImport', 'File not found')) translate('SongsPlugin.SongImport', 'File not found'))
self.close_ooo() self.closeOoo()
def process_ooo_document(self): def processOooDocument(self):
""" """
Handle the import process for OpenOffice files. This method facilitates Handle the import process for OpenOffice files. This method facilitates
allowing subclasses to handle specific types of OpenOffice files. allowing subclasses to handle specific types of OpenOffice files.
""" """
if self.document.supportsService( if self.document.supportsService(
"com.sun.star.presentation.PresentationDocument"): "com.sun.star.presentation.PresentationDocument"):
self.process_pres() self.processPres()
if self.document.supportsService("com.sun.star.text.TextDocument"): if self.document.supportsService("com.sun.star.text.TextDocument"):
self.process_doc() self.processDoc()
def start_ooo(self): def startOoo(self):
""" """
Start OpenOffice.org process Start OpenOffice.org process
TODO: The presentation/Impress plugin may already have it running TODO: The presentation/Impress plugin may already have it running
""" """
if os.name == u'nt': if os.name == u'nt':
self.start_ooo_process() self.startOooProcess()
self.desktop = self.ooo_manager.createInstance( self.desktop = self.oooManager.createInstance(
u'com.sun.star.frame.Desktop') u'com.sun.star.frame.Desktop')
else: else:
context = uno.getComponentContext() context = uno.getComponentContext()
@ -123,7 +123,7 @@ class OooImport(SongImport):
uno_instance = get_uno_instance(resolver) uno_instance = get_uno_instance(resolver)
except NoConnectException: except NoConnectException:
log.exception("Failed to resolve uno connection") log.exception("Failed to resolve uno connection")
self.start_ooo_process() self.startOooProcess()
loop += 1 loop += 1
else: else:
manager = uno_instance.ServiceManager manager = uno_instance.ServiceManager
@ -132,22 +132,22 @@ class OooImport(SongImport):
return return
raise raise
def start_ooo_process(self): def startOooProcess(self):
try: try:
if os.name == u'nt': if os.name == u'nt':
self.ooo_manager = Dispatch(u'com.sun.star.ServiceManager') self.oooManager = Dispatch(u'com.sun.star.ServiceManager')
self.ooo_manager._FlagAsMethod(u'Bridge_GetStruct') self.oooManager._FlagAsMethod(u'Bridge_GetStruct')
self.ooo_manager._FlagAsMethod(u'Bridge_GetValueObject') self.oooManager._FlagAsMethod(u'Bridge_GetValueObject')
else: else:
cmd = get_uno_command() cmd = get_uno_command()
process = QtCore.QProcess() process = QtCore.QProcess()
process.startDetached(cmd) process.startDetached(cmd)
process.waitForStarted() process.waitForStarted()
self.process_started = True self.processStarted = True
except: except:
log.exception("start_ooo_process failed") log.exception("start_ooo_process failed")
def open_ooo_file(self, filepath): def openOooFile(self, filepath):
""" """
Open the passed file in OpenOffice.org Impress Open the passed file in OpenOffice.org Impress
""" """
@ -166,29 +166,29 @@ class OooImport(SongImport):
if not self.document.supportsService( if not self.document.supportsService(
"com.sun.star.presentation.PresentationDocument") and not \ "com.sun.star.presentation.PresentationDocument") and not \
self.document.supportsService("com.sun.star.text.TextDocument"): self.document.supportsService("com.sun.star.text.TextDocument"):
self.close_ooo_file() self.closeOooFile()
else: else:
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
u'Processing file ' + filepath, 0) u'Processing file ' + filepath, 0)
except AttributeError: except AttributeError:
log.exception("open_ooo_file failed: %s", url) log.exception("open_ooo_file failed: %s", url)
return return
def close_ooo_file(self): def closeOooFile(self):
""" """
Close file. Close file.
""" """
self.document.close(True) self.document.close(True)
self.document = None self.document = None
def close_ooo(self): def closeOoo(self):
""" """
Close OOo. But only if we started it and not on windows Close OOo. But only if we started it and not on windows
""" """
if self.process_started: if self.processStarted:
self.desktop.terminate() self.desktop.terminate()
def process_pres(self): def processPres(self):
""" """
Process the file Process the file
""" """
@ -196,8 +196,8 @@ class OooImport(SongImport):
slides = doc.getDrawPages() slides = doc.getDrawPages()
text = u'' text = u''
for slide_no in range(slides.getCount()): for slide_no in range(slides.getCount()):
if self.stop_import_flag: if self.stopImportFlag:
self.import_wizard.incrementProgressBar(u'Import cancelled', 0) self.importWizard.incrementProgressBar(u'Import cancelled', 0)
return return
slide = slides.getByIndex(slide_no) slide = slides.getByIndex(slide_no)
slidetext = u'' slidetext = u''
@ -209,10 +209,10 @@ class OooImport(SongImport):
if slidetext.strip() == u'': if slidetext.strip() == u'':
slidetext = u'\f' slidetext = u'\f'
text += slidetext text += slidetext
self.process_songs_text(text) self.processSongsText(text)
return return
def process_doc(self): def processDoc(self):
""" """
Process the doc file, a paragraph at a time Process the doc file, a paragraph at a time
""" """
@ -231,16 +231,16 @@ class OooImport(SongImport):
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH): if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
paratext += u'\f' paratext += u'\f'
text += paratext + u'\n' text += paratext + u'\n'
self.process_songs_text(text) self.processSongsText(text)
def process_songs_text(self, text): def processSongsText(self, text):
songtexts = self.tidy_text(text).split(u'\f') songtexts = self.tidyText(text).split(u'\f')
self.set_defaults() self.setDefaults()
for songtext in songtexts: for songtext in songtexts:
if songtext.strip(): if songtext.strip():
self.process_song_text(songtext.strip()) self.processSongText(songtext.strip())
if self.check_complete(): if self.checkComplete():
self.finish() self.finish()
self.set_defaults() self.setDefaults()
if self.check_complete(): if self.checkComplete():
self.finish() self.finish()

View File

@ -53,16 +53,16 @@ class OpenLyricsImport(SongImport):
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.openLyrics = OpenLyrics(self.manager) self.openLyrics = OpenLyrics(self.manager)
def do_import(self): def doImport(self):
""" """
Imports the songs. Imports the songs.
""" """
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
parser = etree.XMLParser(remove_blank_text=True) parser = etree.XMLParser(remove_blank_text=True)
for file_path in self.import_source: for file_path in self.importSource:
if self.stop_import_flag: if self.stopImportFlag:
return return
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType % os.path.basename(file_path))
try: try:
# Pass a file object, because lxml does not cope with some # Pass a file object, because lxml does not cope with some
@ -72,4 +72,4 @@ class OpenLyricsImport(SongImport):
self.openLyrics.xml_to_song(xml) self.openLyrics.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception(u'XML syntax error in file %s' % file_path) log.exception(u'XML syntax error in file %s' % file_path)
self.log_error(file_path, SongStrings.XMLSyntaxError) self.logError(file_path, SongStrings.XMLSyntaxError)

View File

@ -107,32 +107,32 @@ class OpenSongImport(SongImport):
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for filename in self.import_source: for filename in self.importSource:
if self.stop_import_flag: if self.stopImportFlag:
return return
song_file = open(filename) song_file = open(filename)
self.do_import_file(song_file) self.doImportFile(song_file)
song_file.close() song_file.close()
def do_import_file(self, file): def doImportFile(self, file):
""" """
Process the OpenSong file - pass in a file-like object, not a file path. Process the OpenSong file - pass in a file-like object, not a file path.
""" """
self.set_defaults() self.setDefaults()
try: try:
tree = objectify.parse(file) tree = objectify.parse(file)
except (Error, LxmlError): except (Error, LxmlError):
self.log_error(file.name, SongStrings.XMLSyntaxError) self.logError(file.name, SongStrings.XMLSyntaxError)
log.exception(u'Error parsing XML') log.exception(u'Error parsing XML')
return return
root = tree.getroot() root = tree.getroot()
fields = dir(root) fields = dir(root)
decode = { decode = {
u'copyright': self.add_copyright, u'copyright': self.addCopyright,
u'ccli': u'ccli_number', u'ccli': u'ccli_number',
u'author': self.parse_author, u'author': self.parseAuthor,
u'title': u'title', u'title': u'title',
u'aka': u'alternate_title', u'aka': u'alternate_title',
u'hymn_number': u'song_number' u'hymn_number': u'song_number'
@ -190,7 +190,10 @@ class OpenSongImport(SongImport):
# the verse tag # the verse tag
verse_tag = content verse_tag = content
verse_num = u'1' verse_num = u'1'
verse_index = VerseType.from_loose_input(verse_tag) if len(verse_tag) == 0:
verse_index = 0
else:
verse_index = VerseType.from_loose_input(verse_tag)
verse_tag = VerseType.Tags[verse_index] verse_tag = VerseType.Tags[verse_index]
inst = 1 inst = 1
if [verse_tag, verse_num, inst] in our_verse_order \ if [verse_tag, verse_num, inst] in our_verse_order \
@ -211,7 +214,7 @@ class OpenSongImport(SongImport):
verses[verse_tag][verse_num][inst] = [] verses[verse_tag][verse_num][inst] = []
our_verse_order.append([verse_tag, verse_num, inst]) our_verse_order.append([verse_tag, verse_num, inst])
# Tidy text and remove the ____s from extended words # Tidy text and remove the ____s from extended words
this_line = self.tidy_text(this_line) this_line = self.tidyText(this_line)
this_line = this_line.replace(u'_', u'') this_line = this_line.replace(u'_', u'')
this_line = this_line.replace(u'|', u'\n') this_line = this_line.replace(u'|', u'\n')
verses[verse_tag][verse_num][inst].append(this_line) verses[verse_tag][verse_num][inst].append(this_line)
@ -220,9 +223,9 @@ class OpenSongImport(SongImport):
for (verse_tag, verse_num, inst) in our_verse_order: for (verse_tag, verse_num, inst) in our_verse_order:
verse_def = u'%s%s' % (verse_tag, verse_num) verse_def = u'%s%s' % (verse_tag, verse_num)
lines = u'\n'.join(verses[verse_tag][verse_num][inst]) lines = u'\n'.join(verses[verse_tag][verse_num][inst])
self.add_verse(lines, verse_def) self.addVerse(lines, verse_def)
if not self.verses: if not self.verses:
self.add_verse('') self.addVerse('')
# figure out the presentation order, if present # figure out the presentation order, if present
if u'presentation' in fields and root.presentation: if u'presentation' in fields and root.presentation:
order = unicode(root.presentation) order = unicode(root.presentation)
@ -243,9 +246,9 @@ class OpenSongImport(SongImport):
verse_def = u'%s%s' % (verse_tag, verse_num) verse_def = u'%s%s' % (verse_tag, verse_num)
if verses.has_key(verse_tag) and \ if verses.has_key(verse_tag) and \
verses[verse_tag].has_key(verse_num): verses[verse_tag].has_key(verse_num):
self.verse_order_list.append(verse_def) self.verseOrderList.append(verse_def)
else: else:
log.info(u'Got order %s but not in verse tags, dropping' log.info(u'Got order %s but not in verse tags, dropping'
u'this item from presentation order', verse_def) u'this item from presentation order', verse_def)
if not self.finish(): if not self.finish():
self.log_error(file.name) self.logError(file.name)

View File

@ -83,32 +83,32 @@ class SofImport(OooImport):
OooImport.__init__(self, manager, **kwargs) OooImport.__init__(self, manager, **kwargs)
self.song = False self.song = False
def process_ooo_document(self): def processOooDocument(self):
""" """
Handle the import process for SoF files. Handle the import process for SoF files.
""" """
self.process_sof_file() self.processSofFile()
def process_sof_file(self): def processSofFile(self):
""" """
Process the RTF file, a paragraph at a time Process the RTF file, a paragraph at a time
""" """
self.blanklines = 0 self.blankLines = 0
self.new_song() self.newSong()
try: try:
paragraphs = self.document.getText().createEnumeration() paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements(): while paragraphs.hasMoreElements():
if self.stop_import_flag: if self.stopImportFlag:
return return
paragraph = paragraphs.nextElement() paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"): if paragraph.supportsService("com.sun.star.text.Paragraph"):
self.process_paragraph(paragraph) self.processParagraph(paragraph)
except RuntimeException as exc: except RuntimeException as exc:
log.exception(u'Error processing file: %s', exc) log.exception(u'Error processing file: %s', exc)
if not self.finish(): if not self.finish():
self.log_error(self.filepath) self.logError(self.filepath)
def process_paragraph(self, paragraph): def processParagraph(self, paragraph):
""" """
Process a paragraph. Process a paragraph.
In the first book, a paragraph is a single line. In the latter ones In the first book, a paragraph is a single line. In the latter ones
@ -124,26 +124,26 @@ class SofImport(OooImport):
while textportions.hasMoreElements(): while textportions.hasMoreElements():
textportion = textportions.nextElement() textportion = textportions.nextElement()
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH): if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
self.process_paragraph_text(text) self.processParagraphText(text)
self.new_song() self.newSong()
text = u'' text = u''
text += self.process_textportion(textportion) text += self.process_textportion(textportion)
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH): if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
self.process_paragraph_text(text) self.processParagraphText(text)
self.new_song() self.newSong()
text = u'' text = u''
self.process_paragraph_text(text) self.processParagraphText(text)
def process_paragraph_text(self, text): def processParagraphText(self, text):
""" """
Split the paragraph text into multiple lines and process Split the paragraph text into multiple lines and process
""" """
for line in text.split(u'\n'): for line in text.split(u'\n'):
self.process_paragraph_line(line) self.processParagraphLine(line)
if self.blanklines > 2: if self.blankLines > 2:
self.new_song() self.newSong()
def process_paragraph_line(self, text): def processParagraphLine(self, text):
""" """
Process a single line. Throw away that text which isn't relevant, i.e. Process a single line. Throw away that text which isn't relevant, i.e.
stuff that appears at the end of the song. stuff that appears at the end of the song.
@ -151,16 +151,16 @@ class SofImport(OooImport):
""" """
text = text.strip() text = text.strip()
if text == u'': if text == u'':
self.blanklines += 1 self.blankLines += 1
if self.blanklines > 1: if self.blankLines > 1:
return return
if self.title != u'': if self.title != u'':
self.finish_verse() self.finishVerse()
return return
self.blanklines = 0 self.blankLines = 0
if self.skip_to_close_bracket: if self.skipToCloseBracket:
if text.endswith(u')'): if text.endswith(u')'):
self.skip_to_close_bracket = False self.skipToCloseBracket = False
return return
if text.startswith(u'CCL Licence'): if text.startswith(u'CCL Licence'):
self.italics = False self.italics = False
@ -169,24 +169,24 @@ class SofImport(OooImport):
return return
if text.startswith(u'(NB.') or text.startswith(u'(Regrettably') \ if text.startswith(u'(NB.') or text.startswith(u'(Regrettably') \
or text.startswith(u'(From'): or text.startswith(u'(From'):
self.skip_to_close_bracket = True self.skipToCloseBracket = True
return return
if text.startswith(u'Copyright'): if text.startswith(u'Copyright'):
self.add_copyright(text) self.addCopyright(text)
return return
if text == u'(Repeat)': if text == u'(Repeat)':
self.finish_verse() self.finishVerse()
self.repeat_verse() self.repeatVerse()
return return
if self.title == u'': if self.title == u'':
if self.copyright == u'': if self.copyright == u'':
self.add_sof_author(text) self.addSofAuthor(text)
else: else:
self.add_copyright(text) self.addCopyright(text)
return return
self.add_verse_line(text) self.addVerseLine(text)
def process_textportion(self, textportion): def processTextPortion(self, textportion):
""" """
Process a text portion. Here we just get the text and detect if Process a text portion. Here we just get the text and detect if
it's bold or italics. If it's bold then its a song number or song title. it's bold or italics. If it's bold then its a song number or song title.
@ -199,53 +199,53 @@ class SofImport(OooImport):
return text return text
if textportion.CharWeight == BOLD: if textportion.CharWeight == BOLD:
boldtext = text.strip() boldtext = text.strip()
if boldtext.isdigit() and self.song_number == '': if boldtext.isdigit() and self.songNumber == '':
self.add_songnumber(boldtext) self.addSongNumber(boldtext)
return u'' return u''
if self.title == u'': if self.title == u'':
text = self.uncap_text(text) text = self.uncap_text(text)
self.add_title(text) self.addTitle(text)
return text return text
if text.strip().startswith(u'('): if text.strip().startswith(u'('):
return text return text
self.italics = (textportion.CharPosture == ITALIC) self.italics = (textportion.CharPosture == ITALIC)
return text return text
def new_song(self): def newSong(self):
""" """
A change of song. Store the old, create a new A change of song. Store the old, create a new
... but only if the last song was complete. If not, stick with it ... but only if the last song was complete. If not, stick with it
""" """
if self.song: if self.song:
self.finish_verse() self.finishVerse()
if not self.check_complete(): if not self.checkComplete():
return return
self.finish() self.finish()
self.song = True self.song = True
self.set_defaults() self.setDefaults()
self.skip_to_close_bracket = False self.skipToCloseBracket = False
self.is_chorus = False self.isChorus = False
self.italics = False self.italics = False
self.currentverse = u'' self.currentVerse = u''
def add_songnumber(self, song_no): def addSongNumber(self, song_no):
""" """
Add a song number, store as alternate title. Also use the song Add a song number, store as alternate title. Also use the song
number to work out which songbook we're in number to work out which songbook we're in
""" """
self.song_number = song_no self.songNumber = song_no
self.alternate_title = song_no + u'.' self.alternateTitle = song_no + u'.'
self.song_book_pub = u'Kingsway Publications' self.songBook_pub = u'Kingsway Publications'
if int(song_no) <= 640: if int(song_no) <= 640:
self.song_book = u'Songs of Fellowship 1' self.songBook = u'Songs of Fellowship 1'
elif int(song_no) <= 1150: elif int(song_no) <= 1150:
self.song_book = u'Songs of Fellowship 2' self.songBook = u'Songs of Fellowship 2'
elif int(song_no) <= 1690: elif int(song_no) <= 1690:
self.song_book = u'Songs of Fellowship 3' self.songBook = u'Songs of Fellowship 3'
else: else:
self.song_book = u'Songs of Fellowship 4' self.songBook = u'Songs of Fellowship 4'
def add_title(self, text): def addTitle(self, text):
""" """
Add the title to the song. Strip some leading/trailing punctuation that Add the title to the song. Strip some leading/trailing punctuation that
we don't want in a title we don't want in a title
@ -256,9 +256,9 @@ class SofImport(OooImport):
if title.endswith(u','): if title.endswith(u','):
title = title[:-1] title = title[:-1]
self.title = title self.title = title
self.import_wizard.incrementProgressBar(u'Processing song ' + title, 0) self.importWizard.incrementProgressBar(u'Processing song ' + title, 0)
def add_sof_author(self, text): def addSofAuthor(self, text):
""" """
Add the author. OpenLP stores them individually so split by 'and', '&' Add the author. OpenLP stores them individually so split by 'and', '&'
and comma. and comma.
@ -266,42 +266,42 @@ class SofImport(OooImport):
"Mr Smith" and "Mrs Smith". "Mr Smith" and "Mrs Smith".
""" """
text = text.replace(u' and ', u' & ') text = text.replace(u' and ', u' & ')
self.parse_author(text) self.parseAuthor(text)
def add_verse_line(self, text): def addVerseLine(self, text):
""" """
Add a line to the current verse. If the formatting has changed and Add a line to the current verse. If the formatting has changed and
we're beyond the second line of first verse, then this indicates we're beyond the second line of first verse, then this indicates
a change of verse. Italics are a chorus a change of verse. Italics are a chorus
""" """
if self.italics != self.is_chorus and ((len(self.verses) > 0) or if self.italics != self.isChorus and ((len(self.verses) > 0) or
(self.currentverse.count(u'\n') > 1)): (self.currentVerse.count(u'\n') > 1)):
self.finish_verse() self.finishVerse()
if self.italics: if self.italics:
self.is_chorus = True self.isChorus = True
self.currentverse += text + u'\n' self.currentVerse += text + u'\n'
def finish_verse(self): def finishVerse(self):
""" """
Verse is finished, store it. Note in book 1+2, some songs are formatted Verse is finished, store it. Note in book 1+2, some songs are formatted
incorrectly. Here we try and split songs with missing line breaks into incorrectly. Here we try and split songs with missing line breaks into
the correct number of verses. the correct number of verses.
""" """
if self.currentverse.strip() == u'': if self.currentVerse.strip() == u'':
return return
if self.is_chorus: if self.isChorus:
versetag = u'C' versetag = u'C'
splitat = None splitat = None
else: else:
versetag = u'V' versetag = u'V'
splitat = self.verse_splits(self.song_number) splitat = self.verseSplits(self.songNumber)
if splitat: if splitat:
ln = 0 ln = 0
verse = u'' verse = u''
for line in self.currentverse.split(u'\n'): for line in self.currentVerse.split(u'\n'):
ln += 1 ln += 1
if line == u'' or ln > splitat: if line == u'' or ln > splitat:
self.add_sof_verse(verse, versetag) self.addSofVerse(verse, versetag)
ln = 0 ln = 0
if line: if line:
verse = line + u'\n' verse = line + u'\n'
@ -310,19 +310,19 @@ class SofImport(OooImport):
else: else:
verse += line + u'\n' verse += line + u'\n'
if verse: if verse:
self.add_sof_verse(verse, versetag) self.addSofVerse(verse, versetag)
else: else:
self.add_sof_verse(self.currentverse, versetag) self.addSofVerse(self.currentVerse, versetag)
self.currentverse = u'' self.currentVerse = u''
self.is_chorus = False self.isChorus = False
def add_sof_verse(self, lyrics, tag): def addSofVerse(self, lyrics, tag):
self.add_verse(lyrics, tag) self.addVerse(lyrics, tag)
if not self.is_chorus and u'C1' in self.verse_order_list_generated: if not self.isChorus and u'C1' in self.verseOrderListGenerated:
self.verse_order_list_generated.append(u'C1') self.verseOrderListGenerated.append(u'C1')
self.verse_order_list_generated_useful = True self.verseOrderListGenerated_useful = True
def uncap_text(self, text): def uncapText(self, text):
""" """
Words in the title are in all capitals, so we lowercase them. Words in the title are in all capitals, so we lowercase them.
However some of these words, e.g. referring to God need a leading However some of these words, e.g. referring to God need a leading
@ -348,7 +348,7 @@ class SofImport(OooImport):
text = u''.join(textarr) text = u''.join(textarr)
return text return text
def verse_splits(self, song_number): def verseSplits(self, song_number):
""" """
Because someone at Kingsway forgot to check the 1+2 RTF file, Because someone at Kingsway forgot to check the 1+2 RTF file,
some verses were not formatted correctly. some verses were not formatted correctly.

View File

@ -91,27 +91,27 @@ class SongBeamerImport(SongImport):
(re.compile(u'<align.*?>'), u''), (re.compile(u'<align.*?>'), u''),
(re.compile(u'<valign.*?>'), u'') (re.compile(u'<valign.*?>'), u'')
] ]
def __init__(self, manager, **kwargs): def __init__(self, manager, **kwargs):
""" """
Initialise the Song Beamer importer. Initialise the Song Beamer importer.
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
""" """
Receive a single file or a list of files to import. Receive a single file or a list of files to import.
""" """
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
if not isinstance(self.import_source, list): if not isinstance(self.importSource, list):
return return
for file in self.import_source: for file in self.importSource:
# TODO: check that it is a valid SongBeamer file # TODO: check that it is a valid SongBeamer file
if self.stop_import_flag: if self.stopImportFlag:
return return
self.set_defaults() self.setDefaults()
self.current_verse = u'' self.currentVerse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse] self.currentVerseType = VerseType.Tags[VerseType.Verse]
read_verses = False read_verses = False
file_name = os.path.split(file)[1] file_name = os.path.split(file)[1]
if os.path.isfile(file): if os.path.isfile(file):
@ -119,48 +119,48 @@ class SongBeamerImport(SongImport):
details = chardet.detect(detect_file.read()) details = chardet.detect(detect_file.read())
detect_file.close() detect_file.close()
infile = codecs.open(file, u'r', details['encoding']) infile = codecs.open(file, u'r', details['encoding'])
songData = infile.readlines() song_data = infile.readlines()
infile.close() infile.close()
else: else:
continue continue
self.title = file_name.split('.sng')[0] self.title = file_name.split('.sng')[0]
read_verses = False read_verses = False
for line in songData: for line in song_data:
# Just make sure that the line is of the type 'Unicode'. # Just make sure that the line is of the type 'Unicode'.
line = unicode(line).strip() line = unicode(line).strip()
if line.startswith(u'#') and not read_verses: if line.startswith(u'#') and not read_verses:
self.parse_tags(line) self.parseTags(line)
elif line.startswith(u'---'): elif line.startswith(u'---'):
if self.current_verse: if self.currentVerse:
self.replace_html_tags() self.replaceHtmlTags()
self.add_verse(self.current_verse, self.addVerse(self.currentVerse,
self.current_verse_type) self.currentVerseType)
self.current_verse = u'' self.currentVerse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse] self.currentVerseType = VerseType.Tags[VerseType.Verse]
read_verses = True read_verses = True
verse_start = True verse_start = True
elif read_verses: elif read_verses:
if verse_start: if verse_start:
verse_start = False verse_start = False
if not self.check_verse_marks(line): if not self.checkVerseMarks(line):
self.current_verse = line + u'\n' self.currentVerse = line + u'\n'
else: else:
self.current_verse += line + u'\n' self.currentVerse += line + u'\n'
if self.current_verse: if self.currentVerse:
self.replace_html_tags() self.replaceHtmlTags()
self.add_verse(self.current_verse, self.current_verse_type) self.addVerse(self.currentVerse, self.currentVerseType)
if not self.finish(): if not self.finish():
self.log_error(file) self.logError(file)
def replace_html_tags(self): def replaceHtmlTags(self):
""" """
This can be called to replace SongBeamer's specific (html) tags with This can be called to replace SongBeamer's specific (html) tags with
OpenLP's specific (html) tags. OpenLP's specific (html) tags.
""" """
for pair in SongBeamerImport.HTML_TAG_PAIRS: for pair in SongBeamerImport.HTML_TAG_PAIRS:
self.current_verse = pair[0].sub(pair[1], self.current_verse) self.currentVerse = pair[0].sub(pair[1], self.currentVerse)
def parse_tags(self, line): def parseTags(self, line):
""" """
Parses a meta data line. Parses a meta data line.
@ -176,11 +176,11 @@ class SongBeamerImport(SongImport):
if not tag_val[0] or not tag_val[1]: if not tag_val[0] or not tag_val[1]:
return return
if tag_val[0] == u'#(c)': if tag_val[0] == u'#(c)':
self.add_copyright(tag_val[1]) self.addCopyright(tag_val[1])
elif tag_val[0] == u'#AddCopyrightInfo': elif tag_val[0] == u'#AddCopyrightInfo':
pass pass
elif tag_val[0] == u'#Author': elif tag_val[0] == u'#Author':
self.parse_author(tag_val[1]) self.parseAuthor(tag_val[1])
elif tag_val[0] == u'#BackgroundImage': elif tag_val[0] == u'#BackgroundImage':
pass pass
elif tag_val[0] == u'#Bible': elif tag_val[0] == u'#Bible':
@ -188,7 +188,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == u'#Categories': elif tag_val[0] == u'#Categories':
self.topics = tag_val[1].split(',') self.topics = tag_val[1].split(',')
elif tag_val[0] == u'#CCLI': elif tag_val[0] == u'#CCLI':
self.ccli_number = tag_val[1] self.ccliNumber = tag_val[1]
elif tag_val[0] == u'#Chords': elif tag_val[0] == u'#Chords':
pass pass
elif tag_val[0] == u'#ChurchSongID': elif tag_val[0] == u'#ChurchSongID':
@ -220,7 +220,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == u'#LangCount': elif tag_val[0] == u'#LangCount':
pass pass
elif tag_val[0] == u'#Melody': elif tag_val[0] == u'#Melody':
self.parse_author(tag_val[1]) self.parseAuthor(tag_val[1])
elif tag_val[0] == u'#NatCopyright': elif tag_val[0] == u'#NatCopyright':
pass pass
elif tag_val[0] == u'#OTitle': elif tag_val[0] == u'#OTitle':
@ -235,10 +235,10 @@ class SongBeamerImport(SongImport):
song_book_pub = tag_val[1] song_book_pub = tag_val[1]
elif tag_val[0] == u'#Songbook' or tag_val[0] == u'#SongBook': elif tag_val[0] == u'#Songbook' or tag_val[0] == u'#SongBook':
book_data = tag_val[1].split(u'/') book_data = tag_val[1].split(u'/')
self.song_book_name = book_data[0].strip() self.songBookName = book_data[0].strip()
if len(book_data) == 2: if len(book_data) == 2:
number = book_data[1].strip() number = book_data[1].strip()
self.song_number = number if number.isdigit() else u'' self.songNumber = number if number.isdigit() else u''
elif tag_val[0] == u'#Speed': elif tag_val[0] == u'#Speed':
pass pass
elif tag_val[0] == u'Tempo': elif tag_val[0] == u'Tempo':
@ -269,7 +269,7 @@ class SongBeamerImport(SongImport):
# TODO: add the verse order. # TODO: add the verse order.
pass pass
def check_verse_marks(self, line): def checkVerseMarks(self, line):
""" """
Check and add the verse's MarkType. Returns ``True`` if the given line Check and add the verse's MarkType. Returns ``True`` if the given line
contains a correct verse mark otherwise ``False``. contains a correct verse mark otherwise ``False``.
@ -279,10 +279,10 @@ class SongBeamerImport(SongImport):
""" """
marks = line.split(u' ') marks = line.split(u' ')
if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes: if len(marks) <= 2 and marks[0] in SongBeamerTypes.MarkTypes:
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0]] self.currentVerseType = SongBeamerTypes.MarkTypes[marks[0]]
if len(marks) == 2: if len(marks) == 2:
# If we have a digit, we append it to current_verse_type. # If we have a digit, we append it to current_verse_type.
if marks[1].isdigit(): if marks[1].isdigit():
self.current_verse_type += marks[1] self.currentVerseType += marks[1]
return True return True
return False return False

View File

@ -26,11 +26,14 @@
############################################################################### ###############################################################################
import logging import logging
import re import re
import shutil
import os
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib import Receiver, translate from openlp.core.lib import Receiver, translate
from openlp.core.ui.wizard import WizardStrings from openlp.core.ui.wizard import WizardStrings
from openlp.core.utils import AppLocation
from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
@ -58,51 +61,51 @@ class SongImport(QtCore.QObject):
self.manager = manager self.manager = manager
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
if kwargs.has_key(u'filename'): if kwargs.has_key(u'filename'):
self.import_source = kwargs[u'filename'] self.importSource = kwargs[u'filename']
elif kwargs.has_key(u'filenames'): elif kwargs.has_key(u'filenames'):
self.import_source = kwargs[u'filenames'] self.importSource = kwargs[u'filenames']
else: else:
raise KeyError(u'Keyword arguments "filename[s]" not supplied.') raise KeyError(u'Keyword arguments "filename[s]" not supplied.')
log.debug(self.import_source) log.debug(self.importSource)
self.import_wizard = None self.importWizard = None
self.song = None self.song = None
self.stop_import_flag = False self.stopImportFlag = False
self.set_defaults() self.setDefaults()
self.error_log = [] self.errorLog = []
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import) QtCore.SIGNAL(u'openlp_stop_wizard'), self.stopImport)
def set_defaults(self): def setDefaults(self):
""" """
Create defaults for properties - call this before each song Create defaults for properties - call this before each song
if importing many songs at once to ensure a clean beginning if importing many songs at once to ensure a clean beginning
""" """
self.title = u'' self.title = u''
self.song_number = u'' self.songNumber = u''
self.alternate_title = u'' self.alternateTitle = u''
self.copyright = u'' self.copyright = u''
self.comments = u'' self.comments = u''
self.theme_name = u'' self.themeName = u''
self.ccli_number = u'' self.ccliNumber = u''
self.authors = [] self.authors = []
self.topics = [] self.topics = []
self.media_files = [] self.mediaFiles = []
self.song_book_name = u'' self.songBookName = u''
self.song_book_pub = u'' self.songBookPub = u''
self.verse_order_list_generated_useful = False self.verseOrderListGeneratedUseful = False
self.verse_order_list_generated = [] self.verseOrderListGenerated = []
self.verse_order_list = [] self.verseOrderList = []
self.verses = [] self.verses = []
self.verse_counts = {} self.verseCounts = {}
self.copyright_string = unicode(translate( self.copyrightString = unicode(translate(
'SongsPlugin.SongImport', 'copyright')) 'SongsPlugin.SongImport', 'copyright'))
def log_error(self, filepath, reason=SongStrings.SongIncomplete): def logError(self, filepath, reason=SongStrings.SongIncomplete):
""" """
This should be called, when a song could not be imported. This should be called, when a song could not be imported.
``filepath`` ``filepath``
This should be the file path if ``self.import_source`` is a list This should be the file path if ``self.importSource`` is a list
with different files. If it is not a list, but a single file (for with different files. If it is not a list, but a single file (for
instance a database), then this should be the song's title. instance a database), then this should be the song's title.
@ -110,30 +113,30 @@ class SongImport(QtCore.QObject):
The reason, why the import failed. The string should be as The reason, why the import failed. The string should be as
informative as possible. informative as possible.
""" """
self.set_defaults() self.setDefaults()
if self.import_wizard is None: if self.importWizard is None:
return return
if self.import_wizard.errorReportTextEdit.isHidden(): if self.importWizard.errorReportTextEdit.isHidden():
self.import_wizard.errorReportTextEdit.setText( self.importWizard.errorReportTextEdit.setText(
translate('SongsPlugin.SongImport', translate('SongsPlugin.SongImport',
'The following songs could not be imported:')) 'The following songs could not be imported:'))
self.import_wizard.errorReportTextEdit.setVisible(True) self.importWizard.errorReportTextEdit.setVisible(True)
self.import_wizard.errorCopyToButton.setVisible(True) self.importWizard.errorCopyToButton.setVisible(True)
self.import_wizard.errorSaveToButton.setVisible(True) self.importWizard.errorSaveToButton.setVisible(True)
self.import_wizard.errorReportTextEdit.append( self.importWizard.errorReportTextEdit.append(
u'- %s (%s)' % (filepath, reason)) u'- %s (%s)' % (filepath, reason))
def stop_import(self): def stopImport(self):
""" """
Sets the flag for importers to stop their import Sets the flag for importers to stop their import
""" """
log.debug(u'Stopping songs import') log.debug(u'Stopping songs import')
self.stop_import_flag = True self.stopImportFlag = True
def register(self, import_wizard): def register(self, import_wizard):
self.import_wizard = import_wizard self.importWizard = import_wizard
def tidy_text(self, text): def tidyText(self, text):
""" """
Get rid of some dodgy unicode and formatting characters we're not Get rid of some dodgy unicode and formatting characters we're not
interested in. Some can be converted to ascii. interested in. Some can be converted to ascii.
@ -151,34 +154,34 @@ class SongImport(QtCore.QObject):
text = re.sub(r' ?(\n{5}|\f)+ ?', u'\f', text) text = re.sub(r' ?(\n{5}|\f)+ ?', u'\f', text)
return text return text
def process_song_text(self, text): def processSongText(self, text):
verse_texts = text.split(u'\n\n') verse_texts = text.split(u'\n\n')
for verse_text in verse_texts: for verse_text in verse_texts:
if verse_text.strip() != u'': if verse_text.strip() != u'':
self.process_verse_text(verse_text.strip()) self.processVerseText(verse_text.strip())
def process_verse_text(self, text): def processVerseText(self, text):
lines = text.split(u'\n') lines = text.split(u'\n')
if text.lower().find(self.copyright_string) >= 0 \ if text.lower().find(self.copyrightString) >= 0 \
or text.find(unicode(SongStrings.CopyrightSymbol)) >= 0: or text.find(unicode(SongStrings.CopyrightSymbol)) >= 0:
copyright_found = False copyright_found = False
for line in lines: for line in lines:
if (copyright_found or if (copyright_found or
line.lower().find(self.copyright_string) >= 0 or line.lower().find(self.copyrightString) >= 0 or
line.find(unicode(SongStrings.CopyrightSymbol)) >= 0): line.find(unicode(SongStrings.CopyrightSymbol)) >= 0):
copyright_found = True copyright_found = True
self.add_copyright(line) self.addCopyright(line)
else: else:
self.parse_author(line) self.parseAuthor(line)
return return
if len(lines) == 1: if len(lines) == 1:
self.parse_author(lines[0]) self.parseAuthor(lines[0])
return return
if not self.title: if not self.title:
self.title = lines[0] self.title = lines[0]
self.add_verse(text) self.addVerse(text)
def add_copyright(self, copyright): def addCopyright(self, copyright):
""" """
Build the copyright field Build the copyright field
""" """
@ -188,7 +191,7 @@ class SongImport(QtCore.QObject):
self.copyright += ' ' self.copyright += ' '
self.copyright += copyright self.copyright += copyright
def parse_author(self, text): def parseAuthor(self, text):
""" """
Add the author. OpenLP stores them individually so split by 'and', '&' Add the author. OpenLP stores them individually so split by 'and', '&'
and comma. However need to check for 'Mr and Mrs Smith' and turn it to and comma. However need to check for 'Mr and Mrs Smith' and turn it to
@ -204,9 +207,9 @@ class SongImport(QtCore.QObject):
if author2.endswith(u'.'): if author2.endswith(u'.'):
author2 = author2[:-1] author2 = author2[:-1]
if author2: if author2:
self.add_author(author2) self.addAuthor(author2)
def add_author(self, author): def addAuthor(self, author):
""" """
Add an author to the list Add an author to the list
""" """
@ -214,15 +217,15 @@ class SongImport(QtCore.QObject):
return return
self.authors.append(author) self.authors.append(author)
def add_media_file(self, filename): def addMediaFile(self, filename, weight=0):
""" """
Add a media file to the list Add a media file to the list
""" """
if filename in self.media_files: if filename in map(lambda x: x[0], self.mediaFiles):
return return
self.media_files.append(filename) self.mediaFiles.append((filename, weight))
def add_verse(self, verse_text, verse_def=u'v', lang=None): def addVerse(self, verse_text, verse_def=u'v', lang=None):
""" """
Add a verse. This is the whole verse, lines split by \\n. It will also Add a verse. This is the whole verse, lines split by \\n. It will also
attempt to detect duplicates. In this case it will just add to the verse attempt to detect duplicates. In this case it will just add to the verse
@ -241,29 +244,29 @@ class SongImport(QtCore.QObject):
""" """
for (old_verse_def, old_verse, old_lang) in self.verses: for (old_verse_def, old_verse, old_lang) in self.verses:
if old_verse.strip() == verse_text.strip(): if old_verse.strip() == verse_text.strip():
self.verse_order_list_generated.append(old_verse_def) self.verseOrderListGenerated.append(old_verse_def)
self.verse_order_list_generated_useful = True self.verseOrderListGeneratedUseful = True
return return
if verse_def[0] in self.verse_counts: if verse_def[0] in self.verseCounts:
self.verse_counts[verse_def[0]] += 1 self.verseCounts[verse_def[0]] += 1
else: else:
self.verse_counts[verse_def[0]] = 1 self.verseCounts[verse_def[0]] = 1
if len(verse_def) == 1: if len(verse_def) == 1:
verse_def += unicode(self.verse_counts[verse_def[0]]) verse_def += unicode(self.verseCounts[verse_def[0]])
elif int(verse_def[1:]) > self.verse_counts[verse_def[0]]: elif int(verse_def[1:]) > self.verseCounts[verse_def[0]]:
self.verse_counts[verse_def[0]] = int(verse_def[1:]) self.verseCounts[verse_def[0]] = int(verse_def[1:])
self.verses.append([verse_def, verse_text.rstrip(), lang]) self.verses.append([verse_def, verse_text.rstrip(), lang])
self.verse_order_list_generated.append(verse_def) self.verseOrderListGenerated.append(verse_def)
def repeat_verse(self): def repeatVerse(self):
""" """
Repeat the previous verse in the verse order Repeat the previous verse in the verse order
""" """
self.verse_order_list_generated.append( self.verseOrderListGenerated.append(
self.verse_order_list_generated[-1]) self.verseOrderListGenerated[-1])
self.verse_order_list_generated_useful = True self.verseOrderListGeneratedUseful = True
def check_complete(self): def checkComplete(self):
""" """
Check the mandatory fields are entered (i.e. title and a verse) Check the mandatory fields are entered (i.e. title and a verse)
Author not checked here, if no author then "Author unknown" is Author not checked here, if no author then "Author unknown" is
@ -278,21 +281,21 @@ class SongImport(QtCore.QObject):
""" """
All fields have been set to this song. Write the song to disk. All fields have been set to this song. Write the song to disk.
""" """
if not self.check_complete(): if not self.checkComplete():
self.set_defaults() self.setDefaults()
return False return False
log.info(u'committing song %s to database', self.title) log.info(u'committing song %s to database', self.title)
song = Song() song = Song()
song.title = self.title song.title = self.title
if self.import_wizard is not None: if self.importWizard is not None:
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
WizardStrings.ImportingType % song.title) WizardStrings.ImportingType % song.title)
song.alternate_title = self.alternate_title song.alternate_title = self.alternateTitle
# Values will be set when cleaning the song. # Values will be set when cleaning the song.
song.search_title = u'' song.search_title = u''
song.search_lyrics = u'' song.search_lyrics = u''
song.verse_order = u'' song.verse_order = u''
song.song_number = self.song_number song.song_number = self.songNumber
verses_changed_to_other = {} verses_changed_to_other = {}
sxml = SongXML() sxml = SongXML()
other_count = 1 other_count = 1
@ -310,18 +313,18 @@ class SongImport(QtCore.QObject):
verse_def = new_verse_def verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang) sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.lyrics = unicode(sxml.extract_xml(), u'utf-8') song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
if not len(self.verse_order_list) and \ if not len(self.verseOrderList) and \
self.verse_order_list_generated_useful: self.verseOrderListGeneratedUseful:
self.verse_order_list = self.verse_order_list_generated self.verseOrderList = self.verseOrderListGenerated
for i, current_verse_def in enumerate(self.verse_order_list): for i, current_verse_def in enumerate(self.verseOrderList):
if verses_changed_to_other.has_key(current_verse_def): if verses_changed_to_other.has_key(current_verse_def):
self.verse_order_list[i] = \ self.verseOrderList[i] = \
verses_changed_to_other[current_verse_def] verses_changed_to_other[current_verse_def]
song.verse_order = u' '.join(self.verse_order_list) song.verse_order = u' '.join(self.verseOrderList)
song.copyright = self.copyright song.copyright = self.copyright
song.comments = self.comments song.comments = self.comments
song.theme_name = self.theme_name song.theme_name = self.themeName
song.ccli_number = self.ccli_number song.ccli_number = self.ccliNumber
for authortext in self.authors: for authortext in self.authors:
author = self.manager.get_object_filtered(Author, author = self.manager.get_object_filtered(Author,
Author.display_name == authortext) Author.display_name == authortext)
@ -330,17 +333,12 @@ class SongImport(QtCore.QObject):
last_name=authortext.split(u' ')[-1], last_name=authortext.split(u' ')[-1],
first_name=u' '.join(authortext.split(u' ')[:-1])) first_name=u' '.join(authortext.split(u' ')[:-1]))
song.authors.append(author) song.authors.append(author)
for filename in self.media_files: if self.songBookName:
media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == filename)
if not media_file:
song.media_files.append(MediaFile.populate(file_name=filename))
if self.song_book_name:
song_book = self.manager.get_object_filtered(Book, song_book = self.manager.get_object_filtered(Book,
Book.name == self.song_book_name) Book.name == self.songBookName)
if song_book is None: if song_book is None:
song_book = Book.populate(name=self.song_book_name, song_book = Book.populate(name=self.songBookName,
publisher=self.song_book_pub) publisher=self.songBookPub)
song.book = song_book song.book = song_book
for topictext in self.topics: for topictext in self.topics:
if not topictext: if not topictext:
@ -350,38 +348,42 @@ class SongImport(QtCore.QObject):
if topic is None: if topic is None:
topic = Topic.populate(name=topictext) topic = Topic.populate(name=topictext)
song.topics.append(topic) song.topics.append(topic)
# We need to save the song now, before adding the media files, so that
# we know where to save the media files to.
clean_song(self.manager, song) clean_song(self.manager, song)
self.manager.save_object(song) self.manager.save_object(song)
self.set_defaults() # Now loop through the media files, copy them to the correct location,
# and save the song again.
for filename, weight in self.mediaFiles:
media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == filename)
if not media_file:
if os.path.dirname(filename):
filename = self.copyMediaFile(song.id, filename)
song.media_files.append(
MediaFile.populate(file_name=filename, weight=weight)
)
self.manager.save_object(song)
self.setDefaults()
return True return True
def print_song(self): def copyMediaFile(self, song_id, filename):
""" """
For debugging This method copies the media file to the correct location and returns
the new file location.
``filename``
The file to copy.
""" """
print u'========================================' \ if not hasattr(self, u'save_path'):
+ u'========================================' self.save_path = os.path.join(
print u'TITLE: ' + self.title AppLocation.get_section_data_path(
print u'ALT TITLE: ' + self.alternate_title self.importWizard.plugin.name),
for (verse_def, verse_text, lang) in self.verses: 'audio', str(song_id))
print u'VERSE ' + verse_def + u': ' + verse_text if not os.path.exists(self.save_path):
print u'ORDER: ' + u' '.join(self.verse_order_list) os.makedirs(self.save_path)
print u'GENERATED ORDER: ' + u' '.join(self.verse_order_list_generated) if not filename.startswith(self.save_path):
for author in self.authors: oldfile, filename = filename, os.path.join(self.save_path,
print u'AUTHOR: ' + author os.path.split(filename)[1])
if self.copyright: shutil.copyfile(oldfile, filename)
print u'COPYRIGHT: ' + self.copyright return filename
if self.song_book_name:
print u'BOOK: ' + self.song_book_name
if self.song_book_pub:
print u'BOOK PUBLISHER: ' + self.song_book_pub
if self.song_number:
print u'NUMBER: ' + self.song_number
for topictext in self.topics:
print u'TOPIC: ' + topictext
if self.comments:
print u'COMMENTS: ' + self.comments
if self.theme_name:
print u'THEME: ' + self.theme_name
if self.ccli_number:
print u'CCLI: ' + self.ccli_number

View File

@ -95,118 +95,120 @@ class SongShowPlusImport(SongImport):
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
""" """
Receive a single file or a list of files to import. Receive a single file or a list of files to import.
""" """
if not isinstance(self.import_source, list): if not isinstance(self.importSource, list):
return return
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.import_source: for file in self.importSource:
if self.stopImportFlag:
return
self.sspVerseOrderList = [] self.sspVerseOrderList = []
otherCount = 0 other_count = 0
otherList = {} other_list = {}
file_name = os.path.split(file)[1] file_name = os.path.split(file)[1]
self.import_wizard.incrementProgressBar( self.importWizard.incrementProgressBar(
WizardStrings.ImportingType % file_name, 0) WizardStrings.ImportingType % file_name, 0)
songData = open(file, 'rb') song_data = open(file, 'rb')
while True: while True:
blockKey, = struct.unpack("I", songData.read(4)) block_key, = struct.unpack("I", song_data.read(4))
# The file ends with 4 NUL's # The file ends with 4 NUL's
if blockKey == 0: if block_key == 0:
break break
nextBlockStarts, = struct.unpack("I", songData.read(4)) next_block_starts, = struct.unpack("I", song_data.read(4))
nextBlockStarts += songData.tell() next_block_starts += song_data.tell()
if blockKey in (VERSE, CHORUS, BRIDGE): if block_key in (VERSE, CHORUS, BRIDGE):
null, verseNo, = struct.unpack("BB", songData.read(2)) null, verse_no, = struct.unpack("BB", song_data.read(2))
elif blockKey == CUSTOM_VERSE: elif block_key == CUSTOM_VERSE:
null, verseNameLength, = struct.unpack("BB", null, verse_name_length, = struct.unpack("BB",
songData.read(2)) song_data.read(2))
verseName = songData.read(verseNameLength) verse_name = song_data.read(verse_name_length)
lengthDescriptorSize, = struct.unpack("B", songData.read(1)) length_descriptor_size, = struct.unpack("B", song_data.read(1))
log.debug(lengthDescriptorSize) log.debug(length_descriptor_size)
# Detect if/how long the length descriptor is # Detect if/how long the length descriptor is
if lengthDescriptorSize == 12 or lengthDescriptorSize == 20: if length_descriptor_size == 12 or length_descriptor_size == 20:
lengthDescriptor, = struct.unpack("I", songData.read(4)) length_descriptor, = struct.unpack("I", song_data.read(4))
elif lengthDescriptorSize == 2: elif length_descriptor_size == 2:
lengthDescriptor = 1 length_descriptor = 1
elif lengthDescriptorSize == 9: elif length_descriptor_size == 9:
lengthDescriptor = 0 length_descriptor = 0
else: else:
lengthDescriptor, = struct.unpack("B", songData.read(1)) length_descriptor, = struct.unpack("B", song_data.read(1))
log.debug(lengthDescriptorSize) log.debug(length_descriptor_size)
data = songData.read(lengthDescriptor) data = song_data.read(length_descriptor)
if blockKey == TITLE: if block_key == TITLE:
self.title = unicode(data, u'cp1252') self.title = unicode(data, u'cp1252')
elif blockKey == AUTHOR: elif block_key == AUTHOR:
authors = data.split(" / ") authors = data.split(" / ")
for author in authors: for author in authors:
if author.find(",") !=-1: if author.find(",") !=-1:
authorParts = author.split(", ") authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0] author = authorParts[1] + " " + authorParts[0]
self.parse_author(unicode(author, u'cp1252')) self.parseAuthor(unicode(author, u'cp1252'))
elif blockKey == COPYRIGHT: elif block_key == COPYRIGHT:
self.add_copyright(unicode(data, u'cp1252')) self.addCopyright(unicode(data, u'cp1252'))
elif blockKey == CCLI_NO: elif block_key == CCLI_NO:
self.ccli_number = int(data) self.ccliNumber = int(data)
elif blockKey == VERSE: elif block_key == VERSE:
self.add_verse(unicode(data, u'cp1252'), self.addVerse(unicode(data, u'cp1252'),
"%s%s" % (VerseType.Tags[VerseType.Verse], verseNo)) "%s%s" % (VerseType.Tags[VerseType.Verse], verse_no))
elif blockKey == CHORUS: elif block_key == CHORUS:
self.add_verse(unicode(data, u'cp1252'), self.addVerse(unicode(data, u'cp1252'),
"%s%s" % (VerseType.Tags[VerseType.Chorus], verseNo)) "%s%s" % (VerseType.Tags[VerseType.Chorus], verse_no))
elif blockKey == BRIDGE: elif block_key == BRIDGE:
self.add_verse(unicode(data, u'cp1252'), self.addVerse(unicode(data, u'cp1252'),
"%s%s" % (VerseType.Tags[VerseType.Bridge], verseNo)) "%s%s" % (VerseType.Tags[VerseType.Bridge], verse_no))
elif blockKey == TOPIC: elif block_key == TOPIC:
self.topics.append(unicode(data, u'cp1252')) self.topics.append(unicode(data, u'cp1252'))
elif blockKey == COMMENTS: elif block_key == COMMENTS:
self.comments = unicode(data, u'cp1252') self.comments = unicode(data, u'cp1252')
elif blockKey == VERSE_ORDER: elif block_key == VERSE_ORDER:
verseTag = self.toOpenLPVerseTag(data, True) verse_tag = self.toOpenLPVerseTag(data, True)
if verseTag: if verse_tag:
if not isinstance(verseTag, unicode): if not isinstance(verse_tag, unicode):
verseTag = unicode(verseTag, u'cp1252') verse_tag = unicode(verse_tag, u'cp1252')
self.sspVerseOrderList.append(verseTag) self.sspVerseOrderList.append(verse_tag)
elif blockKey == SONG_BOOK: elif block_key == SONG_BOOK:
self.song_book_name = unicode(data, u'cp1252') self.songBookName = unicode(data, u'cp1252')
elif blockKey == SONG_NUMBER: elif block_key == SONG_NUMBER:
self.song_number = ord(data) self.songNumber = ord(data)
elif blockKey == CUSTOM_VERSE: elif block_key == CUSTOM_VERSE:
verseTag = self.toOpenLPVerseTag(verseName) verse_tag = self.toOpenLPVerseTag(verse_name)
self.add_verse(unicode(data, u'cp1252'), verseTag) self.addVerse(unicode(data, u'cp1252'), verse_tag)
else: else:
log.debug("Unrecognised blockKey: %s, data: %s" log.debug("Unrecognised blockKey: %s, data: %s"
% (blockKey, data)) % (block_key, data))
songData.seek(nextBlockStarts) song_data.seek(next_block_starts)
self.verse_order_list = self.sspVerseOrderList self.verseOrderList = self.sspVerseOrderList
songData.close() song_data.close()
if not self.finish(): if not self.finish():
self.log_error(file) self.logError(file)
def toOpenLPVerseTag(self, verseName, ignoreUnique=False): def toOpenLPVerseTag(self, verse_name, ignore_unique=False):
if verseName.find(" ") != -1: if verse_name.find(" ") != -1:
verseParts = verseName.split(" ") verse_parts = verse_name.split(" ")
verseType = verseParts[0] verse_type = verse_parts[0]
verseNumber = verseParts[1] verse_number = verse_parts[1]
else: else:
verseType = verseName verse_type = verse_name
verseNumber = "1" verse_number = "1"
verseType = verseType.lower() verse_type = verse_type.lower()
if verseType == "verse": if verse_type == "verse":
verseTag = VerseType.Tags[VerseType.Verse] verse_tag = VerseType.Tags[VerseType.Verse]
elif verseType == "chorus": elif verse_type == "chorus":
verseTag = VerseType.Tags[VerseType.Chorus] verse_tag = VerseType.Tags[VerseType.Chorus]
elif verseType == "bridge": elif verse_type == "bridge":
verseTag = VerseType.Tags[VerseType.Bridge] verse_tag = VerseType.Tags[VerseType.Bridge]
elif verseType == "pre-chorus": elif verse_type == "pre-chorus":
verseTag = VerseType.Tags[VerseType.PreChorus] verse_tag = VerseType.Tags[VerseType.PreChorus]
else: else:
if not self.otherList.has_key(verseName): if not self.otherList.has_key(verse_name):
if ignoreUnique: if ignore_unique:
return None return None
self.otherCount = self.otherCount + 1 self.otherCount = self.otherCount + 1
self.otherList[verseName] = str(self.otherCount) self.otherList[verse_name] = str(self.otherCount)
verseTag = VerseType.Tags[VerseType.Other] verse_tag = VerseType.Tags[VerseType.Other]
verseNumber = self.otherList[verseName] verse_number = self.otherList[verse_name]
return verseTag + verseNumber return verse_tag + verse_number

View File

@ -0,0 +1,88 @@
# -*- 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 #
###############################################################################
"""
The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin
"""
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.sql.expression import func
from migrate import changeset
from migrate.changeset.constraint import ForeignKeyConstraint
__version__ = 2
def upgrade_setup(metadata):
"""
Set up the latest revision all tables, with reflection, needed for the
upgrade process. If you want to drop a table, you need to remove it from
here, and add it to your upgrade function.
"""
tables = {
u'authors': Table(u'authors', metadata, autoload=True),
u'media_files': Table(u'media_files', metadata, autoload=True),
u'song_books': Table(u'song_books', metadata, autoload=True),
u'songs': Table(u'songs', metadata, autoload=True),
u'topics': Table(u'topics', metadata, autoload=True),
u'authors_songs': Table(u'authors_songs', metadata, autoload=True),
u'songs_topics': Table(u'songs_topics', metadata, autoload=True)
}
return tables
def upgrade_1(session, metadata, tables):
"""
Version 1 upgrade.
This upgrade removes the many-to-many relationship between songs and
media_files and replaces it with a one-to-many, which is far more
representative of the real relationship between the two entities.
In order to facilitate this one-to-many relationship, a song_id column is
added to the media_files table, and a weight column so that the media
files can be ordered.
"""
Table(u'media_files_songs', metadata, autoload=True).drop(checkfirst=True)
Column(u'song_id', types.Integer(), default=None)\
.create(table=tables[u'media_files'], populate_default=True)
Column(u'weight', types.Integer(), default=0)\
.create(table=tables[u'media_files'], populate_default=True)
if metadata.bind.url.get_dialect().name != 'sqlite':
# SQLite doesn't support ALTER TABLE ADD CONSTRAINT
ForeignKeyConstraint([u'song_id'], [u'songs.id'],
table=tables[u'media_files']).create()
def upgrade_2(session, metadata, tables):
"""
Version 2 upgrade.
This upgrade adds a create_date and last_modified date to the songs table
"""
Column(u'create_date', types.DateTime(), default=func.now())\
.create(table=tables[u'songs'], populate_default=True)
Column(u'last_modified', types.DateTime(), default=func.now())\
.create(table=tables[u'songs'], populate_default=True)

View File

@ -98,56 +98,58 @@ class WowImport(SongImport):
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
def do_import(self): def doImport(self):
""" """
Receive a single file or a list of files to import. Receive a single file or a list of files to import.
""" """
if isinstance(self.import_source, list): if isinstance(self.importSource, list):
self.import_wizard.progressBar.setMaximum(len(self.import_source)) self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.import_source: for file in self.importSource:
if self.stopImportFlag:
return
file_name = os.path.split(file)[1] file_name = os.path.split(file)[1]
# Get the song title # Get the song title
self.title = file_name.rpartition(u'.')[0] self.title = file_name.rpartition(u'.')[0]
songData = open(file, 'rb') song_data = open(file, 'rb')
if songData.read(19) != u'WoW File\nSong Words': if song_data.read(19) != u'WoW File\nSong Words':
self.log_error(file) self.logError(file)
continue continue
# Seek to byte which stores number of blocks in the song # Seek to byte which stores number of blocks in the song
songData.seek(56) song_data.seek(56)
no_of_blocks = ord(songData.read(1)) no_of_blocks = ord(song_data.read(1))
# Seek to the beging of the first block # Seek to the beging of the first block
songData.seek(82) song_data.seek(82)
for block in range(no_of_blocks): for block in range(no_of_blocks):
self.lines_to_read = ord(songData.read(1)) self.linesToRead = ord(song_data.read(1))
# Skip 3 nulls to the beginnig of the 1st line # Skip 3 nulls to the beginnig of the 1st line
songData.seek(3, os.SEEK_CUR) song_data.seek(3, os.SEEK_CUR)
block_text = u'' block_text = u''
while self.lines_to_read: while self.linesToRead:
self.line_text = unicode( self.lineText = unicode(
songData.read(ord(songData.read(1))), u'cp1252') song_data.read(ord(song_data.read(1))), u'cp1252')
songData.seek(1, os.SEEK_CUR) song_data.seek(1, os.SEEK_CUR)
if block_text: if block_text:
block_text += u'\n' block_text += u'\n'
block_text += self.line_text block_text += self.lineText
self.lines_to_read -= 1 self.linesToRead -= 1
block_type = BLOCK_TYPES[ord(songData.read(1))] block_type = BLOCK_TYPES[ord(song_data.read(1))]
# Skip 3 nulls at the end of the block # Skip 3 nulls at the end of the block
songData.seek(3, os.SEEK_CUR) song_data.seek(3, os.SEEK_CUR)
# Blocks are seperated by 2 bytes, skip them, but not if # Blocks are seperated by 2 bytes, skip them, but not if
# this is the last block! # this is the last block!
if block + 1 < no_of_blocks: if block + 1 < no_of_blocks:
songData.seek(2, os.SEEK_CUR) song_data.seek(2, os.SEEK_CUR)
self.add_verse(block_text, block_type) self.addVerse(block_text, block_type)
# Now to extract the author # Now to extract the author
author_length = ord(songData.read(1)) author_length = ord(song_data.read(1))
if author_length: if author_length:
self.parse_author( self.parseAuthor(
unicode(songData.read(author_length), u'cp1252')) unicode(song_data.read(author_length), u'cp1252'))
# Finally the copyright # Finally the copyright
copyright_length = ord(songData.read(1)) copyright_length = ord(song_data.read(1))
if copyright_length: if copyright_length:
self.add_copyright(unicode( self.addCopyright(unicode(
songData.read(copyright_length), u'cp1252')) song_data.read(copyright_length), u'cp1252'))
songData.close() song_data.close()
if not self.finish(): if not self.finish():
self.log_error(file) self.logError(file)

View File

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

View File

@ -36,7 +36,8 @@ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
from openlp.core.lib.db import Manager from openlp.core.lib.db import Manager
from openlp.core.lib.ui import UiStrings, base_action, icon_action from openlp.core.lib.ui import UiStrings, base_action, icon_action
from openlp.core.utils.actions import ActionList from openlp.core.utils.actions import ActionList
from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab from openlp.plugins.songs.lib import clean_song, upgrade, SongMediaItem, \
SongsTab
from openlp.plugins.songs.lib.db import init_schema, Song from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
@ -58,8 +59,8 @@ class SongsPlugin(Plugin):
Create and set up the Songs plugin. Create and set up the Songs plugin.
""" """
Plugin.__init__(self, u'songs', plugin_helpers, SongMediaItem, SongsTab) Plugin.__init__(self, u'songs', plugin_helpers, SongMediaItem, SongsTab)
self.manager = Manager(u'songs', init_schema, upgrade_mod=upgrade)
self.weight = -10 self.weight = -10
self.manager = Manager(u'songs', init_schema)
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)
@ -195,7 +196,7 @@ class SongsPlugin(Plugin):
def importSongs(self, format, **kwargs): def importSongs(self, format, **kwargs):
class_ = SongFormat.get_class(format) class_ = SongFormat.get_class(format)
importer = class_(self.manager, **kwargs) importer = class_(self.manager, **kwargs)
importer.register(self.mediaItem.import_wizard) importer.register(self.mediaItem.importWizard)
return importer return importer
def setPluginTextStrings(self): def setPluginTextStrings(self):
@ -251,7 +252,7 @@ class SongsPlugin(Plugin):
progress.setValue(idx) progress.setValue(idx)
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
importer = OpenLPSongImport(self.manager, filename=db) importer = OpenLPSongImport(self.manager, filename=db)
importer.do_import() importer.doImport()
progress.setValue(len(song_dbs)) progress.setValue(len(song_dbs))
self.mediaItem.onSearchTextButtonClick() self.mediaItem.onSearchTextButtonClick()

View File

@ -117,9 +117,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
try: try:
fileHandle = open(outname, u'w') fileHandle = open(outname, u'w')
for instance in usage: for instance in usage:
record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % ( record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
instance.usagedate, instance.usagetime, instance.title, u'\"%s\",\"%s\"\n' % ( instance.usagedate,
instance.copyright, instance.ccl_number, instance.authors) instance.usagetime, instance.title, instance.copyright,
instance.ccl_number, instance.authors,
instance.plugin_name, instance.source)
fileHandle.write(record.encode(u'utf-8')) fileHandle.write(record.encode(u'utf-8'))
Receiver.send_message(u'openlp_information_message', { Receiver.send_message(u'openlp_information_message', {
u'title': translate('SongUsagePlugin.SongUsageDetailForm', u'title': translate('SongUsagePlugin.SongUsageDetailForm',

View File

@ -56,7 +56,9 @@ def init_schema(url):
Column(u'title', types.Unicode(255), nullable=False), Column(u'title', types.Unicode(255), nullable=False),
Column(u'authors', types.Unicode(255), nullable=False), Column(u'authors', types.Unicode(255), nullable=False),
Column(u'copyright', types.Unicode(255)), Column(u'copyright', types.Unicode(255)),
Column(u'ccl_number', types.Unicode(65)) Column(u'ccl_number', types.Unicode(65)),
Column(u'plugin_name', types.Unicode(20)),
Column(u'source', types.Unicode(10))
) )
mapper(SongUsageItem, songusage_table) mapper(SongUsageItem, songusage_table)

View File

@ -0,0 +1,58 @@
# -*- 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 #
###############################################################################
"""
The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the SongsUsage plugin
"""
from sqlalchemy import Column, Table, types
from migrate import changeset
__version__ = 1
def upgrade_setup(metadata):
"""
Set up the latest revision all tables, with reflection, needed for the
upgrade process. If you want to drop a table, you need to remove it from
here, and add it to your upgrade function.
"""
tables = {
u'songusage_data': Table(u'songusage_data', metadata, autoload=True)
}
return tables
def upgrade_1(session, metadata, tables):
"""
Version 1 upgrade.
This upgrade adds two new fields to the songusage database
"""
Column(u'plugin_name', types.Unicode(20), default=u'') \
.create(table=tables[u'songusage_data'], populate_default=True)
Column(u'source', types.Unicode(10), default=u'') \
.create(table=tables[u'songusage_data'], populate_default=True)

View File

@ -37,6 +37,7 @@ from openlp.core.lib.ui import base_action, shortcut_action
from openlp.core.utils.actions import ActionList from openlp.core.utils.actions import ActionList
from openlp.plugins.songusage.forms import SongUsageDetailForm, \ from openlp.plugins.songusage.forms import SongUsageDetailForm, \
SongUsageDeleteForm SongUsageDeleteForm
from openlp.plugins.songusage.lib import upgrade
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -46,11 +47,11 @@ class SongUsagePlugin(Plugin):
def __init__(self, plugin_helpers): def __init__(self, plugin_helpers):
Plugin.__init__(self, u'songusage', plugin_helpers) Plugin.__init__(self, u'songusage', plugin_helpers)
self.manager = Manager(u'songusage', init_schema, upgrade_mod=upgrade)
self.weight = -4 self.weight = -4
self.icon = build_icon(u':/plugins/plugin_songusage.png') self.icon = build_icon(u':/plugins/plugin_songusage.png')
self.activeIcon = build_icon(u':/songusage/song_usage_active.png') self.activeIcon = build_icon(u':/songusage/song_usage_active.png')
self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png') self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
self.manager = None
self.songUsageActive = False self.songUsageActive = False
def addToolsMenuItem(self, tools_menu): def addToolsMenuItem(self, tools_menu):
@ -121,7 +122,10 @@ class SongUsagePlugin(Plugin):
Plugin.initialise(self) Plugin.initialise(self)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_live_started'), QtCore.SIGNAL(u'slidecontroller_live_started'),
self.onReceiveSongUsage) self.displaySongUsage)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'print_service_started'),
self.printSongUsage)
self.songUsageActive = QtCore.QSettings().value( self.songUsageActive = QtCore.QSettings().value(
self.settingsSection + u'/active', self.settingsSection + u'/active',
QtCore.QVariant(False)).toBool() QtCore.QVariant(False)).toBool()
@ -134,8 +138,6 @@ class SongUsagePlugin(Plugin):
translate('SongUsagePlugin', 'Song Usage')) translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageReport, action_list.add_action(self.songUsageReport,
translate('SongUsagePlugin', 'Song Usage')) translate('SongUsagePlugin', 'Song Usage'))
if self.manager is None:
self.manager = Manager(u'songusage', init_schema)
self.songUsageDeleteForm = SongUsageDeleteForm(self.manager, self.songUsageDeleteForm = SongUsageDeleteForm(self.manager,
self.formparent) self.formparent)
self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent) self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent)
@ -194,10 +196,21 @@ class SongUsagePlugin(Plugin):
self.songUsageStatus.blockSignals(False) self.songUsageStatus.blockSignals(False)
def onReceiveSongUsage(self, item): def displaySongUsage(self, item):
""" """
Song Usage for live song from SlideController Song Usage for which has been displayed
""" """
self._add_song_usage(unicode(translate('SongUsagePlugin',
'display')), item)
def printSongUsage(self, item):
"""
Song Usage for which has been printed
"""
self._add_song_usage(unicode(translate('SongUsagePlugin',
'printed')), item)
def _add_song_usage(self, source, item):
audit = item[0].audit audit = item[0].audit
if self.songUsageActive and audit: if self.songUsageActive and audit:
song_usage_item = SongUsageItem() song_usage_item = SongUsageItem()
@ -207,6 +220,8 @@ class SongUsagePlugin(Plugin):
song_usage_item.copyright = audit[2] song_usage_item.copyright = audit[2]
song_usage_item.ccl_number = audit[3] song_usage_item.ccl_number = audit[3]
song_usage_item.authors = u' '.join(audit[1]) song_usage_item.authors = u' '.join(audit[1])
song_usage_item.plugin_name = item[0].name
song_usage_item.source = source
self.manager.save_object(song_usage_item) self.manager.save_object(song_usage_item)
def onSongUsageDelete(self): def onSongUsageDelete(self):

View File

@ -11,7 +11,7 @@ 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-sqlalchemy, python-chardet, python-beautifulsoup,
python-lxml, python-sqlite, python-enchant 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,

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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -46,14 +46,14 @@ VERS = {
'sqlalchemy': '0.5', 'sqlalchemy': '0.5',
# pyenchant 1.6 required on Windows # pyenchant 1.6 required on Windows
'enchant': '1.6' if is_win else '1.3' 'enchant': '1.6' if is_win else '1.3'
} }
# pywin32 # pywin32
WIN32_MODULES = [ WIN32_MODULES = [
'win32com', 'win32com',
'win32ui', 'win32ui',
'pywintypes', 'pywintypes',
] ]
MODULES = [ MODULES = [
'PyQt4', 'PyQt4',
@ -72,13 +72,15 @@ MODULES = [
'enchant', 'enchant',
'BeautifulSoup', 'BeautifulSoup',
'mako', 'mako',
] 'migrate',
]
OPTIONAL_MODULES = [ OPTIONAL_MODULES = [
('sqlite', ' (SQLite 2 support)'), ('sqlite', ' (SQLite 2 support)'),
('MySQLdb', ' (MySQL support)'), ('MySQLdb', ' (MySQL support)'),
('psycopg2', ' (PostgreSQL support)'), ('psycopg2', ' (PostgreSQL support)'),
('pytest', ' (testing framework)'),
] ]
w = sys.stdout.write w = sys.stdout.write

View File

@ -186,25 +186,6 @@ def update_export_at_pootle(source_filename):
page = urllib.urlopen(REVIEW_URL) page = urllib.urlopen(REVIEW_URL)
page.close() page.close()
def download_file(source_filename, dest_filename):
"""
Download a file and save it to disk.
``source_filename``
The file to download.
``dest_filename``
The new local file name.
"""
print_verbose(u'Downloading from: %s' % (SERVER_URL + source_filename))
page = urllib.urlopen(SERVER_URL + source_filename)
content = page.read().decode('utf8')
page.close()
file = open(dest_filename, u'w')
file.write(content.encode('utf8'))
file.close()
def download_translations(): def download_translations():
""" """
This method downloads the translation files from the Pootle server. This method downloads the translation files from the Pootle server.
@ -219,7 +200,7 @@ def download_translations():
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n',
language_file) language_file)
print_verbose(u'Get Translation File: %s' % filename) print_verbose(u'Get Translation File: %s' % filename)
download_file(language_file, filename) urllib.urlretrieve(SERVER_URL + language_file, filename)
print_quiet(u' Done.') print_quiet(u' Done.')
def prepare_project(): def prepare_project():
@ -304,7 +285,7 @@ def create_translation(language):
if not language.endswith(u'.ts'): if not language.endswith(u'.ts'):
language += u'.ts' language += u'.ts'
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', language) filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', language)
download_file(u'en.ts', filename) urllib.urlretrieve(SERVER_URL + u'en.ts', filename)
print_quiet(u' ** Please Note **') print_quiet(u' ** Please Note **')
print_quiet(u' In order to get this file into OpenLP and onto the ' print_quiet(u' In order to get this file into OpenLP and onto the '
u'Pootle translation server you will need to subscribe to the ' u'Pootle translation server you will need to subscribe to the '

45
testing/conftest.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
# -*- 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 Millar, 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 #
###############################################################################
"""
Configuration file for pytest framework.
"""
from openlp.core import main as openlp_main
# Test function argument to make openlp gui instance persistent for all tests.
# All test cases have to access the same instance. To allow create multiple
# instances it would be necessary use diffrent configuraion and data files.
# Created instance will use your OpenLP settings.
def pytest_funcarg__openlpapp(request):
def setup():
return openlp_main(['--testing'])
def teardown(app):
pass
return request.cached_setup(setup=setup, teardown=teardown, scope='session')

59
testing/run.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- 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 #
###############################################################################
"""
This script is used to run set of automated tests of OpenLP. To start tests,
simply run this script::
@:~$ ./run.py
"""
import os.path
import sys
TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
SRC_PATH = os.path.join(TESTS_PATH, '..')
PYTEST_OPTIONS = [TESTS_PATH]
# Extend python PATH with openlp source
sys.path.insert(0, SRC_PATH)
# Python testing framework
# http://pytest.org
import pytest
def main():
print 'pytest options:', PYTEST_OPTIONS
pytest.main(PYTEST_OPTIONS)
if __name__ == u'__main__':
main()

36
testing/test_app.py Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env python
# -*- 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 Millar, 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 openlp.core import OpenLP
from openlp.core.ui.mainwindow import MainWindow
def test_start_app(openlpapp):
assert type(openlpapp) == OpenLP
assert type(openlpapp.mainWindow) == MainWindow
assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'