diff --git a/openlp.pyw b/openlp.pyw
index 962109592..64ffb3321 100755
--- a/openlp.pyw
+++ b/openlp.pyw
@@ -25,254 +25,15 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# 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:
# http://support.openlp.org/issues/102
# If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be
# removed.
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__':
"""
diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py
index e19b9a257..896066e73 100644
--- a/openlp/core/__init__.py
+++ b/openlp/core/__init__.py
@@ -24,9 +24,265 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+
+__all__ = ('OpenLP', 'main')
+
+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
+
+log = logging.getLogger()
+
+
"""
The :mod:`core` module provides all core application functions
All the core functions of the OpenLP application including the GUI, settings,
logging and a plugin framework are contained within the openlp.core module.
"""
+
+application_stylesheet = u"""
+QMainWindow::separator
+{
+ 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)
+ logging.addLevelName(15, u'Timer')
+ # 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 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)
+ else:
+ sys.exit(app.run(qt_args))
diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py
index f83e92de7..d9d29efab 100644
--- a/openlp/core/lib/__init__.py
+++ b/openlp/core/lib/__init__.py
@@ -137,7 +137,7 @@ def image_to_byte(image):
# convert to base64 encoding so does not get missed!
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.
diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py
index 41b445cd5..2e5d011cf 100644
--- a/openlp/core/lib/db.py
+++ b/openlp/core/lib/db.py
@@ -31,11 +31,13 @@ import logging
import os
from PyQt4 import QtCore
-from sqlalchemy import create_engine, MetaData
-from sqlalchemy.exc import InvalidRequestError
-from sqlalchemy.orm import scoped_session, sessionmaker
+from sqlalchemy import Table, MetaData, Column, types, create_engine
+from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError
+from sqlalchemy.orm import scoped_session, sessionmaker, mapper
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
log = logging.getLogger(__name__)
@@ -59,6 +61,64 @@ def init_db(url, auto_flush=True, auto_commit=False):
autocommit=auto_commit, bind=engine))
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):
"""
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)
return delete_file(db_file_path)
+
class BaseModel(object):
"""
BaseModel provides a base object with a set of generic functions
@@ -93,12 +154,12 @@ class BaseModel(object):
instance.__setattr__(key, value)
return instance
-
class Manager(object):
"""
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
to the database and the tables if they don't exist.
@@ -109,6 +170,9 @@ class Manager(object):
``init_schema``
The init_schema function for this database
+ ``upgrade_schema``
+ The upgrade_schema function for this database
+
``db_file_name``
The file name to use for this database. Defaults to None resulting
in the plugin_name being used.
@@ -134,7 +198,27 @@ class Manager(object):
unicode(settings.value(u'db hostname').toString()),
unicode(settings.value(u'db database').toString()))
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:
+ 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):
"""
diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py
index 37d1de79c..4d6c90078 100644
--- a/openlp/core/lib/imagemanager.py
+++ b/openlp/core/lib/imagemanager.py
@@ -36,7 +36,7 @@ import Queue
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
log = logging.getLogger(__name__)
@@ -100,12 +100,14 @@ class Image(object):
variables ``image`` and ``image_bytes`` to ``None`` and add the image object
to the queue of images to process.
"""
- def __init__(self, name='', path=''):
+ def __init__(self, name, path, source, background):
self.name = name
self.path = path
self.image = None
self.image_bytes = None
self.priority = Priority.Normal
+ self.source = source
+ self.background = background
class PriorityQueue(Queue.PriorityQueue):
@@ -151,6 +153,8 @@ class ImageManager(QtCore.QObject):
self._cache = {}
self._imageThread = ImageThread(self)
self._conversion_queue = PriorityQueue()
+ QtCore.QObject.connect(Receiver.get_receiver(),
+ QtCore.SIGNAL(u'config_updated'), self.process_updates)
def update_display(self):
"""
@@ -162,12 +166,42 @@ class ImageManager(QtCore.QObject):
self.height = current_screen[u'size'].height()
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
- self._conversion_queue = PriorityQueue()
for key, image in self._cache.iteritems():
- image.priority = Priority.Normal
- image.image = None
- image.image_bytes = None
- self._conversion_queue.put((image.priority, image))
+ self._reset_image(image)
+
+ def update_images(self, image_type, background):
+ """
+ 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.
if not self._imageThread.isRunning():
self._imageThread.start()
@@ -215,13 +249,13 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.remove(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.
"""
log.debug(u'add_image %s:%s' % (name, path))
if not name in self._cache:
- image = Image(name, path)
+ image = Image(name, path, source, background)
self._cache[name] = image
self._conversion_queue.put((image.priority, image))
else:
@@ -247,7 +281,8 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[1]
# Generate the QImage for the image.
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
# more important images first.
if image.priority == Priority.Normal:
diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py
index eed31a689..8c63facb8 100644
--- a/openlp/core/lib/renderer.py
+++ b/openlp/core/lib/renderer.py
@@ -27,7 +27,7 @@
import logging
-from PyQt4 import QtCore, QtWebKit
+from PyQt4 import QtGui, QtCore, QtWebKit
from openlp.core.lib import ServiceItem, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
@@ -166,7 +166,8 @@ class Renderer(object):
# if No file do not update cache
if self.theme_data.background_filename:
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
def generate_preview(self, theme_data, force_page=False):
diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py
index 15c16c551..7be28520c 100644
--- a/openlp/core/lib/serviceitem.py
+++ b/openlp/core/lib/serviceitem.py
@@ -115,6 +115,7 @@ class ServiceItem(object):
self.end_time = 0
self.media_length = 0
self.from_service = False
+ self.image_border = u'#000000'
self._new_item()
def _new_item(self):
@@ -195,7 +196,7 @@ class ServiceItem(object):
self.foot_text = \
u'
'.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.
@@ -205,9 +206,12 @@ class ServiceItem(object):
``title``
A title for the slide in the service item.
"""
+ if background:
+ self.image_border = background
self.service_item_type = ServiceItemType.Image
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()
def add_from_text(self, title, raw_slide, verse_tag=None):
diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py
index c87f9aa2e..3b0a62f5b 100644
--- a/openlp/core/lib/theme.py
+++ b/openlp/core/lib/theme.py
@@ -44,6 +44,7 @@ BLANK_THEME_XML = \
+ #000000
#000000
@@ -282,7 +283,7 @@ class ThemeXML(object):
# Create direction element
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.
@@ -294,6 +295,8 @@ class ThemeXML(object):
self.theme.appendChild(background)
# Create Filename element
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',
bold=u'False', italics=u'False', line_adjustment=0,
@@ -597,7 +600,7 @@ class ThemeXML(object):
self.background_direction)
else:
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.font_main_color,
self.font_main_size,
diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py
index 3e941c051..f4a732fb6 100644
--- a/openlp/core/ui/aboutdialog.py
+++ b/openlp/core/ui/aboutdialog.py
@@ -116,7 +116,7 @@ class Ui_AboutDialog(object):
u'Scott "sguerrieri" Guerrieri',
u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan',
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'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith',
u'Maikel Stuivenberg', u'Frode "frodus" Woldsund']
@@ -125,7 +125,7 @@ class Ui_AboutDialog(object):
packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)',
u'Tim "TRB143" Bentley (Fedora)',
u'Matthias "matthub" Hub (Mac OS X)',
- u'Stevan "StevanP" Pettit (Windows)',
+ u'Stevan "ElderP" Pettit (Windows)',
u'Raoul "superfly" Snyman (Ubuntu)']
translators = {
u'af': [u'Johan "nuvolari" Mynhardt'],
diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py
index 9904868ce..77f2e2f7c 100644
--- a/openlp/core/ui/maindisplay.py
+++ b/openlp/core/ui/maindisplay.py
@@ -228,11 +228,11 @@ class MainDisplay(QtGui.QGraphicsView):
shrinkItem.setVisible(False)
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
"""
- self.imageManager.add_image(name, path)
+ self.imageManager.add_image(name, path, u'image', background)
if hasattr(self, u'serviceItem'):
self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index 4ecf792bc..e77112644 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -30,11 +30,13 @@ import os
import sys
import shutil
from tempfile import gettempdir
+from datetime import datetime
from PyQt4 import QtCore, QtGui
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, \
icon_action, shortcut_action
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
@@ -214,7 +216,7 @@ class Ui_MainWindow(object):
self.mediaManagerDock.isVisible(), UiStrings().View)
self.viewThemeManagerItem = shortcut_action(mainWindow,
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.viewServiceManagerItem = shortcut_action(mainWindow,
u'viewServiceManagerItem', [QtGui.QKeySequence(u'F9')],
@@ -284,6 +286,10 @@ class Ui_MainWindow(object):
self.settingsConfigureItem = icon_action(mainWindow,
u'settingsConfigureItem', u':/system/system_settings.png',
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)
self.aboutItem = shortcut_action(mainWindow, u'aboutItem',
[QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked,
@@ -301,10 +307,10 @@ class Ui_MainWindow(object):
u':/system/system_online_help.png', category=UiStrings().Help)
self.webSiteItem = base_action(
mainWindow, u'webSiteItem', category=UiStrings().Help)
- add_actions(self.fileImportMenu,
- (self.importThemeItem, self.importLanguageItem))
- add_actions(self.fileExportMenu,
- (self.exportThemeItem, self.exportLanguageItem))
+ add_actions(self.fileImportMenu, (self.settingsImportItem, None,
+ self.importThemeItem, self.importLanguageItem))
+ add_actions(self.fileExportMenu, (self.settingsExportItem, None,
+ self.exportThemeItem, self.exportLanguageItem))
add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem,
self.fileSaveItem, self.fileSaveAsItem,
self.recentFilesMenu.menuAction(), None,
@@ -357,6 +363,7 @@ class Ui_MainWindow(object):
self.importLanguageItem.setVisible(False)
self.exportLanguageItem.setVisible(False)
self.setLockPanel(panelLocked)
+ self.settingsImported = False
def retranslateUi(self, mainWindow):
"""
@@ -420,6 +427,15 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Configure &Formatting Tags...'))
self.settingsConfigureItem.setText(
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(
translate('OpenLP.MainWindow', '&Media Manager'))
self.viewMediaManagerItem.setToolTip(
@@ -523,8 +539,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# (not for use by plugins)
self.uiSettingsSection = u'user interface'
self.generalSettingsSection = u'general'
- self.serviceSettingsSection = u'servicemanager'
+ self.advancedlSettingsSection = u'advanced'
+ self.servicemanagerSettingsSection = u'servicemanager'
self.songsSettingsSection = u'songs'
+ self.themesSettingsSection = u'themes'
+ self.displayTagsSection = u'displayTags'
+ self.headerSection = u'SettingsImport'
self.serviceNotSaved = False
self.aboutForm = AboutForm(self)
self.settingsForm = SettingsForm(self, self)
@@ -573,6 +593,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked)
QtCore.QObject.connect(self.settingsShortcutsItem,
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
self.languageGroup.triggered.connect(LanguageManager.set_language)
QtCore.QObject.connect(self.modeDefaultItem,
@@ -767,6 +791,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.themeManagerContents.loadThemes(True)
Receiver.send_message(u'theme_update_global',
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):
"""
@@ -868,6 +895,172 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if self.shortcutForm.exec_():
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
+ importFileName = unicode(QtGui.QFileDialog.getOpenFileName(self,
+ translate('OpenLP.MainWindow', 'Open File'),
+ '',
+ translate('OpenLP.MainWindow',
+ 'OpenLP Export Settings Files (*.conf)')))
+ if not importFileName:
+ return
+ settingSections = []
+ # Add main sections.
+ settingSections.extend([self.generalSettingsSection])
+ settingSections.extend([self.advancedlSettingsSection])
+ settingSections.extend([self.uiSettingsSection])
+ settingSections.extend([self.servicemanagerSettingsSection])
+ settingSections.extend([self.themesSettingsSection])
+ settingSections.extend([self.displayTagsSection])
+ settingSections.extend([self.headerSection])
+ # Add plugin sections.
+ for plugin in self.pluginManager.plugins:
+ settingSections.extend([plugin.name])
+ settings = QtCore.QSettings()
+ importSettings = QtCore.QSettings(importFileName,
+ QtCore.QSettings.IniFormat)
+ importKeys = importSettings.allKeys()
+ for sectionKey in importKeys:
+ # We need to handle the really bad files.
+ try:
+ section, key = sectionKey.split(u'/')
+ except ValueError:
+ section = u'unknown'
+ key = u''
+ # Switch General back to lowercase.
+ if section == u'General':
+ section = u'general'
+ sectionKey = section + "/" + key
+ # Make sure it's a valid section for us.
+ if not section in settingSections:
+ 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 sectionKey in importKeys:
+ value = importSettings.value(sectionKey)
+ settings.setValue(u'%s' % (sectionKey) ,
+ QtCore.QVariant(value))
+ now = datetime.now()
+ settings.beginGroup(self.headerSection)
+ settings.setValue( u'file_imported' , QtCore.QVariant(importFileName))
+ 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, exportFileName=None):
+ """
+ Export settings to an INI file
+ """
+ if not exportFileName:
+ exportFileName = unicode(QtGui.QFileDialog.getSaveFileName(self,
+ translate('OpenLP.MainWindow', 'Export Settings File'), '',
+ translate('OpenLP.MainWindow',
+ 'OpenLP Export Settings File (*.conf)')))
+ if not exportFileName:
+ return
+ # Make sure it's an .ini file.
+ if not exportFileName.endswith(u'conf'):
+ exportFileName = exportFileName + u'.conf'
+ temp_file = os.path.join(unicode(gettempdir()),
+ u'openlp', u'exportIni.tmp')
+ self.saveSettings()
+ settingSections = []
+ # Add main sections.
+ settingSections.extend([self.generalSettingsSection])
+ settingSections.extend([self.advancedlSettingsSection])
+ settingSections.extend([self.uiSettingsSection])
+ settingSections.extend([self.servicemanagerSettingsSection])
+ settingSections.extend([self.themesSettingsSection])
+ settingSections.extend([self.displayTagsSection])
+ # Add plugin sections.
+ for plugin in self.pluginManager.plugins:
+ settingSections.extend([plugin.name])
+ # Delete old files if found.
+ if os.path.exists(temp_file):
+ os.remove(temp_file)
+ if os.path.exists(exportFileName):
+ os.remove(exportFileName)
+ settings = QtCore.QSettings()
+ settings.remove(self.headerSection)
+ # Get the settings.
+ keys = settings.allKeys()
+ exportSettings = QtCore.QSettings(temp_file,
+ QtCore.QSettings.IniFormat)
+ # Add a header section.
+ # This is to insure it's our ini file for import.
+ now = datetime.now()
+ applicationVersion = get_application_version()
+ # Write INI format using Qsettings.
+ # Write our header.
+ exportSettings.beginGroup(self.headerSection)
+ exportSettings.setValue(u'Make_Changes', u'At_Own_RISK')
+ exportSettings.setValue(u'type', u'OpenLP_settings_export')
+ exportSettings.setValue(u'file_date_created',
+ now.strftime("%Y-%m-%d %H:%M"))
+ exportSettings.setValue(u'version', applicationVersion[u'full'])
+ exportSettings.endGroup()
+ # Write all the sections and keys.
+ for sectionKey in keys:
+ section, key = sectionKey.split(u'/')
+ keyValue = settings.value(sectionKey)
+ sectionKey = section + u"/" + key
+ # Change the service section to servicemanager.
+ if section == u'service':
+ sectionKey = u'servicemanager/' + key
+ exportSettings.setValue(sectionKey, keyValue)
+ exportSettings.sync()
+ # Temp INI file has been written. Blanks in keys are now '%20'.
+ # Read the temp file and output the user's INI file with blanks to
+ # make it more readable.
+ tempIni = open(temp_file, u'r')
+ exportIni = open(exportFileName, u'w')
+ for fileRecord in tempIni:
+ fileRecord = fileRecord.replace(u'%20', u' ')
+ exportIni.write(fileRecord)
+ tempIni.close()
+ exportIni.close()
+ os.remove(temp_file)
+ return
+
def onModeDefaultItemClicked(self):
"""
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
"""
+ # If we just did a settings import, close without saving changes.
+ if self.settingsImported:
+ event.accept()
if self.serviceManagerContents.isModified():
ret = self.serviceManagerContents.saveModifiedService()
if ret == QtGui.QMessageBox.Save:
@@ -1117,6 +1313,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
Save the main window settings.
"""
+ # Exit if we just did a settings import.
+ if self.settingsImported:
+ return
log.debug(u'Saving QSettings')
settings = QtCore.QSettings()
settings.beginGroup(self.generalSettingsSection)
diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py
index 55fc6eb3c..c08b6293e 100644
--- a/openlp/core/ui/printserviceform.py
+++ b/openlp/core/ui/printserviceform.py
@@ -31,7 +31,7 @@ import os
from PyQt4 import QtCore, QtGui
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.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize
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
"""
+ self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toPlainText())
def copyHtmlText(self):
"""
Copies the display text to the clipboard as Html
"""
+ self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toHtml())
def printServiceOrder(self):
@@ -341,6 +343,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
"""
if not self.printDialog.exec_():
return
+ self.update_song_usage()
# Print the document.
self.document.print_(self.printer)
@@ -397,3 +400,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
settings.setValue(u'print notes',
QtCore.QVariant(self.notesCheckBox.isChecked()))
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']])
diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py
index 3ab2e9239..ad1161e0d 100644
--- a/openlp/core/ui/servicemanager.py
+++ b/openlp/core/ui/servicemanager.py
@@ -290,7 +290,7 @@ class ServiceManager(QtGui.QWidget):
QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate)
# Last little bits of setting up
self.service_theme = unicode(QtCore.QSettings().value(
- self.mainwindow.serviceSettingsSection + u'/service theme',
+ self.mainwindow.servicemanagerSettingsSection + u'/service theme',
QtCore.QVariant(u'')).toString())
self.servicePath = AppLocation.get_section_data_path(u'servicemanager')
# build the drag and drop context menu
@@ -371,7 +371,7 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.setServiceModified(self.isModified(),
self.shortFileName())
QtCore.QSettings(). \
- setValue(u'service/last file',QtCore.QVariant(fileName))
+ setValue(u'servicemanager/last file',QtCore.QVariant(fileName))
def fileName(self):
"""
@@ -429,14 +429,15 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow,
translate('OpenLP.ServiceManager', 'Open File'),
SettingsManager.get_last_dir(
- self.mainwindow.serviceSettingsSection),
+ self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager',
'OpenLP Service Files (*.osz)')))
if not fileName:
return False
else:
fileName = loadFile
- SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection,
+ SettingsManager.set_last_dir(
+ self.mainwindow.servicemanagerSettingsSection,
split_filename(fileName)[0])
self.loadFile(fileName)
@@ -461,7 +462,7 @@ class ServiceManager(QtGui.QWidget):
self.setFileName(u'')
self.setModified(False)
QtCore.QSettings(). \
- setValue(u'service/last file',QtCore.QVariant(u''))
+ setValue(u'servicemanager/last file',QtCore.QVariant(u''))
def saveFile(self):
"""
@@ -474,7 +475,8 @@ class ServiceManager(QtGui.QWidget):
(basename, extension) = os.path.splitext(file_name)
service_file_name = basename + '.osd'
log.debug(u'ServiceManager.saveFile - %s' % path_file_name)
- SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection,
+ SettingsManager.set_last_dir(
+ self.mainwindow.servicemanagerSettingsSection,
path)
service = []
write_list = []
@@ -562,7 +564,7 @@ class ServiceManager(QtGui.QWidget):
fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow,
UiStrings().SaveService,
SettingsManager.get_last_dir(
- self.mainwindow.serviceSettingsSection),
+ self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)')))
if not fileName:
return False
@@ -624,7 +626,7 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.addRecentFile(fileName)
self.setModified(False)
QtCore.QSettings().setValue(
- 'service/last file', QtCore.QVariant(fileName))
+ 'servicemanager/last file', QtCore.QVariant(fileName))
else:
critical_error_message_box(
message=translate('OpenLP.ServiceManager',
@@ -666,7 +668,7 @@ class ServiceManager(QtGui.QWidget):
present.
"""
fileName = QtCore.QSettings(). \
- value(u'service/last file',QtCore.QVariant(u'')).toString()
+ value(u'servicemanager/last file',QtCore.QVariant(u'')).toString()
if fileName:
self.loadFile(fileName)
@@ -1008,7 +1010,8 @@ class ServiceManager(QtGui.QWidget):
self.service_theme = unicode(self.themeComboBox.currentText())
self.mainwindow.renderer.set_service_theme(self.service_theme)
QtCore.QSettings().setValue(
- self.mainwindow.serviceSettingsSection + u'/service theme',
+ self.mainwindow.servicemanagerSettingsSection +
+ u'/service theme',
QtCore.QVariant(self.service_theme))
self.regenerateServiceItems()
diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py
index d5d955926..dc3c23d0d 100644
--- a/openlp/core/ui/themeform.py
+++ b/openlp/core/ui/themeform.py
@@ -66,6 +66,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.onGradientComboBoxCurrentIndexChanged)
QtCore.QObject.connect(self.colorButton,
QtCore.SIGNAL(u'clicked()'), self.onColorButtonClicked)
+ QtCore.QObject.connect(self.imageColorButton,
+ QtCore.SIGNAL(u'clicked()'), self.onImageColorButtonClicked)
QtCore.QObject.connect(self.gradientStartButton,
QtCore.SIGNAL(u'clicked()'), self.onGradientStartButtonClicked)
QtCore.QObject.connect(self.gradientEndButton,
@@ -330,6 +332,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.theme.background_end_color)
self.setField(u'background_type', QtCore.QVariant(1))
else:
+ self.imageColorButton.setStyleSheet(u'background-color: %s' %
+ self.theme.background_border_color)
self.imageFileEdit.setText(self.theme.background_filename)
self.setField(u'background_type', QtCore.QVariant(2))
if self.theme.background_direction == \
@@ -464,6 +468,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self._colorButton(self.theme.background_color)
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):
"""
Gradient 2 Color button pushed.
@@ -564,7 +576,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
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
self.theme.theme_name = unicode(self.field(u'name').toString())
diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py
index 69c229532..fdd0d74f3 100644
--- a/openlp/core/ui/thememanager.py
+++ b/openlp/core/ui/thememanager.py
@@ -610,6 +610,11 @@ class ThemeManager(QtGui.QWidget):
and to trigger the reload of the theme list
"""
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()
def _writeTheme(self, theme, imageFrom, imageTo):
diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py
index 27ac3a182..6001c83d6 100644
--- a/openlp/core/ui/themewizard.py
+++ b/openlp/core/ui/themewizard.py
@@ -105,6 +105,11 @@ class Ui_ThemeWizard(object):
self.imageLayout = QtGui.QFormLayout(self.imageWidget)
self.imageLayout.setMargin(0)
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.setObjectName(u'ImageLabel')
self.imageFileLayout = QtGui.QHBoxLayout()
@@ -118,7 +123,7 @@ class Ui_ThemeWizard(object):
build_icon(u':/general/general_open.png'))
self.imageFileLayout.addWidget(self.imageBrowseButton)
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.backgroundLayout.addLayout(self.backgroundStack)
themeWizard.addPage(self.backgroundPage)
@@ -443,6 +448,8 @@ class Ui_ThemeWizard(object):
translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right'))
self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom,
translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right'))
+ self.imageColorLabel.setText(
+ translate(u'OpenLP.ThemeWizard', 'Background color:'))
self.imageLabel.setText(u'%s:' % UiStrings().Image)
self.mainAreaPage.setTitle(
translate('OpenLP.ThemeWizard', 'Main Area Font Details'))
diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py
index 91009424c..9083b18a2 100644
--- a/openlp/plugins/bibles/lib/mediaitem.py
+++ b/openlp/plugins/bibles/lib/mediaitem.py
@@ -391,10 +391,13 @@ class BibleMediaItem(MediaManagerItem):
elif len(bibles):
self.initialiseAdvancedBible(bibles[0])
- def reloadBibles(self):
+ def reloadBibles(self, process=False):
log.debug(u'Reloading Bibles')
self.plugin.manager.reload_bibles()
self.loadBibles()
+ # If called from first time wizard re-run, process any new bibles.
+ if process:
+ self.plugin.appStartup()
self.updateAutoCompleter()
def initialiseAdvancedBible(self, bible):
diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py
index 667434a8b..693e1ef8d 100644
--- a/openlp/plugins/custom/lib/mediaitem.py
+++ b/openlp/plugins/custom/lib/mediaitem.py
@@ -217,8 +217,7 @@ class CustomMediaItem(MediaManagerItem):
for item in self.listView.selectedIndexes()]
for id in id_list:
self.plugin.manager.delete_object(CustomSlide, id)
- for row in row_list:
- self.listView.takeItem(row)
+ self.onSearchTextButtonClick()
def onFocus(self):
self.searchTextEdit.setFocus()
diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py
index 1ddbe8357..4b5a6f3c0 100644
--- a/openlp/plugins/images/imageplugin.py
+++ b/openlp/plugins/images/imageplugin.py
@@ -25,10 +25,13 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+from PyQt4 import QtCore, QtGui
+
import logging
-from openlp.core.lib import Plugin, StringContent, build_icon, translate
-from openlp.plugins.images.lib import ImageMediaItem
+from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
+ Receiver
+from openlp.plugins.images.lib import ImageMediaItem, ImageTab
log = logging.getLogger(__name__)
@@ -36,10 +39,13 @@ class ImagePlugin(Plugin):
log.info(u'Image Plugin loaded')
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.icon_path = u':/plugins/plugin_images.png'
self.icon = build_icon(self.icon_path)
+ QtCore.QObject.connect(Receiver.get_receiver(),
+ QtCore.SIGNAL(u'image_updated'), self.image_updated)
def about(self):
about_text = translate('ImagePlugin', 'Image Plugin'
@@ -81,3 +87,13 @@ class ImagePlugin(Plugin):
'Add the selected image to the service.')
}
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)
diff --git a/openlp/plugins/images/lib/__init__.py b/openlp/plugins/images/lib/__init__.py
index b26d00184..e216623cd 100644
--- a/openlp/plugins/images/lib/__init__.py
+++ b/openlp/plugins/images/lib/__init__.py
@@ -26,3 +26,4 @@
###############################################################################
from mediaitem import ImageMediaItem
+from imagetab import ImageTab
diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py
new file mode 100644
index 000000000..98fbd203f
--- /dev/null
+++ b/openlp/plugins/images/lib/imagetab.py
@@ -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')
+
diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py
index acd420880..18d5d2a1c 100644
--- a/openlp/plugins/images/lib/mediaitem.py
+++ b/openlp/plugins/images/lib/mediaitem.py
@@ -140,6 +140,8 @@ class ImageMediaItem(MediaManagerItem):
self.plugin.formparent.finishedProgressBar()
def generateSlideData(self, service_item, item=None, xmlVersion=False):
+ background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection
+ + u'/background color', QtCore.QVariant(u'#000000')))
if item:
items = [item]
else:
@@ -183,7 +185,7 @@ class ImageMediaItem(MediaManagerItem):
for bitem in items:
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
(path, name) = os.path.split(filename)
- service_item.add_from_image(filename, name)
+ service_item.add_from_image(filename, name, background)
return True
def onResetClick(self):
@@ -206,13 +208,16 @@ class ImageMediaItem(MediaManagerItem):
if check_item_selected(self.listView,
translate('ImagePlugin.MediaItem',
'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]
bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
(path, name) = os.path.split(filename)
if self.plugin.liveController.display.directImage(name,
- filename):
+ filename, background):
self.resetAction.setVisible(True)
else:
critical_error_message_box(UiStrings().LiveBGError,
diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py
index 90c3b0275..22020a401 100644
--- a/openlp/plugins/songs/forms/songexportform.py
+++ b/openlp/plugins/songs/forms/songexportform.py
@@ -170,8 +170,8 @@ class SongExportForm(OpenLPWizard):
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
self.informationLabel.setText(
translate('SongsPlugin.ExportWizardForm', 'This wizard will help to'
- ' export your songs to the open and free OpenLyrics worship song '
- 'format.'))
+ ' export your songs to the open and free OpenLyrics'
+ ' worship song format.'))
self.availableSongsPage.setTitle(
translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
self.availableSongsPage.setSubTitle(
@@ -285,7 +285,9 @@ class SongExportForm(OpenLPWizard):
self, songs, unicode(self.directoryLineEdit.text()))
if exporter.do_export():
self.progressLabel.setText(
- translate('SongsPlugin.SongExportForm', 'Finished export.'))
+ translate('SongsPlugin.SongExportForm', 'Finished export. To '
+ 'import these files use the OpenLyrics '
+ 'importer.'))
else:
self.progressLabel.setText(
translate('SongsPlugin.SongExportForm',
diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py
index c5c019c3c..5bfa0c830 100644
--- a/openlp/plugins/songs/lib/db.py
+++ b/openlp/plugins/songs/lib/db.py
@@ -31,6 +31,7 @@ the Songs plugin
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation
+from sqlalchemy.sql.expression import func
from openlp.core.lib.db import BaseModel, init_db
@@ -70,7 +71,6 @@ class Topic(BaseModel):
"""
pass
-
def init_schema(url):
"""
Setup the songs database connection and initialise the database schema.
@@ -111,10 +111,6 @@ def init_schema(url):
* file_name
* type
- **media_files_songs Table**
- * media_file_id
- * song_id
-
**song_books Table**
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
@@ -162,7 +158,7 @@ def init_schema(url):
# Definition of the "authors" table
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'last_name', types.Unicode(128)),
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
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'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
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'publisher', types.Unicode(128))
)
# Definition of the "songs" table
songs_table = Table(u'songs', metadata,
- Column(u'id', types.Integer, primary_key=True),
- Column(u'song_book_id', types.Integer,
+ Column(u'id', types.Integer(), primary_key=True),
+ Column(u'song_book_id', types.Integer(),
ForeignKey(u'song_books.id'), default=None),
Column(u'title', types.Unicode(255), nullable=False),
Column(u'alternate_title', types.Unicode(255)),
@@ -197,36 +196,31 @@ def init_schema(url):
Column(u'song_number', types.Unicode(64)),
Column(u'theme_name', types.Unicode(128)),
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
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)
)
# Definition of the "authors_songs" table
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),
- 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,
+ Column(u'song_id', types.Integer(),
ForeignKey(u'songs.id'), primary_key=True)
)
# Definition of the "songs_topics" table
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),
- Column(u'topic_id', types.Integer,
+ Column(u'topic_id', types.Integer(),
ForeignKey(u'topics.id'), primary_key=True)
)
@@ -238,8 +232,7 @@ def init_schema(url):
'authors': relation(Author, backref='songs',
secondary=authors_songs_table, lazy=False),
'book': relation(Book, backref='songs'),
- 'media_files': relation(MediaFile, backref='songs',
- secondary=media_files_songs_table),
+ 'media_files': relation(MediaFile, backref='songs'),
'topics': relation(Topic, backref='songs',
secondary=songs_topics_table)
})
diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py
new file mode 100644
index 000000000..fae3400c2
--- /dev/null
+++ b/openlp/plugins/songs/lib/upgrade.py
@@ -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)
diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py
index 8a773be90..f2bf36790 100644
--- a/openlp/plugins/songs/songsplugin.py
+++ b/openlp/plugins/songs/songsplugin.py
@@ -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.ui import UiStrings, base_action, icon_action
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.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
@@ -58,8 +59,8 @@ class SongsPlugin(Plugin):
Create and set up the Songs plugin.
"""
Plugin.__init__(self, u'songs', plugin_helpers, SongMediaItem, SongsTab)
+ self.manager = Manager(u'songs', init_schema, upgrade_mod=upgrade)
self.weight = -10
- self.manager = Manager(u'songs', init_schema)
self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path)
diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py
index 303789d20..f7b04a656 100644
--- a/openlp/plugins/songusage/forms/songusagedetailform.py
+++ b/openlp/plugins/songusage/forms/songusagedetailform.py
@@ -117,9 +117,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
try:
fileHandle = open(outname, u'w')
for instance in usage:
- record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % (
- instance.usagedate, instance.usagetime, instance.title,
- instance.copyright, instance.ccl_number, instance.authors)
+ record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
+ u'\"%s\",\"%s\"\n' % ( instance.usagedate,
+ instance.usagetime, instance.title, instance.copyright,
+ instance.ccl_number, instance.authors,
+ instance.plugin_name, instance.source)
fileHandle.write(record.encode(u'utf-8'))
Receiver.send_message(u'openlp_information_message', {
u'title': translate('SongUsagePlugin.SongUsageDetailForm',
diff --git a/openlp/plugins/songusage/lib/db.py b/openlp/plugins/songusage/lib/db.py
index 9a11ef16b..bbd645634 100644
--- a/openlp/plugins/songusage/lib/db.py
+++ b/openlp/plugins/songusage/lib/db.py
@@ -56,7 +56,9 @@ def init_schema(url):
Column(u'title', types.Unicode(255), nullable=False),
Column(u'authors', types.Unicode(255), nullable=False),
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)
diff --git a/openlp/plugins/songusage/lib/upgrade.py b/openlp/plugins/songusage/lib/upgrade.py
new file mode 100644
index 000000000..50ca32fcd
--- /dev/null
+++ b/openlp/plugins/songusage/lib/upgrade.py
@@ -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)
diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py
index d63467792..495d3103d 100644
--- a/openlp/plugins/songusage/songusageplugin.py
+++ b/openlp/plugins/songusage/songusageplugin.py
@@ -37,6 +37,7 @@ from openlp.core.lib.ui import base_action, shortcut_action
from openlp.core.utils.actions import ActionList
from openlp.plugins.songusage.forms import SongUsageDetailForm, \
SongUsageDeleteForm
+from openlp.plugins.songusage.lib import upgrade
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
log = logging.getLogger(__name__)
@@ -46,11 +47,11 @@ class SongUsagePlugin(Plugin):
def __init__(self, plugin_helpers):
Plugin.__init__(self, u'songusage', plugin_helpers)
+ self.manager = Manager(u'songusage', init_schema, upgrade_mod=upgrade)
self.weight = -4
self.icon = build_icon(u':/plugins/plugin_songusage.png')
self.activeIcon = build_icon(u':/songusage/song_usage_active.png')
self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
- self.manager = None
self.songUsageActive = False
def addToolsMenuItem(self, tools_menu):
@@ -121,7 +122,10 @@ class SongUsagePlugin(Plugin):
Plugin.initialise(self)
QtCore.QObject.connect(Receiver.get_receiver(),
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.settingsSection + u'/active',
QtCore.QVariant(False)).toBool()
@@ -134,8 +138,6 @@ class SongUsagePlugin(Plugin):
translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageReport,
translate('SongUsagePlugin', 'Song Usage'))
- if self.manager is None:
- self.manager = Manager(u'songusage', init_schema)
self.songUsageDeleteForm = SongUsageDeleteForm(self.manager,
self.formparent)
self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent)
@@ -194,10 +196,21 @@ class SongUsagePlugin(Plugin):
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
if self.songUsageActive and audit:
song_usage_item = SongUsageItem()
@@ -207,6 +220,8 @@ class SongUsagePlugin(Plugin):
song_usage_item.copyright = audit[2]
song_usage_item.ccl_number = audit[3]
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)
def onSongUsageDelete(self):
diff --git a/resources/debian/debian/control b/resources/debian/debian/control
index 220b500d2..a1c2298e9 100644
--- a/resources/debian/debian/control
+++ b/resources/debian/debian/control
@@ -11,7 +11,7 @@ Package: openlp
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-qt4,
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
Description: Church lyrics projection application
OpenLP is free church presentation software, or lyrics projection software,
diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py
index 7048ceeab..5f2e4c148 100755
--- a/scripts/check_dependencies.py
+++ b/scripts/check_dependencies.py
@@ -46,14 +46,14 @@ VERS = {
'sqlalchemy': '0.5',
# pyenchant 1.6 required on Windows
'enchant': '1.6' if is_win else '1.3'
- }
+}
# pywin32
WIN32_MODULES = [
'win32com',
'win32ui',
'pywintypes',
- ]
+]
MODULES = [
'PyQt4',
@@ -72,7 +72,8 @@ MODULES = [
'enchant',
'BeautifulSoup',
'mako',
- ]
+ 'migrate',
+]
OPTIONAL_MODULES = [
diff --git a/scripts/translation_utils.py b/scripts/translation_utils.py
index db1788aba..935ef97e8 100755
--- a/scripts/translation_utils.py
+++ b/scripts/translation_utils.py
@@ -186,25 +186,6 @@ def update_export_at_pootle(source_filename):
page = urllib.urlopen(REVIEW_URL)
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():
"""
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',
language_file)
print_verbose(u'Get Translation File: %s' % filename)
- download_file(language_file, filename)
+ urllib.urlretrieve(SERVER_URL + language_file, filename)
print_quiet(u' Done.')
def prepare_project():
@@ -304,7 +285,7 @@ def create_translation(language):
if not language.endswith(u'.ts'):
language += u'.ts'
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' In order to get this file into OpenLP and onto the '
u'Pootle translation server you will need to subscribe to the '