diff --git a/.bzrignore b/.bzrignore index 7bea0adb9..00884055d 100644 --- a/.bzrignore +++ b/.bzrignore @@ -10,3 +10,7 @@ openlp.org 2.0.e4* documentation/build/html documentation/build/doctrees *.log* +dist +OpenLP.egg-info +build +resources/innosetup/Output diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..73ff3c545 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +recursive-include openlp *.py +recursive-include openlp *.sqlite +recursive-include openlp *.csv +recursive-include documentation * +recursive-include resources/forms * +recursive-include resources/i18n * +recursive-include resources/images * +recursive-include scripts *.py +include resources/*.desktop +include copyright.txt +include LICENSE +include openlp/.version diff --git a/OpenLP.spec b/OpenLP.spec new file mode 100644 index 000000000..366bdf31d --- /dev/null +++ b/OpenLP.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- +a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'openlp.pyw'], + pathex=['c:\\Documents and Settings\\raoul\\My Documents\\My Projects\\openlp\\pyinstaller']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build\\pyi.win32\\OpenLP', 'OpenLP.exe'), + debug=False, + strip=False, + upx=True, + console=False , icon='resources\\images\\OpenLP.ico') +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', 'OpenLP')) diff --git a/openlp.pyw b/openlp.pyw index 8b847c0ec..c4a5fb428 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -28,16 +28,17 @@ import os import sys import logging -from logging.handlers import RotatingFileHandler +from logging import FileHandler from optparse import OptionParser from PyQt4 import QtCore, QtGui +log = logging.getLogger() + +import openlp from openlp.core.lib import Receiver, str_to_bool from openlp.core.resources import qInitResources -from openlp.core.ui import MainWindow, SplashScreen -from openlp.core.utils import ConfigHelper - -log = logging.getLogger() +from openlp.core.ui import MainWindow, SplashScreen, ScreenList +from openlp.core.utils import AppLocation, ConfigHelper application_stylesheet = u""" QMainWindow::separator @@ -47,9 +48,11 @@ QMainWindow::separator QDockWidget::title { - border: none; + /*background: palette(dark);*/ + border: 1px solid palette(dark); padding-left: 5px; - padding-top: 3px; + padding-top: 2px; + margin: 1px 0; } QToolBar @@ -65,16 +68,21 @@ 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. """ - global log log.info(u'OpenLP Application Loaded') + def notify(self, obj, evt): + #TODO needed for presentation exceptions + return QtGui.QApplication.notify(self, obj, evt) + def run(self): """ Run the OpenLP application. """ #Load and store current Application Version - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join(filepath, u'version.txt')) + filepath = AppLocation.get_directory(AppLocation.AppDir) + if not hasattr(sys, u'frozen'): + filepath = os.path.join(filepath, u'openlp') + filepath = os.path.join(filepath, u'.version') fversion = None try: fversion = open(filepath, u'r') @@ -91,9 +99,9 @@ class OpenLP(QtGui.QApplication): app_version[u'version'], app_version[u'build'])) except: app_version = { - u'full': u'1.9.0-000', + u'full': u'1.9.0-bzr000', u'version': u'1.9.0', - u'build': u'000' + u'build': u'bzr000' } finally: if fversion: @@ -117,10 +125,10 @@ class OpenLP(QtGui.QApplication): self.splash.show() # make sure Qt really display the splash screen self.processEvents() - screens = [] + screens = ScreenList() # Decide how many screens we have and their size for screen in xrange(0, self.desktop().numScreens()): - screens.append({u'number': screen, + screens.add_screen({u'number': screen, u'size': self.desktop().availableGeometry(screen), u'primary': (self.desktop().primaryScreen() == screen)}) log.info(u'Screen %d found with resolution %s', @@ -131,7 +139,8 @@ class OpenLP(QtGui.QApplication): if show_splash: # now kill the splashscreen self.splash.finish(self.mainWindow) - self.mainWindow.versionCheck() + self.mainWindow.repaint() + self.mainWindow.versionThread() return self.exec_() def main(): @@ -143,7 +152,7 @@ def main(): usage = u'Usage: %prog [options] [qt-options]' parser = OptionParser(usage=usage) parser.add_option("-l", "--log-level", dest="loglevel", - default="info", metavar="LEVEL", + default="warning", metavar="LEVEL", help="Set logging to LEVEL level. Valid values are " "\"debug\", \"info\", \"warning\".") parser.add_option("-p", "--portable", dest="portable", @@ -153,10 +162,13 @@ def main(): parser.add_option("-s", "--style", dest="style", help="Set the Qt4 style (passed directly to Qt4).") # Set up logging - filename = u'openlp.log' - logfile = RotatingFileHandler(filename, maxBytes=200000, backupCount=5) + log_path = AppLocation.get_directory(AppLocation.ConfigDir) + if not os.path.exists(log_path): + os.makedirs(log_path) + filename = os.path.join(log_path, u'openlp.log') + logfile = FileHandler(filename, u'w') logfile.setFormatter(logging.Formatter( - u'%(asctime)s %(name)-15s %(levelname)-8s %(message)s')) + u'%(asctime)s %(name)-20s %(levelname)-8s %(message)s')) log.addHandler(logfile) logging.addLevelName(15, u'Timer') # Parse command line options and deal with them. @@ -164,6 +176,7 @@ def main(): 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: @@ -182,4 +195,4 @@ if __name__ == u'__main__': """ Instantiate and run the application. """ - main() \ No newline at end of file + main() diff --git a/openlp/.version b/openlp/.version new file mode 100644 index 000000000..4489023a7 --- /dev/null +++ b/openlp/.version @@ -0,0 +1 @@ +1.9.0-bzr722 diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 22df9a926..899b5cf73 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -136,6 +136,26 @@ def contextMenuSeparator(base): action.setSeparator(True) return action +def resize_image(image, width, height): + """ + Resize an image to fit on the current screen. + + ``image`` + The image to resize. + """ + preview = QtGui.QImage(image) + preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + realw = preview.width() + realh = preview.height() + # and move it to the centre of the preview space + newImage = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) + newImage.fill(QtCore.Qt.black) + painter = QtGui.QPainter(newImage) + painter.drawImage((width - realw) / 2, (height - realh) / 2, preview) + return newImage + + class ThemeLevel(object): Global = 1 Service = 2 @@ -160,6 +180,3 @@ from renderer import Renderer from rendermanager import RenderManager from mediamanageritem import MediaManagerItem from baselistwithdnd import BaseListWithDnD - -#__all__ = [ 'translate', 'get_text_file_string', 'str_to_bool', -# 'contextMenuAction', 'contextMenuSeparator', 'ServiceItem'] \ No newline at end of file diff --git a/openlp/core/lib/baselistwithdnd.py b/openlp/core/lib/baselistwithdnd.py index d2537e0e4..f7095550a 100644 --- a/openlp/core/lib/baselistwithdnd.py +++ b/openlp/core/lib/baselistwithdnd.py @@ -32,6 +32,7 @@ class BaseListWithDnD(QtGui.QListWidget): def __init__(self, parent=None): QtGui.QListWidget.__init__(self, parent) + self.parent = parent # this must be set by the class which is inheriting assert(self.PluginName) @@ -47,4 +48,5 @@ class BaseListWithDnD(QtGui.QListWidget): mimeData = QtCore.QMimeData() drag.setMimeData(mimeData) mimeData.setText(self.PluginName) - dropAction = drag.start(QtCore.Qt.CopyAction) \ No newline at end of file + dropAction = drag.start(QtCore.Qt.CopyAction) + diff --git a/openlp/core/lib/dockwidget.py b/openlp/core/lib/dockwidget.py index 6d205cb29..ce8302b43 100644 --- a/openlp/core/lib/dockwidget.py +++ b/openlp/core/lib/dockwidget.py @@ -27,6 +27,8 @@ import logging from PyQt4 import QtGui +log = logging.getLogger(__name__) + class OpenLPDockWidget(QtGui.QDockWidget): """ Custom DockWidget class to handle events @@ -40,10 +42,9 @@ class OpenLPDockWidget(QtGui.QDockWidget): if name: self.setObjectName(name) self.setFloating(False) - self.log = logging.getLogger(u'OpenLPDockWidget') - self.log.debug(u'Init done') + log.debug(u'Init done') def closeEvent(self, event): self.parent.settingsmanager.setUIItemVisibility( self.objectName(), False) - event.accept() \ No newline at end of file + event.accept() diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index f3b43d2b7..be7dff67a 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -27,6 +27,8 @@ import logging from PyQt4 import QtCore +log = logging.getLogger(__name__) + class EventReceiver(QtCore.QObject): """ Class to allow events to be passed from different parts of the @@ -104,10 +106,10 @@ class EventReceiver(QtCore.QObject): ``remote_edit_clear`` Informs all components that remote edit has been aborted. - """ - global log - log = logging.getLogger(u'EventReceiver') + ``presentation types`` + Informs all components of the presentation types supported. + """ def __init__(self): """ Initialise the event receiver, calling the parent constructor. @@ -161,4 +163,5 @@ class Receiver(): """ Get the global ``eventreceiver`` instance. """ - return Receiver.eventreceiver \ No newline at end of file + return Receiver.eventreceiver + diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 6f6f82818..fd6d37ca6 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -32,6 +32,8 @@ from openlp.core.lib.toolbar import * from openlp.core.lib import contextMenuAction, contextMenuSeparator from serviceitem import ServiceItem +log = logging.getLogger(__name__) + class MediaManagerItem(QtGui.QWidget): """ MediaManagerItem is a helper widget for plugins. @@ -92,9 +94,6 @@ class MediaManagerItem(QtGui.QWidget): method is not defined, a default will be used (treat the filename as an image). """ - - global log - log = logging.getLogger(u'MediaManagerItem') log.info(u'Media Item loaded') def __init__(self, parent=None, icon=None, title=None): @@ -253,7 +252,7 @@ class MediaManagerItem(QtGui.QWidget): def addListViewToToolBar(self): #Add the List widget - self.ListView = self.ListViewWithDnD_class() + self.ListView = self.ListViewWithDnD_class(self) self.ListView.uniformItemSizes = True self.ListView.setGeometry(QtCore.QRect(10, 100, 256, 591)) self.ListView.setSpacing(1) @@ -315,7 +314,7 @@ class MediaManagerItem(QtGui.QWidget): self, self.OnNewPrompt, self.parent.config.get_last_dir(), self.OnNewFileMasks) log.info(u'New files(s)%s', unicode(files)) - if len(files) > 0: + if files: self.loadList(files) dir, filename = os.path.split(unicode(files[0])) self.parent.config.set_last_dir(dir) @@ -400,4 +399,4 @@ class MediaManagerItem(QtGui.QWidget): if self.generateSlideData(service_item): return service_item else: - return None \ No newline at end of file + return None diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 598c594fd..e98c789d0 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -28,6 +28,8 @@ from PyQt4 import QtCore from openlp.core.lib import PluginConfig, Receiver +log = logging.getLogger(__name__) + class PluginStatus(object): """ Defines the status of the plugin @@ -88,8 +90,6 @@ class Plugin(QtCore.QObject): Used in the plugin manager, when a person clicks on the 'About' button. """ - global log - log = logging.getLogger(u'Plugin') log.info(u'loaded') def __init__(self, name, version=None, plugin_helpers=None): @@ -127,6 +127,7 @@ class Plugin(QtCore.QObject): self.service_manager = plugin_helpers[u'service'] self.settings = plugin_helpers[u'settings'] self.mediadock = plugin_helpers[u'toolbox'] + self.maindisplay = plugin_helpers[u'maindisplay'] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_add_service_item' % self.name), self.process_add_service_event) @@ -252,4 +253,10 @@ class Plugin(QtCore.QObject): if self.media_item: self.mediadock.insert_dock(self.media_item, self.icon, self.weight) if self.settings_tab: - self.settings.insertTab(self.settings_tab, self.weight) \ No newline at end of file + self.settings.insertTab(self.settings_tab, self.weight) + + def can_delete_theme(self, theme): + """ + Called to ask the plugin if a theme can be deleted + """ + return True diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index c580555ff..2518f88be 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -29,13 +29,13 @@ import logging from openlp.core.lib import Plugin, PluginStatus +log = logging.getLogger(__name__) + class PluginManager(object): """ This is the Plugin manager, which loads all the plugins, and executes all the hooks, as and when necessary. """ - global log - log = logging.getLogger(u'PluginMgr') log.info(u'Plugin manager loaded') def __init__(self, dir): @@ -54,7 +54,7 @@ class PluginManager(object): log.debug(u'Base path %s ', self.basepath) self.plugins = [] # this has to happen after the UI is sorted self.find_plugins(dir) - log.info(u'Plugin manager done init') + log.info(u'Plugin manager Initialised') def find_plugins(self, dir, plugin_helpers): """ @@ -77,7 +77,7 @@ class PluginManager(object): if name.endswith(u'.py') and not name.startswith(u'__'): path = os.path.abspath(os.path.join(root, name)) thisdepth = len(path.split(os.sep)) - if thisdepth-startdepth > 2: + if thisdepth - startdepth > 2: # skip anything lower down continue modulename, pyext = os.path.splitext(path) @@ -101,7 +101,7 @@ class PluginManager(object): log.debug(u'Loaded plugin %s with helpers', unicode(p)) plugin_objects.append(plugin) except TypeError: - log.error(u'loaded plugin %s has no helpers', unicode(p)) + log.exception(u'loaded plugin %s has no helpers', unicode(p)) plugins_list = sorted(plugin_objects, self.order_by_weight) for plugin in plugins_list: if plugin.check_pre_conditions(): @@ -200,6 +200,7 @@ class PluginManager(object): % (plugin.name, plugin.is_active())) if plugin.is_active(): plugin.initialise() + log.info(u'Initialisation Complete for %s ' % plugin.name) if not plugin.is_active(): plugin.remove_toolbox_item() @@ -211,4 +212,5 @@ class PluginManager(object): log.info(u'finalising plugins') for plugin in self.plugins: if plugin.is_active(): - plugin.finalise() \ No newline at end of file + plugin.finalise() + log.info(u'Finalisation Complete for %s ' % plugin.name) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index c4d7444c4..f0da82b0e 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -26,14 +26,15 @@ import logging from PyQt4 import QtGui, QtCore +from openlp.core.lib import resize_image + +log = logging.getLogger(__name__) class Renderer(object): """ Genarates a pixmap image of a array of text. The Text is formatted to make sure it fits on the screen and if not extra frames are generated. """ - global log - log = logging.getLogger(u'Renderer') log.info(u'Renderer Loaded') def __init__(self): @@ -41,7 +42,7 @@ class Renderer(object): Initialise the renderer. """ self._rect = None - self._debug = 0 + self._debug = False self._right_margin = 64 # the amount of right indent self._display_shadow_size_footer = 0 self._display_outline_size_footer = 0 @@ -90,31 +91,9 @@ class Renderer(object): log.debug(u'set bg image %s', filename) self._bg_image_filename = unicode(filename) if self._frame: - self.scale_bg_image() - - def scale_bg_image(self): - """ - Scale the background image to fit the screen. - """ - assert self._frame - preview = QtGui.QImage(self._bg_image_filename) - width = self._frame.width() - height = self._frame.height() - preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation) - realwidth = preview.width() - realheight = preview.height() - # and move it to the centre of the preview space - self.bg_image = QtGui.QImage(width, height, - QtGui.QImage.Format_ARGB32_Premultiplied) - self.bg_image.fill(QtCore.Qt.black) - painter = QtGui.QPainter() - painter.begin(self.bg_image) - self.background_offsetx = (width - realwidth) / 2 - self.background_offsety = (height - realheight) / 2 - painter.drawImage(self.background_offsetx, - self.background_offsety, preview) - painter.end() + self.bg_image = resize_image(self._bg_image_filename, + self._frame.width(), + self._frame.height()) def set_frame_dest(self, frame_width, frame_height, preview=False): """ @@ -138,7 +117,9 @@ class Renderer(object): self._frameOp = QtGui.QImage(frame_width, frame_height, QtGui.QImage.Format_ARGB32_Premultiplied) if self._bg_image_filename and not self.bg_image: - self.scale_bg_image() + self.bg_image = resize_image(self._bg_image_filename, + self._frame.width(), + self._frame.height()) if self.bg_frame is None: self._generate_background_frame() @@ -167,17 +148,22 @@ class Renderer(object): def pre_render_text(self, text): metrics = QtGui.QFontMetrics(self.mainFont) - #take the width work out approx how many characters and add 50% + #work out line width line_width = self._rect.width() - self._right_margin #number of lines on a page - adjust for rounding up. - page_length = int(self._rect.height() / metrics.height() - 2 ) - 1 + line_height = metrics.height() + if self._theme.display_shadow: + line_height += int(self._theme.display_shadow_size) + if self._theme.display_outline: + # pixels top/bottom + line_height += 2 * int(self._theme.display_outline_size) + page_length = int(self._rect.height() / line_height ) #Average number of characters in line ave_line_width = line_width / metrics.averageCharWidth() #Maximum size of a character max_char_width = metrics.maxWidth() - #Min size of a character - min_char_width = metrics.width(u'i') - char_per_line = line_width / min_char_width + #Max characters pre line based on min size of a character + char_per_line = line_width / metrics.width(u'i') log.debug(u'Page Length area height %s , metrics %s , lines %s' % (int(self._rect.height()), metrics.height(), page_length )) split_pages = [] @@ -188,7 +174,7 @@ class Renderer(object): #Must be a blank line so keep it. if len(line) == 0: line = u' ' - while len(line) > 0: + while line: pos = char_per_line split_text = line[:pos] #line needs splitting @@ -213,7 +199,7 @@ class Renderer(object): split_lines.append(split_text) line = line[pos:].lstrip() #if we have more text add up to 10 spaces on the front. - if len(line) > 0 and self._theme.font_main_indentation > 0: + if line and self._theme.font_main_indentation > 0: line = u'%s%s' % \ (u' '[:int(self._theme.font_main_indentation)], line) #Text fits in a line now @@ -224,7 +210,7 @@ class Renderer(object): len(page) == page_length: split_pages.append(page) page = [] - if len(page) > 0 and page != u' ': + if page and page != u' ': split_pages.append(page) return split_pages @@ -276,8 +262,13 @@ class Renderer(object): Results are cached for performance reasons. """ assert(self._theme) - self.bg_frame = QtGui.QImage(self._frame.width(), self._frame.height(), - QtGui.QImage.Format_ARGB32_Premultiplied) + if self._theme.background_mode == u'transparent': + self.bg_frame = \ + QtGui.QPixmap(self._frame.width(), self._frame.height()) + self.bg_frame.fill(QtCore.Qt.transparent) + else: + self.bg_frame = QtGui.QImage(self._frame.width(), self._frame.height(), + QtGui.QImage.Format_ARGB32_Premultiplied) log.debug(u'render background %s start', self._theme.background_type) painter = QtGui.QPainter() painter.begin(self.bg_frame) @@ -415,13 +406,21 @@ class Renderer(object): Defaults to *False*. Whether or not this is a live screen. """ x, y = tlcorner - maxx = self._rect.width(); - maxy = self._rect.height(); + maxx = self._rect.width() + maxy = self._rect.height() lines = [] lines.append(line) startx = x starty = y rightextent = None + self.painter = QtGui.QPainter() + self.painter.begin(self._frame) + self.painter.setRenderHint(QtGui.QPainter.Antialiasing) + if self._theme.display_slideTransition: + self.painter2 = QtGui.QPainter() + self.painter2.begin(self._frameOp) + self.painter2.setRenderHint(QtGui.QPainter.Antialiasing) + self.painter2.setOpacity(0.7) # dont allow alignment messing with footers if footer: align = 0 @@ -459,7 +458,7 @@ class Renderer(object): x = maxx - w # centre elif align == 2: - x = (maxx - w) / 2; + x = (maxx - w) / 2 rightextent = x + w if live: # now draw the text, and any outlines/shadows @@ -467,49 +466,21 @@ class Renderer(object): self._get_extent_and_render(line, footer, tlcorner=(x + display_shadow_size, y + display_shadow_size), draw=True, color = self._theme.display_shadow_color) - if self._theme.display_outline: - self._get_extent_and_render(line, footer, - (x + display_outline_size, y), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x, y + display_outline_size), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x, y - display_outline_size), draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y), draw=True, - color = self._theme.display_outline_color) - if display_outline_size > 1: - self._get_extent_and_render(line, footer, - (x + display_outline_size, y + display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y + display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x + display_outline_size, y - display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer, - (x - display_outline_size, y - display_outline_size), - draw=True, - color = self._theme.display_outline_color) - self._get_extent_and_render(line, footer,tlcorner=(x, y), - draw=True) + self._get_extent_and_render(line, footer, tlcorner=(x, y), draw=True, + outline_size=display_outline_size, + outline_color=self._theme.display_outline_color) y += h if linenum == 0: self._first_line_right_extent = rightextent # draw a box around the text - debug only + if self._debug: - painter = QtGui.QPainter() - painter.begin(self._frame) - painter.setPen(QtGui.QPen(QtGui.QColor(0,255,0))) - painter.drawRect(startx, starty, rightextent-startx, y-starty) - painter.end() + self.painter.setPen(QtGui.QPen(QtGui.QColor(0,255,0))) + self.painter.drawRect(startx, starty, rightextent-startx, y-starty) brcorner = (rightextent, y) + self.painter.end() + if self._theme.display_slideTransition: + self.painter2.end() return brcorner def _set_theme_font(self): @@ -519,6 +490,7 @@ class Renderer(object): footer_weight = 50 if self._theme.font_footer_weight == u'Bold': footer_weight = 75 + #TODO Add myfont.setPixelSize((screen_height / 100) * font_size) self.footerFont = QtGui.QFont(self._theme.font_footer_name, self._theme.font_footer_proportion, # size footer_weight, # weight @@ -534,7 +506,7 @@ class Renderer(object): self.mainFont.setPixelSize(self._theme.font_main_proportion) def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False, - color=None): + color=None, outline_size=None, outline_color=None): """ Find bounding box of text - as render_single_line. If draw is set, actually draw the text to the current DC as well return width and @@ -556,45 +528,42 @@ class Renderer(object): Defaults to *None*. The colour to draw with. """ # setup defaults - painter = QtGui.QPainter() - painter.begin(self._frame) - painter.setRenderHint(QtGui.QPainter.Antialiasing); if footer : font = self.footerFont else: font = self.mainFont - painter.setFont(font) - if color is None: - if footer: - painter.setPen(QtGui.QColor(self._theme.font_footer_color)) - else: - painter.setPen(QtGui.QColor(self._theme.font_main_color)) - else: - painter.setPen(QtGui.QColor(color)) - x, y = tlcorner metrics = QtGui.QFontMetrics(font) w = metrics.width(line) - h = metrics.height() - 2 + h = metrics.height() if draw: - painter.drawText(x, y + metrics.ascent(), line) - painter.end() - if self._theme.display_slideTransition: - # Print 2nd image with 70% weight - painter = QtGui.QPainter() - painter.begin(self._frameOp) - painter.setRenderHint(QtGui.QPainter.Antialiasing); - painter.setOpacity(0.7) - painter.setFont(font) + self.painter.setFont(font) if color is None: if footer: - painter.setPen(QtGui.QColor(self._theme.font_footer_color)) + pen = QtGui.QColor(self._theme.font_footer_color) else: - painter.setPen(QtGui.QColor(self._theme.font_main_color)) + pen = QtGui.QColor(self._theme.font_main_color) else: - painter.setPen(QtGui.QColor(color)) - if draw: - painter.drawText(x, y + metrics.ascent(), line) - painter.end() + pen = QtGui.QColor(color) + x, y = tlcorner + if outline_size: + path = QtGui.QPainterPath() + path.addText(QtCore.QPointF(x, y + metrics.ascent()), font, line) + self.painter.setBrush(self.painter.pen().brush()) + self.painter.setPen(QtGui.QPen(QtGui.QColor(outline_color), outline_size)) + self.painter.drawPath(path) + self.painter.setPen(pen) + self.painter.drawText(x, y + metrics.ascent(), line) + if self._theme.display_slideTransition: + # Print 2nd image with 70% weight + if outline_size: + path = QtGui.QPainterPath() + path.addText(QtCore.QPointF(x, y + metrics.ascent()), font, line) + self.painter2.setBrush(self.painter2.pen().brush()) + self.painter2.setPen(QtGui.QPen(QtGui.QColor(outline_color), outline_size)) + self.painter2.drawPath(path) + self.painter2.setFont(font) + self.painter2.setPen(pen) + self.painter2.drawText(x, y + metrics.ascent(), line) return (w, h) def snoop_Image(self, image, image2=None): @@ -609,4 +578,4 @@ class Renderer(object): """ image.save(u'renderer.png', u'png') if image2: - image2.save(u'renderer2.png', u'png') \ No newline at end of file + image2.save(u'renderer2.png', u'png') diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index a0e2ce0af..edab3d309 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -25,11 +25,13 @@ import logging -from PyQt4 import QtGui, QtCore +from PyQt4 import QtCore from renderer import Renderer from openlp.core.lib import ThemeLevel +log = logging.getLogger(__name__) + class RenderManager(object): """ Class to pull all Renderer interactions into one place. The plugins will @@ -39,37 +41,29 @@ class RenderManager(object): ``theme_manager`` The ThemeManager instance, used to get the current theme details. - ``screen_list`` - The list of screens available. + ``screens`` + Contains information about the Screens. ``screen_number`` Defaults to *0*. The index of the output/display screen. """ - global log - log = logging.getLogger(u'RenderManager') log.info(u'RenderManager Loaded') - def __init__(self, theme_manager, screen_list, screen_number=0): + def __init__(self, theme_manager, screens, screen_number=0): """ Initialise the render manager. """ log.debug(u'Initilisation started') - self.screen_list = screen_list + self.screens = screens self.theme_manager = theme_manager - self.displays = len(screen_list) - if (screen_number + 1) > len(screen_list): - self.current_display = 0 - else: - self.current_display = screen_number self.renderer = Renderer() - self.calculate_default(self.screen_list[self.current_display][u'size']) + self.screens.set_current_display(screen_number) + self.calculate_default(self.screens.current[u'size']) self.theme = u'' self.service_theme = u'' self.theme_level = u'' self.override_background = None self.themedata = None - self.save_bg_frame = None - self.override_background_changed = False def update_display(self, screen_number): """ @@ -79,10 +73,8 @@ class RenderManager(object): The updated index of the output/display screen. """ log.debug(u'Update Display') - if self.current_display != screen_number: - self.current_display = screen_number - self.calculate_default( - self.screen_list[self.current_display][u'size']) + self.calculate_default(self.screens.current[u'size']) + self.renderer.bg_frame = None def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): """ @@ -137,31 +129,14 @@ class RenderManager(object): if self.theme != self.renderer.theme_name or self.themedata is None: log.debug(u'theme is now %s', self.theme) self.themedata = self.theme_manager.getThemeData(self.theme) - self.calculate_default( - self.screen_list[self.current_display][u'size']) + self.calculate_default(self.screens.current[u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) - #Replace the backgrount image from renderer with one from image - if self.override_background: - if self.save_bg_frame is None: - self.save_bg_frame = self.renderer.bg_frame - if self.override_background_changed: - self.renderer.bg_frame = self.resize_image( - self.override_background) - self.override_background_changed = False - else: - if self.override_background_changed: - self.renderer.bg_frame = self.resize_image( - self.override_background) - self.override_background_changed = False - if self.save_bg_frame: - self.renderer.bg_frame = self.save_bg_frame - self.save_bg_frame = None def build_text_rectangle(self, theme): """ - Builds a text block using the settings in ``theme``. - One is needed per slide + Builds a text block using the settings in ``theme`` + and the size of the display screen.height. ``theme`` The theme to build a text block for. @@ -170,14 +145,14 @@ class RenderManager(object): main_rect = None footer_rect = None if not theme.font_main_override: - main_rect = QtCore.QRect(10, 0, self.width - 1, - self.footer_start - 20) + main_rect = QtCore.QRect(10, 0, + self.width - 1, self.footer_start) else: main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y, theme.font_main_width - 1, theme.font_main_height - 1) if not theme.font_footer_override: - footer_rect = QtCore.QRect(10, self.footer_start, self.width - 1, - self.height-self.footer_start) + footer_rect = QtCore.QRect(10, self.footer_start, + self.width - 1, self.height - self.footer_start) else: footer_rect = QtCore.QRect(theme.font_footer_x, theme.font_footer_y, theme.font_footer_width - 1, @@ -192,10 +167,13 @@ class RenderManager(object): The theme to generated a preview for. """ log.debug(u'generate preview') - self.calculate_default(QtCore.QSize(1024, 768)) + #set the default image size for previews + self.calculate_default(self.screens.preview[u'size']) self.renderer.set_theme(themedata) self.build_text_rectangle(themedata) self.renderer.set_frame_dest(self.width, self.height, True) + #Reset the real screen size for subsequent render requests + self.calculate_default(self.screens.current[u'size']) verse = u'Amazing Grace!\n'\ 'How sweet the sound\n'\ 'To save a wretch like me;\n'\ @@ -206,6 +184,7 @@ class RenderManager(object): footer.append(u'Public Domain') footer.append(u'CCLI 123456') formatted = self.renderer.format_slide(verse, False) + #Only Render the first slide page returned return self.renderer.generate_frame_from_lines(formatted[0], footer)[u'main'] def format_slide(self, words): @@ -234,48 +213,18 @@ class RenderManager(object): self.renderer.set_frame_dest(self.width, self.height) return self.renderer.generate_frame_from_lines(main_text, footer_text) - def resize_image(self, image, width=0, height=0): - """ - Resize an image to fit on the current screen. - - ``image`` - The image to resize. - """ - preview = QtGui.QImage(image) - if width == 0: - w = self.width - h = self.height - else: - w = width - h = height - preview = preview.scaled(w, h, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation) - realw = preview.width(); - realh = preview.height() - # and move it to the centre of the preview space - newImage = QtGui.QImage(w, h, QtGui.QImage.Format_ARGB32_Premultiplied) - newImage.fill(QtCore.Qt.black) - painter = QtGui.QPainter(newImage) - painter.drawImage((w - realw) / 2, (h - realh) / 2, preview) - return newImage - def calculate_default(self, screen): """ Calculate the default dimentions of the screen. ``screen`` - The QWidget instance of the screen. + The QSize of the screen. """ log.debug(u'calculate default %s', screen) - #size fixed so reflects the preview size. - if self.current_display == 0: - self.width = 1024 - self.height = 768 - else: - self.width = screen.width() - self.height = screen.height() + self.width = screen.width() + self.height = screen.height() self.screen_ratio = float(self.height) / float(self.width) log.debug(u'calculate default %d, %d, %f', self.width, self.height, self.screen_ratio ) # 90% is start of footer - self.footer_start = int(self.height * 0.90) \ No newline at end of file + self.footer_start = int(self.height * 0.90) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 004973462..7d869a610 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -30,7 +30,9 @@ import uuid from PyQt4 import QtGui -from openlp.core.lib import build_icon, Receiver +from openlp.core.lib import build_icon, Receiver, resize_image + +log = logging.getLogger(__name__) class ServiceItemType(object): """ @@ -46,8 +48,6 @@ class ServiceItem(object): the service manager, the slide controller, and the projection screen compositor. """ - global log - log = logging.getLogger(u'ServiceItem') log.info(u'Service Item created') def __init__(self, plugin=None): @@ -72,6 +72,8 @@ class ServiceItem(object): self._raw_frames = [] self._display_frames = [] self._uuid = unicode(uuid.uuid1()) + self.autoPreviewAllowed = False + self.notes = u'' def addIcon(self, icon): """ @@ -102,16 +104,19 @@ class ServiceItem(object): formated = self.RenderManager.format_slide(slide[u'raw_slide']) for format in formated: lines = u'' + title = u'' for line in format: + if title == u'': + title = line lines += line + u'\n' - title = lines.split(u'\n')[0] self._display_frames.append({u'title': title, \ - u'text': lines, u'verseTag': slide[u'verseTag'] }) + u'text': lines.rstrip(), u'verseTag': slide[u'verseTag'] }) log.log(15, u'Formatting took %4s' % (time.time() - before)) elif self.service_item_type == ServiceItemType.Image: for slide in self._raw_frames: slide[u'image'] = \ - self.RenderManager.resize_image(slide[u'image']) + resize_image(slide[u'image'], self.RenderManager.width, + self.RenderManager.height) elif self.service_item_type == ServiceItemType.Command: pass else: @@ -119,7 +124,7 @@ class ServiceItem(object): def render_individual(self, row): """ - Takes an array of text and geneates an Image from the + Takes an array of text and generates an Image from the theme. It assumes the text will fit on the screen as it has generated by the render method above. """ @@ -129,8 +134,12 @@ class ServiceItem(object): else: self.RenderManager.set_override_theme(self.theme) format = self._display_frames[row][u'text'].split(u'\n') - frame = self.RenderManager.generate_slide(format, - self.raw_footer) + #if screen blank then do not display footer + if format[0]: + frame = self.RenderManager.generate_slide(format, + self.raw_footer) + else: + frame = self.RenderManager.generate_slide(format,u'') return frame def add_from_image(self, path, title, image): @@ -197,7 +206,9 @@ class ServiceItem(object): u'icon':self.icon, u'footer':self.raw_footer, u'type':self.service_item_type, - u'audit':self.audit + u'audit':self.audit, + u'notes':self.notes, + u'preview':self.autoPreviewAllowed } service_data = [] if self.service_item_type == ServiceItemType.Text: @@ -231,6 +242,8 @@ class ServiceItem(object): self.addIcon(header[u'icon']) self.raw_footer = header[u'footer'] self.audit = header[u'audit'] + self.autoPreviewAllowed = header[u'preview'] + self.notes = header[u'notes'] if self.service_item_type == ServiceItemType.Text: for slide in serviceitem[u'serviceitem'][u'data']: self._raw_frames.append(slide) @@ -309,4 +322,4 @@ class ServiceItem(object): def request_audit(self): if self.audit: - Receiver.send_message(u'songusage_live', self.audit) \ No newline at end of file + Receiver.send_message(u'songusage_live', self.audit) diff --git a/openlp/core/lib/settingsmanager.py b/openlp/core/lib/settingsmanager.py index 580ec9b31..be5c14af1 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -33,7 +33,7 @@ class SettingsManager(object): individual components. """ def __init__(self, screen): - self.screen = screen[0] + self.screen = screen.current self.width = self.screen[u'size'].width() self.height = self.screen[u'size'].height() self.mainwindow_height = self.height * 0.8 @@ -72,4 +72,4 @@ class SettingsManager(object): u'media manager', isVisible) def togglePreviewPanel(self, isVisible): - ConfigHelper.set_config(u'user interface', u'preview panel', isVisible) \ No newline at end of file + ConfigHelper.set_config(u'user interface', u'preview panel', isVisible) diff --git a/openlp/core/lib/songxmlhandler.py b/openlp/core/lib/songxmlhandler.py index 596866934..7a532970d 100644 --- a/openlp/core/lib/songxmlhandler.py +++ b/openlp/core/lib/songxmlhandler.py @@ -27,6 +27,8 @@ import logging from xml.dom.minidom import Document from xml.etree.ElementTree import ElementTree, XML, dump +log = logging.getLogger(__name__) + class SongXMLBuilder(object): """ This class builds the XML used to describe songs. @@ -42,8 +44,6 @@ class SongXMLBuilder(object): """ - global log - log = logging.getLogger(u'SongXMLBuilder') log.info(u'SongXMLBuilder Loaded') def __init__(self): @@ -123,8 +123,6 @@ class SongXMLParser(object): """ - global log - log = logging.getLogger(u'SongXMLParser') log.info(u'SongXMLParser Loaded') def __init__(self, xml): @@ -158,4 +156,4 @@ class SongXMLParser(object): """ Debugging aid to dump XML so that we can see what we have. """ - return dump(self.song_xml) \ No newline at end of file + return dump(self.song_xml) diff --git a/openlp/core/lib/themexmlhandler.py b/openlp/core/lib/themexmlhandler.py index 697c161e2..cbd46d597 100644 --- a/openlp/core/lib/themexmlhandler.py +++ b/openlp/core/lib/themexmlhandler.py @@ -171,7 +171,8 @@ class ThemeXML(object): self.child_element(background, u'filename', filename) def add_font(self, name, color, proportion, override, fonttype=u'main', - weight=u'Normal', italics=u'False', indentation=0, xpos=0, ypos=0, width=0, height=0): + weight=u'Normal', italics=u'False', indentation=0, xpos=0, ypos=0, + width=0, height=0): """ Add a Font. @@ -363,14 +364,14 @@ class ThemeXML(object): master = u'' for element in iter: element.text = unicode(element.text).decode('unicode-escape') - if len(element.getchildren()) > 0: + if element.getchildren(): master = element.tag + u'_' else: #background transparent tags have no children so special case if element.tag == u'background': for e in element.attrib.iteritems(): setattr(self, element.tag + u'_' + e[0], e[1]) - if len(element.attrib) > 0: + if element.attrib: for e in element.attrib.iteritems(): if master == u'font_' and e[0] == u'type': master += e[1] + u'_' @@ -402,4 +403,4 @@ class ThemeXML(object): for key in dir(self): if key[0:1] != u'_': theme_strings.append(u'%30s: %s' % (key, getattr(self, key))) - return u'\n'.join(theme_strings) \ No newline at end of file + return u'\n'.join(theme_strings) diff --git a/openlp/core/lib/toolbar.py b/openlp/core/lib/toolbar.py index d4985fd70..3753fedc0 100644 --- a/openlp/core/lib/toolbar.py +++ b/openlp/core/lib/toolbar.py @@ -29,6 +29,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon +log = logging.getLogger(__name__) + class OpenLPToolbar(QtGui.QToolBar): """ Lots of toolbars around the place, so it makes sense to have a common way @@ -43,8 +45,7 @@ class OpenLPToolbar(QtGui.QToolBar): self.icons = {} self.setIconSize(QtCore.QSize(20, 20)) self.actions = {} - self.log = logging.getLogger(u'OpenLPToolbar') - self.log.debug(u'Init done') + log.debug(u'Init done') def addToolbarButton(self, title, icon, tooltip=None, slot=None, checkable=False): @@ -119,7 +120,7 @@ class OpenLPToolbar(QtGui.QToolBar): if self.icons[title]: return self.icons[title] else: - self.log.error(u'getIconFromTitle - no icon for %s' % title) + log.error(u'getIconFromTitle - no icon for %s' % title) return QtGui.QIcon() def makeWidgetsInvisible(self, widgets): @@ -152,4 +153,4 @@ class OpenLPToolbar(QtGui.QToolBar): push_button.setCheckable(True) push_button.setFlat(True) self.addWidget(push_button) - return push_button \ No newline at end of file + return push_button diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 92ffbfed2..27969c9d9 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -30,7 +30,7 @@ from PyQt4 import QtGui DelphiColors={"clRed":0xFF0000, "clBlue":0x0000FF, - "clYellow":0x0FFFF00, + "clYellow":0xFFFF00, "clBlack":0x000000, "clWhite":0xFFFFFF} @@ -113,6 +113,7 @@ class Theme(object): root = ElementTree(element=XML(xml)) iter = root.getiterator() for element in iter: + delphiColorChange = False if element.tag != u'Theme': t = element.text val = 0 @@ -128,6 +129,7 @@ class Theme(object): pass elif DelphiColors.has_key(t): val = DelphiColors[t] + delphiColorChange = True else: try: val = int(t) @@ -136,7 +138,10 @@ class Theme(object): if (element.tag.find(u'Color') > 0 or (element.tag.find(u'BackgroundParameter') == 0 and type(val) == type(0))): # convert to a wx.Colour - val = QtGui.QColor((val>>16) & 0xFF, (val>>8)&0xFF, val&0xFF) + if not delphiColorChange: + val = QtGui.QColor(val&0xFF, (val>>8)&0xFF, (val>>16)&0xFF) + else: + val = QtGui.QColor((val>>16)&0xFF, (val>>8)&0xFF, val&0xFF) setattr(self, element.tag, val) def __str__(self): diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 6b187f5fc..c2f571c3b 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -23,16 +23,15 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -#from slidecontroller import MasterToolbar +from serviceitemform import ServiceItemNoteForm +from screen import ScreenList from maindisplay import MainDisplay from amendthemeform import AmendThemeForm from slidecontroller import SlideController from splashscreen import SplashScreen -from alertstab import AlertsTab from generaltab import GeneralTab from themestab import ThemesTab from aboutform import AboutForm -from alertform import AlertForm from pluginform import PluginForm from settingsform import SettingsForm from mediadockmanager import MediaDockManager @@ -42,4 +41,4 @@ from mainwindow import MainWindow __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', - 'AmendThemeForm', 'MediaDockManager', 'ThemeLevel'] \ No newline at end of file + 'AmendThemeForm', 'MediaDockManager', 'ServiceItemNoteForm'] diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index d6a97e2c9..c3eb7bdcb 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -25,7 +25,6 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import build_icon from aboutdialog import Ui_AboutDialog class AboutForm(QtGui.QDialog, Ui_AboutDialog): diff --git a/openlp/core/ui/alertform.py b/openlp/core/ui/alertform.py deleted file mode 100644 index 4b099b954..000000000 --- a/openlp/core/ui/alertform.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 -from PyQt4 import QtCore, QtGui -from openlp.core.lib import build_icon - -class AlertForm(QtGui.QDialog): - global log - log = logging.getLogger(u'AlertForm') - - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.parent = parent - self.setupUi(self) - log.debug(u'Defined') - - def setupUi(self, AlertForm): - AlertForm.setObjectName(u'AlertForm') - AlertForm.resize(370, 110) - icon = build_icon(u':/icon/openlp-logo-16x16.png') - AlertForm.setWindowIcon(icon) - self.AlertFormLayout = QtGui.QVBoxLayout(AlertForm) - self.AlertFormLayout.setSpacing(8) - self.AlertFormLayout.setMargin(8) - self.AlertFormLayout.setObjectName(u'AlertFormLayout') - self.AlertEntryWidget = QtGui.QWidget(AlertForm) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.AlertEntryWidget.sizePolicy().hasHeightForWidth()) - self.AlertEntryWidget.setSizePolicy(sizePolicy) - self.AlertEntryWidget.setObjectName(u'AlertEntryWidget') - self.AlertEntryLabel = QtGui.QLabel(self.AlertEntryWidget) - self.AlertEntryLabel.setGeometry(QtCore.QRect(0, 0, 353, 16)) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.AlertEntryLabel.sizePolicy().hasHeightForWidth()) - self.AlertEntryLabel.setSizePolicy(sizePolicy) - self.AlertEntryLabel.setObjectName(u'AlertEntryLabel') - self.AlertEntryEditItem = QtGui.QLineEdit(self.AlertEntryWidget) - self.AlertEntryEditItem.setGeometry(QtCore.QRect(0, 20, 353, 26)) - self.AlertEntryEditItem.setObjectName(u'AlertEntryEditItem') - self.AlertFormLayout.addWidget(self.AlertEntryWidget) - self.ButtonBoxWidget = QtGui.QWidget(AlertForm) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ButtonBoxWidget.sizePolicy().hasHeightForWidth()) - self.ButtonBoxWidget.setSizePolicy(sizePolicy) - self.ButtonBoxWidget.setObjectName(u'ButtonBoxWidget') - self.horizontalLayout = QtGui.QHBoxLayout(self.ButtonBoxWidget) - self.horizontalLayout.setSpacing(8) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(u'horizontalLayout') - spacerItem = QtGui.QSpacerItem(267, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem) - self.DisplayButton = QtGui.QPushButton(self.ButtonBoxWidget) - self.DisplayButton.setObjectName(u'DisplayButton') - self.horizontalLayout.addWidget(self.DisplayButton) - self.CancelButton = QtGui.QPushButton(self.ButtonBoxWidget) - self.CancelButton.setObjectName(u'CancelButton') - self.horizontalLayout.addWidget(self.CancelButton) - self.AlertFormLayout.addWidget(self.ButtonBoxWidget) - - self.retranslateUi(AlertForm) - - QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL(u'clicked()'), AlertForm.close) - QtCore.QObject.connect(self.DisplayButton, QtCore.SIGNAL(u'clicked()'), self.onDisplayClicked) - QtCore.QMetaObject.connectSlotsByName(AlertForm) - - def retranslateUi(self, AlertForm): - AlertForm.setWindowTitle(self.trUtf8('Alert Message')) - self.AlertEntryLabel.setText(self.trUtf8('Alert Text:')) - self.DisplayButton.setText(self.trUtf8('Display')) - self.CancelButton.setText(self.trUtf8('Cancel')) - - def onDisplayClicked(self): - self.parent.mainDisplay.displayAlert(unicode(self.AlertEntryEditItem.text())) \ No newline at end of file diff --git a/openlp/core/ui/amendthemeform.py b/openlp/core/ui/amendthemeform.py index 24a22a7cf..97eecd1e8 100644 --- a/openlp/core/ui/amendthemeform.py +++ b/openlp/core/ui/amendthemeform.py @@ -694,8 +694,14 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): if self.allowPreview: #calculate main number of rows metrics = self._getThemeMetrics() + line_height = metrics.height() + if self.theme.display_shadow: + line_height += int(self.theme.display_shadow_size) + if self.theme.display_outline: + # pixels top/bottom + line_height += 2 * int(self.theme.display_outline_size) page_length = \ - (self.FontMainHeightSpinBox.value() / metrics.height() - 2) - 1 + ((self.FontMainHeightSpinBox.value()) / line_height ) log.debug(u'Page Length area height %s, metrics %s, lines %s' % (self.FontMainHeightSpinBox.value(), metrics.height(), page_length)) @@ -719,4 +725,4 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): if self.theme.font_main_width < metrics.maxWidth() * 2 + 64: self.theme.font_main_width = metrics.maxWidth() * 2 + 64 self.FontMainWidthSpinBox.setValue(self.theme.font_main_width) - return metrics \ No newline at end of file + return metrics diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index b5abc2a2e..c65ea3dd0 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -87,6 +87,10 @@ class GeneralTab(SettingsTab): self.SaveCheckServiceCheckBox.setObjectName(u'SaveCheckServiceCheckBox') self.SettingsLayout.addWidget(self.SaveCheckServiceCheckBox) self.GeneralLeftLayout.addWidget(self.SettingsGroupBox) + self.AutoPreviewCheckBox = QtGui.QCheckBox(self.SettingsGroupBox) + self.AutoPreviewCheckBox.setObjectName(u'AutoPreviewCheckBox') + self.SettingsLayout.addWidget(self.AutoPreviewCheckBox) + self.GeneralLeftLayout.addWidget(self.SettingsGroupBox) self.GeneralLeftSpacer = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.GeneralLeftLayout.addItem(self.GeneralLeftSpacer) @@ -137,6 +141,8 @@ class GeneralTab(SettingsTab): QtCore.SIGNAL(u'stateChanged(int)'), self.onShowSplashCheckBoxChanged) QtCore.QObject.connect(self.SaveCheckServiceCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onSaveCheckServiceCheckBox) + QtCore.QObject.connect(self.AutoPreviewCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), self.onAutoPreviewCheckBox) QtCore.QObject.connect(self.NumberEdit, QtCore.SIGNAL(u'editingFinished()'), self.onNumberEditLostFocus) QtCore.QObject.connect(self.UsernameEdit, @@ -153,6 +159,7 @@ class GeneralTab(SettingsTab): self.ShowSplashCheckBox.setText(self.trUtf8('Show the splash screen')) self.SettingsGroupBox.setTitle(self.trUtf8('Application Settings')) self.SaveCheckServiceCheckBox.setText(self.trUtf8('Prompt to save Service before starting New')) + self.AutoPreviewCheckBox.setText(self.trUtf8('Preview Next Song from Service Manager')) self.CCLIGroupBox.setTitle(self.trUtf8('CCLI Details')) self.NumberLabel.setText(self.trUtf8('CCLI Number:')) self.UsernameLabel.setText(self.trUtf8('SongSelect Username:')) @@ -173,6 +180,9 @@ class GeneralTab(SettingsTab): def onSaveCheckServiceCheckBox(self, value): self.PromptSaveService = (value == QtCore.Qt.Checked) + def onAutoPreviewCheckBox(self, value): + self.AutoPreview = (value == QtCore.Qt.Checked) + def onNumberEditLostFocus(self): self.CCLINumber = self.NumberEdit.displayText() @@ -183,7 +193,7 @@ class GeneralTab(SettingsTab): self.Password = self.PasswordEdit.displayText() def load(self): - for screen in self.screen_list: + for screen in self.screen_list.screen_list: screen_name = u'%s %d' % (self.trUtf8('Screen'), screen[u'number'] + 1) if screen[u'primary']: screen_name = u'%s (%s)' % (screen_name, self.trUtf8('primary')) @@ -194,6 +204,7 @@ class GeneralTab(SettingsTab): self.AutoOpen = str_to_bool(self.config.get_config(u'auto open', u'False')) self.ShowSplash = str_to_bool(self.config.get_config(u'show splash', u'True')) self.PromptSaveService = str_to_bool(self.config.get_config(u'save prompt', u'False')) + self.AutoPreview = str_to_bool(self.config.get_config(u'auto preview', u'False')) self.CCLINumber = unicode(self.config.get_config(u'ccli number', u'')) self.Username = unicode(self.config.get_config(u'songselect username', u'')) self.Password = unicode(self.config.get_config(u'songselect password', u'')) @@ -203,6 +214,7 @@ class GeneralTab(SettingsTab): self.WarningCheckBox.setChecked(self.Warning) self.AutoOpenCheckBox.setChecked(self.AutoOpen) self.ShowSplashCheckBox.setChecked(self.ShowSplash) + self.AutoPreviewCheckBox.setChecked(self.AutoPreview) self.NumberEdit.setText(self.CCLINumber) self.UsernameEdit.setText(self.Username) self.PasswordEdit.setText(self.Password) @@ -213,6 +225,7 @@ class GeneralTab(SettingsTab): self.config.set_config(u'auto open', self.AutoOpen) self.config.set_config(u'show splash', self.ShowSplash) self.config.set_config(u'save prompt', self.PromptSaveService) + self.config.set_config(u'auto preview', self.AutoPreview) self.config.set_config(u'ccli number', self.CCLINumber) self.config.set_config(u'songselect username', self.Username) - self.config.set_config(u'songselect password', self.Password) \ No newline at end of file + self.config.set_config(u'songselect password', self.Password) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 46da99ac2..510b0ecc7 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -25,25 +25,29 @@ import logging import os -import time from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import Receiver +from openlp.core.lib import Receiver, resize_image + +log = logging.getLogger(__name__) class DisplayWidget(QtGui.QWidget): """ Customised version of QTableWidget which can respond to keyboard events. """ - global log - log = logging.getLogger(u'MainDisplay') log.info(u'MainDisplay loaded') def __init__(self, parent=None, name=None): QtGui.QWidget.__init__(self, parent) self.parent = parent + self.hotkey_map = {QtCore.Qt.Key_Return: 'servicemanager_next_item', + QtCore.Qt.Key_Space: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_Enter: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_0: 'servicemanager_next_item', + QtCore.Qt.Key_Backspace: 'live_slidecontroller_previous_noloop'} def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: @@ -60,6 +64,9 @@ class DisplayWidget(QtGui.QWidget): elif event.key() == QtCore.Qt.Key_PageDown: Receiver.send_message(u'live_slidecontroller_last') event.accept() + elif event.key() in self.hotkey_map: + Receiver.send_message(self.hotkey_map[event.key()]) + event.accept() elif event.key() == QtCore.Qt.Key_Escape: self.resetDisplay() event.accept() @@ -71,8 +78,6 @@ class MainDisplay(DisplayWidget): """ This is the form that is used to display things on the projector. """ - global log - log = logging.getLogger(u'MainDisplay') log.info(u'MainDisplay Loaded') def __init__(self, parent, screens): @@ -90,31 +95,26 @@ class MainDisplay(DisplayWidget): self.parent = parent self.setWindowTitle(u'OpenLP Display') self.screens = screens - self.layout = QtGui.QVBoxLayout(self) - self.layout.setSpacing(0) - self.layout.setMargin(0) - self.layout.setObjectName(u'layout') self.mediaObject = Phonon.MediaObject(self) self.video = Phonon.VideoWidget() self.video.setVisible(False) self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) - self.layout.insertWidget(0, self.video) - self.display = QtGui.QLabel(self) - self.display.setScaledContents(True) - self.layout.insertWidget(0, self.display) + self.display_image = QtGui.QLabel(self) + self.display_image.setScaledContents(True) + self.display_text = QtGui.QLabel(self) + self.display_text.setScaledContents(True) + self.display_alert = QtGui.QLabel(self) + self.display_alert.setScaledContents(True) self.primary = True self.displayBlank = False self.blankFrame = None self.frame = None - self.alertactive = False - self.timer_id = 0 self.firstTime = True self.mediaLoaded = False self.hasTransition = False - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'alert_text'), self.displayAlert) + self.mediaBackground = False QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_slide_hide'), self.hideDisplay) QtCore.QObject.connect(Receiver.get_receiver(), @@ -126,11 +126,10 @@ class MainDisplay(DisplayWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_play'), self.onMediaPlay) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'media_pause'), self.onMediaPaws) + QtCore.SIGNAL(u'media_pause'), self.onMediaPause) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'media_stop'), self.onMediaStop) - def setup(self, screenNumber): """ Sets up the screen on a particular screen. @@ -138,54 +137,88 @@ class MainDisplay(DisplayWidget): """ log.debug(u'Setup %s for %s ' %(self.screens, screenNumber)) self.setVisible(False) - screen = self.screens[screenNumber] - if screen[u'number'] != screenNumber: - # We will most probably never actually hit this bit, but just in - # case the index in the list doesn't match the screen number, we - # search for it. - for scrn in self.screens: - if scrn[u'number'] == screenNumber: - screen = scrn - break - self.setGeometry(screen[u'size']) + self.screen = self.screens.current + #Sort out screen locations and sizes + self.setGeometry(self.screen[u'size']) + self.display_alert.setGeometry(self.screen[u'size']) + self.video.setGeometry(self.screen[u'size']) + self.display_image.resize(self.screen[u'size'].width(), + self.screen[u'size'].height()) + self.display_text.resize(self.screen[u'size'].width(), + self.screen[u'size'].height()) #Build a custom splash screen self.InitialFrame = QtGui.QImage( - screen[u'size'].width(), screen[u'size'].height(), + self.screen[u'size'].width(), + self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) splash_image = QtGui.QImage(u':/graphics/openlp-splash-screen.png') painter_image = QtGui.QPainter() painter_image.begin(self.InitialFrame) painter_image.fillRect(self.InitialFrame.rect(), QtCore.Qt.white) painter_image.drawImage( - (screen[u'size'].width() - splash_image.width()) / 2, - (screen[u'size'].height() - splash_image.height()) / 2, + (self.screen[u'size'].width() - splash_image.width()) / 2, + (self.screen[u'size'].height() - splash_image.height()) / 2, splash_image) - self.frameView(self.InitialFrame) + self.display_image.setPixmap(QtGui.QPixmap.fromImage(self.InitialFrame)) + self.repaint() #Build a Black screen painter = QtGui.QPainter() self.blankFrame = QtGui.QImage( - screen[u'size'].width(), screen[u'size'].height(), + self.screen[u'size'].width(), + self.screen[u'size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter.begin(self.blankFrame) - painter.fillRect(self.blankFrame.rect(), QtCore.Qt.black) + #TODO make black when testing finished + painter.fillRect(self.blankFrame.rect(), QtCore.Qt.red) + #build a blank transparent image + self.transparent = QtGui.QPixmap(self.screen[u'size'].width(), + self.screen[u'size'].height()) + self.transparent.fill(QtCore.Qt.transparent) + self.display_alert.setPixmap(self.transparent) + self.frameView(self.transparent) # To display or not to display? - if not screen[u'primary']: + if not self.screen[u'primary']: self.showFullScreen() self.primary = False else: self.setVisible(False) self.primary = True + Receiver.send_message(u'screen_changed') def resetDisplay(self): + Receiver.send_message(u'stop_display_loop') if self.primary: self.setVisible(False) + else: + self.showFullScreen() def hideDisplay(self): + self.mediaLoaded = True self.setVisible(False) def showDisplay(self): + self.mediaLoaded = False if not self.primary: self.setVisible(True) + self.showFullScreen() + Receiver.send_message(u'flush_alert') + + def addImageWithText(self, frame): + frame = resize_image(frame, + self.screen[u'size'].width(), + self.screen[u'size'].height() ) + self.display_image.setPixmap(QtGui.QPixmap.fromImage(frame)) + + def setAlertSize(self, top, height): + self.display_alert.setGeometry( + QtCore.QRect(0, top, + self.screen[u'size'].width(), height)) + + def addAlertImage(self, frame, blank=False): + if blank: + self.display_alert.setPixmap(self.transparent) + else: + self.display_alert.setPixmap(frame) def frameView(self, frame, transition=False): """ @@ -194,87 +227,43 @@ class MainDisplay(DisplayWidget): ``frame`` Image frame to be rendered """ - if self.timer_id != 0 : - self.displayAlert() - elif not self.displayBlank: + if not self.displayBlank: if transition: - if self.hasTransition: - if self.frame[u'trans'] is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame[u'trans'])) - self.repaint() - if frame[u'trans'] is not None: - self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) - self.repaint() - self.hasTransition = True - self.display.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) + if self.frame is not None: + self.display_text.setPixmap(QtGui.QPixmap.fromImage(self.frame)) + self.repaint() + self.frame = None + if frame[u'trans'] is not None: + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame[u'trans'])) + self.repaint() + self.frame = frame[u'trans'] + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame[u'main'])) + self.display_frame = frame[u'main'] self.repaint() else: - self.display.setPixmap(QtGui.QPixmap.fromImage(frame)) + if isinstance(frame, QtGui.QPixmap): + self.display_text.setPixmap(frame) + else: + self.display_text.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.display_frame = frame if not self.isVisible(): self.setVisible(True) self.showFullScreen() - self.frame = frame def blankDisplay(self, blanked=True): if blanked: self.displayBlank = True - self.display.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) + self.display_text.setPixmap(QtGui.QPixmap.fromImage(self.blankFrame)) else: self.displayBlank = False - if self.frame: - self.frameView(self.frame) - if blanked != self.parent.LiveController.blankButton.isChecked(): - self.parent.LiveController.blankButton.setChecked(self.displayBlank) - self.parent.generalConfig.set_config(u'screen blank', self.displayBlank) - - def displayAlert(self, text=u''): - """ - Called from the Alert Tab to display an alert - - ``text`` - display text - """ - log.debug(u'display alert called %s' % text) - alertTab = self.parent.settingsForm.AlertsTab - if isinstance(self.frame, QtGui.QImage): - alertframe = QtGui.QPixmap.fromImage(self.frame) - else: - alertframe = QtGui.QPixmap.fromImage(self.frame[u'main']) - painter = QtGui.QPainter(alertframe) - top = alertframe.rect().height() * 0.9 - painter.fillRect( - QtCore.QRect( - 0, top, alertframe.rect().width(), - alertframe.rect().height() - top), - QtGui.QColor(alertTab.bg_color)) - font = QtGui.QFont() - font.setFamily(alertTab.font_face) - font.setBold(True) - font.setPointSize(40) - painter.setFont(font) - painter.setPen(QtGui.QColor(alertTab.font_color)) - x, y = (0, top) - metrics = QtGui.QFontMetrics(font) - painter.drawText( - x, y + metrics.height() - metrics.descent() - 1, text) - painter.end() - self.display.setPixmap(alertframe) - # check to see if we have a timer running - if self.timer_id == 0: - self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) - - def timerEvent(self, event): - if event.timerId() == self.timer_id: - if isinstance(self.frame, QtGui.QImage): - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame)) - else: - self.display.setPixmap(QtGui.QPixmap.fromImage(self.frame[u'main'])) - self.killTimer(self.timer_id) - self.timer_id = 0 + if self.display_frame: + self.frameView(self.display_frame) def onMediaQueue(self, message): log.debug(u'Queue new media message %s' % message) - self.display.close() + self.display_image.close() + self.display_text.close() + self.display_alert.close() file = os.path.join(message[1], message[2]) if self.firstTime: self.mediaObject.setCurrentSource(Phonon.MediaSource(file)) @@ -287,29 +276,33 @@ class MainDisplay(DisplayWidget): log.debug(u'Play the new media, Live ') if not self.mediaLoaded and not self.displayBlank: self.blankDisplay() + self.display_frame = self.blankFrame self.firstTime = True self.mediaLoaded = True - self.display.hide() + self.display_image.hide() + self.display_text.hide() + self.display_alert.hide() self.video.setFullScreen(True) self.video.setVisible(True) self.mediaObject.play() - if self.primary: - self.setVisible(True) + self.setVisible(True) + self.hide() - def onMediaPaws(self): + def onMediaPause(self): log.debug(u'Media paused by user') self.mediaObject.pause() def onMediaStop(self): log.debug(u'Media stopped by user') self.mediaObject.stop() + self.onMediaFinish() def onMediaFinish(self): log.debug(u'Reached end of media playlist') - if self.primary: - self.setVisible(False) self.mediaObject.stop() self.mediaObject.clearQueue() self.mediaLoaded = False self.video.setVisible(False) - self.display.show() \ No newline at end of file + self.display_text.show() + self.display_image.show() + self.blankDisplay(False) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 854811484..41b6d245f 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -25,31 +25,55 @@ import os import logging +import time from PyQt4 import QtCore, QtGui -from openlp.core.ui import AboutForm, SettingsForm, AlertForm, \ +from openlp.core.ui import AboutForm, SettingsForm, \ ServiceManager, ThemeManager, MainDisplay, SlideController, \ PluginForm, MediaDockManager from openlp.core.lib import RenderManager, PluginConfig, build_icon, \ OpenLPDockWidget, SettingsManager, PluginManager, Receiver, str_to_bool -from openlp.core.utils import check_latest_version +from openlp.core.utils import check_latest_version, AppLocation + +log = logging.getLogger(__name__) media_manager_style = """ QToolBox::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(midlight), stop: 1.0 palette(mid)); + stop: 0 palette(button), stop: 1.0 palette(dark)); border-width: 1px; border-style: outset; - border-color: palette(midlight); + border-color: palette(dark); border-radius: 5px; } QToolBox::tab:selected { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(light), stop: 1.0 palette(mid)); - border-color: palette(light); + stop: 0 palette(light), stop: 1.0 palette(button)); + border-color: palette(button); } """ +class VersionThread(QtCore.QThread): + """ + A special Qt thread class to fetch the version of OpenLP from the website. + This is threaded so that it doesn't affect the loading time of OpenLP. + """ + def __init__(self, parent, app_version, generalConfig): + QtCore.QThread.__init__(self, parent) + self.parent = parent + self.app_version = app_version + self.generalConfig = generalConfig + + def run(self): + """ + Run the thread. + """ + time.sleep(2) + version = check_latest_version(self.generalConfig, self.app_version) + #new version has arrived + if version != self.app_version: + Receiver.send_message(u'version_check', u'%s' % version) + class Ui_MainWindow(object): def setupUi(self, MainWindow): @@ -218,13 +242,8 @@ class Ui_MainWindow(object): self.settingsmanager.showServiceManager) self.ViewServiceManagerItem.setIcon(ServiceManagerIcon) self.ViewServiceManagerItem.setObjectName(u'ViewServiceManagerItem') - self.ToolsAlertItem = QtGui.QAction(MainWindow) - AlertIcon = build_icon(u':/tools/tools_alert.png') - self.ToolsAlertItem.setIcon(AlertIcon) - self.ToolsAlertItem.setObjectName(u'ToolsAlertItem') self.PluginItem = QtGui.QAction(MainWindow) - #PluginIcon = build_icon(u':/tools/tools_alert.png') - self.PluginItem.setIcon(AlertIcon) + #self.PluginItem.setIcon(AlertIcon) self.PluginItem.setObjectName(u'PluginItem') self.HelpDocumentationItem = QtGui.QAction(MainWindow) ContentsIcon = build_icon(u':/system/system_help_contents.png') @@ -283,7 +302,6 @@ class Ui_MainWindow(object): self.OptionsMenu.addAction(self.OptionsViewMenu.menuAction()) self.OptionsMenu.addSeparator() self.OptionsMenu.addAction(self.OptionsSettingsItem) - self.ToolsMenu.addAction(self.ToolsAlertItem) self.ToolsMenu.addAction(self.PluginItem) self.ToolsMenu.addSeparator() self.ToolsMenu.addAction(self.ToolsAddToolItem) @@ -385,9 +403,6 @@ class Ui_MainWindow(object): self.action_Preview_Panel.setStatusTip( self.trUtf8('Toggle the visibility of the Preview Panel')) self.action_Preview_Panel.setShortcut(self.trUtf8('F11')) - self.ToolsAlertItem.setText(self.trUtf8('&Alert')) - self.ToolsAlertItem.setStatusTip(self.trUtf8('Show an alert message')) - self.ToolsAlertItem.setShortcut(self.trUtf8('F7')) self.PluginItem.setText(self.trUtf8('&Plugin List')) self.PluginItem.setStatusTip(self.trUtf8('List the Plugins')) self.PluginItem.setShortcut(self.trUtf8('Alt+F7')) @@ -415,8 +430,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ The main window. """ - global log - log = logging.getLogger(u'MainWindow') log.info(u'MainWindow loaded') def __init__(self, screens, applicationVersion): @@ -425,19 +438,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): plugins. """ QtGui.QMainWindow.__init__(self) - self.screenList = screens + self.screens = screens self.applicationVersion = applicationVersion self.serviceNotSaved = False self.settingsmanager = SettingsManager(screens) self.generalConfig = PluginConfig(u'General') self.mainDisplay = MainDisplay(self, screens) - self.alertForm = AlertForm(self) self.aboutForm = AboutForm(self, applicationVersion) - self.settingsForm = SettingsForm(self.screenList, self, self) + self.settingsForm = SettingsForm(self.screens, self, self) # Set up the path with plugins - pluginpath = os.path.split(os.path.abspath(__file__))[0] - pluginpath = os.path.abspath( - os.path.join(pluginpath, u'..', u'..', u'plugins')) + pluginpath = AppLocation.get_directory(AppLocation.PluginsDir) self.plugin_manager = PluginManager(pluginpath) self.plugin_helpers = {} # Set up the interface @@ -476,14 +486,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.action_Preview_Panel.setChecked) QtCore.QObject.connect(self.HelpAboutItem, QtCore.SIGNAL(u'triggered()'), self.onHelpAboutItemClicked) - QtCore.QObject.connect(self.ToolsAlertItem, - QtCore.SIGNAL(u'triggered()'), self.onToolsAlertItemClicked) QtCore.QObject.connect(self.PluginItem, QtCore.SIGNAL(u'triggered()'), self.onPluginItemClicked) QtCore.QObject.connect(self.OptionsSettingsItem, QtCore.SIGNAL(u'triggered()'), self.onOptionsSettingsItemClicked) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'update_global_theme'), self.defaultThemeChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'version_check'), self.versionCheck) QtCore.QObject.connect(self.FileNewItem, QtCore.SIGNAL(u'triggered()'), self.ServiceManagerContents.onNewService) @@ -500,7 +510,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): #RenderManager needs to call ThemeManager and #ThemeManager needs to call RenderManager self.RenderManager = RenderManager(self.ThemeManagerContents, - self.screenList, self.getMonitorNumber()) + self.screens, self.getMonitorNumber()) #Define the media Dock Manager self.mediaDockManager = MediaDockManager(self.MediaToolBox) log.info(u'Load Plugins') @@ -511,6 +521,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.plugin_helpers[u'service'] = self.ServiceManagerContents self.plugin_helpers[u'settings'] = self.settingsForm self.plugin_helpers[u'toolbox'] = self.mediaDockManager + self.plugin_helpers[u'maindisplay'] = self.mainDisplay self.plugin_manager.find_plugins(pluginpath, self.plugin_helpers) # hook methods have to happen after find_plugins. Find plugins needs # the controllers hence the hooks have moved from setupUI() to here @@ -536,20 +547,18 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): log.info(u'Load data from Settings') self.settingsForm.postSetUp() - def versionCheck(self): + def versionCheck(self, version): """ Checks the version of the Application called from openlp.pyw """ app_version = self.applicationVersion[u'full'] - version = check_latest_version(self.generalConfig, app_version) - if app_version != version: - version_text = unicode(self.trUtf8('OpenLP version %s has been updated ' - 'to version %s\n\nYou can obtain the latest version from http://openlp.org')) - QtGui.QMessageBox.question(None, - self.trUtf8('OpenLP Version Updated'), - version_text % (app_version, version), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), - QtGui.QMessageBox.Ok) + version_text = unicode(self.trUtf8('OpenLP version %s has been updated ' + 'to version %s\n\nYou can obtain the latest version from http://openlp.org')) + QtGui.QMessageBox.question(self, + self.trUtf8('OpenLP Version Updated'), + version_text % (app_version, version), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok) def getMonitorNumber(self): """ @@ -558,11 +567,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): monitor number does not exist. """ screen_number = int(self.generalConfig.get_config(u'monitor', 0)) - monitor_exists = False - for screen in self.screenList: - if screen[u'number'] == screen_number: - monitor_exists = True - if not monitor_exists: + if not self.screens.screen_exists(screen_number): screen_number = 0 return screen_number @@ -580,12 +585,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.ServiceManagerContents.onLoadService(True) if str_to_bool(self.generalConfig.get_config(u'screen blank', False)) \ and str_to_bool(self.generalConfig.get_config(u'blank warning', False)): - QtGui.QMessageBox.question(None, + self.LiveController.onBlankDisplay(True) + QtGui.QMessageBox.question(self, self.trUtf8('OpenLP Main Display Blanked'), self.trUtf8('The Main Display has been blanked out'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok) - self.LiveController.blankButton.setChecked(True) + + def versionThread(self): + app_version = self.applicationVersion[u'full'] + vT = VersionThread(self, app_version, self.generalConfig) + vT.start() def onHelpAboutItemClicked(self): """ @@ -594,12 +604,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.aboutForm.applicationVersion = self.applicationVersion self.aboutForm.exec_() - def onToolsAlertItemClicked(self): - """ - Show the Alert form - """ - self.alertForm.exec_() - def onPluginItemClicked(self): """ Show the Plugin form @@ -613,7 +617,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ self.settingsForm.exec_() updated_display = self.getMonitorNumber() - if updated_display != self.RenderManager.current_display: + if updated_display != self.screens.current_display: + self.screens.set_current_display(updated_display) self.RenderManager.update_display(updated_display) self.mainDisplay.setup(updated_display) self.activateWindow() @@ -623,7 +628,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): Hook to close the main window and display windows on exit """ if self.serviceNotSaved: - ret = QtGui.QMessageBox.question(None, + ret = QtGui.QMessageBox.question(self, self.trUtf8('Save Changes to Service?'), self.trUtf8('Your service has changed, do you want to save those changes?'), QtGui.QMessageBox.StandardButtons( @@ -704,4 +709,4 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): def togglePreviewPanel(self): previewBool = self.PreviewController.Panel.isVisible() self.PreviewController.Panel.setVisible(not previewBool) - self.settingsmanager.togglePreviewPanel(not previewBool) \ No newline at end of file + self.settingsmanager.togglePreviewPanel(not previewBool) diff --git a/openlp/core/ui/mediadockmanager.py b/openlp/core/ui/mediadockmanager.py index 4d76075df..0873133b2 100644 --- a/openlp/core/ui/mediadockmanager.py +++ b/openlp/core/ui/mediadockmanager.py @@ -25,7 +25,7 @@ import logging -log = logging.getLogger(u'MediaDockManager') +log = logging.getLogger(__name__) class MediaDockManager(object): @@ -58,4 +58,4 @@ class MediaDockManager(object): if self.media_dock.widget(dock_index): if self.media_dock.widget(dock_index).ConfigSection == name: self.media_dock.widget(dock_index).hide() - self.media_dock.removeItem(dock_index) \ No newline at end of file + self.media_dock.removeItem(dock_index) diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index fa48c0723..9af8b7ca3 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -30,9 +30,9 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib.plugin import PluginStatus from plugindialog import Ui_PluginViewDialog +log = logging.getLogger(__name__) + class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): - global log - log = logging.getLogger(u'PluginForm') def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent) @@ -126,4 +126,4 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog): elif self.activePlugin.status == PluginStatus.Disabled: status_text = 'Disabled' self.PluginListWidget.currentItem().setText( - u'%s (%s)' % (self.activePlugin.name, status_text)) \ No newline at end of file + u'%s (%s)' % (self.activePlugin.name, status_text)) diff --git a/openlp/core/ui/screen.py b/openlp/core/ui/screen.py new file mode 100644 index 000000000..b85b3b003 --- /dev/null +++ b/openlp/core/ui/screen.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 + +log = logging.getLogger(__name__) + +class ScreenList(object): + """ + Wrapper to handle the parameters of the display screen + """ + log.info(u'Screen loaded') + + def __init__(self): + self.preview = None + self.current = None + self.screen_list = [] + self.count = 0 + self.current_display = 0 + + def add_screen(self, screen): + if screen[u'primary']: + self.current = screen + self.screen_list.append(screen) + self.count += 1 + + def screen_exists(self, number): + for screen in self.screen_list: + if screen[u'number'] == number: + return True + return False + + def set_current_display(self, number): + if number + 1 > self.count: + self.current = self.screen_list[0] + self.current_display = 0 + else: + self.current = self.screen_list[number] + self.preview = self.current + self.current_display = number + if self.count == 1: + self.preview = self.screen_list[0] + +# if self.screen[u'number'] != screenNumber: +# # We will most probably never actually hit this bit, but just in +# # case the index in the list doesn't match the screen number, we +# # search for it. +# for scrn in self.screens: +# if scrn[u'number'] == screenNumber: +# self.screen = scrn +# break diff --git a/openlp/core/ui/serviceitemdialog.py b/openlp/core/ui/serviceitemdialog.py new file mode 100644 index 000000000..1fe86a913 --- /dev/null +++ b/openlp/core/ui/serviceitemdialog.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 + +class Ui_ServiceNoteEdit(object): + def setupUi(self, ServiceNoteEdit): + ServiceNoteEdit.setObjectName(u'ServiceNoteEdit') + ServiceNoteEdit.resize(400, 243) + self.widget = QtGui.QWidget(ServiceNoteEdit) + self.widget.setGeometry(QtCore.QRect(20, 10, 361, 223)) + self.widget.setObjectName(u'widget') + self.verticalLayout = QtGui.QVBoxLayout(self.widget) + self.verticalLayout.setObjectName(u'verticalLayout') + self.textEdit = QtGui.QTextEdit(self.widget) + self.textEdit.setObjectName(u'textEdit') + self.verticalLayout.addWidget(self.textEdit) + self.buttonBox = QtGui.QDialogButtonBox(self.widget) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) + self.buttonBox.setObjectName(u'buttonBox') + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(ServiceNoteEdit) + QtCore.QMetaObject.connectSlotsByName(ServiceNoteEdit) + + def retranslateUi(self, ServiceNoteEdit): + ServiceNoteEdit.setWindowTitle(self.trUtf8('Service Item Notes')) diff --git a/openlp/core/ui/serviceitemform.py b/openlp/core/ui/serviceitemform.py new file mode 100644 index 000000000..43011ead8 --- /dev/null +++ b/openlp/core/ui/serviceitemform.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 serviceitemdialog import Ui_ServiceNoteEdit + +class ServiceItemNoteForm(QtGui.QDialog, Ui_ServiceNoteEdit): + """ + This is the form that is used to edit the verses of the song. + """ + def __init__(self, parent=None): + """ + Constructor + """ + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'accepted()'), + self.accept) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), + self.reject) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 7e9fca933..a0a198f2d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -28,10 +28,13 @@ import logging import cPickle import zipfile +log = logging.getLogger(__name__) + from PyQt4 import QtCore, QtGui + from openlp.core.lib import PluginConfig, OpenLPToolbar, ServiceItem, \ - ServiceItemType, contextMenuAction, contextMenuSeparator, contextMenu, \ - Receiver, contextMenu, str_to_bool + contextMenuAction, Receiver, str_to_bool, build_icon +from openlp.core.ui import ServiceItemNoteForm class ServiceManagerList(QtGui.QTreeWidget): @@ -39,23 +42,6 @@ class ServiceManagerList(QtGui.QTreeWidget): QtGui.QTreeWidget.__init__(self,parent) self.parent = parent -# def mousePressEvent(self, event): -# if event.button() == QtCore.Qt.RightButton: -# item = self.itemAt(event.pos()) -# parentitem = item.parent() -# if parentitem is None: -# pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] -# else: -# pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] -# serviceItem = self.parent.serviceItems[pos - 1] -# if serviceItem[u'data'].edit_enabled: -# self.parent.editAction.setVisible(True) -# else: -# self.parent.editAction.setVisible(False) -# event.accept() -# else: -# event.ignore() - def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: #here accept the event and do something @@ -91,6 +77,7 @@ class ServiceManagerList(QtGui.QTreeWidget): just tell it what plugin to call """ if event.buttons() != QtCore.Qt.LeftButton: + event.ignore() return drag = QtGui.QDrag(self) mimeData = QtCore.QMimeData() @@ -105,9 +92,6 @@ class ServiceManager(QtGui.QWidget): the resources used into one OSZ file for use on any OpenLP v2 installation. Also handles the UI tasks of moving things up and down etc. """ - global log - log = logging.getLogger(u'ServiceManager') - def __init__(self, parent): """ Sets up the service manager, toolbars, list view, et al. @@ -121,6 +105,7 @@ class ServiceManager(QtGui.QWidget): #Indicates if remoteTriggering is active. If it is the next addServiceItem call #will replace the currently selected one. self.remoteEditTriggered = False + self.serviceItemNoteForm = ServiceItemNoteForm() #start with the layout self.Layout = QtGui.QVBoxLayout(self) self.Layout.setSpacing(0) @@ -161,62 +146,38 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList.setAlternatingRowColors(True) self.ServiceManagerList.setHeaderHidden(True) self.ServiceManagerList.setExpandsOnDoubleClick(False) + self.ServiceManagerList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.ServiceManagerList.customContextMenuRequested.connect(self.contextMenu) self.ServiceManagerList.setObjectName(u'ServiceManagerList') # enable drop self.ServiceManagerList.__class__.dragEnterEvent = self.dragEnterEvent self.ServiceManagerList.__class__.dragMoveEvent = self.dragEnterEvent self.ServiceManagerList.__class__.dropEvent = self.dropEvent - # Add a context menu to the service manager list - self.ServiceManagerList.setContextMenuPolicy( - QtCore.Qt.ActionsContextMenu) - self.editAction = contextMenuAction( - self.ServiceManagerList, ':/system/system_live.png', - self.trUtf8('&Edit Item'), self.remoteEdit) - self.ServiceManagerList.addAction(self.editAction) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/system/system_preview.png', - self.trUtf8('&Preview Verse'), self.makePreview)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/system/system_live.png', - self.trUtf8('&Show Live'), self.makeLive)) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.ServiceManagerList.addAction(contextMenuAction( - self.ServiceManagerList, ':/services/service_delete', - self.trUtf8('&Remove from Service'), self.onDeleteFromService)) - self.ServiceManagerList.addAction(contextMenuSeparator( - self.ServiceManagerList)) - self.ThemeMenu = contextMenu( - self.ServiceManagerList, '', - self.trUtf8('&Change Item Theme')) - self.ServiceManagerList.addAction(self.ThemeMenu.menuAction()) self.Layout.addWidget(self.ServiceManagerList) # Add the bottom toolbar self.OrderToolbar = OpenLPToolbar(self) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move to top'), u':/services/service_top.png', + self.trUtf8('Move to &top'), u':/services/service_top.png', self.trUtf8('Move to top'), self.onServiceTop) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move up'), u':/services/service_up.png', + self.trUtf8('Move &up'), u':/services/service_up.png', self.trUtf8('Move up order'), self.onServiceUp) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move down'), u':/services/service_down.png', + self.trUtf8('Move &down'), u':/services/service_down.png', self.trUtf8('Move down order'), self.onServiceDown) self.OrderToolbar.addToolbarButton( - self.trUtf8('Move to bottom'), u':/services/service_bottom.png', + self.trUtf8('Move to &bottom'), u':/services/service_bottom.png', self.trUtf8('Move to end'), self.onServiceEnd) self.OrderToolbar.addSeparator() self.OrderToolbar.addToolbarButton( - self.trUtf8('Delete From Service'), u':/services/service_delete.png', + self.trUtf8('&Delete From Service'), u':/services/service_delete.png', self.trUtf8('Delete From Service'), self.onDeleteFromService) self.Layout.addWidget(self.OrderToolbar) # Connect up our signals and slots QtCore.QObject.connect(self.ThemeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onThemeComboBoxSelected) QtCore.QObject.connect(self.ServiceManagerList, - QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive) + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive) QtCore.QObject.connect(self.ServiceManagerList, QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'), self.collapsed) QtCore.QObject.connect(self.ServiceManagerList, @@ -225,18 +186,99 @@ class ServiceManager(QtGui.QWidget): QtCore.SIGNAL(u'update_themes'), self.updateThemeList) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'remote_edit_clear'), self.onRemoteEditClear) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'presentation types'), self.onPresentationTypes) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'servicemanager_next_item'), self.nextItem) # Last little bits of setting up self.config = PluginConfig(u'ServiceManager') self.servicePath = self.config.get_data_path() self.service_theme = unicode( self.config.get_config(u'service theme', u'')) + #build the context menu + self.menu = QtGui.QMenu() + self.editAction = self.menu.addAction(self.trUtf8('&Edit Item')) + self.editAction.setIcon(build_icon(u':/services/service_edit.png')) + self.notesAction = self.menu.addAction(self.trUtf8('&Notes')) + self.notesAction.setIcon(build_icon(u':/services/service_notes.png')) + self.deleteAction = self.menu.addAction(self.trUtf8('&Delete From Service')) + self.deleteAction.setIcon(build_icon(u':/services/service_delete.png')) + self.sep1 = self.menu.addAction(u'') + self.sep1.setSeparator(True) + self.previewAction = self.menu.addAction(self.trUtf8('&Preview Verse')) + self.previewAction.setIcon(build_icon(u':/system/system_preview.png')) + self.liveAction = self.menu.addAction(self.trUtf8('&Live Verse')) + self.liveAction.setIcon(build_icon(u':/system/system_live.png')) + self.sep2 = self.menu.addAction(u'') + self.sep2.setSeparator(True) + self.themeMenu = QtGui.QMenu(self.trUtf8(u'&Change Item Theme')) + self.menu.addMenu(self.themeMenu) + + def contextMenu(self, point): + item = self.ServiceManagerList.itemAt(point) + if item.parent() is None: + pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] + else: + pos = item.parent().data(0, QtCore.Qt.UserRole).toInt()[0] + serviceItem = self.serviceItems[pos - 1] + self.editAction.setVisible(False) + self.notesAction.setVisible(False) + if serviceItem[u'service_item'].edit_enabled: + self.editAction.setVisible(True) + if item.parent() is None: + self.notesAction.setVisible(True) + self.themeMenu.menuAction().setVisible(False) + if serviceItem[u'service_item'].is_text(): + self.themeMenu.menuAction().setVisible(True) + action = self.menu.exec_(self.ServiceManagerList.mapToGlobal(point)) + if action == self.editAction: + self.remoteEdit() + if action == self.deleteAction: + self.onDeleteFromService() + if action == self.notesAction: + self.onServiceItemNoteForm() + if action == self.previewAction: + self.makePreview() + if action == self.liveAction: + self.makeLive() + + def onPresentationTypes(self, presentation_types): + self.presentation_types = presentation_types + + def onServiceItemNoteForm(self): + item, count = self.findServiceItem() + self.serviceItemNoteForm.textEdit.setPlainText( + self.serviceItems[item][u'service_item'].notes) + if self.serviceItemNoteForm.exec_(): + self.serviceItems[item][u'service_item'].notes = \ + self.serviceItemNoteForm.textEdit.toPlainText() + self.repaintServiceList(item, 0) + + def nextItem(self): + """ + Called by the SlideController to select the + next service item + """ + if len(self.ServiceManagerList.selectedItems()) == 0: + return + selected = self.ServiceManagerList.selectedItems()[0] + lookFor = 0 + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) + while serviceIterator.value(): + if lookFor == 1 and serviceIterator.value().parent() is None: + self.ServiceManagerList.setCurrentItem(serviceIterator.value()) + self.makeLive() + return + if serviceIterator.value() == selected: + lookFor = 1 + serviceIterator += 1 def onMoveSelectionUp(self): """ Moves the selection up the window Called by the up arrow """ - serviceIterator = QTreeWidgetItemIterator(self.ServiceManagerList) + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) tempItem = None setLastItem = False while serviceIterator: @@ -261,7 +303,7 @@ class ServiceManager(QtGui.QWidget): Moves the selection down the window Called by the down arrow """ - serviceIterator = QTreeWidgetItemIterator(self.ServiceManagerList) + serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList) firstItem = serviceIterator setSelected = False while serviceIterator: @@ -348,7 +390,7 @@ class ServiceManager(QtGui.QWidget): if self.parent.serviceNotSaved and \ str_to_bool(PluginConfig(u'General'). get_config(u'save prompt', u'False')): - ret = QtGui.QMessageBox.question(None, + ret = QtGui.QMessageBox.question(self, self.trUtf8('Save Changes to Service?'), self.trUtf8('Your service is unsaved, do you want to save those ' 'changes before creating a new one ?'), @@ -390,19 +432,35 @@ class ServiceManager(QtGui.QWidget): for itemcount, item in enumerate(self.serviceItems): serviceitem = item[u'service_item'] treewidgetitem = QtGui.QTreeWidgetItem(self.ServiceManagerList) - treewidgetitem.setText(0,serviceitem.title) - treewidgetitem.setIcon(0,serviceitem.iconic_representation) + if serviceitem.notes: + icon = QtGui.QImage(serviceitem.icon) + icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + overlay = QtGui.QImage(':/services/service_item_notes.png') + overlay = overlay.scaled(80, 80, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation) + painter = QtGui.QPainter(icon) + painter.drawImage(0, 0, overlay) + painter.end() + treewidgetitem.setIcon(0, build_icon(icon)) + else: + treewidgetitem.setIcon(0, serviceitem.iconic_representation) + treewidgetitem.setText(0, serviceitem.title) + treewidgetitem.setToolTip(0, serviceitem.notes) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) - treewidgetitem.setExpanded(item[u'expanded']) for count, frame in enumerate(serviceitem.get_frames()): treewidgetitem1 = QtGui.QTreeWidgetItem(treewidgetitem) text = frame[u'title'] - treewidgetitem1.setText(0,text[:40]) + treewidgetitem1.setText(0, text[:40]) treewidgetitem1.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) if serviceItem == itemcount and serviceItemCount == count: - self.ServiceManagerList.setCurrentItem(treewidgetitem1) + #preserve expanding status as setCurrentItem sets it to True + temp = item[u'expanded'] + self.ServiceManagerList.setCurrentItem(treewidgetitem1) + item[u'expanded'] = temp + treewidgetitem.setExpanded(item[u'expanded']) def onSaveService(self, quick=False): """ @@ -433,10 +491,10 @@ class ServiceManager(QtGui.QWidget): for item in self.serviceItems: service.append({u'serviceitem':item[u'service_item'].get_service_repr()}) if item[u'service_item'].uses_file(): - for frame in item[u'service_item'].get_frames: + for frame in item[u'service_item'].get_frames(): path_from = unicode(os.path.join( item[u'service_item'].service_item_path, - frame.get_frame_title())) + frame[u'title'])) zip.write(path_from) file = open(servicefile, u'wb') cPickle.dump(service, file) @@ -499,7 +557,8 @@ class ServiceManager(QtGui.QWidget): serviceitem = ServiceItem() serviceitem.RenderManager = self.parent.RenderManager serviceitem.set_from_service(item, self.servicePath) - self.addServiceItem(serviceitem) + if self.validateItem(serviceitem): + self.addServiceItem(serviceitem) try: if os.path.isfile(p_file): os.remove(p_file) @@ -516,6 +575,14 @@ class ServiceManager(QtGui.QWidget): self.serviceName = name[len(name) - 1] self.parent.serviceChanged(True, self.serviceName) + def validateItem(self, serviceItem): +# print "---" +# print serviceItem.name +# print serviceItem.title +# print serviceItem.service_item_path +# print serviceItem.service_item_type + return True + def cleanUp(self): """ Empties the servicePath of temporary files @@ -538,13 +605,20 @@ class ServiceManager(QtGui.QWidget): self.regenerateServiceItems() def regenerateServiceItems(self): - if len(self.serviceItems) > 0: + #force reset of renderer as theme data has changed + self.parent.RenderManager.themedata = None + if self.serviceItems: tempServiceItems = self.serviceItems - self.onNewService() + self.ServiceManagerList.clear() + self.serviceItems = [] + self.isNew = True for item in tempServiceItems: - self.addServiceItem(item[u'service_item']) + self.addServiceItem(item[u'service_item'], False, item[u'expanded']) + #Set to False as items may have changed rendering + #does not impact the saved song so True may aslo be valid + self.parent.serviceChanged(False, self.serviceName) - def addServiceItem(self, item): + def addServiceItem(self, item, rebuild=False, expand=True): """ Add a Service item to the list @@ -564,13 +638,16 @@ class ServiceManager(QtGui.QWidget): if sitem == -1: self.serviceItems.append({u'service_item': item, u'order': len(self.serviceItems) + 1, - u'expanded':True}) + u'expanded':expand}) self.repaintServiceList(len(self.serviceItems) + 1, 0) else: self.serviceItems.insert(sitem + 1, {u'service_item': item, u'order': len(self.serviceItems)+1, - u'expanded':True}) + u'expanded':expand}) self.repaintServiceList(sitem + 1, 0) + #if rebuilding list make sure live is fixed. + if rebuild: + self.parent.LiveController.replaceServiceManagerItem(item) self.parent.serviceChanged(False, self.serviceName) def makePreview(self): @@ -581,6 +658,7 @@ class ServiceManager(QtGui.QWidget): self.parent.PreviewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) + def makeLive(self): """ Send the current item to the Live slide controller @@ -588,6 +666,13 @@ class ServiceManager(QtGui.QWidget): item, count = self.findServiceItem() self.parent.LiveController.addServiceManagerItem( self.serviceItems[item][u'service_item'], count) + if str_to_bool(PluginConfig(u'General'). + get_config(u'auto preview', u'False')): + item += 1 + if self.serviceItems and item < len(self.serviceItems) and \ + self.serviceItems[item][u'service_item'].autoPreviewAllowed: + self.parent.PreviewController.addServiceManagerItem( + self.serviceItems[item][u'service_item'], 0) def remoteEdit(self): """ @@ -617,7 +702,7 @@ class ServiceManager(QtGui.QWidget): else: pos = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] count = item.data(0, QtCore.Qt.UserRole).toInt()[0] - #adjuest for zero based arrays + #adjust for zero based arrays pos = pos - 1 return pos, count @@ -646,7 +731,7 @@ class ServiceManager(QtGui.QWidget): if plugin == u'ServiceManager': startpos, startCount = self.findServiceItem() item = self.ServiceManagerList.itemAt(event.pos()) - if item == None: + if item is None: endpos = len(self.serviceItems) else: parentitem = item.parent() @@ -674,7 +759,7 @@ class ServiceManager(QtGui.QWidget): A list of current themes to be displayed """ self.ThemeComboBox.clear() - self.ThemeMenu.clear() + self.themeMenu.clear() self.ThemeComboBox.addItem(u'') for theme in theme_list: self.ThemeComboBox.addItem(theme) @@ -682,7 +767,7 @@ class ServiceManager(QtGui.QWidget): self.ServiceManagerList, None, theme , self.onThemeChangeAction) - self.ThemeMenu.addAction(action) + self.themeMenu.addAction(action) id = self.ThemeComboBox.findText(self.service_theme, QtCore.Qt.MatchExactly) # Not Found @@ -697,4 +782,4 @@ class ServiceManager(QtGui.QWidget): theme = unicode(self.sender().text()) item, count = self.findServiceItem() self.serviceItems[item][u'service_item'].theme = theme - self.regenerateServiceItems() \ No newline at end of file + self.regenerateServiceItems() diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index ed5bd9d76..4a3902347 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -27,11 +27,11 @@ import logging from PyQt4 import QtGui -from openlp.core.ui import GeneralTab, ThemesTab, AlertsTab +from openlp.core.ui import GeneralTab, ThemesTab from openlp.core.lib import Receiver from settingsdialog import Ui_SettingsDialog -log = logging.getLogger(u'SettingsForm') +log = logging.getLogger(__name__) class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): @@ -44,9 +44,6 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): # Themes tab self.ThemesTab = ThemesTab(mainWindow) self.addTab(u'Themes', self.ThemesTab) - # Alert tab - self.AlertsTab = AlertsTab() - self.addTab(u'Alerts', self.AlertsTab) def addTab(self, name, tab): log.info(u'Adding %s tab' % tab.tabTitle) @@ -73,4 +70,4 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog): def postSetUp(self): for tab_index in range(0, self.SettingsTabWidget.count()): - self.SettingsTabWidget.widget(tab_index).postSetUp() \ No newline at end of file + self.SettingsTabWidget.widget(tab_index).postSetUp() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 59dd857ac..e26df7a18 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -30,7 +30,10 @@ import os from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, PluginConfig +from openlp.core.lib import OpenLPToolbar, Receiver, str_to_bool, \ + PluginConfig, resize_image + +log = logging.getLogger(__name__) class SlideList(QtGui.QTableWidget): """ @@ -40,6 +43,11 @@ class SlideList(QtGui.QTableWidget): def __init__(self, parent=None, name=None): QtGui.QTableWidget.__init__(self, parent.Controller) self.parent = parent + self.hotkey_map = {QtCore.Qt.Key_Return: 'servicemanager_next_item', + QtCore.Qt.Key_Space: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_Enter: 'live_slidecontroller_next_noloop', + QtCore.Qt.Key_0: 'servicemanager_next_item', + QtCore.Qt.Key_Backspace: 'live_slidecontroller_previous_noloop'} def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: @@ -56,6 +64,9 @@ class SlideList(QtGui.QTableWidget): elif event.key() == QtCore.Qt.Key_PageDown: self.parent.onSlideSelectedLast() event.accept() + elif event.key() in self.hotkey_map and self.parent.isLive: + Receiver.send_message(self.hotkey_map[event.key()]) + event.accept() event.ignore() else: event.ignore() @@ -65,9 +76,6 @@ class SlideController(QtGui.QWidget): SlideController is the slide controller widget. This widget is what the user uses to control the displaying of verses/slides/etc on the screen. """ - global log - log = logging.getLogger(u'SlideController') - def __init__(self, parent, settingsmanager, isLive=False): """ Set up the Slide Controller. @@ -99,15 +107,14 @@ class SlideController(QtGui.QWidget): # Type label for the top of the slide controller self.TypeLabel = QtGui.QLabel(self.Panel) if self.isLive: - self.TypeLabel.setText(u'%s' % - self.trUtf8('Live')) + self.TypeLabel.setText(self.trUtf8('Live')) self.split = 1 prefix = u'live_slidecontroller' else: - self.TypeLabel.setText(u'%s' % - self.trUtf8('Preview')) + self.TypeLabel.setText(self.trUtf8('Preview')) self.split = 0 prefix = u'preview_slidecontroller' + self.TypeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;') self.TypeLabel.setAlignment(QtCore.Qt.AlignCenter) self.PanelLayout.addWidget(self.TypeLabel) # Splitter @@ -135,6 +142,7 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setEditTriggers( QtGui.QAbstractItemView.NoEditTriggers) self.PreviewListWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.PreviewListWidget.setAlternatingRowColors(True) self.ControllerLayout.addWidget(self.PreviewListWidget) # Build the full toolbar self.Toolbar = OpenLPToolbar(self) @@ -163,9 +171,9 @@ class SlideController(QtGui.QWidget): self.Toolbar.addToolbarSeparator(u'Close Separator') self.blankButton = self.Toolbar.addToolbarButton( u'Blank Screen', u':/slides/slide_close.png', - self.trUtf8('Blank Screen'), self.onBlankScreen, True) + self.trUtf8('Blank Screen'), self.onBlankDisplay, True) QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'live_slide_blank'), self.onBlankDisplay) + QtCore.SIGNAL(u'live_slide_blank'), self.blankScreen) if not self.isLive: self.Toolbar.addToolbarSeparator(u'Close Separator') self.Toolbar.addToolbarButton( @@ -173,7 +181,7 @@ class SlideController(QtGui.QWidget): self.trUtf8('Move to live'), self.onGoLive) self.Toolbar.addToolbarSeparator(u'Close Separator') self.Toolbar.addToolbarButton( - u'Edit Song', u':/songs/song_edit.png', + u'Edit Song', u':/services/service_edit.png', self.trUtf8('Edit and re-preview Song'), self.onEditSong) if isLive: self.Toolbar.addToolbarSeparator(u'Loop Separator') @@ -184,6 +192,8 @@ class SlideController(QtGui.QWidget): u'Stop Loop', u':/media/media_stop.png', self.trUtf8('Stop continuous loop'), self.onStopLoop) self.DelaySpinBox = QtGui.QSpinBox() + self.DelaySpinBox.setMinimum(1) + self.DelaySpinBox.setMaximum(180) self.Toolbar.addToolbarWidget( u'Image SpinBox', self.DelaySpinBox) self.DelaySpinBox.setSuffix(self.trUtf8('s')) @@ -235,6 +245,9 @@ class SlideController(QtGui.QWidget): self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) Phonon.createPath(self.mediaObject, self.video) Phonon.createPath(self.mediaObject, self.audio) + if not self.isLive: + self.video.setGeometry(QtCore.QRect(0, 0, 300, 225)) + self.video.setVisible(False) self.SlideLayout.insertWidget(0, self.video) # Actual preview screen self.SlidePreview = QtGui.QLabel(self) @@ -246,7 +259,8 @@ class SlideController(QtGui.QWidget): self.SlidePreview.sizePolicy().hasHeightForWidth()) self.SlidePreview.setSizePolicy(sizePolicy) self.SlidePreview.setFixedSize( - QtCore.QSize(self.settingsmanager.slidecontroller_image,self.settingsmanager.slidecontroller_image / 1.3 )) + QtCore.QSize(self.settingsmanager.slidecontroller_image, + self.settingsmanager.slidecontroller_image / 1.3 )) self.SlidePreview.setFrameShape(QtGui.QFrame.Box) self.SlidePreview.setFrameShadow(QtGui.QFrame.Plain) self.SlidePreview.setLineWidth(1) @@ -257,8 +271,6 @@ class SlideController(QtGui.QWidget): # Signals QtCore.QObject.connect(self.PreviewListWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected) - QtCore.QObject.connect(self.PreviewListWidget, - QtCore.SIGNAL(u'activated(QModelIndex)'), self.onSlideSelected) if isLive: QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'update_spin_delay'), self.receiveSpinDelay) @@ -268,12 +280,19 @@ class SlideController(QtGui.QWidget): else: self.Toolbar.makeWidgetsInvisible(self.song_edit_list) self.Mediabar.setVisible(False) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'stop_display_loop'), self.onStopLoop) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_first' % prefix), self.onSlideSelectedFirst) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_next' % prefix), self.onSlideSelectedNext) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_previous' % prefix), self.onSlideSelectedPrevious) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_next_noloop' % prefix), self.onSlideSelectedNextNoloop) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_previous_noloop' % prefix), + self.onSlideSelectedPreviousNoloop) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_last' % prefix), self.onSlideSelectedLast) QtCore.QObject.connect(Receiver.get_receiver(), @@ -393,9 +412,13 @@ class SlideController(QtGui.QWidget): if item.is_media(): self.onMediaStart(item) elif item.is_command(): + if self.isLive: + blanked = self.blankButton.isChecked() + else: + blanked = False Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), slideno, self.isLive]) + item.get_frame_title(), slideno, self.isLive, blanked]) self.displayServiceManagerItems(item, slideno) def displayServiceManagerItems(self, serviceItem, slideno): @@ -428,9 +451,11 @@ class SlideController(QtGui.QWidget): tag = None #If verse handle verse number else tag only if bits[0] == self.trUtf8('Verse'): - tag = u'%s%s' % (bits[0][0], bits[1][0] ) + tag = u'%s%s' % (bits[0][0], bits[1][0:] ) + row = bits[1][0:] else: tag = bits[0] + row = bits[0][0:1] try: test = self.slideList[tag] except: @@ -441,7 +466,9 @@ class SlideController(QtGui.QWidget): else: label = QtGui.QLabel() label.setMargin(4) - pixmap = self.parent.RenderManager.resize_image(frame[u'image']) + pixmap = resize_image(frame[u'image'], + self.parent.RenderManager.width, + self.parent.RenderManager.height) label.setScaledContents(True) label.setPixmap(QtGui.QPixmap.fromImage(pixmap)) self.PreviewListWidget.setCellWidget(framenumber, 0, label) @@ -480,18 +507,26 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.selectRow(0) self.onSlideSelected() - def onBlankDisplay(self): - self.blankButton.setChecked(self.parent.mainDisplay.displayBlank) + def onBlankDisplay(self, force=False): + """ + Handle the blank screen button + """ + if force: + self.blankButton.setChecked(True) + self.blankScreen(self.blankButton.isChecked()) + self.parent.generalConfig.set_config(u'screen blank', + self.blankButton.isChecked()) - def onBlankScreen(self, blanked): + def blankScreen(self, blanked=False): """ - Blank the screen. + Blank the display screen. """ - if not self.serviceItem and self.serviceItem.is_command(): - if blanked: - Receiver.send_message(u'%s_blank'% self.serviceItem.name.lower()) - else: - Receiver.send_message(u'%s_unblank'% self.serviceItem.name.lower()) + if self.serviceItem is not None: + if self.serviceItem.is_command(): + if blanked: + Receiver.send_message(u'%s_blank'% self.serviceItem.name.lower()) + else: + Receiver.send_message(u'%s_unblank'% self.serviceItem.name.lower()) else: self.parent.mainDisplay.blankDisplay(blanked) @@ -531,7 +566,7 @@ class SlideController(QtGui.QWidget): def updatePreview(self): rm = self.parent.RenderManager - if not rm.screen_list[rm.current_display][u'primary']: + if not rm.screens.current[u'primary']: # Grab now, but try again in a couple of seconds if slide change is slow QtCore.QTimer.singleShot(0.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) @@ -543,12 +578,15 @@ class SlideController(QtGui.QWidget): def grabMainDisplay(self): rm = self.parent.RenderManager winid = QtGui.QApplication.desktop().winId() - rect = rm.screen_list[rm.current_display][u'size'] + rect = rm.screens.current[u'size'] winimg = QtGui.QPixmap.grabWindow(winid, rect.x(), rect.y(), rect.width(), rect.height()) self.SlidePreview.setPixmap(winimg) - def onSlideSelectedNext(self): + def onSlideSelectedNextNoloop(self): + self.onSlideSelectedNext(False) + + def onSlideSelectedNext(self, loop=True): """ Go to the next slide. """ @@ -561,11 +599,18 @@ class SlideController(QtGui.QWidget): else: row = self.PreviewListWidget.currentRow() + 1 if row == self.PreviewListWidget.rowCount(): - row = 0 + if loop: + row = 0 + else: + Receiver.send_message('servicemanager_next_item') + return self.PreviewListWidget.selectRow(row) self.onSlideSelected() - def onSlideSelectedPrevious(self): + def onSlideSelectedPreviousNoloop(self): + self.onSlideSelectedPrevious(False) + + def onSlideSelectedPrevious(self, loop=True): """ Go to the previous slide. """ @@ -578,7 +623,10 @@ class SlideController(QtGui.QWidget): else: row = self.PreviewListWidget.currentRow() - 1 if row == -1: - row = self.PreviewListWidget.rowCount() - 1 + if loop: + row = self.PreviewListWidget.rowCount() - 1 + else: + row = 0 self.PreviewListWidget.selectRow(row) self.onSlideSelected() @@ -635,7 +683,7 @@ class SlideController(QtGui.QWidget): if self.isLive: Receiver.send_message(u'%s_start' % item.name.lower(), \ [item.title, item.service_item_path, - item.get_frame_title(), slideno, self.isLive]) + item.get_frame_title(), self.isLive, self.blankButton.isChecked()]) else: self.mediaObject.stop() self.mediaObject.clearQueue() @@ -659,9 +707,9 @@ class SlideController(QtGui.QWidget): def onMediaStop(self): if self.isLive: - Receiver.send_message(u'%s_stop'% self.serviceItem.name.lower()) + Receiver.send_message(u'%s_stop'% self.serviceItem.name.lower(), self.isLive) else: self.mediaObject.stop() self.video.hide() - self.SlidePreview.clear() - self.SlidePreview.show() \ No newline at end of file + self.SlidePreview.clear() + self.SlidePreview.show() diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index fd0284412..5cad41f58 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -34,17 +34,16 @@ from PyQt4 import QtCore, QtGui from openlp.core.ui import AmendThemeForm from openlp.core.theme import Theme from openlp.core.lib import PluginConfig, OpenLPToolbar, contextMenuAction, \ - ThemeXML, ThemeLevel, str_to_bool, get_text_file_string, build_icon, \ - Receiver, contextMenuSeparator + ThemeXML, str_to_bool, get_text_file_string, build_icon, Receiver, \ + contextMenuSeparator from openlp.core.utils import ConfigHelper +log = logging.getLogger(__name__) + class ThemeManager(QtGui.QWidget): """ Manages the orders of Theme. """ - global log - log = logging.getLogger(u'ThemeManager') - def __init__(self, parent): QtGui.QWidget.__init__(self, parent) self.parent = parent @@ -108,6 +107,8 @@ class ThemeManager(QtGui.QWidget): self.themelist = [] self.path = os.path.join(ConfigHelper.get_data_path(), u'themes') self.checkThemesExists(self.path) + self.thumbPath = os.path.join(self.path, u'.thumbnails') + self.checkThemesExists(self.thumbPath) self.amendThemeForm.path = self.path # Last little bits of setting up self.config = PluginConfig(u'themes') @@ -179,12 +180,26 @@ class ThemeManager(QtGui.QWidget): self.trUtf8('You are unable to delete the default theme!'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) else: + for plugin in self.parent.plugin_manager.plugins: + if not plugin.can_delete_theme(theme): + QtGui.QMessageBox.critical( + self, self.trUtf8('Error'), + self.trUtf8('theme %s is use in %s plugin' % (theme, plugin.name)), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + return + if unicode(self.parent.ServiceManagerContents.ThemeComboBox.currentText()) == theme: + QtGui.QMessageBox.critical( + self, self.trUtf8('Error'), + self.trUtf8('theme %s is use Service Manager' % theme), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + return self.themelist.remove(theme) th = theme + u'.png' row = self.ThemeListWidget.row(item) self.ThemeListWidget.takeItem(row) try: os.remove(os.path.join(self.path, th)) + os.remove(os.path.join(self.thumbPath, th)) shutil.rmtree(os.path.join(self.path, theme)) except: #if not present do not worry @@ -231,9 +246,9 @@ class ThemeManager(QtGui.QWidget): self, self.trUtf8('Select Theme Import File'), self.config.get_last_dir(), u'Theme (*.*)') log.info(u'New Themes %s', unicode(files)) - if len(files) > 0: + if files: for file in files: - self.config.set_last_dir(filename) + self.config.set_last_dir(unicode(file)) self.unzipTheme(file, self.path) self.loadThemes() @@ -246,25 +261,33 @@ class ThemeManager(QtGui.QWidget): log.debug(u'Load themes from dir') self.themelist = [] self.ThemeListWidget.clear() - for root, dirs, files in os.walk(self.path): - for name in files: - if name.endswith(u'.png'): - #check to see file is in theme root directory - theme = os.path.join(self.path, name) - if os.path.exists(theme): - (path, filename) = os.path.split(unicode(file)) - textName = os.path.splitext(name)[0] - if textName == self.global_theme: - name = u'%s (%s)' % (textName, - self.trUtf8('default')) - else: - name = textName - item_name = QtGui.QListWidgetItem(name) - item_name.setIcon(build_icon(theme)) - item_name.setData(QtCore.Qt.UserRole, - QtCore.QVariant(textName)) - self.ThemeListWidget.addItem(item_name) - self.themelist.append(textName) + #root, dirs, files = os.walk(self.path) + dirList = os.listdir(self.path) + for name in dirList: + if name.endswith(u'.png'): + #check to see file is in theme root directory + theme = os.path.join(self.path, name) + if os.path.exists(theme): + (path, filename) = os.path.split(unicode(file)) + textName = os.path.splitext(name)[0] + if textName == self.global_theme: + name = u'%s (%s)' % (textName, + self.trUtf8('default')) + else: + name = textName + thumb = os.path.join(self.thumbPath, u'%s.png' % textName) + item_name = QtGui.QListWidgetItem(name) + if os.path.exists(thumb): + icon = build_icon(thumb) + else: + icon = build_icon(theme) + pixmap = icon.pixmap(QtCore.QSize(88,50)) + pixmap.save(thumb, u'png') + item_name.setIcon(icon) + item_name.setData(QtCore.Qt.UserRole, + QtCore.QVariant(textName)) + self.ThemeListWidget.addItem(item_name) + self.themelist.append(textName) self.pushThemes() def pushThemes(self): @@ -302,17 +325,23 @@ class ThemeManager(QtGui.QWidget): filexml = None themename = None for file in zip.namelist(): - if file.endswith(os.path.sep): - theme_dir = os.path.join(dir, file) + osfile = unicode(QtCore.QDir.toNativeSeparators(file)) + theme_dir = None + if osfile.endswith(os.path.sep): + theme_dir = os.path.join(dir, osfile) if not os.path.exists(theme_dir): - os.mkdir(os.path.join(dir, file)) + os.mkdir(os.path.join(dir, osfile)) else: - fullpath = os.path.join(dir, file) - names = file.split(os.path.sep) + fullpath = os.path.join(dir, osfile) + names = osfile.split(os.path.sep) if len(names) > 1: # not preview file if themename is None: themename = names[0] + if theme_dir is None: + theme_dir = os.path.join(dir, names[0]) + if not os.path.exists(theme_dir): + os.mkdir(os.path.join(dir, names[0])) xml_data = zip.read(file) if os.path.splitext(file)[1].lower() in [u'.xml']: if self.checkVersion1(xml_data): @@ -324,7 +353,7 @@ class ThemeManager(QtGui.QWidget): outfile = open(fullpath, u'w') outfile.write(filexml) else: - outfile = open(fullpath, u'w') + outfile = open(fullpath, u'wb') outfile.write(zip.read(file)) self.generateAndSaveImage(dir, themename, filexml) except: @@ -332,7 +361,7 @@ class ThemeManager(QtGui.QWidget): self, self.trUtf8('Error'), self.trUtf8('File is not a valid theme!'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) - log.exception(u'Importing theme from zip file failed') + log.exception(u'Importing theme from zip file failed %s' % filename) finally: if zip: zip.close() @@ -373,7 +402,6 @@ class ThemeManager(QtGui.QWidget): unicode(theme.BackgroundParameter2.name()), direction) else: newtheme.add_background_image(unicode(theme.BackgroundParameter1)) - newtheme.add_font(unicode(theme.FontName), unicode(theme.FontColor.name()), unicode(theme.FontProportion * 3), u'False') @@ -386,10 +414,15 @@ class ThemeManager(QtGui.QWidget): shadow = True if theme.Outline == 1: outline = True + vAlignCorrection = 0 + if theme.VerticalAlign == 2: + vAlignCorrection = 1 + elif theme.VerticalAlign == 1: + vAlignCorrection = 2 newtheme.add_display(unicode(shadow), unicode(theme.ShadowColor.name()), unicode(outline), unicode(theme.OutlineColor.name()), - unicode(theme.HorizontalAlign), unicode(theme.VerticalAlign), - unicode(theme.WrapStyle), 0) + unicode(theme.HorizontalAlign), unicode(vAlignCorrection), + unicode(theme.WrapStyle), unicode(0)) return newtheme.extract_xml() def saveTheme(self, name, theme_xml, theme_pretty_xml, image_from, @@ -427,8 +460,6 @@ class ThemeManager(QtGui.QWidget): if outfile: outfile.close() if image_from and image_from != image_to: - print "if", image_from - print "it", image_to try: shutil.copyfile(image_from, image_to) except: @@ -448,6 +479,10 @@ class ThemeManager(QtGui.QWidget): if os.path.exists(samplepathname): os.unlink(samplepathname) frame.save(samplepathname, u'png') + thumb = os.path.join(self.thumbPath, u'%s.png' % name) + icon = build_icon(frame) + pixmap = icon.pixmap(QtCore.QSize(88,50)) + pixmap.save(thumb, u'png') log.debug(u'Theme image written to %s', samplepathname) def generateImage(self, themedata): @@ -529,4 +564,4 @@ class ThemeManager(QtGui.QWidget): theme.font_main_y = int(theme.font_main_y.strip()) #theme.theme_mode theme.theme_name = theme.theme_name.strip() - #theme.theme_version \ No newline at end of file + #theme.theme_version diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 962aa69bd..4083300a6 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -22,21 +22,65 @@ # 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 urllib2 from datetime import datetime -from registry import Registry -from confighelper import ConfigHelper - log = logging.getLogger(__name__) -__all__ = ['Registry', 'ConfigHelper'] +class AppLocation(object): + """ + Retrieve a directory based on the directory type. + """ + AppDir = 1 + ConfigDir = 2 + DataDir = 3 + PluginsDir = 4 + + @staticmethod + def get_directory(dir_type): + if dir_type == AppLocation.AppDir: + return os.path.abspath(os.path.split(sys.argv[0])[0]) + elif dir_type == AppLocation.ConfigDir: + if os.name == u'nt': + path = os.path.join(os.getenv(u'APPDATA'), u'openlp') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp') + return path + elif dir_type == AppLocation.DataDir: + if os.name == u'nt': + path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') + elif os.name == u'mac': + path = os.path.join(os.getenv(u'HOME'), u'Library', + u'Application Support', u'openlp', u'Data') + else: + try: + from xdg import BaseDirectory + path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') + except ImportError: + path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') + return path + elif dir_type == AppLocation.PluginsDir: + app_path = os.path.abspath(os.path.split(sys.argv[0])[0]) + if hasattr(sys, u'frozen') and sys.frozen == 1: + return os.path.join(app_path, u'plugins') + else: + return os.path.join(app_path, u'openlp', u'plugins') -log = logging.getLogger(__name__) def check_latest_version(config, current_version): version_string = current_version + #set to prod in the distribution confif file. last_test = config.get_config(u'last version test', datetime.now().date()) this_test = unicode(datetime.now().date()) config.set_config(u'last version test', this_test) @@ -52,3 +96,8 @@ def check_latest_version(config, current_version): if hasattr(e, u'reason'): log.exception(u'Reason for failure: %s', e.reason) return version_string + +from registry import Registry +from confighelper import ConfigHelper + +__all__ = [u'Registry', u'ConfigHelper', u'AppLocations', u'check_latest_version'] diff --git a/openlp/core/utils/confighelper.py b/openlp/core/utils/confighelper.py index 112712675..7920013f2 100644 --- a/openlp/core/utils/confighelper.py +++ b/openlp/core/utils/confighelper.py @@ -24,6 +24,8 @@ ############################################################################### import os + +from openlp.core.utils import AppLocation from openlp.core.utils.registry import Registry class ConfigHelper(object): @@ -34,20 +36,7 @@ class ConfigHelper(object): @staticmethod def get_data_path(): - if os.name == u'nt': - # ask OS for path to application data, set on Windows XP and Vista - path = os.path.join(os.getenv(u'APPDATA'), u'openlp', u'data') - elif os.name == u'mac': - path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp', u'Data') - else: - try: - from xdg import BaseDirectory - path = os.path.join(BaseDirectory.xdg_data_home, u'openlp') - except ImportError: - path = os.path.join(os.getenv(u'HOME'), u'.openlp', u'data') - #reg = ConfigHelper.get_registry() - #path = ConfigHelper.get_config(u'main', 'data path', path) + path = AppLocation.get_directory(AppLocation.DataDir) if not os.path.exists(path): os.makedirs(path) return path @@ -81,17 +70,7 @@ class ConfigHelper(object): current operating system, and returns an instantiation of that class. """ if ConfigHelper.__registry__ is None: - config_path = u'' - if os.name == u'nt': - config_path = os.path.join(os.getenv(u'APPDATA'), u'openlp') - elif os.name == u'mac': - config_path = os.path.join(os.getenv(u'HOME'), u'Library', - u'Application Support', u'openlp') - else: - try: - from xdg import BaseDirectory - config_path = os.path.join(BaseDirectory.xdg_config_home, u'openlp') - except ImportError: - config_path = os.path.join(os.getenv(u'HOME'), u'.openlp') + config_path = AppLocation.get_directory(AppLocation.ConfigDir) ConfigHelper.__registry__ = Registry(config_path) - return ConfigHelper.__registry__ \ No newline at end of file + return ConfigHelper.__registry__ + diff --git a/openlp/migration/display.py b/openlp/migration/display.py index 9a3f6e44e..52951d31f 100644 --- a/openlp/migration/display.py +++ b/openlp/migration/display.py @@ -25,18 +25,18 @@ import logging +log = logging.getLogger(__name__) + class Display(): - global log - log = logging.getLogger(u'Display Logger') log.info(u'Display Class loaded') @staticmethod def output(string): - log.debug(string); - print (string) + log.debug(string) + #print (string) @staticmethod def sub_output(string): if not string is None: - log.debug(u' '+string); - print (u' '+string) \ No newline at end of file + log.debug(u' '+string) + #print (u' '+string) diff --git a/openlp/migration/migratebibles.py b/openlp/migration/migratebibles.py index 92504e907..f9e10b756 100644 --- a/openlp/migration/migratebibles.py +++ b/openlp/migration/migratebibles.py @@ -28,5 +28,5 @@ class MigrateBibles(): self.display = display def process(self): - self.display.output(u'Bible process started'); - self.display.output(u'Bible process finished'); \ No newline at end of file + self.display.output(u'Bible process started') + self.display.output(u'Bible process finished') \ No newline at end of file diff --git a/openlp/migration/migratefiles.py b/openlp/migration/migratefiles.py index 9c38fbb88..f1c9435ab 100644 --- a/openlp/migration/migratefiles.py +++ b/openlp/migration/migratefiles.py @@ -30,20 +30,20 @@ class MigrateFiles(): self.display = display def process(self): - self.display.output(u'Files process started'); + self.display.output(u'Files process started') self._initial_setup() - self.display.output(u'Files process finished'); + self.display.output(u'Files process finished') def _initial_setup(self): - self.display.output(u'Initial Setup started'); + self.display.output(u'Initial Setup started') ConfigHelper.get_data_path() - self.display.sub_output(u'Config created'); + self.display.sub_output(u'Config created') ConfigHelper.get_config(u'bible', u'data path') - self.display.sub_output(u'Config created'); + self.display.sub_output(u'Config created') ConfigHelper.get_config(u'videos', u'data path') - self.display.sub_output(u'videos created'); + self.display.sub_output(u'videos created') ConfigHelper.get_config(u'images', u'data path') - self.display.sub_output(u'images created'); + self.display.sub_output(u'images created') ConfigHelper.get_config(u'presentations', u'data path') - self.display.sub_output(u'presentations created'); - self.display.output(u'Initial Setup finished'); \ No newline at end of file + self.display.sub_output(u'presentations created') + self.display.output(u'Initial Setup finished') \ No newline at end of file diff --git a/openlp/plugins/alerts/__init__.py b/openlp/plugins/alerts/__init__.py new file mode 100644 index 000000000..bc50edda3 --- /dev/null +++ b/openlp/plugins/alerts/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### \ No newline at end of file diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py new file mode 100644 index 000000000..90e7946c7 --- /dev/null +++ b/openlp/plugins/alerts/alertsplugin.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Plugin, build_icon, PluginStatus +from openlp.plugins.alerts.lib import AlertsManager, DBManager +from openlp.plugins.alerts.forms import AlertsTab, AlertForm, AlertEditForm + +log = logging.getLogger(__name__) + +class alertsPlugin(Plugin): + log.info(u'Alerts Plugin loaded') + + def __init__(self, plugin_helpers): + Plugin.__init__(self, u'Alerts', u'1.9.1', plugin_helpers) + self.weight = -3 + self.icon = build_icon(u':/media/media_image.png') + self.alertsmanager = AlertsManager(self) + self.manager = DBManager(self.config) + self.alertForm = AlertForm(self.manager, self) + self.alertEditForm = AlertEditForm(self.manager, self) + self.status = PluginStatus.Active + + def get_settings_tab(self): + self.alertsTab = AlertsTab(self) + return self.alertsTab + + def add_tools_menu_item(self, tools_menu): + """ + Give the alerts plugin the opportunity to add items to the + **Tools** menu. + + ``tools_menu`` + The actual **Tools** menu item, so that your actions can + use it as their parent. + """ + log.info(u'add tools menu') + self.toolsAlertItem = QtGui.QAction(tools_menu) + AlertIcon = build_icon(u':/tools/tools_alert.png') + self.toolsAlertItem.setIcon(AlertIcon) + self.toolsAlertItem.setObjectName(u'toolsAlertItem') + self.toolsAlertItem.setText(self.trUtf8('&Alert')) + self.toolsAlertItem.setStatusTip(self.trUtf8('Show an alert message')) + self.toolsAlertItem.setShortcut(u'F7') + self.service_manager.parent.ToolsMenu.addAction(self.toolsAlertItem) + QtCore.QObject.connect(self.toolsAlertItem, + QtCore.SIGNAL(u'triggered()'), self.onAlertsTrigger) + self.toolsAlertItem.setVisible(False) + + def initialise(self): + log.info(u'Alerts Initialising') + Plugin.initialise(self) + self.toolsAlertItem.setVisible(True) + + def finalise(self): + log.info(u'Plugin Finalise') + self.toolsAlertItem.setVisible(False) + #stop any events being processed + + def togglealertsState(self): + self.alertsActive = not self.alertsActive + self.config.set_config(u'active', self.alertsActive) + + def onAlertsTrigger(self): + self.alertForm.loadList() + self.alertForm.exec_() + + def onAlertsEdit(self): + self.alertEditForm.loadList() + self.alertEditForm.exec_() + + def about(self): + about_text = self.trUtf8('Alerts Plugin
This plugin ' + 'controls the displaying of alerts on the presentations screen') + return about_text diff --git a/openlp/plugins/alerts/forms/__init__.py b/openlp/plugins/alerts/forms/__init__.py new file mode 100644 index 000000000..14c30d73b --- /dev/null +++ b/openlp/plugins/alerts/forms/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 alertstab import AlertsTab +from alertform import AlertForm +from alerteditform import AlertEditForm diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py new file mode 100644 index 000000000..53fc1fff5 --- /dev/null +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'alertform.ui' +# +# Created: Sat Feb 13 08:19:51 2010 +# by: PyQt4 UI code generator 4.6.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_AlertDialog(object): + def setupUi(self, AlertForm): + AlertForm.setObjectName(u'AlertDialog') + AlertForm.resize(430, 320) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(u':/icon/openlp.org-icon-32.bmp'), QtGui.QIcon.Normal, QtGui.QIcon.Off) + AlertForm.setWindowIcon(icon) + self.AlertFormLayout = QtGui.QVBoxLayout(AlertForm) + self.AlertFormLayout.setSpacing(8) + self.AlertFormLayout.setMargin(8) + self.AlertFormLayout.setObjectName(u'AlertFormLayout') + self.AlertEntryWidget = QtGui.QWidget(AlertForm) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.AlertEntryWidget.sizePolicy().hasHeightForWidth()) + self.AlertEntryWidget.setSizePolicy(sizePolicy) + self.AlertEntryWidget.setObjectName(u'AlertEntryWidget') + self.verticalLayout_2 = QtGui.QVBoxLayout(self.AlertEntryWidget) + self.verticalLayout_2.setObjectName(u'verticalLayout_2') + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName(u'verticalLayout') + self.AlertEntryLabel = QtGui.QLabel(self.AlertEntryWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.AlertEntryLabel.sizePolicy().hasHeightForWidth()) + self.AlertEntryLabel.setSizePolicy(sizePolicy) + self.AlertEntryLabel.setObjectName(u'AlertEntryLabel') + self.verticalLayout.addWidget(self.AlertEntryLabel) + self.AlertEntryEditItem = QtGui.QLineEdit(self.AlertEntryWidget) + self.AlertEntryEditItem.setObjectName(u'AlertEntryEditItem') + self.verticalLayout.addWidget(self.AlertEntryEditItem) + self.AlertListWidget = QtGui.QListWidget(self.AlertEntryWidget) + self.AlertListWidget.setAlternatingRowColors(True) + self.AlertListWidget.setObjectName(u'AlertListWidget') + self.verticalLayout.addWidget(self.AlertListWidget) + self.verticalLayout_2.addLayout(self.verticalLayout) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(u'horizontalLayout') + spacerItem = QtGui.QSpacerItem(181, 38, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.DisplayButton = QtGui.QPushButton(self.AlertEntryWidget) + self.DisplayButton.setObjectName(u'DisplayButton') + self.horizontalLayout.addWidget(self.DisplayButton) + self.CancelButton = QtGui.QPushButton(self.AlertEntryWidget) + self.CancelButton.setObjectName(u'CancelButton') + self.horizontalLayout.addWidget(self.CancelButton) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.AlertFormLayout.addWidget(self.AlertEntryWidget) + + self.retranslateUi(AlertForm) + QtCore.QObject.connect(self.CancelButton, QtCore.SIGNAL(u'clicked()'), self.close) + QtCore.QMetaObject.connectSlotsByName(AlertForm) + + def retranslateUi(self, AlertForm): + AlertForm.setWindowTitle(self.trUtf8('Alert Message')) + self.AlertEntryLabel.setText(self.trUtf8('Alert Text:')) + self.DisplayButton.setText(self.trUtf8('Display')) + self.CancelButton.setText(self.trUtf8('Cancel')) diff --git a/openlp/plugins/alerts/forms/alerteditdialog.py b/openlp/plugins/alerts/forms/alerteditdialog.py new file mode 100644 index 000000000..6cf4769ef --- /dev/null +++ b/openlp/plugins/alerts/forms/alerteditdialog.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'alerteditdialog.ui' +# +# Created: Sun Feb 14 16:45:10 2010 +# by: PyQt4 UI code generator 4.6.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_AlertEditDialog(object): + def setupUi(self, AlertEditDialog): + AlertEditDialog.setObjectName(u'AlertEditDialog') + AlertEditDialog.resize(400, 300) + self.buttonBox = QtGui.QDialogButtonBox(AlertEditDialog) + self.buttonBox.setGeometry(QtCore.QRect(220, 270, 173, 27)) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel) + self.buttonBox.setObjectName(u'buttonBox') + self.layoutWidget = QtGui.QWidget(AlertEditDialog) + self.layoutWidget.setGeometry(QtCore.QRect(20, 10, 361, 251)) + self.layoutWidget.setObjectName(u'layoutWidget') + self.verticalLayout_2 = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout_2.setObjectName(u'verticalLayout_2') + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName(u'horizontalLayout_2') + self.AlertLineEdit = QtGui.QLineEdit(self.layoutWidget) + self.AlertLineEdit.setObjectName(u'AlertLineEdit') + self.horizontalLayout_2.addWidget(self.AlertLineEdit) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(u'horizontalLayout') + self.AlertListWidget = QtGui.QListWidget(self.layoutWidget) + self.AlertListWidget.setAlternatingRowColors(True) + self.AlertListWidget.setObjectName(u'AlertListWidget') + self.horizontalLayout.addWidget(self.AlertListWidget) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName(u'verticalLayout') + self.SaveButton = QtGui.QPushButton(self.layoutWidget) + self.SaveButton.setObjectName(u'SaveButton') + self.verticalLayout.addWidget(self.SaveButton) + self.ClearButton = QtGui.QPushButton(self.layoutWidget) + self.ClearButton.setObjectName(u'ClearButton') + self.verticalLayout.addWidget(self.ClearButton) + self.AddButton = QtGui.QPushButton(self.layoutWidget) + self.AddButton.setObjectName(u'AddButton') + self.verticalLayout.addWidget(self.AddButton) + self.EditButton = QtGui.QPushButton(self.layoutWidget) + self.EditButton.setObjectName(u'EditButton') + self.verticalLayout.addWidget(self.EditButton) + self.DeleteButton = QtGui.QPushButton(self.layoutWidget) + self.DeleteButton.setObjectName(u'DeleteButton') + self.verticalLayout.addWidget(self.DeleteButton) + self.horizontalLayout.addLayout(self.verticalLayout) + self.verticalLayout_2.addLayout(self.horizontalLayout) + + self.retranslateUi(AlertEditDialog) + QtCore.QMetaObject.connectSlotsByName(AlertEditDialog) + + def retranslateUi(self, AlertEditDialog): + AlertEditDialog.setWindowTitle(self.trUtf8('Maintain Alerts')) + self.SaveButton.setText(self.trUtf8('Save')) + self.ClearButton.setText(self.trUtf8('Clear')) + self.AddButton.setText(self.trUtf8('Add')) + self.EditButton.setText(self.trUtf8('Edit')) + self.DeleteButton.setText(self.trUtf8('Delete')) + diff --git a/openlp/plugins/alerts/forms/alerteditform.py b/openlp/plugins/alerts/forms/alerteditform.py new file mode 100644 index 000000000..62c129508 --- /dev/null +++ b/openlp/plugins/alerts/forms/alerteditform.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 QtGui, QtCore +from openlp.plugins.alerts.lib.models import AlertItem + +from alerteditdialog import Ui_AlertEditDialog + +class AlertEditForm(QtGui.QDialog, Ui_AlertEditDialog): + """ + Class documentation goes here. + """ + def __init__(self, manager, parent): + """ + Constructor + """ + self.manager = manager + self.parent = parent + QtGui.QDialog.__init__(self, None) + self.setupUi(self) + QtCore.QObject.connect(self.DeleteButton, + QtCore.SIGNAL(u'clicked()'), + self.onDeleteClick) + QtCore.QObject.connect(self.ClearButton, + QtCore.SIGNAL(u'clicked()'), + self.onClearClick) + QtCore.QObject.connect(self.EditButton, + QtCore.SIGNAL(u'clicked()'), + self.onEditClick) + QtCore.QObject.connect(self.AddButton, + QtCore.SIGNAL(u'clicked()'), + self.onAddClick) + QtCore.QObject.connect(self.SaveButton, + QtCore.SIGNAL(u'clicked()'), + self.onSaveClick) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), self.close) + QtCore.QObject.connect(self.AlertLineEdit, + QtCore.SIGNAL(u'textChanged(const QString&)'), + self.onTextChanged) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onItemSelected) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'clicked(QModelIndex)'), + self.onItemSelected) + + def loadList(self): + self.AlertListWidget.clear() + alerts = self.manager.get_all_alerts() + for alert in alerts: + item_name = QtGui.QListWidgetItem(alert.text) + item_name.setData( + QtCore.Qt.UserRole, QtCore.QVariant(alert.id)) + self.AlertListWidget.addItem(item_name) + self.AddButton.setEnabled(True) + self.ClearButton.setEnabled(False) + self.SaveButton.setEnabled(False) + self.EditButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + + def onItemSelected(self): + if self.AlertLineEdit.text(): + QtGui.QMessageBox.information(self, + self.trUtf8('Item selected to Edit'), + self.trUtf8('Please Save or Clear seletced item')) + else: + self.EditButton.setEnabled(True) + self.DeleteButton.setEnabled(True) + + def onDeleteClick(self): + item = self.AlertListWidget.currentItem() + if item: + item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + self.parent.manager.delete_alert(item_id) + row = self.AlertListWidget.row(item) + self.AlertListWidget.takeItem(row) + self.AddButton.setEnabled(True) + self.SaveButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + self.EditButton.setEnabled(False) + + def onEditClick(self): + item = self.AlertListWidget.currentItem() + if item: + self.item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] + self.AlertLineEdit.setText(unicode(item.text())) + self.AddButton.setEnabled(True) + self.ClearButton.setEnabled(True) + self.SaveButton.setEnabled(True) + self.DeleteButton.setEnabled(True) + self.EditButton.setEnabled(False) + + def onClearClick(self): + self.AlertLineEdit.setText(u'') + self.AddButton.setEnabled(False) + self.ClearButton.setEnabled(True) + self.SaveButton.setEnabled(False) + self.DeleteButton.setEnabled(False) + self.EditButton.setEnabled(False) + + def onAddClick(self): + if len(self.AlertLineEdit.text()) == 0: + QtGui.QMessageBox.information(self, + self.trUtf8('Item selected to Add'), + self.trUtf8('Missing data')) + else: + alert = AlertItem() + alert.text = unicode(self.AlertLineEdit.text()) + self.manager.save_alert(alert) + self.onClearClick() + self.loadList() + + def onSaveClick(self): + alert = self.manager.get_alert(self.item_id) + alert.text = unicode(self.AlertLineEdit.text()) + self.manager.save_alert(alert) + self.onClearClick() + self.loadList() + + def onTextChanged(self): + self.AddButton.setEnabled(True) + + def onDoubleClick(self): + """ + List item has been double clicked to display it + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.triggerAlert(bitem.text()) + + def onSingleClick(self): + """ + List item has been single clicked to add it to + the edit field so it can be changed. + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.AlertEntryEditItem.setText(bitem.text()) + + def triggerAlert(self, text): + self.parent.alertsmanager.displayAlert(text) diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py new file mode 100644 index 000000000..2d7dd1c21 --- /dev/null +++ b/openlp/plugins/alerts/forms/alertform.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 QtGui, QtCore + +from openlp.plugins.alerts.lib.models import AlertItem + +from alertdialog import Ui_AlertDialog + +class AlertForm(QtGui.QDialog, Ui_AlertDialog): + """ + Class documentation goes here. + """ + def __init__(self, manager, parent): + """ + Constructor + """ + self.manager = manager + self.parent = parent + self.history_required = True + QtGui.QDialog.__init__(self, None) + self.setupUi(self) + QtCore.QObject.connect(self.DisplayButton, + QtCore.SIGNAL(u'clicked()'), + self.onDisplayClicked) + QtCore.QObject.connect(self.AlertEntryEditItem, + QtCore.SIGNAL(u'textChanged(const QString&)'), + self.onTextChanged) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), + self.onDoubleClick) + QtCore.QObject.connect(self.AlertListWidget, + QtCore.SIGNAL(u'clicked(QModelIndex)'), + self.onSingleClick) + + def loadList(self): + self.AlertListWidget.clear() + alerts = self.manager.get_all_alerts() + for alert in alerts: + item_name = QtGui.QListWidgetItem(alert.text) + self.AlertListWidget.addItem(item_name) + + def onDisplayClicked(self): + self.triggerAlert(unicode(self.AlertEntryEditItem.text())) + if self.parent.alertsTab.save_history and self.history_required: + alert = AlertItem() + alert.text = unicode(self.AlertEntryEditItem.text()) + self.manager.save_alert(alert) + self.history_required = False + self.loadList() + + def onTextChanged(self): + #Data has changed by editing it so potential storage + self.history_required = True + + def onDoubleClick(self): + """ + List item has been double clicked to display it + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.triggerAlert(bitem.text()) + self.history_required = False + + def onSingleClick(self): + """ + List item has been single clicked to add it to + the edit field so it can be changed. + """ + items = self.AlertListWidget.selectedIndexes() + for item in items: + bitem = self.AlertListWidget.item(item.row()) + self.AlertEntryEditItem.setText(bitem.text()) + self.history_required = False + + def triggerAlert(self, text): + self.parent.alertsmanager.displayAlert(text) diff --git a/openlp/core/ui/alertstab.py b/openlp/plugins/alerts/forms/alertstab.py similarity index 62% rename from openlp/core/ui/alertstab.py rename to openlp/plugins/alerts/forms/alertstab.py index d2e38e048..c842c2e20 100644 --- a/openlp/core/ui/alertstab.py +++ b/openlp/plugins/alerts/forms/alertstab.py @@ -25,16 +25,15 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import SettingsTab +from openlp.core.lib import SettingsTab, str_to_bool class AlertsTab(SettingsTab): """ AlertsTab is the alerts settings tab in the settings dialog. """ - def __init__(self): - SettingsTab.__init__(self, u'Alerts') - self.font_color = '#ffffff' - self.bg_color = '#660000' + def __init__(self, parent, section=None): + self.parent = parent + SettingsTab.__init__(self, parent.name, section) def setupUi(self): self.setObjectName(u'AlertsTab') @@ -83,6 +82,22 @@ class AlertsTab(SettingsTab): self.BackgroundColorButton.setObjectName(u'BackgroundColorButton') self.ColorLayout.addWidget(self.BackgroundColorButton) self.FontLayout.addWidget(self.ColorWidget) + self.FontSizeWidget = QtGui.QWidget(self.FontGroupBox) + self.FontSizeWidget.setObjectName(u'FontSizeWidget') + self.FontSizeLayout = QtGui.QHBoxLayout(self.FontSizeWidget) + self.FontSizeLayout.setSpacing(8) + self.FontSizeLayout.setMargin(0) + self.FontSizeLayout.setObjectName(u'FontSizeLayout') + self.FontSizeLabel = QtGui.QLabel(self.FontSizeWidget) + self.FontSizeLabel.setObjectName(u'FontSizeLabel') + self.FontSizeLayout.addWidget(self.FontSizeLabel) + self.FontSizeSpinBox = QtGui.QSpinBox(self.FontSizeWidget) + self.FontSizeSpinBox.setObjectName(u'FontSizeSpinBox') + self.FontSizeLayout.addWidget(self.FontSizeSpinBox) + self.FontSizeSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.FontSizeLayout.addItem(self.FontSizeSpacer) + self.FontLayout.addWidget(self.FontSizeWidget) self.TimeoutWidget = QtGui.QWidget(self.FontGroupBox) self.TimeoutWidget.setObjectName(u'TimeoutWidget') self.TimeoutLayout = QtGui.QHBoxLayout(self.TimeoutWidget) @@ -100,6 +115,56 @@ class AlertsTab(SettingsTab): QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.TimeoutLayout.addItem(self.TimeoutSpacer) self.FontLayout.addWidget(self.TimeoutWidget) + self.LocationWidget = QtGui.QWidget(self.FontGroupBox) + self.LocationWidget.setObjectName(u'LocationWidget') + self.LocationLayout = QtGui.QHBoxLayout(self.LocationWidget) + self.LocationLayout.setSpacing(8) + self.LocationLayout.setMargin(0) + self.LocationLayout.setObjectName(u'LocationLayout') + self.LocationLabel = QtGui.QLabel(self.LocationWidget) + self.LocationLabel.setObjectName(u'LocationLabel') + self.LocationLayout.addWidget(self.LocationLabel) + self.LocationComboBox = QtGui.QComboBox(self.LocationWidget) + self.LocationComboBox.addItem(QtCore.QString()) + self.LocationComboBox.addItem(QtCore.QString()) + self.LocationComboBox.setObjectName(u'LocationComboBox') + self.LocationLayout.addWidget(self.LocationComboBox) + self.LocationSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.LocationLayout.addItem(self.LocationSpacer) + self.FontLayout.addWidget(self.LocationWidget) + self.HistoryWidget = QtGui.QWidget(self.FontGroupBox) + self.HistoryWidget.setObjectName(u'HistoryWidget') + self.HistoryLayout = QtGui.QHBoxLayout(self.HistoryWidget) + self.HistoryLayout.setSpacing(8) + self.HistoryLayout.setMargin(0) + self.HistoryLayout.setObjectName(u'HistoryLayout') + self.HistoryLabel = QtGui.QLabel(self.HistoryWidget) + self.HistoryLabel.setObjectName(u'HistoryLabel') + self.HistoryLayout.addWidget(self.HistoryLabel) + self.HistoryCheckBox = QtGui.QCheckBox(self.HistoryWidget) + self.HistoryCheckBox.setObjectName(u'HistoryCheckBox') + self.HistoryLayout.addWidget(self.HistoryCheckBox) + self.HistorySpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.HistoryLayout.addItem(self.HistorySpacer) + self.FontLayout.addWidget(self.HistoryWidget) + self.HistoryEditWidget = QtGui.QWidget(self.FontGroupBox) + self.HistoryEditWidget.setObjectName(u'HistoryEditWidget') + self.HistoryEditLayout = QtGui.QHBoxLayout(self.HistoryEditWidget) + self.HistoryEditLayout.setSpacing(8) + self.HistoryEditLayout.setMargin(0) + self.HistoryEditLayout.setObjectName(u'HistoryEditLayout') + self.HistoryEditLabel = QtGui.QLabel(self.HistoryEditWidget) + self.HistoryEditLabel.setObjectName(u'HistoryEditLabel') + self.HistoryEditLayout.addWidget(self.HistoryEditLabel) + self.HistoryEditPushButton = QtGui.QPushButton(self.HistoryEditWidget) + self.HistoryEditPushButton.setObjectName(u'HistoryEditPushButton') + self.HistoryEditLayout.addWidget(self.HistoryEditPushButton) + self.HistoryEditSpacer = QtGui.QSpacerItem(147, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.HistoryEditLayout.addItem(self.HistoryEditSpacer) + self.FontLayout.addWidget(self.HistoryEditWidget) self.SlideLeftLayout.addWidget(self.FontGroupBox) self.SlideLeftSpacer = QtGui.QSpacerItem(20, 94, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) @@ -125,7 +190,7 @@ class AlertsTab(SettingsTab): self.PreviewLayout.setMargin(8) self.PreviewLayout.setObjectName(u'PreviewLayout') self.FontPreview = QtGui.QLineEdit(self.PreviewGroupBox) - self.FontPreview.setMinimumSize(QtCore.QSize(280, 100)) + self.FontPreview.setFixedSize(QtCore.QSize(350, 100)) self.FontPreview.setReadOnly(True) self.FontPreview.setFocusPolicy(QtCore.Qt.NoFocus) self.FontPreview.setAlignment( @@ -138,24 +203,40 @@ class AlertsTab(SettingsTab): self.SlideRightLayout.addItem(self.SlideRightSpacer) self.AlertsLayout.addWidget(self.AlertRightColumn) # Signals and slots + QtCore.QObject.connect(self.HistoryCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onHistoryCheckBoxChanged) QtCore.QObject.connect(self.BackgroundColorButton, QtCore.SIGNAL(u'pressed()'), self.onBackgroundColorButtonClicked) QtCore.QObject.connect(self.FontColorButton, QtCore.SIGNAL(u'pressed()'), self.onFontColorButtonClicked) + QtCore.QObject.connect(self.HistoryEditPushButton, + QtCore.SIGNAL(u'pressed()'), self.onHistoryEditButtonClicked) QtCore.QObject.connect(self.FontComboBox, QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) + QtCore.QObject.connect(self.LocationComboBox, + QtCore.SIGNAL(u'activated(int)'), self.onLocationComboBoxClicked) QtCore.QObject.connect(self.TimeoutSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onTimeoutSpinBoxChanged) + QtCore.QObject.connect(self.FontSizeSpinBox, + QtCore.SIGNAL(u'valueChanged(int)'), self.onFontSizeSpinBoxChanged) def retranslateUi(self): self.FontGroupBox.setTitle(self.trUtf8('Font')) self.FontLabel.setText(self.trUtf8('Font Name:')) self.FontColorLabel.setText(self.trUtf8('Font Color:')) self.BackgroundColorLabel.setText(self.trUtf8('Background Color:')) + self.FontSizeLabel.setText(self.trUtf8('Font Size:')) + self.FontSizeSpinBox.setSuffix(self.trUtf8('pt')) self.TimeoutLabel.setText(self.trUtf8('Alert timeout:')) self.TimeoutSpinBox.setSuffix(self.trUtf8('s')) + self.LocationLabel.setText(self.trUtf8('Location:')) + self.HistoryLabel.setText(self.trUtf8('Keep History:')) + self.HistoryEditLabel.setText(self.trUtf8('Edit History:')) self.PreviewGroupBox.setTitle(self.trUtf8('Preview')) - self.FontPreview.setText(self.trUtf8('openlp.org 2.0 rocks!')) + self.FontPreview.setText(self.trUtf8('openlp.org')) + self.LocationComboBox.setItemText(0, self.trUtf8('Top')) + self.LocationComboBox.setItemText(1, self.trUtf8('Bottom')) def onBackgroundColorButtonClicked(self): self.bg_color = QtGui.QColorDialog.getColor( @@ -167,6 +248,15 @@ class AlertsTab(SettingsTab): def onFontComboBoxClicked(self): self.updateDisplay() + def onLocationComboBoxClicked(self, location): + self.location = location + + def onHistoryCheckBoxChanged(self, check_state): + self.save_history = False + # we have a set value convert to True/False + if check_state == QtCore.Qt.Checked: + self.save_history = True + def onFontColorButtonClicked(self): self.font_color = QtGui.QColorDialog.getColor( QtGui.QColor(self.font_color), self).name() @@ -177,19 +267,33 @@ class AlertsTab(SettingsTab): def onTimeoutSpinBoxChanged(self): self.timeout = self.TimeoutSpinBox.value() + def onFontSizeSpinBoxChanged(self): + self.font_size = self.FontSizeSpinBox.value() + self.updateDisplay() + + def onHistoryEditButtonClicked(self): + self.parent.onAlertsEdit() + def load(self): self.timeout = int(self.config.get_config(u'timeout', 5)) self.font_color = unicode( self.config.get_config(u'font color', u'#ffffff')) + self.font_size = int(self.config.get_config(u'font size', 40)) self.bg_color = unicode( self.config.get_config(u'background color', u'#660000')) self.font_face = unicode( self.config.get_config(u'font face', QtGui.QFont().family())) + self.location = int(self.config.get_config(u'location', 0)) + self.save_history = str_to_bool( + self.config.get_config(u'save history', u'False')) + self.FontSizeSpinBox.setValue(self.font_size) self.TimeoutSpinBox.setValue(self.timeout) self.FontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) self.BackgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) + self.LocationComboBox.setCurrentIndex(self.location) + self.HistoryCheckBox.setChecked(self.save_history) font = QtGui.QFont() font.setFamily(self.font_face) self.FontComboBox.setCurrentFont(font) @@ -199,14 +303,18 @@ class AlertsTab(SettingsTab): self.font_face = self.FontComboBox.currentFont().family() self.config.set_config(u'background color', unicode(self.bg_color)) self.config.set_config(u'font color', unicode(self.font_color)) + self.config.set_config(u'font size', unicode(self.font_size)) self.config.set_config(u'font face', unicode(self.font_face)) self.config.set_config(u'timeout', unicode(self.timeout)) + self.config.set_config(u'location', + unicode(self.LocationComboBox.currentIndex())) + self.config.set_config(u'save history', unicode(self.save_history)) def updateDisplay(self): font = QtGui.QFont() font.setFamily(self.FontComboBox.currentFont().family()) font.setBold(True) - font.setPointSize(16) + font.setPointSize(self.font_size) self.FontPreview.setFont(font) self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % \ - (self.bg_color, self.font_color)) \ No newline at end of file + (self.bg_color, self.font_color)) diff --git a/openlp/plugins/alerts/lib/__init__.py b/openlp/plugins/alerts/lib/__init__.py new file mode 100644 index 000000000..c39574719 --- /dev/null +++ b/openlp/plugins/alerts/lib/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 alertsmanager import AlertsManager +from manager import DBManager diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py new file mode 100644 index 000000000..41fc25562 --- /dev/null +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import Receiver + +log = logging.getLogger(__name__) + +class AlertsManager(QtCore.QObject): + """ + AlertsTab is the Alerts settings tab in the settings dialog. + """ + log.info(u'Alert Manager loaded') + + def __init__(self, parent): + QtCore.QObject.__init__(self) + self.parent = parent + self.timer_id = 0 + self.alertList = [] + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'flush_alert'), self.generateAlert) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'alert_text'), self.displayAlert) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'screen_changed'), self.screenChanged) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'config_updated'), self.screenChanged) + + def screenChanged(self): + log.debug(u'screen changed') + self.screen = self.parent.maindisplay.screen + self.alertTab = self.parent.alertsTab + self.font = QtGui.QFont() + self.font.setFamily(self.alertTab.font_face) + self.font.setBold(True) + self.font.setPointSize(self.alertTab.font_size) + self.metrics = QtGui.QFontMetrics(self.font) + self.alertHeight = self.metrics.height() + 4 + if self.alertTab.location == 0: + self.alertScreenPosition = 0 + else: + self.alertScreenPosition = self.screen[u'size'].height() - self.alertHeight + self.alertHeight = self.screen[u'size'].height() - self.alertScreenPosition + self.parent.maindisplay.setAlertSize(self.alertScreenPosition, self.alertHeight) + + def displayAlert(self, text=u''): + """ + Called from the Alert Tab to display an alert + + ``text`` + display text + """ + log.debug(u'display alert called %s' % text) + self.parent.maindisplay.parent.StatusBar.showMessage(u'') + self.alertList.append(text) + if self.timer_id != 0 or self.parent.maindisplay.mediaLoaded: + self.parent.maindisplay.parent.StatusBar.showMessage(\ + self.trUtf8(u'Alert message created and delayed')) + return + self.generateAlert() + + def generateAlert(self): + log.debug(u'Generate Alert called') + if len(self.alertList) == 0: + return + text = self.alertList.pop(0) + alertTab = self.parent.alertsTab + alertframe = \ + QtGui.QPixmap(self.screen[u'size'].width(), self.alertHeight) + alertframe.fill(QtCore.Qt.transparent) + painter = QtGui.QPainter(alertframe) + painter.fillRect(alertframe.rect(), QtCore.Qt.transparent) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.fillRect( + QtCore.QRect( + 0, 0, alertframe.rect().width(), + alertframe.rect().height()), + QtGui.QColor(self.alertTab.bg_color)) + painter.setFont(self.font) + painter.setPen(QtGui.QColor(self.alertTab.font_color)) + x, y = (0, 2) + painter.drawText( + x, y + self.metrics.height() - self.metrics.descent() - 1, text) + painter.end() + self.parent.maindisplay.addAlertImage(alertframe) + # check to see if we have a timer running + if self.timer_id == 0: + self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) + + def timerEvent(self, event): + if event.timerId() == self.timer_id: + self.parent.maindisplay.addAlertImage(None, True) + self.killTimer(self.timer_id) + self.timer_id = 0 + self.generateAlert() diff --git a/openlp/plugins/alerts/lib/classes.py b/openlp/plugins/alerts/lib/classes.py new file mode 100644 index 000000000..eec21300c --- /dev/null +++ b/openlp/plugins/alerts/lib/classes.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +class BaseModel(object): + """ + BaseModel provides a base object with a set of generic functions + """ + + @classmethod + def populate(cls, **kwargs): + """ + Creates an instance of a class and populates it, returning the instance + """ + me = cls() + keys = kwargs.keys() + for key in keys: + me.__setattr__(key, kwargs[key]) + return me + +class AlertItem(BaseModel): + """ + Custom Slide model + """ + pass diff --git a/openlp/plugins/alerts/lib/manager.py b/openlp/plugins/alerts/lib/manager.py new file mode 100644 index 000000000..a3ed1ee7e --- /dev/null +++ b/openlp/plugins/alerts/lib/manager.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 + +from openlp.plugins.alerts.lib.models import init_models, metadata, AlertItem + +log = logging.getLogger(__name__) + +class DBManager(): + """ + The Song Manager provides a central location for all database code. This + class takes care of connecting to the database and running all the queries. + """ + log.info(u'Alerts DB loaded') + + def __init__(self, config): + """ + Creates the connection to the database, and creates the tables if they + don't exist. + """ + self.config = config + log.debug(u'Alerts Initialising') + self.db_url = u'' + db_type = self.config.get_config(u'db type', u'sqlite') + if db_type == u'sqlite': + self.db_url = u'sqlite:///%s/alerts.sqlite' % \ + self.config.get_data_path() + else: + self.db_url = u'%s://%s:%s@%s/%s' % \ + (db_type, self.config.get_config(u'db username'), + self.config.get_config(u'db password'), + self.config.get_config(u'db hostname'), + self.config.get_config(u'db database')) + self.session = init_models(self.db_url) + metadata.create_all(checkfirst=True) + + log.debug(u'Alerts Initialised') + + def get_all_alerts(self): + """ + Returns the details of a Alert Show + """ + return self.session.query(AlertItem).order_by(AlertItem.text).all() + + def save_alert(self, AlertItem): + """ + Saves a Alert show to the database + """ + log.debug(u'Alert added') + try: + self.session.add(AlertItem) + self.session.commit() + log.debug(u'Alert saved') + return True + except: + self.session.rollback() + log.exception(u'Alert save failed') + return False + + def get_alert(self, id=None): + """ + Returns the details of a Alert + """ + if id is None: + return AlertItem() + else: + return self.session.query(AlertItem).get(id) + + def delete_alert(self, id): + """ + Delete a Alert show + """ + if id != 0: + AlertItem = self.get_alert(id) + try: + self.session.delete(AlertItem) + self.session.commit() + return True + except: + self.session.rollback() + log.exception(u'Alert deleton failed') + return False + else: + return True diff --git a/openlp/plugins/alerts/lib/meta.py b/openlp/plugins/alerts/lib/meta.py new file mode 100644 index 000000000..38b0f7206 --- /dev/null +++ b/openlp/plugins/alerts/lib/meta.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 sqlalchemy import MetaData + +__all__ = ['session', 'metadata', 'engine'] + +# SQLAlchemy database engine. Updated by model.init_model() +engine = None + +# SQLAlchemy session manager. Updated by model.init_model() +session = None + +# Global metadata. If you have multiple databases with overlapping table +# names, you'll need a metadata for each database +metadata = MetaData() \ No newline at end of file diff --git a/openlp/plugins/alerts/lib/models.py b/openlp/plugins/alerts/lib/models.py new file mode 100644 index 000000000..4f556cd23 --- /dev/null +++ b/openlp/plugins/alerts/lib/models.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker, mapper + +from openlp.plugins.alerts.lib.meta import metadata +from openlp.plugins.alerts.lib.tables import * +from openlp.plugins.alerts.lib.classes import * + +def init_models(url): + engine = create_engine(url) + metadata.bind = engine + session = scoped_session(sessionmaker(autoflush=True, autocommit=False, + bind=engine)) + mapper(AlertItem, alerts_table) + return session diff --git a/openlp/plugins/alerts/lib/tables.py b/openlp/plugins/alerts/lib/tables.py new file mode 100644 index 000000000..0a731fb13 --- /dev/null +++ b/openlp/plugins/alerts/lib/tables.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 sqlalchemy import Column, Table, types + +from openlp.plugins.alerts.lib.meta import metadata + +# Definition of the "alerts" table +alerts_table = Table(u'alerts', metadata, + Column(u'id', types.Integer(), primary_key=True), + Column(u'text', types.UnicodeText, nullable=False)) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 912a02212..bd65b6622 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -27,25 +27,26 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem +log = logging.getLogger(__name__) + class BiblePlugin(Plugin): - global log - log = logging.getLogger(u'BiblePlugin') log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Bibles', u'1.9.1', plugin_helpers) self.weight = -9 self.icon = build_icon(u':/media/media_bible.png') #Register the bible Manager - self.biblemanager = None + self.status = PluginStatus.Active + self.manager = None def initialise(self): log.info(u'bibles Initialising') - if self.biblemanager is None: - self.biblemanager = BibleManager(self.config) + if self.manager is None: + self.manager = BibleManager(self, self.config) Plugin.initialise(self) self.insert_toolbox_item() self.ImportBibleItem.setVisible(True) @@ -90,4 +91,10 @@ class BiblePlugin(Plugin): about_text = self.trUtf8('Bible Plugin
This ' 'plugin allows bible verses from different sources to be ' 'displayed on the screen during the service.') - return about_text \ No newline at end of file + return about_text + + + def can_delete_theme(self, theme): + if self.settings_tab.bible_theme == theme: + return False + return True diff --git a/openlp/plugins/bibles/forms/bibleimportwizard.py b/openlp/plugins/bibles/forms/bibleimportwizard.py index 1f655a52d..59e38e39a 100644 --- a/openlp/plugins/bibles/forms/bibleimportwizard.py +++ b/openlp/plugins/bibles/forms/bibleimportwizard.py @@ -91,15 +91,6 @@ class Ui_BibleImportWizard(object): self.OsisLayout.setMargin(0) self.OsisLayout.setSpacing(8) self.OsisLayout.setObjectName(u'OsisLayout') - self.OsisBibleNameLabel = QtGui.QLabel(self.OsisPage) - self.OsisBibleNameLabel.setIndent(0) - self.OsisBibleNameLabel.setObjectName(u'OsisBibleNameLabel') - self.OsisLayout.setWidget(0, QtGui.QFormLayout.LabelRole, - self.OsisBibleNameLabel) - self.OsisBibleNameEdit = QtGui.QLineEdit(self.OsisPage) - self.OsisBibleNameEdit.setObjectName(u'OsisBibleNameEdit') - self.OsisLayout.setWidget(0, QtGui.QFormLayout.FieldRole, - self.OsisBibleNameEdit) self.OsisLocationLabel = QtGui.QLabel(self.OsisPage) self.OsisLocationLabel.setObjectName(u'OsisLocationLabel') self.OsisLayout.setWidget(1, QtGui.QFormLayout.LabelRole, @@ -302,13 +293,11 @@ class Ui_BibleImportWizard(object): self.ImportProgressLabel.setObjectName(u'ImportProgressLabel') self.ImportLayout.addWidget(self.ImportProgressLabel) self.ImportProgressBar = QtGui.QProgressBar(self.ImportPage) - self.ImportProgressBar.setProperty(u'value', 0) - self.ImportProgressBar.setInvertedAppearance(False) + self.ImportProgressBar.setValue(0) self.ImportProgressBar.setObjectName(u'ImportProgressBar') self.ImportLayout.addWidget(self.ImportProgressBar) BibleImportWizard.addPage(self.ImportPage) - self.retranslateUi(BibleImportWizard) self.FormatWidget.setCurrentIndex(0) self.WebDownloadTabWidget.setCurrentIndex(0) @@ -334,7 +323,6 @@ class Ui_BibleImportWizard(object): self.FormatComboBox.setItemText(1, self.trUtf8('CSV')) self.FormatComboBox.setItemText(2, self.trUtf8('OpenSong')) self.FormatComboBox.setItemText(3, self.trUtf8('Web Download')) - self.OsisBibleNameLabel.setText(self.trUtf8('Bible Name:')) self.OsisLocationLabel.setText(self.trUtf8('File Location:')) self.BooksLocationLabel.setText(self.trUtf8('Books Location:')) self.VerseLocationLabel.setText(self.trUtf8('Verse Location:')) @@ -362,4 +350,4 @@ class Ui_BibleImportWizard(object): self.ImportPage.setSubTitle( self.trUtf8('Please wait while your Bible is imported.')) self.ImportProgressLabel.setText(self.trUtf8('Ready.')) - #self.ImportProgressBar.setFormat(u'%p') + self.ImportProgressBar.setFormat(u'%p%') diff --git a/openlp/plugins/bibles/forms/importwizardform.py b/openlp/plugins/bibles/forms/importwizardform.py index 7e82b6b7f..742598fdb 100644 --- a/openlp/plugins/bibles/forms/importwizardform.py +++ b/openlp/plugins/bibles/forms/importwizardform.py @@ -23,10 +23,10 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import csv import logging import os import os.path -from time import sleep from PyQt4 import QtCore, QtGui @@ -34,6 +34,8 @@ from bibleimportwizard import Ui_BibleImportWizard from openlp.core.lib import Receiver from openlp.plugins.bibles.lib.manager import BibleFormat +log = logging.getLogger(__name__) + class DownloadLocation(object): Unknown = -1 Crosswalk = 0 @@ -45,8 +47,8 @@ class DownloadLocation(object): } @classmethod - def get_name(class_, id): - return class_.Names[id] + def get_name(cls, id): + return cls.Names[id] class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): @@ -54,12 +56,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): This is the Bible Import Wizard, which allows easy importing of Bibles into OpenLP from other formats like OSIS, CSV and OpenSong. """ - - global log - log = logging.getLogger(u'BibleImportForm') log.info(u'BibleImportForm loaded') - def __init__(self, parent, config, biblemanager, bibleplugin): + def __init__(self, parent, config, manager, bibleplugin): ''' Constructor ''' @@ -68,10 +67,10 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): self.registerFields() self.finishButton = self.button(QtGui.QWizard.FinishButton) self.cancelButton = self.button(QtGui.QWizard.CancelButton) - self.biblemanager = biblemanager + self.manager = manager self.config = config self.bibleplugin = bibleplugin - self.biblemanager.set_process_dialog(self) + self.manager.set_process_dialog(self) self.web_bible_list = {} self.loadWebBibles() QtCore.QObject.connect(self.LocationComboBox, @@ -96,9 +95,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) - def show(self): + def exec_(self): self.setDefaults() - return QtGui.QWizard.show() + return QtGui.QWizard.exec_(self) def validateCurrentPage(self): if self.currentId() == 0: @@ -107,14 +106,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): elif self.currentId() == 1: # Select page if self.field(u'source_format').toInt()[0] == BibleFormat.OSIS: - if self.field(u'osis_biblename').toString() == u'': - QtGui.QMessageBox.critical(self, - self.trUtf8('Invalid Bible Name'), - self.trUtf8('You need to specify a name for your ' - 'Bible!'), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) - self.OsisBibleNameEdit.setFocus() - return False if self.field(u'osis_location').toString() == u'': QtGui.QMessageBox.critical(self, self.trUtf8('Invalid Bible Location'), @@ -169,6 +160,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) self.CopyrightEdit.setFocus() return False + elif self.manager.exists( + self.field(u'license_version').toString()): + QtGui.QMessageBox.critical(self, + self.trUtf8('Bible Exists'), + self.trUtf8('This Bible already exists! Please import ' + 'a different Bible or first delete the existing one.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) + self.VersionNameEdit.setFocus() + return False return True if self.currentId() == 3: # Progress page @@ -209,8 +209,6 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): def registerFields(self): self.SelectPage.registerField( u'source_format', self.FormatComboBox) - self.SelectPage.registerField( - u'osis_biblename', self.OsisBibleNameEdit) self.SelectPage.registerField( u'osis_location', self.OSISLocationEdit) self.SelectPage.registerField( @@ -237,24 +235,23 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): u'license_permission', self.PermissionEdit) def setDefaults(self): - self.setField(u'source_format', 0) - self.setField(u'osis_biblename', u'') - self.setField(u'osis_location', u'') - self.setField(u'csv_booksfile', u'') - self.setField(u'csv_versefile', u'') - self.setField(u'opensong_file', u'') - self.setField(u'web_location', 0) - self.setField(u'web_biblename', self.BibleComboBox) + self.setField(u'source_format', QtCore.QVariant(0)) + self.setField(u'osis_location', QtCore.QVariant('')) + self.setField(u'csv_booksfile', QtCore.QVariant('')) + self.setField(u'csv_versefile', QtCore.QVariant('')) + self.setField(u'opensong_file', QtCore.QVariant('')) + self.setField(u'web_location', QtCore.QVariant(DownloadLocation.Crosswalk)) + self.setField(u'web_biblename', QtCore.QVariant(self.BibleComboBox)) self.setField(u'proxy_server', - self.config.get_config(u'proxy address', u'')) + QtCore.QVariant(self.config.get_config(u'proxy address', ''))) self.setField(u'proxy_username', - self.config.get_config(u'proxy username',u'')) + QtCore.QVariant(self.config.get_config(u'proxy username',''))) self.setField(u'proxy_password', - self.config.get_config(u'proxy password',u'')) - self.setField(u'license_version', self.VersionNameEdit) - self.setField(u'license_copyright', self.CopyrightEdit) - self.setField(u'license_permission', self.PermissionEdit) - self.onLocationComboBoxChanged(0) + QtCore.QVariant(self.config.get_config(u'proxy password',''))) + self.setField(u'license_version', QtCore.QVariant(self.VersionNameEdit)) + self.setField(u'license_copyright', QtCore.QVariant(self.CopyrightEdit)) + self.setField(u'license_permission', QtCore.QVariant(self.PermissionEdit)) + self.onLocationComboBoxChanged(DownloadLocation.Crosswalk) def loadWebBibles(self): """ @@ -267,29 +264,33 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): fbibles = None try: self.web_bible_list[DownloadLocation.Crosswalk] = {} - fbibles = open(os.path.join(filepath, u'crosswalkbooks.csv'), 'r') - for line in fbibles: - parts = line.split(u',') - self.web_bible_list[DownloadLocation.Crosswalk][parts[0]] = \ - parts[1].rstrip() + books_file = open(os.path.join(filepath, u'crosswalkbooks.csv'), 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: + self.web_bible_list[DownloadLocation.Crosswalk][line[0]] = \ + unicode(line[1], u'utf-8').strip() except: log.exception(u'Crosswalk resources missing') finally: - if fbibles: - fbibles.close() + if books_file: + books_file.close() #Load and store BibleGateway Bibles try: self.web_bible_list[DownloadLocation.BibleGateway] = {} - fbibles = open(os.path.join(filepath, u'biblegateway.csv'), 'r') - for line in fbibles: - parts = line.split(u',') - self.web_bible_list[DownloadLocation.BibleGateway][parts[0]] = \ - parts[1].rstrip() + books_file = open(os.path.join(filepath, u'biblegateway.csv'), 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: + self.web_bible_list[DownloadLocation.BibleGateway][line[0]] = \ + unicode(line[1], u'utf-8').strip() except: log.exception(u'Biblegateway resources missing') finally: - if fbibles: - fbibles.close() + if books_file: + books_file.close() def getFileName(self, title, editbox): filename = QtGui.QFileDialog.getOpenFileName(self, title, @@ -317,22 +318,22 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): success = False if bible_type == BibleFormat.OSIS: # Import an OSIS bible - success = self.biblemanager.register_osis_file_bible( - unicode(self.field(u'license_version').toString()), - unicode(self.field(u'osis_location').toString()) + success = self.manager.import_bible(BibleFormat.OSIS, + name=unicode(self.field(u'license_version').toString()), + filename=unicode(self.field(u'osis_location').toString()) ) elif bible_type == BibleFormat.CSV: # Import a CSV bible - success = self.biblemanager.register_csv_file_bible( - unicode(self.field(u'license_version').toString()), - self.field(u'csv_booksfile').toString(), - self.field(u'csv_versefile').toString() + success = self.manager.import_bible(BibleFormat.CSV, + name=unicode(self.field(u'license_version').toString()), + booksfile=self.field(u'csv_booksfile').toString(), + versefile=self.field(u'csv_versefile').toString() ) elif bible_type == BibleFormat.OpenSong: # Import an OpenSong bible - success = self.biblemanager.register_opensong_bible( - unicode(self.field(u'license_version').toString()), - self.field(u'opensong_file').toString() + success = self.manager.import_bible(BibleFormat.OpenSong, + name=unicode(self.field(u'license_version').toString()), + filename=self.field(u'opensong_file').toString() ) elif bible_type == BibleFormat.WebDownload: # Import a bible from the web @@ -344,21 +345,22 @@ class ImportWizardForm(QtGui.QWizard, Ui_BibleImportWizard): elif download_location == DownloadLocation.BibleGateway: bible = self.web_bible_list[DownloadLocation.BibleGateway][ unicode(self.BibleComboBox.currentText())] - success = self.biblemanager.register_http_bible( - unicode(self.field(u'license_version').toString()), - unicode(DownloadLocation.get_name(download_location)), - unicode(bible), - unicode(self.field(u'proxy_server').toString()), - unicode(self.field(u'proxy_username').toString()), - unicode(self.field(u'proxy_password').toString()) + success = self.manager.import_bible(BibleFormat.WebDownload, + name=unicode(self.field(u'license_version').toString()), + download_source=unicode(DownloadLocation.get_name(download_location)), + download_name=unicode(bible), + proxy_server=unicode(self.field(u'proxy_server').toString()), + proxy_username=unicode(self.field(u'proxy_username').toString()), + proxy_password=unicode(self.field(u'proxy_password').toString()) ) if success: - self.biblemanager.save_meta_data( + self.manager.save_meta_data( unicode(self.field(u'license_version').toString()), unicode(self.field(u'license_version').toString()), unicode(self.field(u'license_copyright').toString()), unicode(self.field(u'license_permission').toString()) ) + self.manager.reload_bibles() self.ImportProgressLabel.setText(self.trUtf8('Finished import.')) else: self.ImportProgressLabel.setText( diff --git a/openlp/plugins/bibles/lib/bibleDBimpl.py b/openlp/plugins/bibles/lib/bibleDBimpl.py deleted file mode 100644 index 9c65a7eaa..000000000 --- a/openlp/plugins/bibles/lib/bibleDBimpl.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 os -import logging - -from common import BibleCommon -from openlp.plugins.bibles.lib.models import * - -class BibleDBImpl(BibleCommon): - global log - log = logging.getLogger(u'BibleDBImpl') - log.info(u'BibleDBimpl loaded') - - def __init__(self, biblepath, biblename, config): - # Connect to database - self.config = config - self.biblefile = os.path.join(biblepath, biblename + u'.sqlite') - log.debug(u'Load bible %s on path %s', biblename, self.biblefile) - db_type = self.config.get_config(u'db type', u'sqlite') - db_url = u'' - if db_type == u'sqlite': - db_url = u'sqlite:///' + self.biblefile - else: - db_url = u'%s://%s:%s@%s/%s' % \ - (db_type, self.config.get_config(u'db username'), - self.config.get_config(u'db password'), - self.config.get_config(u'db hostname'), - self.config.get_config(u'db database')) - self.metadata, self.session = init_models(db_url) - self.metadata.create_all(checkfirst=True) - - def create_tables(self): - log.debug(u'createTables') - self.save_meta(u'dbversion', u'2') - self._load_testament(u'Old Testament') - self._load_testament(u'New Testament') - self._load_testament(u'Apocrypha') - - def add_verse(self, bookid, chap, vse, text): - verse = Verse() - verse.book_id = bookid - verse.chapter = chap - verse.verse = vse - verse.text = text - self.session.add(verse) - return verse - - def save_verses(self): - log.debug('Saving verses...') - self.session.commit() - - def create_chapter(self, bookid, chap, textlist): - log.debug(u'create_chapter %s,%s', bookid, chap) - #text list has book and chapter as first to elements of the array - for verse_number, verse_text in textlist.iteritems(): - verse = Verse() - verse.book_id = bookid - verse.chapter = chap - verse.verse = verse_number - verse.text = verse_text - self.session.add(verse) - self.session.commit() - - def create_book(self, bookname, bookabbrev, testament=1): - log.debug(u'create_book %s,%s', bookname, bookabbrev) - book = Book() - book.testament_id = testament - book.name = bookname - book.abbreviation = bookabbrev - self.session.add(book) - self.session.commit() - return book - - def save_meta(self, key, value): - log.debug(u'save_meta %s/%s', key, value) - bmeta = BibleMeta() - bmeta.key = key - bmeta.value = value - self.session.add(bmeta) - self.session.commit() - - def get_meta(self, metakey): - log.debug(u'get meta %s', metakey) - return self.session.query(BibleMeta).filter_by(key=metakey).first() - - def delete_meta(self, metakey): - biblemeta = self.get_meta(metakey) - try: - self.session.delete(biblemeta) - self.session.commit() - return True - except: - return False - - def _load_testament(self, testament): - log.debug(u'load_testaments %s', testament) - test = ONTestament() - test.name = testament - self.session.add(test) - self.session.commit() - - def get_bible_books(self): - log.debug(u'get_bible_books') - return self.session.query(Book).order_by(Book.id).all() - - def get_max_bible_book_verses(self, bookname, chapter): - log.debug(u'get_max_bible_book_verses %s, %s', bookname, chapter) - verse = self.session.query(Verse).join(Book).filter( - Book.name == bookname).filter( - Verse.chapter == chapter).order_by(Verse.verse.desc()).first() - if verse == None: - return 0 - else: - return verse.verse - - def get_max_bible_book_chapter(self, bookname): - log.debug(u'get_max_bible_book_chapter %s', bookname) - verse = self.session.query(Verse).join(Book).filter( - Book.name == bookname).order_by(Verse.chapter.desc()).first() - if verse == None: - return 0 - else: - return verse.chapter - - def get_bible_book(self, bookname): - log.debug(u'get_bible_book %s', bookname) - book = self.session.query(Book).filter( - Book.name.like(bookname + u'%')).first() - if book is None: - book = self.session.query(Book).filter( - Book.abbreviation.like(bookname + u'%')).first() - return book - - def get_bible_chapter(self, id, chapter): - log.debug(u'get_bible_chapter %s, %s', id, chapter) - return self.session.query(Verse).filter_by(chapter=chapter).filter_by( - book_id=id).first() - - def get_bible_text(self, bookname, chapter, sverse, everse): - log.debug(u'get_bible_text %s, %s, %s, %s', bookname, chapter, sverse, - everse) - #Look up book name or abbreviation - book = self.get_bible_book(bookname) - if book: - bookname = book.name - log.debug(u'bookname corrected to %s' % bookname) - verses = self.session.query(Verse).join(Book).filter( - Book.name == bookname).filter(Verse.chapter == chapter).filter( - Verse.verse>=sverse).filter(Verse.verse<=everse).order_by( - Verse.verse).all() - return verses - - def get_verses_from_text(self, versetext): - log.debug(u'get_verses_from_text %s',versetext) - versetext = u'%%%s%%' % versetext - verses = self.session.query(Verse).filter( - Verse.text.like(versetext)).all() - return verses - - def dump_bible(self): - log.debug( u'.........Dumping Bible Database') - log.debug( '...............................Books ') - books = self.session.query(Book).all() - log.debug(books) - log.debug( u'...............................Verses ') - verses = self.session.query(Verse).all() - log.debug(verses) \ No newline at end of file diff --git a/openlp/plugins/bibles/lib/bibleHTTPimpl.py b/openlp/plugins/bibles/lib/bibleHTTPimpl.py deleted file mode 100644 index f8cad5c18..000000000 --- a/openlp/plugins/bibles/lib/bibleHTTPimpl.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 - -from common import BibleCommon, SearchResults - -class BGExtract(BibleCommon): - global log - log = logging.getLogger(u'BibleHTTPMgr(BG_extract)') - log.info(u'BG_extract loaded') - - def __init__(self, proxyurl= None): - log.debug(u'init %s', proxyurl) - self.proxyurl = proxyurl - - def get_bible_chapter(self, version, bookname, chapter) : - """ - Access and decode bibles via the BibleGateway website - - ``Version`` - The version of the bible like 31 for New International version - - ``bookname`` - Name of the Book - - ``chapter`` - Chapter number - """ - log.debug(u'get_bible_chapter %s,%s,%s', - version, bookname, chapter) - urlstring = \ - u'http://www.biblegateway.com/passage/?search=%s+%d&version=%s' % \ - (bookname, chapter, version) - log.debug(u'BibleGateway urm = %s' % urlstring) - xml_string = self._get_web_text(urlstring, self.proxyurl) - verseSearch = u' -1: - # clear out string - verseText = u'' - versePos = xml_string.find(u'', versePos) + 6 - i = xml_string.find(verseSearch, versePos + 1) - # Not sure if this is needed now - if i == -1: - i = xml_string.find(u' 0 and j < i: - i = j - verseText = xml_string[versePos + 7 : i ] - # store the verse - bible[verse] = self._clean_text(verseText) - versePos = -1 - else: - verseText = xml_string[versePos: i] - start_tag = verseText.find(verseFootnote) - while start_tag > -1: - end_tag = verseText.find(u'') - verseText = verseText[:start_tag] + verseText[end_tag + 6:len(verseText)] - start_tag = verseText.find(verseFootnote) - # Chop off verse and start again - xml_string = xml_string[i:] - #look for the next verse - versePos = xml_string.find(verseSearch) - # store the verse - bible[verse] = self._clean_text(verseText) - verse += 1 - return SearchResults(bookname, chapter, bible) - -class CWExtract(BibleCommon): - global log - log = logging.getLogger(u'BibleHTTPMgr(CWExtract)') - log.info(u'CWExtract loaded') - - def __init__(self, proxyurl=None): - log.debug(u'init %s', proxyurl) - self.proxyurl = proxyurl - - def get_bible_chapter(self, version, bookname, chapter) : - log.debug(u'getBibleChapter %s,%s,%s', - version,bookname, chapter) - """ - Access and decode bibles via the Crosswalk website - - ``version`` - The version of the bible like niv for New International Version - - ``bookname`` - Text name of in english e.g. 'gen' for Genesis - - ``chapter`` - Chapter number - """ - log.debug(u'get_bible_chapter %s,%s,%s', - version, bookname, chapter) - bookname = bookname.replace(u' ', u'') - urlstring = u'http://bible.crosswalk.com/OnlineStudyBible/bible.cgi?word=%s+%d&version=%s'\ - % (bookname, chapter, version) - xml_string = self._get_web_text(urlstring, self.proxyurl) - ## Strip Book Title from Heading to return it to system - ## - i = xml_string.find(u'') - j = xml_string.find(u'-', i) - book_title = xml_string[i + 7:j] - book_title = book_title.rstrip() - log.debug(u'Book Title %s', book_title) - i = book_title.rfind(u' ') - book_chapter = book_title[i+1:len(book_title)].rstrip() - book_title = book_title[:i].rstrip() - log.debug(u'Book Title %s', book_title) - log.debug(u'Book Chapter %s', book_chapter) - # Strip Verse Data from Page and build an array - - i = xml_string.find(u'NavCurrentChapter') - xml_string = xml_string[i:len(xml_string)] - i = xml_string.find(u'<TABLE') - xml_string = xml_string[i:len(xml_string)] - i = xml_string.find(u'<B>') - #remove the <B> at the front - xml_string = xml_string[i + 3 :len(xml_string)] - # Remove the heading for the book - i = xml_string.find(u'<B>') - #remove the <B> at the front - xml_string = xml_string[i + 3 :len(xml_string)] - versePos = xml_string.find(u'<BLOCKQUOTE>') - bible = {} - while versePos > 0: - verseText = u'' - versePos = xml_string.find(u'<B><I>', versePos) + 6 - i = xml_string.find(u'</I></B>', versePos) - # Got the Chapter - verse = xml_string[versePos:i] - # move the starting position to begining of the text - versePos = i + 8 - # find the start of the next verse - i = xml_string.find(u'<B><I>', versePos) - if i == -1: - i = xml_string.find(u'</BLOCKQUOTE>',versePos) - verseText = xml_string[versePos: i] - versePos = 0 - else: - verseText = xml_string[versePos: i] - versePos = i - bible[verse] = self._clean_text(verseText) - return SearchResults(book_title, book_chapter, bible) - -class BibleHTTPImpl(): - global log - log = logging.getLogger(u'BibleHTTPMgr') - log.info(u'BibleHTTP manager loaded') - def __init__(self): - """ - Finds all the bibles defined for the system - Creates an Interface Object for each bible containing connection - information - - Throws Exception if no Bibles are found. - - Init confirms the bible exists and stores the database path. - """ - self.biblesource = u'' - self.proxyurl = None - self.bibleid = None - - def set_proxy(self, proxyurl): - """ - Set the Proxy Url - """ - log.debug(u'set_proxy %s', proxyurl) - self.proxyurl = proxyurl - - def set_bibleid(self, bibleid): - """ - Set the bible id. - The shore identifier of the the bible. - """ - log.debug(u'set_bibleid %s', bibleid) - self.bibleid = bibleid - - def set_bible_source(self, biblesource): - """ - Set the source of where the bible text is coming from - """ - log.debug(u'set_bible_source %s', biblesource) - self.biblesource = biblesource - - def get_bible_chapter(self, version, bookname, chapter): - """ - Receive the request and call the relevant handler methods - """ - log.debug(u'get_bible_chapter %s,%s,%s', - version, bookname, chapter) - log.debug(u'biblesource = %s', self.biblesource) - try: - if self.biblesource.lower() == u'crosswalk': - ev = CWExtract(self.proxyurl) - else: - ev = BGExtract(self.proxyurl) - return ev.get_bible_chapter(self.bibleid, bookname, chapter) - except: - log.exception("Failed to get bible chapter") \ No newline at end of file diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 9c7e6b280..398040fd8 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -27,15 +27,14 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import str_to_bool, Receiver -from openlp.core.lib import SettingsTab +from openlp.core.lib import str_to_bool, Receiver, SettingsTab + +log = logging.getLogger(__name__) class BiblesTab(SettingsTab): """ BiblesTab is the Bibles settings tab in the settings dialog. """ - global log - log = logging.getLogger(u'BibleTab') log.info(u'Bible Tab loaded') def __init__(self, title, section=None): @@ -226,4 +225,4 @@ class BiblesTab(SettingsTab): # Not Found id = 0 self.bible_theme = u'' - self.BibleThemeComboBox.setCurrentIndex(id) \ No newline at end of file + self.BibleThemeComboBox.setCurrentIndex(id) diff --git a/openlp/plugins/bibles/lib/common.py b/openlp/plugins/bibles/lib/common.py index 3669b8e5f..9a876266d 100644 --- a/openlp/plugins/bibles/lib/common.py +++ b/openlp/plugins/bibles/lib/common.py @@ -24,10 +24,99 @@ ############################################################################### import urllib2 -import chardet import logging +import re +import chardet -class SearchResults: +only_verses = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)' + r'(?:[ ]*-[ ]*([0-9]+|end))?(?:[ ]*,[ ]*([0-9]+)(?:[ ]*-[ ]*([0-9]+|end))?)?', + re.UNICODE) +chapter_range = re.compile(r'([\w .]+)[ ]+([0-9]+)[ ]*[:|v|V][ ]*' + r'([0-9]+)[ ]*-[ ]*([0-9]+)[ ]*[:|v|V][ ]*([0-9]+)', + re.UNICODE) + +log = logging.getLogger(__name__) + +def parse_reference(reference): + """ + This is the über-awesome function that takes a person's typed in string + and converts it to a reference list, a list of references to be queried + from the Bible database files. + + The reference list is a list of tuples, with each tuple structured like + this:: + + (book, chapter, start_verse, end_verse) + """ + reference = reference.strip() + log.debug('parse_reference("%s")', reference) + reference_list = [] + # We start with the most "complicated" match first, so that they are found + # first, and we don't have any "false positives". + match = chapter_range.match(reference) + if match: + log.debug('Found a chapter range.') + book = match.group(1) + from_verse = match.group(3) + to_verse = match.group(5) + if int(match.group(2)) == int(match.group(4)): + reference_list.append( + (match.group(1), int(match.group(2)), from_verse, to_verse) + ) + else: + if int(match.group(2)) > int(match.group(4)): + from_chapter = int(match.group(4)) + to_chapter = int(match.group(2)) + else: + from_chapter = int(match.group(2)) + to_chapter = int(match.group(4)) + for chapter in xrange(from_chapter, to_chapter + 1): + if chapter == from_chapter: + reference_list.append( + (match.group(1), chapter, from_verse, -1) + ) + elif chapter == to_chapter: + reference_list.append( + (match.group(1), chapter, 1, to_verse) + ) + else: + reference_list.append( + (match.group(1), chapter, 1, -1) + ) + else: + match = only_verses.match(reference) + if match: + log.debug('Found a verse range.') + book = match.group(1) + chapter = match.group(2) + verse = match.group(3) + if match.group(4) is None: + reference_list.append((book, chapter, verse, verse)) + elif match.group(5) is None: + end_verse = match.group(4) + if end_verse == u'end': + end_verse = -1 + reference_list.append((book, chapter, verse, end_verse)) + elif match.group(6) is None: + reference_list.extend([ + (book, chapter, verse, match.group(4)), + (book, chapter, match.group(5), match.group(5)) + ]) + else: + end_verse = match.group(6) + if end_verse == u'end': + end_verse = -1 + reference_list.extend([ + (book, chapter, verse, match.group(4)), + (book, chapter, match.group(5), end_verse) + ]) + else: + log.debug('Didn\'t find anything.') + log.debug(reference_list) + return reference_list + + +class SearchResults(object): """ Encapsulate a set of search results. This is Bible-type independant. """ @@ -77,16 +166,8 @@ class BibleCommon(object): """ A common ancestor for bible download sites. """ - global log - log = logging.getLogger(u'BibleCommon') log.info(u'BibleCommon') - def __init__(self): - """ - An empty constructor... not sure why I'm here. - """ - pass - def _get_web_text(self, urlstring, proxyurl): """ Get the HTML from the web page. @@ -165,4 +246,4 @@ class BibleCommon(object): text = text[:start_tag] + text[end_tag + 1:] start_tag = text.find(u'<') text = text.replace(u'>', u'') - return text.rstrip().lstrip() \ No newline at end of file + return text.rstrip().lstrip() diff --git a/openlp/plugins/bibles/lib/bibleCSVimpl.py b/openlp/plugins/bibles/lib/csvbible.py similarity index 52% rename from openlp/plugins/bibles/lib/bibleCSVimpl.py rename to openlp/plugins/bibles/lib/csvbible.py index cc837a6db..a1a16339c 100644 --- a/openlp/plugins/bibles/lib/bibleCSVimpl.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -25,96 +25,97 @@ import logging import chardet +import csv -from openlp.plugins.bibles.lib.common import BibleCommon from openlp.core.lib import Receiver +from db import BibleDB -class BibleCSVImpl(BibleCommon): - global log - log = logging.getLogger(u'BibleCSVImpl') - log.info(u'BibleCVSImpl loaded') - def __init__(self, bibledb): +log = logging.getLogger(__name__) + +class CSVBible(BibleDB): + """ + This class provides a specialisation for importing of CSV Bibles. + """ + + def __init__(self, parent, **kwargs): """ Loads a Bible from a pair of CVS files passed in This class assumes the files contain all the information and a clean bible is being loaded. """ - self.bibledb = bibledb - self.loadbible = True + BibleDB.__init__(self, parent, **kwargs) + log.info(self.__class__.__name__) + if u'booksfile' not in kwargs: + raise KeyError(u'You have to supply a file to import books from.') + self.booksfile = kwargs[u'booksfile'] + if u'versesfile' not in kwargs: + raise KeyError(u'You have to supply a file to import verses from.') + self.versesfile = kwargs[u'versesfile'] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) def stop_import(self): - self.loadbible = False + """ + Stops the import of the Bible. + """ + log.debug('Stopping import!') + self.stop_import_flag = True - def load_data(self, booksfile, versesfile, dialogobject): + def do_import(self): #Populate the Tables success = True - fbooks = None + books_file = None try: - fbooks = open(booksfile, 'r') - count = 0 - for line in fbooks: + books_file = open(self.booksfile, 'r') + dialect = csv.Sniffer().sniff(books_file.read(1024)) + books_file.seek(0) + books_reader = csv.reader(books_file, dialect) + for line in books_reader: # cancel pressed - if not self.loadbible: + if self.stop_import_flag: break - details = chardet.detect(line) - line = unicode(line, details['encoding']) - p = line.split(u',') - p1 = p[1].replace(u'"', u'') - p2 = p[2].replace(u'"', u'') - p3 = p[3].replace(u'"', u'') - self.bibledb.create_book(p2, p3, int(p1)) - count += 1 - #Flush the screen events - if count % 3 == 0: - Receiver.send_message(u'process_events') - count = 0 + details = chardet.detect(line[1]) + self.create_book(unicode(line[1], details['encoding']), + line[2], int(line[0])) + Receiver.send_message(u'process_events') except: log.exception(u'Loading books from file failed') success = False finally: - if fbooks: - fbooks.close() + if books_file: + books_file.close() if not success: return False - fverse = None + verse_file = None try: - fverse = open(versesfile, 'r') - count = 0 book_ptr = None - for line in fverse: - if not self.loadbible: # cancel pressed + verse_file = open(versesfile, 'r') + dialect = csv.Sniffer().sniff(verse_file.read(1024)) + verse_file.seek(0) + verse_reader = csv.reader(verse_file, dialect) + for line in verse_reader: + if self.stop_import_flag: # cancel pressed break - details = chardet.detect(line) - line = unicode(line, details['encoding']) - # split into 3 units and leave the rest as a single field - p = line.split(u',', 3) - p0 = p[0].replace(u'"', u'') - p3 = p[3].replace(u'"', u'') - if book_ptr is not p0: - book = self.bibledb.get_bible_book(p0) + details = chardet.detect(line[3]) + if book_ptr != line[0]: + book = self.get_book(line[0]) book_ptr = book.name - # increament the progress bar - dialogobject.incrementProgressBar(u'Importing %s %s' % \ - book.name) - self.bibledb.add_verse(book.id, p[1], p[2], p3) - count += 1 - #Every x verses repaint the screen - if count % 3 == 0: - Receiver.send_message(u'process_events') - count = 0 - self.bibledb.save_verses() + self.wizard.incrementProgressBar( + u'Importing %s %s' % (book.name, line[1])) + self.commit() + self.create_verse(book.id, line[1], line[2], + unicode(line[3], details['encoding'])) + Receiver.send_message(u'process_events') + self.commit() except: log.exception(u'Loading verses from file failed') success = False finally: - if fverse: - fverse.close() - if not self.loadbible: - dialogobject.incrementProgressBar(u'Import canceled!') - dialogobject.ImportProgressBar.setValue( - dialogobject.ImportProgressBar.maximum()) + if verse_file: + verse_file.close() + if self.stop_import_flag: + self.wizard.incrementProgressBar(u'Import canceled!') return False else: - return success \ No newline at end of file + return success + diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py new file mode 100644 index 000000000..be4112a54 --- /dev/null +++ b/openlp/plugins/bibles/lib/db.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 os +import logging +import chardet + +from sqlalchemy import or_ +from PyQt4 import QtCore + +from openlp.plugins.bibles.lib.models import * + +log = logging.getLogger(__name__) + +class BibleDB(QtCore.QObject): + """ + This class represents a database-bound Bible. It is used as a base class + for all the custom importers, so that the can implement their own import + methods, but benefit from the database methods in here via inheritance, + rather than depending on yet another object. + """ + + def __init__(self, parent, **kwargs): + """ + The constructor loads up the database and creates and initialises the + tables if the database doesn't exist. + + **Required keyword arguments:** + + ``path`` + The path to the bible database file. + + ``name`` + The name of the database. This is also used as the file name for + SQLite databases. + + ``config`` + The configuration object, passed in from the plugin. + """ + log.info(u'BibleDB loaded') + QtCore.QObject.__init__(self) + if u'path' not in kwargs: + raise KeyError(u'Missing keyword argument "path".') + if u'name' not in kwargs: + raise KeyError(u'Missing keyword argument "name".') + if u'config' not in kwargs: + raise KeyError(u'Missing keyword argument "config".') + self.stop_import_flag = False + self.name = kwargs[u'name'] + self.config = kwargs[u'config'] + self.db_file = os.path.join(kwargs[u'path'], + u'%s.sqlite' % kwargs[u'name']) + log.debug(u'Load bible %s on path %s', kwargs[u'name'], self.db_file) + db_type = self.config.get_config(u'db type', u'sqlite') + db_url = u'' + if db_type == u'sqlite': + db_url = u'sqlite:///' + self.db_file + else: + db_url = u'%s://%s:%s@%s/%s' % \ + (db_type, self.config.get_config(u'db username'), + self.config.get_config(u'db password'), + self.config.get_config(u'db hostname'), + self.config.get_config(u'db database')) + self.metadata, self.session = init_models(db_url) + self.metadata.create_all(checkfirst=True) + + def register(self, wizard): + """ + This method basically just initialialises the database. It is called + from the Bible Manager when a Bible is imported. Descendant classes + may want to override this method to supply their own custom + initialisation as well. + """ + self.wizard = wizard + self.create_tables() + return self.name + + def commit(self): + log.debug('Committing...') + self.session.commit() + + def create_tables(self): + log.debug(u'createTables') + self.create_meta(u'dbversion', u'2') + self.create_testament(u'Old Testament') + self.create_testament(u'New Testament') + self.create_testament(u'Apocrypha') + + def create_testament(self, testament): + log.debug(u'BibleDB.create_testament("%s")', testament) + self.session.add(Testament.populate(name=testament)) + self.commit() + + def create_book(self, name, abbrev, testament=1): + log.debug(u'create_book %s,%s', name, abbrev) + book = Book.populate(name=name, abbreviation=abbrev, + testament_id=testament) + self.session.add(book) + self.commit() + return book + + def create_chapter(self, book_id, chapter, textlist): + log.debug(u'create_chapter %s,%s', book_id, chapter) + #text list has book and chapter as first two elements of the array + for verse_number, verse_text in textlist.iteritems(): + verse = Verse.populate( + book_id = book_id, + chapter = chapter, + verse = verse_number, + text = verse_text + ) + self.session.add(verse) + self.commit() + + def create_verse(self, book_id, chapter, verse, text): + if not isinstance(text, unicode): + details = chardet.detect(text) + text = unicode(text, details[u'encoding']) + verse = Verse.populate( + book_id=book_id, + chapter=chapter, + verse=verse, + text=text + ) + self.session.add(verse) + return verse + + def create_meta(self, key, value): + log.debug(u'save_meta %s/%s', key, value) + self.session.add(BibleMeta.populate(key=key, value=value)) + self.commit() + + def get_books(self): + log.debug(u'BibleDB.get_books()') + return self.session.query(Book).order_by(Book.id).all() + + def get_book(self, book): + log.debug(u'BibleDb.get_book("%s")', book) + db_book = self.session.query(Book)\ + .filter(Book.name.like(book + u'%'))\ + .first() + if db_book is None: + db_book = self.session.query(Book)\ + .filter(Book.abbreviation.like(book + u'%'))\ + .first() + return db_book + + def get_chapter(self, id, chapter): + log.debug(u'BibleDB.get_chapter("%s", %s)', id, chapter) + return self.session.query(Verse)\ + .filter_by(chapter=chapter)\ + .filter_by(book_id=id)\ + .first() + + def get_verses(self, reference_list): + """ + This is probably the most used function. It retrieves the list of + verses based on the user's query. + + ``reference_list`` + This is the list of references the media manager item wants. It is + a list of tuples, with the following format:: + + (book, chapter, start_verse, end_verse) + + Therefore, when you are looking for multiple items, simply break + them up into references like this, bundle them into a list. This + function then runs through the list, and returns an amalgamated + list of ``Verse`` objects. For example:: + + [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + """ + log.debug(u'BibleDB.get_verses: %s', reference_list) + verse_list = [] + for book, chapter, start_verse, end_verse in reference_list: + db_book = self.get_book(book) + if end_verse == -1: + end_verse = self.get_verse_count(book, chapter) + if db_book: + book = db_book.name + log.debug(u'Book name corrected to "%s"', book) + verses = self.session.query(Verse)\ + .filter_by(book_id=db_book.id)\ + .filter_by(chapter=chapter)\ + .filter(Verse.verse >= start_verse)\ + .filter(Verse.verse <= end_verse)\ + .order_by(Verse.verse)\ + .all() + verse_list.extend(verses) + return verse_list + + def verse_search(self, text): + """ + Search for verses containing text ``text``. + + ``text`` + The text to search for. If the text contains commas, it will be + split apart and OR'd on the list of values. If the text just + contains spaces, it will split apart and AND'd on the list of + values. + """ + log.debug(u'BibleDB.verse_search("%s")', text) + verses = self.session.query(Verse) + if text.find(u',') > -1: + or_clause = [] + keywords = [u'%%%s%%' % keyword.strip() for keyword in text.split(u',')] + for keyword in keywords: + or_clause.append(Verse.text.like(keyword)) + verses = verses.filter(or_(*or_clause)) + else: + keywords = [u'%%%s%%' % keyword.strip() for keyword in text.split(u' ')] + for keyword in keywords: + verses = verses.filter(Verse.text.like(keyword)) + verses = verses.all() + return verses + + def get_chapter_count(self, book): + log.debug(u'BibleDB.get_chapter_count("%s")', book) + count = self.session.query(Verse.chapter).join(Book)\ + .filter(Book.name==book)\ + .distinct().count() + #verse = self.session.query(Verse).join(Book).filter( + # Book.name == bookname).order_by(Verse.chapter.desc()).first() + if not count: + return 0 + else: + return count + + def get_verse_count(self, book, chapter): + log.debug(u'BibleDB.get_verse_count("%s", %s)', book, chapter) + count = self.session.query(Verse).join(Book)\ + .filter(Book.name==book)\ + .filter(Verse.chapter==chapter)\ + .count() + #verse = self.session.query(Verse).join(Book).filter( + # Book.name == bookname).filter( + # Verse.chapter == chapter).order_by(Verse.verse.desc()).first() + if not count: + return 0 + else: + return count + + def get_meta(self, key): + log.debug(u'get meta %s', key) + return self.session.query(BibleMeta).get(key) + + def delete_meta(self, metakey): + biblemeta = self.get_meta(metakey) + try: + self.session.delete(biblemeta) + self.commit() + return True + except: + return False + + def dump_bible(self): + log.debug(u'.........Dumping Bible Database') + log.debug('...............................Books ') + books = self.session.query(Book).all() + log.debug(books) + log.debug(u'...............................Verses ') + verses = self.session.query(Verse).all() + log.debug(verses) + diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py new file mode 100644 index 000000000..bd4ad8e0b --- /dev/null +++ b/openlp/plugins/bibles/lib/http.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 urllib2 +import os +import sqlite3 + +from BeautifulSoup import BeautifulSoup + +from openlp.core.lib import Receiver +from common import BibleCommon, SearchResults +from db import BibleDB +from openlp.plugins.bibles.lib.models import Book + +log = logging.getLogger(__name__) + +class HTTPBooks(object): + cursor = None + + @staticmethod + def get_cursor(): + if HTTPBooks.cursor is None: + filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), + u'..', u'resources', u'httpbooks.sqlite') + conn = sqlite3.connect(filepath) + HTTPBooks.cursor = conn.cursor() + return HTTPBooks.cursor + + @staticmethod + def run_sql(query, parameters=()): + cursor = HTTPBooks.get_cursor() + cursor.execute(query, parameters) + return cursor.fetchall() + + @staticmethod + def get_books(): + books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM books ORDER BY id') + book_list = [] + for book in books: + book_list.append({ + u'id': book[0], + u'testament_id': book[1], + u'name': unicode(book[2]), + u'abbreviation': unicode(book[3]), + u'chapters': book[4] + }) + return book_list + + @staticmethod + def get_book(name): + if not isinstance(name, unicode): + name = unicode(name) + books = HTTPBooks.run_sql(u'SELECT id, testament_id, name, ' + u'abbreviation, chapters FROM books WHERE name = ? OR ' + u'abbreviation = ?', (name, name)) + if books: + return { + u'id': books[0][0], + u'testament_id': books[0][1], + u'name': unicode(books[0][2]), + u'abbreviation': unicode(books[0][3]), + u'chapters': books[0][4] + } + else: + return None + + @staticmethod + def get_chapter(name, chapter): + if not isinstance(name, int): + chapter = int(chapter) + book = HTTPBooks.get_book(name) + chapters = HTTPBooks.run_sql(u'SELECT id, book_id, chapter, ' + u'verses FROM chapters WHERE book_id = ?', (book[u'id'],)) + if chapters: + return { + u'id': chapters[0][0], + u'book_id': chapters[0][1], + u'chapter': chapters[0][2], + u'verses': chapters[0][3] + } + else: + return None + + @staticmethod + def get_chapter_count(book): + details = HTTPBooks.get_book(book) + if details: + return details[u'chapters'] + return 0 + + @staticmethod + def get_verse_count(book, chapter): + details = HTTPBooks.get_chapter(book, chapter) + if details: + return details[u'verses'] + return 0 + + +class BGExtract(BibleCommon): + log.info(u'%s BGExtract loaded', __name__) + + def __init__(self, proxyurl=None): + log.debug(u'init %s', proxyurl) + self.proxyurl = proxyurl + + def get_bible_chapter(self, version, bookname, chapter) : + """ + Access and decode bibles via the BibleGateway website + + ``Version`` + The version of the bible like 31 for New International version + + ``bookname`` + Name of the Book + + ``chapter`` + Chapter number + """ + log.debug(u'get_bible_chapter %s, %s, %s', version, bookname, chapter) + urlstring = u'http://www.biblegateway.com/passage/?search=%s+%s' \ + u'&version=%s' % (bookname, chapter, version) + log.debug(u'BibleGateway url = %s' % urlstring) + xml_string = self._get_web_text(urlstring, self.proxyurl) + verseSearch = u'<sup class=\"versenum' + verseFootnote = u'<sup class=\'footnote' + verse = 1 + i = xml_string.find(u'result-text-style-normal') + 26 + xml_string = xml_string[i:len(xml_string)] + versePos = xml_string.find(verseSearch) + bible = {} + while versePos > -1: + # clear out string + verseText = u'' + versePos = xml_string.find(u'</sup>', versePos) + 6 + i = xml_string.find(verseSearch, versePos + 1) + # Not sure if this is needed now + if i == -1: + i = xml_string.find(u'</div', versePos + 1) + j = xml_string.find(u'<strong', versePos + 1) + if j > 0 and j < i: + i = j + verseText = xml_string[versePos + 7 : i ] + # store the verse + bible[verse] = self._clean_text(verseText) + versePos = -1 + else: + verseText = xml_string[versePos: i] + start_tag = verseText.find(verseFootnote) + while start_tag > -1: + end_tag = verseText.find(u'</sup>') + verseText = verseText[:start_tag] + verseText[end_tag + 6:len(verseText)] + start_tag = verseText.find(verseFootnote) + # Chop off verse and start again + xml_string = xml_string[i:] + #look for the next verse + versePos = xml_string.find(verseSearch) + # store the verse + bible[verse] = self._clean_text(verseText) + verse += 1 + return SearchResults(bookname, chapter, bible) + +class CWExtract(BibleCommon): + log.info(u'%s CWExtract loaded', __name__) + + def __init__(self, proxyurl=None): + log.debug(u'init %s', proxyurl) + self.proxyurl = proxyurl + + def get_bible_chapter(self, version, bookname, chapter): + log.debug(u'%s %s, %s, %s', __name__, version, bookname, chapter) + """ + Access and decode bibles via the Crosswalk website + + ``version`` + The version of the bible like niv for New International Version + + ``bookname`` + Text name of in english e.g. 'gen' for Genesis + + ``chapter`` + Chapter number + """ + log.debug(u'get_bible_chapter %s,%s,%s', + version, bookname, chapter) + bookname = bookname.replace(u' ', u'') + chapter_url = u'http://www.biblestudytools.com/%s/%s/%s.html' % \ + (version, bookname.lower(), chapter) + log.debug(u'URL: %s', chapter_url) + page = urllib2.urlopen(chapter_url) + if not page: + return None + soup = BeautifulSoup(page) + htmlverses = soup.findAll(u'span', u'versetext') + verses = {} + for verse in htmlverses: + Receiver.send_message(u'process_events') + versenumber = int(verse.contents[0].contents[0]) + versetext = u'' + for part in verse.contents: + if str(part)[0] != u'<': + versetext = versetext + part + elif part and part.attrMap and part.attrMap[u'class'] == u'WordsOfChrist': + for subpart in part.contents: + if str(subpart)[0] != '<': + versetext = versetext + subpart + versetext = versetext.strip(u'\n\r\t ') + verses[versenumber] = versetext + return SearchResults(bookname, chapter, verses) + + +class HTTPBible(BibleDB): + log.info(u'%s HTTPBible loaded' , __name__) + + def __init__(self, parent, **kwargs): + """ + Finds all the bibles defined for the system + Creates an Interface Object for each bible containing connection + information + + Throws Exception if no Bibles are found. + + Init confirms the bible exists and stores the database path. + """ + BibleDB.__init__(self, parent, **kwargs) + if u'download_source' not in kwargs: + raise KeyError(u'Missing keyword argument "download_source"') + if u'download_name' not in kwargs: + raise KeyError(u'Missing keyword argument "download_name"') + self.download_source = kwargs[u'download_source'] + self.download_name = kwargs[u'download_name'] + if u'proxy_server' in kwargs: + self.proxy_server = kwargs[u'proxy_server'] + else: + self.proxy_server = None + if u'proxy_username' in kwargs: + self.proxy_username = kwargs[u'proxy_username'] + else: + self.proxy_username = None + if u'proxy_password' in kwargs: + self.proxy_password = kwargs[u'proxy_password'] + else: + self.proxy_password = None + + def do_import(self): + self.wizard.ImportProgressBar.setMaximum(2) + self.wizard.incrementProgressBar('Registering bible...') + self.create_meta(u'download source', self.download_source) + self.create_meta(u'download name', self.download_name) + if self.proxy_server: + self.create_meta(u'proxy server', self.proxy_server) + if self.proxy_username: + # store the proxy userid + self.create_meta(u'proxy username', self.proxy_username) + if self.proxy_password: + # store the proxy password + self.create_meta(u'proxy password', self.proxy_password) + self.wizard.incrementProgressBar('Registered.') + return True + + def get_verses(self, reference_list): + """ + A reimplementation of the ``BibleDB.get_verses`` method, this one is + specifically for web Bibles. It first checks to see if the particular + chapter exists in the DB, and if not it pulls it from the web. If the + chapter DOES exist, it simply pulls the verses from the DB using the + ancestor method. + + ``reference_list`` + This is the list of references the media manager item wants. It is + a list of tuples, with the following format:: + + (book, chapter, start_verse, end_verse) + + Therefore, when you are looking for multiple items, simply break + them up into references like this, bundle them into a list. This + function then runs through the list, and returns an amalgamated + list of ``Verse`` objects. For example:: + + [(u'Genesis', 1, 1, 1), (u'Genesis', 2, 2, 3)] + """ + for reference in reference_list: + log.debug('Reference: %s', reference) + book = reference[0] + db_book = self.get_book(book) + if not db_book: + book_details = self.lookup_book(book) + if not book_details: + Receiver.send_message(u'bible_nobook') + return [] + db_book = self.create_book(book_details[u'name'], + book_details[u'abbreviation'], book_details[u'testament_id']) + book = db_book.name + if BibleDB.get_verse_count(self, book, reference[1]) == 0: + Receiver.send_message(u'bible_showprogress') + Receiver.send_message(u'process_events') + search_results = self.get_chapter(self.name, book, reference[1]) + if search_results and search_results.has_verselist(): + ## We have found a book of the bible lets check to see + ## if it was there. By reusing the returned book name + ## we get a correct book. For example it is possible + ## to request ac and get Acts back. + bookname = search_results.get_book() + # check to see if book/chapter exists + db_book = self.get_book(bookname) + self.create_chapter(db_book.id, search_results.get_chapter(), + search_results.get_verselist()) + Receiver.send_message(u'bible_hideprogress') + Receiver.send_message(u'process_events') + return BibleDB.get_verses(self, reference_list) + + def get_chapter(self, version, book, chapter): + """ + Receive the request and call the relevant handler methods + """ + log.debug(u'get_chapter %s, %s, %s', version, book, chapter) + log.debug(u'source = %s', self.download_source) + try: + if self.download_source.lower() == u'crosswalk': + ev = CWExtract(self.proxy_server) + else: + ev = BGExtract(self.proxy_server) + return ev.get_bible_chapter(self.download_name, book, chapter) + except: + log.exception("Failed to get bible chapter") + return None + + def get_books(self): + return [Book.populate(name=book['name']) for book in HTTPBooks.get_books()] + + def lookup_book(self, book): + return HTTPBooks.get_book(book) + + def get_chapter_count(self, book): + return HTTPBooks.get_chapter_count(book) + + def get_verse_count(self, book, chapter): + return HTTPBooks.get_verse_count(book, chapter) + + def set_proxy_server(self, server): + self.proxy_server = server + diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index d44a18262..d24982532 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -26,340 +26,202 @@ import logging import os -from bibleOpenSongimpl import BibleOpenSongImpl -from bibleOSISimpl import BibleOSISImpl -from bibleCSVimpl import BibleCSVImpl -from bibleDBimpl import BibleDBImpl -from bibleHTTPimpl import BibleHTTPImpl +from common import parse_reference +from opensong import OpenSongBible +from osis import OSISBible +from csvbible import CSVBible +from db import BibleDB +from http import HTTPBible + +log = logging.getLogger(__name__) class BibleMode(object): + """ + This is basically an enumeration class which specifies the mode of a Bible. + Mode refers to whether or not a Bible in OpenLP is a full Bible or needs to + be downloaded from the Internet on an as-needed basis. + """ Full = 1 Partial = 2 class BibleFormat(object): + """ + This is a special enumeration class that holds the various types of Bibles, + plus a few helper functions to facilitate generic handling of Bible types + for importing. + """ Unknown = -1 OSIS = 0 CSV = 1 OpenSong = 2 WebDownload = 3 - @classmethod - def get_handler(class_, id): - if id == class_.OSIS: - return BibleOSISImpl - elif id == class_.CSV: - return BibleCSVImpl - elif id == class_.OpenSong: - return BibleOpenSongImpl - elif id == class_.WebDownload: - return BibleHTTPImpl + @staticmethod + def get_class(id): + """ + Return the appropriate imeplementation class. + """ + if id == BibleFormat.OSIS: + return OSISBible + elif id == BibleFormat.CSV: + return CSVBible + elif id == BibleFormat.OpenSong: + return OpenSongBible + elif id == BibleFormat.WebDownload: + return HTTPBible else: return None + @staticmethod + def list(): + return [ + BibleFormat.OSIS, + BibleFormat.CSV, + BibleFormat.OpenSong, + BibleFormat.WebDownload + ] + class BibleManager(object): """ The Bible manager which holds and manages all the Bibles. """ - global log - log = logging.getLogger(u'BibleManager') log.info(u'Bible manager loaded') - def __init__(self, config): + def __init__(self, parent, config): """ - Finds all the bibles defined for the system and creates an - interface object for each bible containing connection - information. Throws Exception if no Bibles are found. + Finds all the bibles defined for the system and creates an interface + object for each bible containing connection information. Throws + Exception if no Bibles are found. Init confirms the bible exists and stores the database path. ``config`` The plugin's configuration object. """ - self.config = config log.debug(u'Bible Initialising') + self.config = config + self.parent = parent self.web = u'Web' - # dict of bible database objects - self.bible_db_cache = None - # dict of bible http readers - self.bible_http_cache = None - self.biblePath = self.config.get_data_path() - #get proxy name for screen - self.proxyname = self.config.get_config(u'proxy name') - self.bibleSuffix = u'sqlite' - self.dialogobject = None + self.db_cache = None + self.path = self.config.get_data_path() + self.proxy_name = self.config.get_config(u'proxy name') + self.suffix = u'sqlite' + self.import_wizard = None self.reload_bibles() self.media = None def reload_bibles(self): + """ + Reloads the Bibles from the available Bible databases on disk. If a web + Bible is encountered, an instance of HTTPBible is loaded instead of the + BibleDB class. + """ log.debug(u'Reload bibles') - files = self.config.get_files(self.bibleSuffix) + files = self.config.get_files(self.suffix) log.debug(u'Bible Files %s', files) - self.bible_db_cache = {} - self.bible_http_cache = {} - # books of the bible with testaments - self.book_testaments = {} - # books of the bible with chapter count - self.book_chapters = [] - # books of the bible with abbreviation - self.book_abbreviations = {} - self.web_bibles_present = False - for f in files: - nme = f.split(u'.') - bname = nme[0] - self.bible_db_cache[bname] = BibleDBImpl(self.biblePath, - bname, self.config) + self.db_cache = {} + for filename in files: + name, extension = os.path.splitext(filename) + self.db_cache[name] = BibleDB(self.parent, path=self.path, name=name, config=self.config) # look to see if lazy load bible exists and get create getter. - biblesource = self.bible_db_cache[bname].get_meta(u'WEB') - if biblesource: - self.web_bibles_present = True - nhttp = BibleHTTPImpl() - # tell The Server where to get the verses from. - nhttp.set_bible_source(biblesource.value) - self.bible_http_cache [bname] = nhttp - # look to see if lazy load bible exists and get create getter. - meta = self.bible_db_cache[bname].get_meta(u'proxy') - proxy = None - if meta: - proxy = meta.value - # tell The Server where to get the verses from. - nhttp.set_proxy(proxy) - # look to see if lazy load bible exists and get create getter. - bibleid = self.bible_db_cache[bname].get_meta(u'bibleid').value - # tell The Server where to get the verses from. - nhttp.set_bibleid(bibleid) - else: - # makes the Full / partial code easier. - self.bible_http_cache [bname] = None - if self.web_bibles_present: - # books of the bible linked to bibleid {osis, name} - self.book_testaments = {} - # books of the bible linked to bibleid {osis, abbrev} - self.book_abbreviations = {} - filepath = os.path.split(os.path.abspath(__file__))[0] - filepath = os.path.abspath(os.path.join( - filepath, u'..', u'resources',u'httpbooks.csv')) - fbibles = None - try: - fbibles = open(filepath, u'r') - for line in fbibles: - p = line.split(u',') - self.book_abbreviations[p[0]] = p[1].replace(u'\n', '') - self.book_testaments[p[0]] = p[2].replace(u'\n', '') - self.book_chapters.append({u'book':p[0], u'total':p[3].replace(u'\n', '')}) - except: - log.exception(u'Failed to load bible') - finally: - if fbibles: - fbibles.close() - log.debug(u'Bible Initialised') + source = self.db_cache[name].get_meta(u'download source') + if source: + download_name = self.db_cache[name].get_meta(u'download name').value + meta_proxy = self.db_cache[name].get_meta(u'proxy url') + web_bible = HTTPBible(self.parent, path=self.path, name=name, + config=self.config, download_source=source.value, + download_name=download_name) + if meta_proxy: + web_bible.set_proxy_server(meta_proxy.value) + #del self.db_cache[name] + self.db_cache[name] = web_bible + log.debug(u'Bibles reloaded') - def set_process_dialog(self, dialogobject): + def set_process_dialog(self, wizard): """ Sets the reference to the dialog with the progress bar on it. - ``dialogobject`` - The reference to the dialog. + ``dialog`` + The reference to the import wizard. """ - self.dialogobject = dialogobject + self.import_wizard = wizard def import_bible(self, type, **kwargs): """ Register a bible in the bible cache, and then import the verses. ``type`` - What type of Bible, + What type of Bible, one of the ``BibleFormat`` values. + + ``**kwargs`` + Keyword arguments to send to the actual importer class. """ - pass + class_ = BibleFormat.get_class(type) + kwargs['path'] = self.path + kwargs['config'] = self.config + importer = class_(self.parent, **kwargs) + name = importer.register(self.import_wizard) + self.db_cache[name] = importer + return importer.do_import() - def register_http_bible(self, biblename, biblesource, bibleid, - proxyurl=None, proxyid=None, proxypass=None): + def get_bibles(self): """ - Return a list of bibles from a given URL. The selected Bible - can then be registered and LazyLoaded into a database. - - ``biblename`` - The name of the bible to register. - - ``biblesource`` - Where this Bible stores it's verses. - - ``bibleid`` - The identifier for a Bible. - - ``proxyurl`` - Defaults to *None*. An optional URL to a proxy server. - - ``proxyid`` - Defaults to *None*. A username for logging into the proxy - server. - - ``proxypass`` - Defaults to *None*. The password to accompany the username. - """ - log.debug(u'register_HTTP_bible %s, %s, %s, %s, %s, %s', - biblename, biblesource, bibleid, proxyurl, proxyid, proxypass) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) - # Create Database - nbible.create_tables() - self.bible_db_cache[biblename] = nbible - nhttp = BibleHTTPImpl() - nhttp.set_bible_source(biblesource) - self.bible_http_cache[biblename] = nhttp - # register a lazy loading interest - nbible.save_meta(u'WEB', biblesource) - # store the web id of the bible - nbible.save_meta(u'bibleid', bibleid) - if proxyurl: - # store the proxy URL - nbible.save_meta(u'proxy', proxyurl) - nhttp.set_proxy(proxyurl) - if proxyid: - # store the proxy userid - nbible.save_meta(u'proxyid', proxyid) - if proxypass: - # store the proxy password - nbible.save_meta(u'proxypass', proxypass) - return True - else: - log.debug(u'register_http_file_bible %s not created already exists', - biblename) - return False - - def register_csv_file_bible(self, biblename, booksfile, versefile): - """ - Method to load a bible from a set of files into a database. - If the database exists it is deleted and the database is reloaded - from scratch. - """ - log.debug(u'register_CSV_file_bible %s,%s,%s', - biblename, booksfile, versefile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) - # Create database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bcsv = BibleCSVImpl(nbible) - return bcsv.load_data(booksfile, versefile, self.dialogobject) - else: - log.debug(u'register_csv_file_bible %s not created already exists', - biblename) - return False - - def register_osis_file_bible(self, biblename, osisfile): - """ - Method to load a bible from a osis xml file extracted from Sword bible - viewer. If the database exists it is deleted and the database is - reloaded from scratch. - """ - log.debug(u'register_OSIS_file_bible %s, %s', biblename, osisfile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) - # Create Database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bosis = BibleOSISImpl(self.biblePath, nbible) - return bosis.load_data(osisfile, self.dialogobject) - else: - log.debug( - u'register_OSIS_file_bible %s, %s not created already exists', - biblename, osisfile) - return False - - def register_opensong_bible(self, biblename, opensongfile): - """ - Method to load a bible from an OpenSong xml file. If the database - exists it is deleted and the database is reloaded from scratch. - """ - log.debug(u'register_opensong_file_bible %s, %s', biblename, opensongfile) - if self._is_new_bible(biblename): - # Create new Bible - nbible = BibleDBImpl(self.biblePath, biblename, self.config) - # Create Database - nbible.create_tables() - # Cache the database for use later - self.bible_db_cache[biblename] = nbible - # Create the loader and pass in the database - bcsv = BibleOpenSongImpl(self.biblePath, nbible) - bcsv.load_data(opensongfile, self.dialogobject) - return True - else: - log.debug(u'register_opensong_file_bible %s, %s not created ' - u'already exists', biblename, opensongfile) - return False - - def get_bibles(self, mode=BibleMode.Full): - """ - Returns a list of Books of the bible. When ``mode`` is set to - ``BibleMode.Full`` this method returns all the Bibles for the - Advanced Search, and when the mode is ``BibleMode.Partial`` - this method returns all the bibles for the Quick Search. + Returns a list of the names of available Bibles. """ log.debug(u'get_bibles') - bible_list = [] - for bible_name, bible_object in self.bible_db_cache.iteritems(): - if self.bible_http_cache[bible_name]: - bible_name = u'%s (%s)' % (bible_name, self.web) - bible_list.append(bible_name) - return bible_list + return [name for name, bible in self.db_cache.iteritems()] - def is_bible_web(self, bible): - pos_end = bible.find(u' (%s)' % self.web) - if pos_end != -1: - return True, bible[:pos_end] - return False, bible - - def get_bible_books(self): + def get_books(self, bible): """ - Returns a list of the books of the bible - """ - log.debug(u'get_bible_books') - return self.book_chapters + Returns a list of Bible books, and the number of chapters in that book. - def get_book_chapter_count(self, book): + ``bible`` + Unicode. The Bible to get the list of books from. + """ + log.debug(u'BibleManager.get_books("%s")', bible) + return [ + { + u'name': book.name, + u'chapters': self.db_cache[bible].get_chapter_count(book.name) + } + for book in self.db_cache[bible].get_books() + ] + + def get_chapter_count(self, bible, book): """ Returns the number of Chapters for a given book """ log.debug(u'get_book_chapter_count %s', book) - return self.book_chapters[book] + return self.db_cache[bible].get_chapter_count(book) - def get_book_verse_count(self, bible, book, chapter): + def get_verse_count(self, bible, book, chapter): """ Returns all the number of verses for a given book and chapterMaxBibleBookVerses """ - log.debug(u'get_book_verse_count %s,%s,%s', bible, book, chapter) - web, bible = self.is_bible_web(bible) - if web: - count = self.bible_db_cache[bible].get_max_bible_book_verses( - book, chapter) - if count == 0: - # Make sure the first chapter has been downloaded - self.get_verse_text(bible, book, chapter, chapter, 1, 1) - count = self.bible_db_cache[bible].get_max_bible_book_verses( - book, chapter) - return count - else: - return self.bible_db_cache[bible].get_max_bible_book_verses( - book, chapter) + log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter) + return self.db_cache[bible].get_verse_count(book, chapter) - def get_verse_from_text(self, bible, versetext): + def get_verses(self, bible, versetext): """ - Returns all the number of verses for a given - book and chapterMaxBibleBookVerses + Parses a scripture reference, fetches the verses from the Bible + specified, and returns a list of ``Verse`` objects. + + ``bible`` + Unicode. The Bible to use. + + ``versetext`` + Unicode. The scripture reference. Valid scripture references are: + + - Genesis 1:1 + - Genesis 1:1-10 + - Genesis 1:1-2:10 """ - log.debug(u'get_verses_from_text %s,%s', bible, versetext) - web, bible = self.is_bible_web(bible) - return self.bible_db_cache[bible].get_verses_from_text(versetext) + log.debug(u'BibleManager.get_verses("%s", "%s")', bible, versetext) + reflist = parse_reference(versetext) + return self.db_cache[bible].get_verses(reflist) def save_meta_data(self, bible, version, copyright, permissions): """ @@ -367,124 +229,28 @@ class BibleManager(object): """ log.debug(u'save_meta data %s,%s, %s,%s', bible, version, copyright, permissions) - self.bible_db_cache[bible].save_meta(u'Version', version) - self.bible_db_cache[bible].save_meta(u'Copyright', copyright) - self.bible_db_cache[bible].save_meta(u'Permissions', permissions) + self.db_cache[bible].create_meta(u'Version', version) + self.db_cache[bible].create_meta(u'Copyright', copyright) + self.db_cache[bible].create_meta(u'Permissions', permissions) def get_meta_data(self, bible, key): """ Returns the meta data for a given key """ log.debug(u'get_meta %s,%s', bible, key) - web, bible = self.is_bible_web(bible) - return self.bible_db_cache[bible].get_meta(key) + return self.db_cache[bible].get_meta(key) - def get_verse_text(self, bible, bookname, schapter, echapter, sverse, - everse=0): - """ - Returns a list of verses for a given Book, Chapter and ranges of verses. - If the end verse(everse) is less then the start verse(sverse) - then only one verse is returned - - ``bible`` - The name of the bible to be used - - Rest can be guessed at ! - """ - text = [] - self.media.setQuickMessage(u'') - log.debug(u'get_verse_text %s,%s,%s,%s,%s,%s', - bible, bookname, schapter, echapter, sverse, everse) - # check to see if book/chapter exists fow HTTP bibles and load cache - # if necessary - web, bible = self.is_bible_web(bible) - if self.bible_http_cache[bible]: - book = self.bible_db_cache[bible].get_bible_book(bookname) - if book is None: - log.debug(u'get_verse_text : new book') - for chapter in range(schapter, echapter + 1): - self.media.setQuickMessage( - unicode(self.media.trUtf8('Downloading %s: %s')) % - (bookname, chapter)) - search_results = \ - self.bible_http_cache[bible].get_bible_chapter( - bible, bookname, chapter) - if search_results.has_verselist() : - ## We have found a book of the bible lets check to see - ## if it was there. By reusing the returned book name - ## we get a correct book. For example it is possible - ## to request ac and get Acts back. - bookname = search_results.get_book() - # check to see if book/chapter exists - book = self.bible_db_cache[bible].get_bible_book( - bookname) - if book is None: - ## Then create book, chapter and text - book = self.bible_db_cache[bible].create_book( - bookname, self.book_abbreviations[bookname], - self.book_testaments[bookname]) - log.debug(u'New http book %s, %s, %s', - book, book.id, book.name) - self.bible_db_cache[bible].create_chapter( - book.id, search_results.get_chapter(), - search_results.get_verselist()) - else: - ## Book exists check chapter and texts only. - v = self.bible_db_cache[bible].get_bible_chapter( - book.id, chapter) - if v is None: - self.media.setQuickMessage( - unicode(self.media.trUtf8('%Downloading %s: %s'))\ - % (bookname, chapter)) - self.bible_db_cache[bible].create_chapter( - book.id, chapter, - search_results.get_verselist()) - else: - log.debug(u'get_verse_text : old book') - for chapter in range(schapter, echapter + 1): - v = self.bible_db_cache[bible].get_bible_chapter( - book.id, chapter) - if v is None: - try: - self.media.setQuickMessage(\ - unicode(self.media.trUtf8('Downloading %s: %s')) - % (bookname, chapter)) - search_results = \ - self.bible_http_cache[bible].get_bible_chapter( - bible, bookname, chapter) - if search_results.has_verselist(): - self.bible_db_cache[bible].create_chapter( - book.id, search_results.get_chapter(), - search_results.get_verselist()) - except: - log.exception(u'Problem getting scripture online') - #Now get verses from database - if schapter == echapter: - text = self.bible_db_cache[bible].get_bible_text(bookname, - schapter, sverse, everse) - else: - for i in range (schapter, echapter + 1): - if i == schapter: - start = sverse - end = self.get_book_verse_count(bible, bookname, i) - elif i == echapter: - start = 1 - end = everse - else: - start = 1 - end = self.get_book_verse_count(bible, bookname, i) - - txt = self.bible_db_cache[bible].get_bible_text( - bookname, i, start, end) - text.extend(txt) - return text - - def _is_new_bible(self, name): + def exists(self, name): """ Check cache to see if new bible """ - for bible, o in self.bible_db_cache.iteritems(): + if not isinstance(name, unicode): + name = unicode(name) + for bible, db_object in self.db_cache.iteritems(): log.debug(u'Bible from cache in is_new_bible %s', bible) + if not isinstance(bible, unicode): + bible = unicode(bible) if bible == name: - return False - return True \ No newline at end of file + return True + return False + diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 69bfbbef7..278988594 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -31,7 +31,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, Receiver, str_to_bool, \ BaseListWithDnD from openlp.plugins.bibles.forms import ImportWizardForm -from openlp.plugins.bibles.lib.manager import BibleMode + +log = logging.getLogger(__name__) class BibleListView(BaseListWithDnD): """ @@ -41,13 +42,13 @@ class BibleListView(BaseListWithDnD): self.PluginName = u'Bibles' BaseListWithDnD.__init__(self, parent) + def resizeEvent(self, event): + self.parent.onListViewResize(event.size().width(), event.size().width()) class BibleMediaItem(MediaManagerItem): """ This is the custom media manager item for Bibles. """ - global log - log = logging.getLogger(u'BibleMediaItem') log.info(u'Bible Media Item loaded') def __init__(self, parent, icon, title): @@ -56,6 +57,7 @@ class BibleMediaItem(MediaManagerItem): self.IconPath = u'songs/song' self.ListViewWithDnD_class = BibleListView self.servicePath = None + self.lastReference = [] MediaManagerItem.__init__(self, parent, icon, title) # place to store the search results self.search_results = {} @@ -241,6 +243,24 @@ class BibleMediaItem(MediaManagerItem): QtCore.SIGNAL(u'pressed()'), self.onQuickSearchButton) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_updated'), self.configUpdated) + # Other stuff + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'bible_showprogress'), self.onSearchProgressShow) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'bible_hideprogress'), self.onSearchProgressHide) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'bible_nobook'), self.onNoBookFound) + + def addListViewToToolBar(self): + MediaManagerItem.addListViewToToolBar(self) + # Progress Bar + self.SearchProgress = QtGui.QProgressBar(self) + self.SearchProgress.setFormat('%p%') + self.SearchProgress.setMaximum(3) + self.SearchProgress.setGeometry(self.ListView.geometry().left(), + self.ListView.geometry().top(), 81, 23) + self.SearchProgress.setVisible(False) + self.SearchProgress.setObjectName(u'SearchProgress') def configUpdated(self): if str_to_bool( @@ -281,7 +301,7 @@ class BibleMediaItem(MediaManagerItem): def initialise(self): log.debug(u'bible manager initialise') - self.parent.biblemanager.media = self + self.parent.manager.media = self self.loadBibles() self.configUpdated() log.debug(u'bible manager initialise complete') @@ -301,23 +321,40 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedSecondBibleComboBox.clear() self.QuickSecondBibleComboBox.addItem(u'') self.AdvancedSecondBibleComboBox.addItem(u'') - bibles = self.parent.biblemanager.get_bibles(BibleMode.Full) + bibles = self.parent.manager.get_bibles() # load bibles into the combo boxes + first = True for bible in bibles: self.QuickVersionComboBox.addItem(bible) self.QuickSecondBibleComboBox.addItem(bible) - # Without HTTP - bibles = self.parent.biblemanager.get_bibles(BibleMode.Partial) - first = True - # load bibles into the combo boxes - for bible in bibles: self.AdvancedVersionComboBox.addItem(bible) self.AdvancedSecondBibleComboBox.addItem(bible) if first: first = False - # use the first bible as the trigger self.initialiseBible(bible) + def onListViewResize(self, width, height): + self.SearchProgress.setGeometry(self.ListView.geometry().x(), + (self.ListView.geometry().y() + self.ListView.geometry().height())\ + - 23, 81, 23) + + def onSearchProgressShow(self): + self.SearchProgress.setVisible(True) + self.SearchProgress.setMinimum(0) + self.SearchProgress.setMaximum(2) + self.SearchProgress.setValue(1) + + def onSearchProgressHide(self): + self.SearchProgress.setVisible(False) + + def onNoBookFound(self): + QtGui.QMessageBox.critical(self, + self.trUtf8('No Book Found'), + self.trUtf8('No matching book could be found in this Bible.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), + QtGui.QMessageBox.Ok + ) + def onAdvancedVersionComboBox(self): self.initialiseBible( unicode(self.AdvancedVersionComboBox.currentText())) @@ -330,11 +367,8 @@ class BibleMediaItem(MediaManagerItem): self.AdvancedBookComboBox.itemData(item).toInt()[0]) def onNewClick(self): - #self.bibleimportform = BibleImportForm( - # self.parent.config, self.parent.biblemanager, self) - #self.bibleimportform.exec_() self.bibleimportform = ImportWizardForm(self, self.parent.config, - self.parent.biblemanager, self.parent) + self.parent.manager, self.parent) self.bibleimportform.exec_() self.reloadBibles() @@ -343,14 +377,13 @@ class BibleMediaItem(MediaManagerItem): self.adjustComboBox(frm, self.verses, self.AdvancedToVerse) def onAdvancedToChapter(self): - text1 = unicode(self.AdvancedFromChapter.currentText()) - text2 = unicode(self.AdvancedToChapter.currentText()) - if text1 != text2: + frm = unicode(self.AdvancedFromChapter.currentText()) + to = unicode(self.AdvancedToChapter.currentText()) + if frm != to: bible = unicode(self.AdvancedVersionComboBox.currentText()) book = unicode(self.AdvancedBookComboBox.currentText()) # get the verse count for new chapter - verses = self.parent.biblemanager.get_book_verse_count( - bible, book, int(text2)) + verses = self.parent.manager.get_verse_count(bible, book, int(to)) self.adjustComboBox(1, verses, self.AdvancedToVerse) def onAdvancedSearchButton(self): @@ -361,10 +394,13 @@ class BibleMediaItem(MediaManagerItem): chapter_to = int(self.AdvancedToChapter.currentText()) verse_from = int(self.AdvancedFromVerse.currentText()) verse_to = int(self.AdvancedToVerse.currentText()) - self.search_results = self.parent.biblemanager.get_verse_text( - bible, book, chapter_from, chapter_to, verse_from, verse_to) + versetext = u'%s %s:%s-%s:%s' % (book, chapter_from, verse_from, \ + chapter_to, verse_to) + self.search_results = self.parent.manager.get_verses(bible, versetext) if self.ClearAdvancedSearchComboBox.currentIndex() == 0: self.ListView.clear() + self.lastReference = [] + self.lastReference.append(versetext) self.displayResults(bible) def onAdvancedFromChapter(self): @@ -373,7 +409,7 @@ class BibleMediaItem(MediaManagerItem): cf = int(self.AdvancedFromChapter.currentText()) self.adjustComboBox(cf, self.chapters_from, self.AdvancedToChapter) # get the verse count for new chapter - vse = self.parent.biblemanager.get_book_verse_count(bible, book, cf) + vse = self.parent.manager.get_verse_count(bible, book, cf) self.adjustComboBox(1, vse, self.AdvancedFromVerse) self.adjustComboBox(1, vse, self.AdvancedToVerse) @@ -383,11 +419,9 @@ class BibleMediaItem(MediaManagerItem): text = unicode(self.QuickSearchEdit.displayText()) if self.ClearQuickSearchComboBox.currentIndex() == 0: self.ListView.clear() - if self.QuickSearchComboBox.currentIndex() == 1: - self.search_results = self.parent.biblemanager.get_verse_from_text( - bible, text) - else: - self.searchByReference(bible, text) + self.lastReference = [] + self.lastReference.append(text) + self.search_results = self.parent.manager.get_verses(bible, text) if self.search_results: self.displayResults(bible) @@ -400,60 +434,64 @@ class BibleMediaItem(MediaManagerItem): raw_slides = [] raw_footer = [] bible_text = u'' + service_item.autoPreviewAllowed = True + #If we want to use a 2nd translation / version + bible2 = u'' + if self.SearchTabWidget.currentIndex() == 0: + bible2 = unicode(self.QuickSecondBibleComboBox.currentText()) + else: + bible2 = unicode(self.AdvancedSecondBibleComboBox.currentText()) + if bible2: + bible2_verses = [] + for scripture in self.lastReference: + bible2_verses.extend(self.parent.manager.get_verses(bible2, scripture)) + bible2_version = self.parent.manager.get_meta_data(bible2, u'Version') + bible2_copyright = self.parent.manager.get_meta_data(bible2, u'Copyright') + bible2_permission = self.parent.manager.get_meta_data(bible2, u'Permission') + # Let's loop through the main lot, and assemble our verses for item in items: bitem = self.ListView.item(item.row()) - text = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) - search_verse = text[:text.find(u'(')] - bible = text[text.find(u'(') + 1:-1] - self.searchByReference(bible, search_verse) - book = self.search_results[0].book.name - chapter = unicode(self.search_results[0].chapter) - verse = unicode(self.search_results[0].verse) - text = self.search_results[0].text + reference = bitem.data(QtCore.Qt.UserRole).toPyObject() + bible = unicode(reference[QtCore.QString('bible')]) + book = unicode(reference[QtCore.QString('book')]) + chapter = unicode(reference[QtCore.QString('chapter')]) + verse = unicode(reference[QtCore.QString('verse')]) + text = unicode(reference[QtCore.QString('text')]) + version = unicode(reference[QtCore.QString('version')]) + copyright = unicode(reference[QtCore.QString('copyright')]) + permission = unicode(reference[QtCore.QString('permission')]) if self.parent.settings_tab.display_style == 1: - loc = self.formatVerse(old_chapter, chapter, verse, u'(u', u')') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'(u', u')') elif self.parent.settings_tab.display_style == 2: - loc = self.formatVerse(old_chapter, chapter, verse, u'{', u'}') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'{', u'}') elif self.parent.settings_tab.display_style == 3: - loc = self.formatVerse(old_chapter, chapter, verse, u'[', u']') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'[', u']') else: - loc = self.formatVerse(old_chapter, chapter, verse, u'', u'') + verse_text = self.formatVerse(old_chapter, chapter, verse, u'', u'') old_chapter = chapter - footer = u'%s (%s %s)' % (book, self.version, self.copyright) - #If not found throws and error so add.s - try: - raw_footer.index(footer) - except: + footer = u'%s (%s %s)' % (book, version, copyright) + #If not found add to footer + if footer not in raw_footer: raw_footer.append(footer) - #If we want to use a 2nd translation / version - bible2 = u'' - if self.SearchTabWidget.currentIndex() == 0: - bible2 = unicode(self.QuickSecondBibleComboBox.currentText()) - else: - bible2 = unicode(self.AdvancedSecondBibleComboBox.currentText()) - if len(bible2) > 0: - self.searchByReference(bible2, search_verse) - footer = u'%s (%s %s)' % (book, self.version, self.copyright) - #If not found throws and error so add.s - try: - raw_footer.index(footer) - except: + if bible2: + footer = u'%s (%s %s)' % (book, version, copyright) + #If not found add to footer + if footer not in raw_footer: raw_footer.append(footer) - bible_text = u'%s %s \n\n\n %s %s)' % \ - (loc, text, loc, self.search_results[0].text) + bible_text = u'%s %s \n\n %s %s' % \ + (verse_text, text, verse_text, bible2_verses[item.row()].text) raw_slides.append(bible_text) bible_text = u'' else: #Paragraph style force new line per verse if self.parent.settings_tab.layout_style == 1: text = text + u'\n\n' - bible_text = u'%s %s %s' % (bible_text, loc, text) + bible_text = u'%s %s %s' % (bible_text, verse_text, text) #if we are verse per slide then create slide if self.parent.settings_tab.layout_style == 0: raw_slides.append(bible_text) bible_text = u'' - service_item.title = u'%s %s' % (book, loc) - + service_item.title = u'%s %s' % (book, verse_text) if len(self.parent.settings_tab.bible_theme) == 0: service_item.theme = None else: @@ -467,40 +505,39 @@ class BibleMediaItem(MediaManagerItem): return True def formatVerse(self, old_chapter, chapter, verse, opening, closing): - loc = opening + verse_text = opening if old_chapter != chapter: - loc += chapter + u':' + verse_text += chapter + u':' elif not self.parent.settings_tab.show_new_chapters: - loc += chapter + u':' - loc += verse - loc += closing - return loc + verse_text += chapter + u':' + verse_text += verse + verse_text += closing + return verse_text def reloadBibles(self): log.debug(u'Reloading Bibles') - self.parent.biblemanager.reload_bibles() + self.parent.manager.reload_bibles() self.loadBibles() def initialiseBible(self, bible): log.debug(u'initialiseBible %s', bible) - book_data = self.parent.biblemanager.get_bible_books() + book_data = self.parent.manager.get_books(bible) self.AdvancedBookComboBox.clear() first = True for book in book_data: row = self.AdvancedBookComboBox.count() - self.AdvancedBookComboBox.addItem(book[u'book']) + self.AdvancedBookComboBox.addItem(book[u'name']) self.AdvancedBookComboBox.setItemData( - row, QtCore.QVariant(book[u'total'])) + row, QtCore.QVariant(book[u'chapters'])) if first: first = False self.initialiseChapterVerse( - bible, book[u'book'], book[u'total']) + bible, book[u'name'], book[u'chapters']) def initialiseChapterVerse(self, bible, book, chapters): log.debug(u'initialiseChapterVerse %s, %s', bible, book) self.chapters_from = chapters - self.verses = self.parent.biblemanager.get_book_verse_count(bible, - book, 1) + self.verses = self.parent.manager.get_verse_count(bible, book, 1) if self.verses == 0: self.AdvancedSearchButton.setEnabled(False) self.AdvancedMessage.setText(self.trUtf8('Bible not fully loaded')) @@ -519,12 +556,30 @@ class BibleMediaItem(MediaManagerItem): combo.addItem(unicode(i)) def displayResults(self, bible): + version = self.parent.manager.get_meta_data(bible, u'Version') + copyright = self.parent.manager.get_meta_data(bible, u'Copyright') + permission = self.parent.manager.get_meta_data(bible, u'Permission') + if not permission: + permission = u'' + else: + permission = permission.value for count, verse in enumerate(self.search_results): - bible_text = u' %s %d:%d (%s)' % (verse.book.name, - verse.chapter, verse.verse, bible) + bible_text = u' %s %d:%d (%s)' % \ + (verse.book.name, verse.chapter, verse.verse, bible) bible_verse = QtGui.QListWidgetItem(bible_text) - bible_verse.setData(QtCore.Qt.UserRole, - QtCore.QVariant(bible_text)) + #bible_verse.setData(QtCore.Qt.UserRole, + # QtCore.QVariant(bible_text)) + vdict = { + 'bible': QtCore.QVariant(bible), + 'version': QtCore.QVariant(version.value), + 'copyright': QtCore.QVariant(copyright.value), + 'permission': QtCore.QVariant(permission), + 'book': QtCore.QVariant(verse.book.name), + 'chapter': QtCore.QVariant(verse.chapter), + 'verse': QtCore.QVariant(verse.verse), + 'text': QtCore.QVariant(verse.text) + } + bible_verse.setData(QtCore.Qt.UserRole, QtCore.QVariant(vdict)) self.ListView.addItem(bible_verse) row = self.ListView.setCurrentRow(count) if row: @@ -532,85 +587,4 @@ class BibleMediaItem(MediaManagerItem): def searchByReference(self, bible, search): log.debug(u'searchByReference %s, %s', bible, search) - book = u'' - start_chapter = u'' - end_chapter = u'' - start_verse = u'' - end_verse = u'' - search = search.replace(u' ', u' ').strip() - #original = search - message = None - # Remove book beware 0 index arrays - for i in range (len(search)-1, 0, - 1): - if search[i] == u' ': - book = search[:i] - # remove book from string - search = search[i:] - break - # allow V or v for verse instead of : - search = search.replace(u'v', ':') - search = search.replace(u'V', ':') - search = search.strip() - colon = search.find(u':') - if colon == -1: - # number : found - i = search.rfind(u' ') - if i == -1: - chapter = u'' - else: - chapter = search[i:len(search)] - hyphen = chapter.find(u'-') - if hyphen != -1: - start_chapter= chapter[:hyphen] - end_chapter= chapter[hyphen + 1:len(chapter)] - else: - start_chapter = chapter - else: - # more complex - sp = search.split(u'-') #find first - sp1 = sp[0].split(u':') - if len(sp1) == 1: - start_chapter = sp1[0] - start_verse = 1 - else: - start_chapter = sp1[0] - start_verse = sp1[1] - if len(sp)== 1: - end_chapter = start_chapter - end_verse = start_verse - else: - sp1 = sp[1].split(u':') - if len(sp1) == 1: - end_chapter = start_chapter - end_verse = sp1[0] - else: - end_chapter = sp1[0] - end_verse = sp1[1] - if end_chapter == u'': - end_chapter = start_chapter.rstrip() - if start_verse == u'': - if end_verse == u'': - start_verse = 1 - else: - start_verse = end_verse - if end_verse == u'': - end_verse = 99 - if start_chapter == u'': - message = self.trUtf8('No chapter found for search criteria') - log.debug(u'results = %s @ %s : %s @ %s : %s'% \ - (unicode(book), unicode(start_chapter), unicode(end_chapter), - unicode(start_verse), unicode(end_verse))) - if message is None: - self.search_results = None - self.search_results = self.parent.biblemanager.get_verse_text( - bible, book, int(start_chapter), int(end_chapter), - int(start_verse), int(end_verse)) - self.copyright = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Copyright').value) - self.permissions = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Permissions').value) - self.version = unicode(self.parent.biblemanager.get_meta_data( - bible, u'Version').value) - else: - QtGui.QMessageBox.information( - self, self.trUtf8('Information'), message) \ No newline at end of file + self.search_results = self.parent.manager.get_verses(bible, search) diff --git a/openlp/plugins/bibles/lib/models.py b/openlp/plugins/bibles/lib/models.py index 931133921..2802cb27f 100644 --- a/openlp/plugins/bibles/lib/models.py +++ b/openlp/plugins/bibles/lib/models.py @@ -50,7 +50,7 @@ class BibleMeta(BaseModel): pass -class ONTestament(BaseModel): +class Testament(BaseModel): """ Bible Testaments """ @@ -101,8 +101,8 @@ verse_table = Table(u'verse', metadata, Column(u'text', types.UnicodeText, index=True), ) mapper(BibleMeta, meta_table) -mapper(ONTestament, testament_table, +mapper(Testament, testament_table, properties={'books': relation(Book, backref='testament')}) mapper(Book, book_table, properties={'verses': relation(Verse, backref='book')}) -mapper(Verse, verse_table) \ No newline at end of file +mapper(Verse, verse_table) diff --git a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py b/openlp/plugins/bibles/lib/opensong.py similarity index 54% rename from openlp/plugins/bibles/lib/bibleOpenSongimpl.py rename to openlp/plugins/bibles/lib/opensong.py index 4d171d57c..6fa18cf6d 100644 --- a/openlp/plugins/bibles/lib/bibleOpenSongimpl.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -23,39 +23,31 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os -import os.path import logging -import chardet -import codecs -from lxml import objectify +from lxml import objectify from PyQt4 import QtCore from openlp.core.lib import Receiver +from db import BibleDB -class BibleOpenSongImpl(): +log = logging.getLogger(__name__) + +class OpenSongBible(BibleDB): """ - OSIS Bible format importer class. + OpenSong Bible format importer class. """ - global log - log = logging.getLogger(__name__) - log.info(u'BibleOpenSongImpl loaded') - def __init__(self, biblepath, bibledb): + def __init__(self, parent, **kwargs): """ - Constructor to create and set up an instance of the - BibleOpenSongImpl class. - - ``biblepath`` - This does not seem to be used. - - ``bibledb`` - A reference to a Bible database object. + Constructor to create and set up an instance of the OpenSongBible + class. This class is used to import Bibles from OpenSong's XML format. """ - log.info(u'BibleOpenSongImpl Initialising') - self.bibledb = bibledb - self.loadbible = True + log.debug(__name__) + BibleDB.__init__(self, parent, **kwargs) + if 'filename' not in kwargs: + raise KeyError(u'You have to supply a file name to import from.') + self.filename = kwargs['filename'] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlpstopimport'), self.stop_import) @@ -63,64 +55,55 @@ class BibleOpenSongImpl(): """ Stops the import of the Bible. """ - self.loadbible = False + log.debug('Stopping import!') + self.stop_import_flag = True - def load_data(self, bible_file, dialogobject=None): + def do_import(self): """ Loads a Bible from file. - - ``bible_file`` - The file to import from. - - ``dialogobject`` - The Import dialog, so that we can increase the counter on - the progress bar. """ - log.info(u'Load data for %s' % bible_file) - bible_file = unicode(bible_file) - detect_file = None - try: - detect_file = open(bible_file, u'r') - details = chardet.detect(detect_file.read(2048)) - except: - log.exception(u'Failed to detect OpenSong file encoding') - return - finally: - if detect_file: - detect_file.close() - opensong_bible = None + log.debug(u'Starting OpenSong import from "%s"' % self.filename) + self.filename = unicode(self.filename, u'utf-8') + self.wizard.incrementProgressBar(u'Preparing for import...') + file = None success = True try: - opensong_bible = codecs.open(bible_file, u'r', details['encoding']) - opensong = objectify.parse(opensong_bible) + # NOTE: We don't need to do any of the normal encoding detection + # here, because lxml does it's own encoding detection, and the two + # mechanisms together interfere with each other. + file = open(self.filename, u'r') + opensong = objectify.parse(file) bible = opensong.getroot() for book in bible.b: - if not self.loadbible: + if self.stop_import_flag: break - dbbook = self.bibledb.create_book(book.attrib[u'n'], - book.attrib[u'n'][:4]) + db_book = self.create_book(unicode(book.attrib[u'n']), + unicode(book.attrib[u'n'][:4])) for chapter in book.c: - if not self.loadbible: + if self.stop_import_flag: break for verse in chapter.v: - if not self.loadbible: + if self.stop_import_flag: break - self.bibledb.add_verse(dbbook.id, chapter.attrib[u'n'], - verse.attrib[u'n'], verse.text) + self.create_verse( + db_book.id, + int(chapter.attrib[u'n']), + int(verse.attrib[u'n']), + unicode(verse.text) + ) Receiver.send_message(u'process_events') - dialogobject.incrementProgressBar(u'Importing %s %s' % \ - (dbbook.name, str(chapter.attrib[u'n']))) - self.bibledb.save_verses() + self.wizard.incrementProgressBar( + QtCore.QString('%s %s %s' % (self.trUtf8('Importing'),\ + db_book.name, chapter.attrib[u'n']))) + self.commit() except: log.exception(u'Loading bible from OpenSong file failed') success = False finally: - if opensong_bible: - opensong_bible.close() - if not self.loadbible: - dialogobject.incrementProgressBar(u'Import canceled!') - dialogobject.ImportProgressBar.setValue( - dialogobject.ImportProgressBar.maximum()) + if file: + file.close() + if self.stop_import: + self.wizard.incrementProgressBar(u'Import canceled!') return False else: return success diff --git a/openlp/plugins/bibles/lib/bibleOSISimpl.py b/openlp/plugins/bibles/lib/osis.py similarity index 78% rename from openlp/plugins/bibles/lib/bibleOSISimpl.py rename to openlp/plugins/bibles/lib/osis.py index 0f1aafe5d..658ac8a0b 100644 --- a/openlp/plugins/bibles/lib/bibleOSISimpl.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -33,27 +33,26 @@ import re from PyQt4 import QtCore from openlp.core.lib import Receiver +from db import BibleDB -class BibleOSISImpl(): +log = logging.getLogger(__name__) + +class OSISBible(BibleDB): """ OSIS Bible format importer class. """ - global log - log = logging.getLogger(u'BibleOSISImpl') log.info(u'BibleOSISImpl loaded') - def __init__(self, biblepath, bibledb): + def __init__(self, parent, **kwargs): """ - Constructor to create and set up an instance of the - BibleOSISImpl class. - - ``biblepath`` - This does not seem to be used. - - ``bibledb`` - A reference to a Bible database object. + Constructor to create and set up an instance of the OpenSongBible + class. This class is used to import Bibles from OpenSong's XML format. """ - log.info(u'BibleOSISImpl Initialising') + log.debug(__name__) + BibleDB.__init__(self, parent, **kwargs) + if u'filename' not in kwargs: + raise KeyError(u'You have to supply a file name to import from.') + self.filename = kwargs[u'filename'] self.verse_regex = re.compile( r'<verse osisID="([a-zA-Z0-9 ]*).([0-9]*).([0-9]*)">(.*?)</verse>') self.note_regex = re.compile(r'<note(.*?)>(.*?)</note>') @@ -66,13 +65,11 @@ class BibleOSISImpl(): self.w_regex = re.compile(r'<w (.*?)>') self.q_regex = re.compile(r'<q (.*?)>') self.spaces_regex = re.compile(r'([ ]{2,})') - self.bibledb = bibledb self.books = {} filepath = os.path.split(os.path.abspath(__file__))[0] filepath = os.path.abspath(os.path.join( filepath, u'..', u'resources', u'osisbooks.csv')) fbibles = None - self.loadbible = True try: fbibles = open(filepath, u'r') for line in fbibles: @@ -92,24 +89,18 @@ class BibleOSISImpl(): Stops the import of the Bible. """ log.debug('Stopping import!') - self.loadbible = False + self.stop_import_flag = True - def load_data(self, osisfile_record, dialogobject=None): + def do_import(self): """ Loads a Bible from file. - - ``osisfile_record`` - The file to import from. - - ``dialogobject`` - The Import dialog, so that we can increase the counter on - the progress bar. """ - log.info(u'Load data for %s' % osisfile_record) + log.debug(u'Starting OSIS import from "%s"' % self.filename) + self.wizard.incrementProgressBar(u'Detecting encoding (this may take a few minutes)...') detect_file = None try: - detect_file = open(osisfile_record, u'r') - details = chardet.detect(detect_file.read(3000)) + detect_file = open(self.filename, u'r') + details = chardet.detect(detect_file.read()) except: log.exception(u'Failed to detect OSIS file encoding') return @@ -119,12 +110,12 @@ class BibleOSISImpl(): osis = None success = True try: - osis = codecs.open(osisfile_record, u'r', details['encoding']) + osis = codecs.open(self.filename, u'r', details['encoding']) last_chapter = 0 testament = 1 db_book = None for file_record in osis: - if not self.loadbible: + if self.stop_import_flag: break match = self.verse_regex.search(file_record) if match: @@ -136,19 +127,19 @@ class BibleOSISImpl(): log.debug('New book: "%s"', self.books[book][0]) if book == u'Matt': testament += 1 - db_book = self.bibledb.create_book( + db_book = self.create_book( unicode(self.books[book][0]), unicode(self.books[book][1]), testament) if last_chapter == 0: if book == u'Gen': - dialogobject.ImportProgressBar.setMaximum(1188) + self.wizard.ImportProgressBar.setMaximum(1188) else: - dialogobject.ImportProgressBar.setMaximum(260) + self.wizard.ImportProgressBar.setMaximum(260) if last_chapter != chapter: if last_chapter != 0: - self.bibledb.save_verses() - dialogobject.incrementProgressBar( + self.commit() + self.wizard.incrementProgressBar( u'Importing %s %s...' % \ (self.books[match.group(1)][0], chapter)) last_chapter = chapter @@ -170,20 +161,18 @@ class BibleOSISImpl(): .replace(u'</lg>', u'').replace(u'</q>', u'')\ .replace(u'</div>', u'') verse_text = self.spaces_regex.sub(u' ', verse_text) - self.bibledb.add_verse(db_book.id, chapter, verse, verse_text) + self.create_verse(db_book.id, chapter, verse, verse_text) Receiver.send_message(u'process_events') - self.bibledb.save_verses() - dialogobject.incrementProgressBar(u'Finishing import...') + self.commit() + self.wizard.incrementProgressBar(u'Finishing import...') except: log.exception(u'Loading bible from OSIS file failed') success = False finally: if osis: osis.close() - if not self.loadbible: - dialogobject.incrementProgressBar(u'Import canceled!') - dialogobject.ImportProgressBar.setValue( - dialogobject.ImportProgressBar.maximum()) + if self.stop_import_flag: + self.wizard.incrementProgressBar(u'Import canceled!') return False else: - return success \ No newline at end of file + return success diff --git a/openlp/plugins/bibles/resources/httpbooks.csv b/openlp/plugins/bibles/resources/httpbooks.csv deleted file mode 100644 index 2d8afa20e..000000000 --- a/openlp/plugins/bibles/resources/httpbooks.csv +++ /dev/null @@ -1,66 +0,0 @@ -Genesis,Gen,1,50 -Exodus,Exod,1,40 -Leviticus,Lev,1,27 -Numbers,Num,1,36 -Deuteronomy,Deut,1,34 -Joshua,Josh,1,24 -Judges,Judg,1,21 -Ruth,Ruth,1,4 -1 Samual,1Sam,1,31 -2 Samual,2Sam,1,24 -1 Kings,1Kgs,1,22 -2 Kings,2Kgs,1,25 -1 Chronicles,1Chr,1,29 -2 Chronicles,2Chr,1,36 -Ezra,Esra,1,10 -Nehemiah,Neh,1,13 -Esther,Esth,1,10 -Job,Job,1,42 -Psalms,Ps,1,150 -Proverbs,Prov,1,31 -Ecclesiastes,Eccl,1,12 -Song of Songs,Song,1,8 -Isaiah,Isa,1,66 -Jeremiah,Jer,1,5 -Lamentations,Lam,1,5 -Ezekiel,Ezek,1,48 -Daniel,Dan,1,12 -Hosea,Hos,1,14 -Joel,Joel,1,3 -Amos,Amos,1,9 -Obad,Obad,1,1 -Jonah,Jonah,1,4 -Micah,Mic,1,7 -Naham,Nah,1,3 -Habakkuk,Hab,1,3 -Zephaniah,Zeph,1,3 -Haggai,Hag,1,2 -Zechariah,Zech,1,3 -Malachi,Mal,1,4 -Matthew,Matt,2,28 -Mark,Mark,2,16 -Luke,Luke,2,24 -John,John,2,21 -Acts,Acts,2,28 -Romans,Rom,2,16 -1 Corinthans,1Cor,2,16 -2 Corinthans,2Cor,2,13 -Galatians,Gal,2,6 -Ephesians,Eph,2,6 -Philippians,Phil,2,4 -Colossians,Col,2,4 -1 Thessalonians,1Thess,2,5 -2 Thessalonians,2Thess,2,3 -1 Timothy,1Tim,2,6 -2 Timothy,2Tim,2,4 -Titus,Titus,2,3 -Philemon,Phlm,2,1 -Hebrews,Heb,2,13 -James,Jas,2,5 -1 Peter,1Pet,2,5 -2 Peter,2Pet,2,3 -1 John,1John,2,5 -2 John,2John,2,1 -3 John,3John,2,1 -Jude,Jude,2,1 -Revelation,Rev,2,22 diff --git a/openlp/plugins/bibles/resources/httpbooks.sqlite b/openlp/plugins/bibles/resources/httpbooks.sqlite new file mode 100644 index 000000000..ea0c40530 Binary files /dev/null and b/openlp/plugins/bibles/resources/httpbooks.sqlite differ diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 8da4fabfd..ac5384390 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -26,9 +26,10 @@ import logging from forms import EditCustomForm -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.custom.lib import CustomManager, CustomMediaItem, CustomTab +log = logging.getLogger(__name__) class CustomPlugin(Plugin): """ @@ -39,17 +40,15 @@ class CustomPlugin(Plugin): the songs plugin has become restrictive. Examples could be Welcome slides, Bible Reading information, Orders of service. """ - - global log - log = logging.getLogger(u'CustomPlugin') log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Custom', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Custom', u'1.9.1', plugin_helpers) self.weight = -5 self.custommanager = CustomManager(self.config) self.edit_custom_form = EditCustomForm(self.custommanager) self.icon = build_icon(u':/media/media_custom.png') + self.status = PluginStatus.Active def get_settings_tab(self): return CustomTab(self.name) @@ -72,4 +71,9 @@ class CustomPlugin(Plugin): 'allows slides to be displayed on the screen in the same way ' 'songs are. This plugin provides greater freedom over the ' 'songs plugin.<br>') - return about_text \ No newline at end of file + return about_text + + def can_delete_theme(self, theme): + if len(self.custommanager.get_customs_for_theme(theme)) == 0: + return True + return False diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 98eb5598a..b4402ceb3 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -30,12 +30,12 @@ from editcustomdialog import Ui_customEditDialog from openlp.core.lib import SongXMLBuilder, SongXMLParser, Receiver from openlp.plugins.custom.lib.models import CustomSlide +log = logging.getLogger(__name__) + class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): """ Class documentation goes here. """ - global log - log = logging.getLogger(u'EditCustomForm') log.info(u'Custom Editor loaded') def __init__(self, custommanager, parent = None): """ @@ -153,10 +153,10 @@ class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): sxml.add_verse_to_lyrics(u'custom', unicode(count), unicode(self.VerseListView.item(i).text())) count += 1 - self.customSlide.title = unicode(self.TitleEdit.displayText()) - self.customSlide.text = unicode(sxml.extract_xml()) - self.customSlide.credits = unicode(self.CreditEdit.displayText()) - self.customSlide.theme_name = unicode(self.ThemeComboBox.currentText()) + self.customSlide.title = unicode(self.TitleEdit.displayText(), u'utf-8') + self.customSlide.text = unicode(sxml.extract_xml(), u'utf-8') + self.customSlide.credits = unicode(self.CreditEdit.displayText(), u'utf-8') + self.customSlide.theme_name = unicode(self.ThemeComboBox.currentText(), u'utf-8') self.custommanager.save_slide(self.customSlide) return True @@ -254,7 +254,7 @@ class EditCustomForm(QtGui.QDialog, Ui_customEditDialog): if self.VerseListView.count() == 0: self.VerseTextEdit.setFocus() return False, self.trUtf8('You need to enter a slide') - if len(self.VerseTextEdit.toPlainText()) > 0: + if self.VerseTextEdit.toPlainText(): self.VerseTextEdit.setFocus() return False, self.trUtf8('You have unsaved data') - return True, u'' \ No newline at end of file + return True, u'' diff --git a/openlp/plugins/custom/lib/manager.py b/openlp/plugins/custom/lib/manager.py index 387368016..1368b89ee 100644 --- a/openlp/plugins/custom/lib/manager.py +++ b/openlp/plugins/custom/lib/manager.py @@ -27,14 +27,13 @@ import logging from openlp.plugins.custom.lib.models import init_models, metadata, CustomSlide +log = logging.getLogger(__name__) + class CustomManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'CustomManager') log.info(u'Custom manager loaded') def __init__(self, config): @@ -78,7 +77,7 @@ class CustomManager(): return True except: self.session.rollback() - log.excertion(u'Custom Slide save failed') + log.exception(u'Custom Slide save failed') return False def get_custom(self, id=None): @@ -94,7 +93,7 @@ class CustomManager(): """ Delete a Custom slide show """ - if id !=0: + if id != 0: customslide = self.get_custom(id) try: self.session.delete(customslide) @@ -102,7 +101,10 @@ class CustomManager(): return True except: self.session.rollback() - log.excertion(u'Custom Slide deleton failed') + log.exception(u'Custom Slide deleton failed') return False else: - return True \ No newline at end of file + return True + + def get_customs_for_theme(self, theme): + return self.session.query(CustomSlide).filter(CustomSlide.theme_name == theme).all() diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index c9ca91112..61d1b05d7 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -30,6 +30,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, SongXMLParser, BaseListWithDnD,\ Receiver, str_to_bool +log = logging.getLogger(__name__) + class CustomListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Custom' @@ -39,8 +41,6 @@ class CustomMediaItem(MediaManagerItem): """ This is the custom media manager item for Custom Slides. """ - global log - log = logging.getLogger(u'CustomMediaItem') log.info(u'Custom Media Item loaded') def __init__(self, parent, icon, title): @@ -144,13 +144,14 @@ class CustomMediaItem(MediaManagerItem): item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] else: item_id = self.remoteCustom + service_item.autoPreviewAllowed = True customSlide = self.parent.custommanager.get_custom(item_id) title = customSlide.title credit = customSlide.credits service_item.edit_enabled = True service_item.editId = item_id theme = customSlide.theme_name - if len(theme) is not 0 : + if theme: service_item.theme = theme songXML = SongXMLParser(customSlide.text) verseList = songXML.get_verses() @@ -159,10 +160,10 @@ class CustomMediaItem(MediaManagerItem): service_item.title = title for slide in raw_slides: service_item.add_from_text(slide[:30], slide) - if str_to_bool(self.parent.config.get_config(u'display footer', True)) or \ - len(credit) > 0: - raw_footer.append(title + u' '+ credit) + if str_to_bool(self.parent.config.get_config(u'display footer', True)) \ + or credit: + raw_footer.append(title + u' ' + credit) else: raw_footer.append(u'') service_item.raw_footer = raw_footer - return True \ No newline at end of file + return True diff --git a/openlp/plugins/custom/lib/tables.py b/openlp/plugins/custom/lib/tables.py index 061c6d5c3..13c9de5b9 100644 --- a/openlp/plugins/custom/lib/tables.py +++ b/openlp/plugins/custom/lib/tables.py @@ -27,11 +27,11 @@ from sqlalchemy import Column, Table, types from openlp.plugins.custom.lib.meta import metadata -# Definition of the "songs" table +# Definition of the "custom slide" table custom_slide_table = Table(u'custom_slide', metadata, Column(u'id', types.Integer(), primary_key=True), Column(u'title', types.Unicode(255), nullable=False), Column(u'text', types.UnicodeText, nullable=False), Column(u'credits', types.UnicodeText), Column(u'theme_name', types.Unicode(128)) -) \ No newline at end of file +) diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index dc4b7eec2..37219b5db 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -25,18 +25,19 @@ import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.images.lib import ImageMediaItem, ImageTab +log = logging.getLogger(__name__) + class ImagePlugin(Plugin): - global log - log = logging.getLogger(u'ImagePlugin') log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Images', u'1.9.1', plugin_helpers) self.weight = -7 self.icon = build_icon(u':/media/media_image.png') + self.status = PluginStatus.Active def initialise(self): log.info(u'Plugin Initialising') @@ -60,6 +61,6 @@ class ImagePlugin(Plugin): 'together and presented on the live controller it is possible ' 'to turn them into a timed loop.<br<br>From the plugin if the ' '<i>Override background</i> is chosen and an image is selected ' - 'any somgs which are rendered will use the selected image from ' + 'any songs which are rendered will use the selected image from ' 'the background instead of the one provied by the theme.<br>') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 2dbea8181..b70006bdb 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -49,6 +49,7 @@ class ImageTab(SettingsTab): self.TimeoutLabel.setObjectName(u'TimeoutLabel') self.TimeoutLayout.addWidget(self.TimeoutLabel) self.TimeoutSpinBox = QtGui.QSpinBox(self.ImageSettingsGroupBox) + self.TimeoutSpinBox.setMinimum(1) self.TimeoutSpinBox.setMaximum(180) self.TimeoutSpinBox.setObjectName(u'TimeoutSpinBox') self.TimeoutLayout.addWidget(self.TimeoutSpinBox) @@ -78,4 +79,4 @@ class ImageTab(SettingsTab): Receiver.send_message(u'update_spin_delay', self.loop_delay) def postSetUp(self): - Receiver.send_message(u'update_spin_delay', self.loop_delay) \ No newline at end of file + Receiver.send_message(u'update_spin_delay', self.loop_delay) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 9ee3a0c6f..75f2fd981 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -29,6 +29,8 @@ import os from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon +log = logging.getLogger(__name__) + # We have to explicitly create separate classes for each plugin # in order for DnD to the Service manager to work correctly. class ImageListView(BaseListWithDnD): @@ -40,8 +42,6 @@ class ImageMediaItem(MediaManagerItem): """ This is the custom media manager item for images. """ - global log - log = logging.getLogger(u'ImageMediaItem') log.info(u'Image Media Item loaded') def __init__(self, parent, icon, title): @@ -61,7 +61,7 @@ class ImageMediaItem(MediaManagerItem): def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Image(s)') self.OnNewFileMasks = \ - self.trUtf8('Images (*.jpg *jpeg *.gif *.png *.bmp)') + self.trUtf8('Images (*.jpg *jpeg *.gif *.png *.bmp);; All files (*)') def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -144,6 +144,7 @@ class ImageMediaItem(MediaManagerItem): items = self.ListView.selectedIndexes() if items: service_item.title = self.trUtf8('Image(s)') + service_item.autoPreviewAllowed = True for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) @@ -172,7 +173,6 @@ class ImageMediaItem(MediaManagerItem): filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) self.OverrideLabel.setText(bitem.text()) frame = QtGui.QImage(unicode(filename)) - self.parent.render_manager.override_background = frame - self.parent.render_manager.override_background_changed = True + self.parent.maindisplay.addImageWithText(frame) else: - MediaManagerItem.onPreviewClick(self) \ No newline at end of file + MediaManagerItem.onPreviewClick(self) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index f9b9f7929..c91440c5c 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -30,6 +30,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon +log = logging.getLogger(__name__) + class MediaListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Media' @@ -39,9 +41,7 @@ class MediaMediaItem(MediaManagerItem): """ This is the custom media manager item for Media Slides. """ - global log - log = logging.getLogger(u'MediaMediaItem') - log.info(u'Media Media Item loaded') + log.info(u'%s MediaMediaItem loaded', __name__) def __init__(self, parent, icon, title): self.PluginNameShort = u'Media' @@ -54,15 +54,16 @@ class MediaMediaItem(MediaManagerItem): self.PreviewFunction = self.video_get_preview MediaManagerItem.__init__(self, parent, icon, title) self.ServiceItemIconName = u':/media/media_video.png' - self.MainDisplay = self.parent.live_controller.parent.mainDisplay + self.MainDisplay = self.parent.maindisplay def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Media') def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Media') - self.OnNewFileMasks = self.trUtf8('Videos (*.avi *.mpeg *.mpg' - '*.mp4);;Audio (*.ogg *.mp3 *.wma);;All files (*)') + self.OnNewFileMasks = self.trUtf8('Videos (%s);;' + 'Audio (%s);;' + 'All files (*)' % (self.parent.video_list, self.parent.audio_list)) def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -84,7 +85,7 @@ class MediaMediaItem(MediaManagerItem): for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) - frame = u':/media/media_video.png' + frame = u':/media/image_clapperboard.png' (path, name) = os.path.split(filename) service_item.add_from_command(path, name, frame) return True @@ -110,4 +111,4 @@ class MediaMediaItem(MediaManagerItem): img = self.video_get_preview() item_name.setIcon(build_icon(img)) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) - self.ListView.addItem(item_name) \ No newline at end of file + self.ListView.addItem(item_name) diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index a338fa021..096d53aea 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -25,20 +25,41 @@ import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.media.lib import MediaMediaItem +from PyQt4.phonon import Phonon + +log = logging.getLogger(__name__) class MediaPlugin(Plugin): - global log - log = logging.getLogger(u'MediaPlugin') - log.info(u'Media Plugin loaded') + log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Media', u'1.9.1', plugin_helpers) self.weight = -6 self.icon = build_icon(u':/media/media_video.png') # passed with drag and drop messages self.dnd_id = u'Media' + self.status = PluginStatus.Active + self.audio_list = u'' + self.video_list = u'' + for mimetype in Phonon.BackendCapabilities.availableMimeTypes(): + mimetype = unicode(mimetype) + type = mimetype.split(u'audio/x-') + self.audio_list, mimetype = self._add_to_list(self.audio_list, type, mimetype) + type = mimetype.split(u'audio/') + self.audio_list, mimetype = self._add_to_list(self.audio_list, type, mimetype) + type = mimetype.split(u'video/x-') + self.video_list, mimetype = self._add_to_list(self.video_list, type, mimetype) + type = mimetype.split(u'video/') + self.video_list, mimetype = self._add_to_list(self.video_list, type, mimetype) + + def _add_to_list(self, list, value, type): + if len(value) == 2: + if list.find(value[1]) == -1: + list += u'*.%s ' % value[1] + type = u'' + return list, type def initialise(self): log.info(u'Plugin Initialising') @@ -56,4 +77,4 @@ class MediaPlugin(Plugin): def about(self): about_text = self.trUtf8('<b>Media Plugin</b><br>This plugin ' 'allows the playing of audio and video media') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index cd8db7e08..2756f718e 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -25,6 +25,7 @@ # OOo API documentation: # http://api.openoffice.org/docs/common/ref/com/sun/star/presentation/XSlideShowController.html +# http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/ProUNO/Basic/Getting_Information_about_UNO_Objects#Inspecting_interfaces_during_debugging # http://docs.go-oo.org/sd/html/classsd_1_1SlideShow.html # http://www.oooforum.org/forum/viewtopic.phtml?t=5252 # http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/Working_with_Presentations @@ -44,7 +45,9 @@ else: from PyQt4 import QtCore -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument + +log = logging.getLogger(__name__) class ImpressController(PresentationController): """ @@ -52,9 +55,7 @@ class ImpressController(PresentationController): It creates the runtime environment, loads and closes the presentation as well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'ImpressController') - log.info(u'loaded') + log.info(u'ImpressController loaded') def __init__(self, plugin): """ @@ -62,10 +63,10 @@ class ImpressController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Impress') + self.supports = [u'.odp'] + self.alsosupports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None - self.document = None - self.presentation = None - self.controller = None + self.desktop = None def check_available(self): """ @@ -85,7 +86,7 @@ class ImpressController(PresentationController): It is not displayed to the user but is available to the UNO interface when required. """ - log.debug(u'start Openoffice') + log.debug(u'start process Openoffice') if os.name == u'nt': self.manager = self.get_com_servicemanager() self.manager._FlagAsMethod(u'Bridge_GetStruct') @@ -97,20 +98,88 @@ class ImpressController(PresentationController): self.process.startDetached(cmd) self.process.waitForStarted() + def get_uno_desktop(self): + log.debug(u'get UNO Desktop Openoffice') + ctx = None + loop = 0 + log.debug(u'get UNO Desktop Openoffice - getComponentContext') + context = uno.getComponentContext() + log.debug(u'get UNO Desktop Openoffice - createInstaneWithContext - UnoUrlResolver') + resolver = context.ServiceManager.createInstanceWithContext( + u'com.sun.star.bridge.UnoUrlResolver', context) + while ctx is None and loop < 3: + try: + log.debug(u'get UNO Desktop Openoffice - resolve') + ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') + except: + log.exception(u'Unable to find running instance ') + self.start_process() + loop += 1 + try: + self.manager = ctx.ServiceManager + log.debug(u'get UNO Desktop Openoffice - createInstanceWithContext - Desktop') + desktop = self.manager.createInstanceWithContext( + "com.sun.star.frame.Desktop", ctx ) + return desktop + except: + log.exception(u'Failed to get UNO desktop') + return None + + def get_com_desktop(self): + log.debug(u'get COM Desktop OpenOffice') + try: + desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') + return desktop + except: + log.exception(u'Failed to get COM desktop') + return None + + def get_com_servicemanager(self): + log.debug(u'get_com_servicemanager openoffice') + try: + return Dispatch(u'com.sun.star.ServiceManager') + except: + log.exception(u'Failed to get COM service manager') + return None + def kill(self): """ Called at system exit to clean up any running presentations """ - log.debug(u'Kill') - self.close_presentation() + log.debug(u'Kill OpenOffice') + for doc in self.docs: + doc.close_presentation() if os.name != u'nt': desktop = self.get_uno_desktop() + else: + desktop = self.get_com_desktop() + docs = desktop.getComponents() + if docs.hasElements(): + log.debug(u'OpenOffice not terminated') + else: try: desktop.terminate() + log.debug(u'OpenOffice killed') except: - pass + log.exception(u'Failed to terminate OpenOffice') - def load_presentation(self, presentation): + def add_doc(self, name): + log.debug(u'Add Doc OpenOffice') + doc = ImpressDocument(self, name) + self.docs.append(doc) + return doc + +class ImpressDocument(PresentationDocument): + + def __init__(self, controller, presentation): + log.debug(u'Init Presentation OpenOffice') + self.controller = controller + self.document = None + self.presentation = None + self.control = None + self.store_filename(presentation) + + def load_presentation(self): """ Called when a presentation is added to the SlideController. It builds the environment, starts communcations with the background @@ -121,20 +190,21 @@ class ImpressController(PresentationController): ``presentation`` The file name of the presentatios to the run. """ - log.debug(u'LoadPresentation') - self.store_filename(presentation) + log.debug(u'Load Presentation OpenOffice') + #print "s.dsk1 ", self.desktop if os.name == u'nt': - desktop = self.get_com_desktop() + desktop = self.controller.get_com_desktop() if desktop is None: - self.start_process() - desktop = self.get_com_desktop() - url = u'file:///' + presentation.replace(u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') + self.controller.start_process() + desktop = self.controller.get_com_desktop() + url = u'file:///' + self.filepath.replace(u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') else: - desktop = self.get_uno_desktop() - url = uno.systemPathToFileUrl(presentation) + desktop = self.controller.get_uno_desktop() + url = uno.systemPathToFileUrl(self.filepath) if desktop is None: return self.desktop = desktop + #print "s.dsk2 ", self.desktop properties = [] properties.append(self.create_property(u'Minimized', True)) properties = tuple(properties) @@ -145,17 +215,17 @@ class ImpressController(PresentationController): log.exception(u'Failed to load presentation') return self.presentation = self.document.getPresentation() - self.presentation.Display = self.plugin.render_manager.current_display + 1 - self.controller = None + self.presentation.Display = self.controller.plugin.render_manager.screens.current_display + 1 + self.control = None self.create_thumbnails() - + def create_thumbnails(self): """ Create thumbnail images for presentation """ + log.debug(u'create thumbnails OpenOffice') if self.check_thumbnails(): return - if os.name == u'nt': thumbdir = u'file:///' + self.thumbnailpath.replace( u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20') @@ -169,129 +239,120 @@ class ImpressController(PresentationController): for idx in range(pages.getCount()): page = pages.getByIndex(idx) doc.getCurrentController().setCurrentPage(page) - doc.storeToURL(thumbdir + u'/' + self.thumbnailprefix + - unicode(idx+1) + u'.png', props) + path = u'%s/%s%s.png'% (thumbdir, self.controller.thumbnailprefix, + unicode(idx + 1)) + try: + doc.storeToURL(path , props) + except: + log.exception(u'%s\nUnable to store preview' % path) def create_property(self, name, value): + log.debug(u'create property OpenOffice') if os.name == u'nt': - prop = self.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') + prop = self.controller.manager.Bridge_GetStruct(u'com.sun.star.beans.PropertyValue') else: prop = PropertyValue() prop.Name = name prop.Value = value return prop - def get_uno_desktop(self): - log.debug(u'getUNODesktop') - ctx = None - loop = 0 - context = uno.getComponentContext() - resolver = context.ServiceManager.createInstanceWithContext( - u'com.sun.star.bridge.UnoUrlResolver', context) - while ctx is None and loop < 3: - try: - ctx = resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') - except: - self.start_process() - loop += 1 - try: - self.manager = ctx.ServiceManager - desktop = self.manager.createInstanceWithContext( - "com.sun.star.frame.Desktop", ctx ) - return desktop - except: - log.exception(u'Failed to get UNO desktop') - return None - - def get_com_desktop(self): - log.debug(u'getCOMDesktop') - try: - desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') - return desktop - except: - log.exception(u'Failed to get COM desktop') - return None - - def get_com_servicemanager(self): - log.debug(u'get_com_servicemanager') - try: - return Dispatch(u'com.sun.star.ServiceManager') - except: - log.exception(u'Failed to get COM service manager') - return None - def close_presentation(self): """ Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down + Triggered by new object being added to SlideController or OpenLP + being shutdown """ + log.debug(u'close Presentation OpenOffice') if self.document: if self.presentation: - self.presentation.end() - self.presentation = None - self.document.dispose() + try: + self.presentation.end() + self.presentation = None + self.document.dispose() + except: + #We tried! + pass self.document = None + self.controller.remove_doc(self) def is_loaded(self): + log.debug(u'is loaded OpenOffice') + #print "is_loaded " if self.presentation is None or self.document is None: + #print "no present or document" return False try: if self.document.getPresentation() is None: + #print "no getPresentation" return False except: return False return True def is_active(self): + log.debug(u'is active OpenOffice') + #print "is_active " if not self.is_loaded(): + #print "False " return False - if self.controller is None: + #print "self.con ", self.control + if self.control is None: return False - return self.controller.isRunning() and self.controller.isActive() + return True def unblank_screen(self): - return self.controller.resume() + log.debug(u'unblank screen OpenOffice') + return self.control.resume() def blank_screen(self): - self.controller.blankScreen(0) + log.debug(u'blank screen OpenOffice') + self.control.blankScreen(0) + + def is_blank(self): + """ + Returns true if screen is blank + """ + log.debug(u'is blank OpenOffice') + return self.control.isPaused() def stop_presentation(self): - self.controller.deactivate() + log.debug(u'stop presentation OpenOffice') + self.control.deactivate() def start_presentation(self): - if self.controller is None or not self.controller.isRunning(): + log.debug(u'start presentation OpenOffice') + if self.control is None or not self.control.isRunning(): self.presentation.start() # start() returns before the getCurrentComponent is ready. Try for 5 seconds i = 1 while self.desktop.getCurrentComponent() is None and i < 50: time.sleep(0.1) i = i + 1 - self.controller = self.desktop.getCurrentComponent().Presentation.getController() + self.control = self.desktop.getCurrentComponent().Presentation.getController() else: - self.controller.activate() + self.control.activate() self.goto_slide(1) def get_slide_number(self): - return self.controller.getCurrentSlideIndex() + 1 + return self.control.getCurrentSlideIndex() + 1 def get_slide_count(self): return self.document.getDrawPages().getCount() def goto_slide(self, slideno): - self.controller.gotoSlideIndex(slideno-1) + self.control.gotoSlideIndex(slideno-1) def next_step(self): """ Triggers the next effect of slide on the running presentation """ - self.controller.gotoNextEffect() + self.control.gotoNextEffect() def previous_step(self): """ Triggers the previous slide on the running presentation """ - self.controller.gotoPreviousSlide() + self.control.gotoPreviousSlide() def get_slide_preview_file(self, slide_no): """ @@ -301,8 +362,43 @@ class ImpressController(PresentationController): The slide an image is required for, starting at 1 """ path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.png') + self.controller.thumbnailprefix + unicode(slide_no) + u'.png') if os.path.isfile(path): return path else: - return None \ No newline at end of file + return None + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + doc = self.document + pages = doc.getDrawPages() + text = '' + page = pages.getByIndex(slide_no - 1) + for idx in range(page.getCount()): + shape = page.getByIndex(idx) + if shape.supportsService("com.sun.star.drawing.Text"): + text += shape.getString() + '\n' + return text + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + doc = self.document + pages = doc.getDrawPages() + text = '' + page = pages.getByIndex(slide_no - 1) + notes = page.getNotesPage() + for idx in range(notes.getCount()): + shape = notes.getByIndex(idx) + if shape.supportsService("com.sun.star.drawing.Text"): + text += shape.getString() + '\n' + return text diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index c1394c547..37d50d01c 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -31,6 +31,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, BaseListWithDnD from openlp.plugins.presentations.lib import MessageListener +log = logging.getLogger(__name__) + # We have to explicitly create separate classes for each plugin # in order for DnD to the Service manager to work correctly. class PresentationListView(BaseListWithDnD): @@ -43,8 +45,6 @@ class PresentationMediaItem(MediaManagerItem): This is the Presentation media manager item for Presentation Items. It can present files using Openoffice """ - global log - log = logging.getLogger(u'PresentationsMediaItem') log.info(u'Presentations Media Item loaded') def __init__(self, parent, icon, title, controllers): @@ -52,18 +52,27 @@ class PresentationMediaItem(MediaManagerItem): self.PluginNameShort = u'Presentation' self.ConfigSection = title self.IconPath = u'presentations/presentation' + self.Automatic = u'' # this next is a class, not an instance of a class - it will # be instanced by the base MediaManagerItem self.ListViewWithDnD_class = PresentationListView MediaManagerItem.__init__(self, parent, icon, title) - self.message_listener = MessageListener(controllers) - + self.message_listener = MessageListener(self) + def initPluginNameVisible(self): self.PluginNameVisible = self.trUtf8('Presentation') def retranslateUi(self): self.OnNewPrompt = self.trUtf8('Select Presentation(s)') - self.OnNewFileMasks = self.trUtf8('Presentations (*.ppt *.pps *.odp)') + self.Automatic = self.trUtf8('Automatic') + fileType = u'' + for controller in self.controllers: + if self.controllers[controller].enabled: + types = self.controllers[controller].supports + self.controllers[controller].alsosupports + for type in types: + if fileType.find(type) == -1: + fileType += u'*%s ' % type + self.OnNewFileMasks = self.trUtf8('Presentations (%s)' % fileType) def requiredIcons(self): MediaManagerItem.requiredIcons(self) @@ -100,6 +109,9 @@ class PresentationMediaItem(MediaManagerItem): #load the drop down selection if self.controllers[item].enabled: self.DisplayTypeComboBox.addItem(item) + if self.DisplayTypeComboBox.count() > 1: + self.DisplayTypeComboBox.insertItem(0, self.Automatic) + self.DisplayTypeComboBox.setCurrentIndex(0) def loadList(self, list): currlist = self.getFileList() @@ -129,7 +141,9 @@ class PresentationMediaItem(MediaManagerItem): self.ConfigSection, self.getFileList()) filepath = unicode((item.data(QtCore.Qt.UserRole)).toString()) for cidx in self.controllers: - self.controllers[cidx].presentation_deleted(filepath) + doc = self.controllers[cidx].add_doc(filepath) + doc.presentation_deleted() + self.controllers[cidx].remove_doc(doc) def generateSlideData(self, service_item): items = self.ListView.selectedIndexes() @@ -137,18 +151,39 @@ class PresentationMediaItem(MediaManagerItem): return False service_item.title = unicode(self.DisplayTypeComboBox.currentText()) service_item.shortname = unicode(self.DisplayTypeComboBox.currentText()) - controller = self.controllers[service_item.shortname] + shortname = service_item.shortname + for item in items: bitem = self.ListView.item(item.row()) filename = unicode((bitem.data(QtCore.Qt.UserRole)).toString()) + if shortname == self.Automatic: + service_item.shortname = self.findControllerByType(filename) + if not service_item.shortname: + return False + controller = self.controllers[service_item.shortname] (path, name) = os.path.split(filename) - controller.store_filename(filename) - if controller.get_slide_preview_file(1) is None: - controller.load_presentation(filename) + doc = controller.add_doc(filename) + if doc.get_slide_preview_file(1) is None: + doc.load_presentation() i = 1 - img = controller.get_slide_preview_file(i) + img = doc.get_slide_preview_file(i) while img: service_item.add_from_command(path, name, img) i = i + 1 - img = controller.get_slide_preview_file(i) - return True \ No newline at end of file + img = doc.get_slide_preview_file(i) + controller.remove_doc(doc) + return True + + def findControllerByType(self, filename): + filetype = os.path.splitext(filename)[1] + if not filetype: + return None + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[controller].supports: + return controller + for controller in self.controllers: + if self.controllers[controller].enabled: + if filetype in self.controllers[controller].alsosupports: + return controller + return None diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 8094b804e..08aa7e73a 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -30,136 +30,160 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver +log = logging.getLogger(__name__) + class Controller(object): """ This is the Presentation listener who acts on events from the slide controller and passes the messages on the the correct presentation handlers """ - global log - log = logging.getLogger(u'Controller') log.info(u'Controller loaded') def __init__(self, live): self.isLive = live + self.doc = None log.info(u'%s controller loaded' % live) - def addHandler(self, controller, file): + def addHandler(self, controller, file, isBlank): log.debug(u'Live = %s, addHandler %s' % (self.isLive, file)) self.controller = controller - if self.controller.is_loaded(): - self.shutdown(None) - self.controller.load_presentation(file) + if self.doc is not None: + self.shutdown() + self.doc = self.controller.add_doc(file) + self.doc.load_presentation() if self.isLive: - self.controller.start_presentation() + self.doc.start_presentation() + if isBlank: + self.blank() Receiver.send_message(u'live_slide_hide') - self.controller.slidenumber = 0 + self.doc.slidenumber = 0 def activate(self): log.debug(u'Live = %s, activate' % self.isLive) - if self.controller.is_active(): + if self.doc.is_active(): return - if not self.controller.is_loaded(): - self.controller.load_presentation(self.controller.filepath) + if not self.doc.is_loaded(): + self.doc.load_presentation() if self.isLive: - self.controller.start_presentation() - if self.controller.slidenumber > 1: - self.controller.goto_slide(self.controller.slidenumber) + self.doc.start_presentation() + if self.doc.slidenumber > 1: + self.doc.goto_slide(self.doc.slidenumber) def slide(self, slide, live): log.debug(u'Live = %s, slide' % live) -# if not isLive: -# return + if not live: + return + if self.doc.is_blank(): + self.doc.slidenumber = int(slide) + 1 + return self.activate() - self.controller.goto_slide(int(slide) + 1) - self.controller.poll_slidenumber(live) + self.doc.goto_slide(int(slide) + 1) + self.doc.poll_slidenumber(live) - def first(self, message): + def first(self): """ Based on the handler passed at startup triggers the first slide """ log.debug(u'Live = %s, first' % self.isLive) - print "first ", message if not self.isLive: return + if self.doc.is_blank(): + self.doc.slidenumber = 1 + return self.activate() - self.controller.start_presentation() - self.controller.poll_slidenumber(self.isLive) + self.doc.start_presentation() + self.doc.poll_slidenumber(self.isLive) - def last(self, message): + def last(self): """ Based on the handler passed at startup triggers the first slide """ log.debug(u'Live = %s, last' % self.isLive) - print "last ", message if not self.isLive: return + if self.doc.is_blank(): + self.doc.slidenumber = self.doc.get_slide_count() + return self.activate() - self.controller.goto_slide(self.controller.get_slide_count()) - self.controller.poll_slidenumber(self.isLive) + self.doc.goto_slide(self.doc.get_slide_count()) + self.doc.poll_slidenumber(self.isLive) - def next(self, message): + def next(self): """ Based on the handler passed at startup triggers the next slide event """ log.debug(u'Live = %s, next' % self.isLive) - print "next ", message if not self.isLive: return + if self.doc.is_blank(): + if self.doc.slidenumber < self.doc.get_slide_count(): + self.doc.slidenumber = self.doc.slidenumber + 1 + return self.activate() - self.controller.next_step() - self.controller.poll_slidenumber(self.isLive) + self.doc.next_step() + self.doc.poll_slidenumber(self.isLive) - def previous(self, message): + def previous(self): """ Based on the handler passed at startup triggers the previous slide event """ log.debug(u'Live = %s, previous' % self.isLive) if not self.isLive: return - print "previous ", message + if self.doc.is_blank(): + if self.doc.slidenumber > 1: + self.doc.slidenumber = self.doc.slidenumber - 1 + return self.activate() - self.controller.previous_step() - self.controller.poll_slidenumber(self.isLive) + self.doc.previous_step() + self.doc.poll_slidenumber(self.isLive) - def shutdown(self, message): + def shutdown(self): """ Based on the handler passed at startup triggers slide show to shut down """ log.debug(u'Live = %s, shutdown' % self.isLive) - self.controller.close_presentation() - self.controller.slidenumber = 0 + if self.isLive: + Receiver.send_message(u'live_slide_show') + self.doc.close_presentation() + self.doc = None + #self.doc.slidenumber = 0 #self.timer.stop() def blank(self): + log.debug(u'Live = %s, blank' % self.isLive) if not self.isLive: return - if not self.controller.is_loaded(): + if not self.doc.is_loaded(): return - if not self.controller.is_active(): + if not self.doc.is_active(): return - self.controller.blank_screen() + self.doc.blank_screen() def unblank(self): - if not self.is_live: + log.debug(u'Live = %s, unblank' % self.isLive) + if not self.isLive: return self.activate() - self.controller.unblank_screen() + if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number(): + self.doc.goto_slide(self.doc.slidenumber) + self.doc.unblank_screen() + def poll(self): + self.doc.poll_slidenumber(self.isLive) class MessageListener(object): """ This is the Presentation listener who acts on events from the slide controller and passes the messages on the the correct presentation handlers """ - global log - log = logging.getLogger(u'MessageListener') log.info(u'Message Listener loaded') - def __init__(self, controllers): - self.controllers = controllers + def __init__(self, mediaitem): + self.controllers = mediaitem.controllers + self.mediaitem = mediaitem self.previewHandler = Controller(False) self.liveHandler = Controller(True) - self.isLive = None # messages are sent from core.ui.slidecontroller QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'presentations_start'), self.startup) @@ -189,11 +213,17 @@ class MessageListener(object): Save the handler as any new presentations start here """ log.debug(u'Startup called with message %s' % message) - self.handler, file, isLive = self.decodeMessage(message) + self.handler, file, isLive, isBlank = self.decodeMessage(message) + if self.handler == self.mediaitem.Automatic: + self.handler = self.mediaitem.findControllerByType(file) + if not self.handler: + return + if isLive: - self.liveHandler.addHandler(self.controllers[self.handler], file) + controller = self.liveHandler else: - self.previewHandler.addHandler(self.controllers[self.handler], file) + controller = self.previewHandler + controller.addHandler(self.controllers[self.handler], file, isBlank) def slide(self, message): slide, live = self.splitMessage(message) @@ -202,48 +232,42 @@ class MessageListener(object): else: self.previewHandler.slide(slide, live) - def first(self, message): - if self.isLive: - self.liveHandler.first(message) + def first(self, isLive): + if isLive: + self.liveHandler.first() else: - self.previewHandler.first(message) + self.previewHandler.first() - def last(self, message): - if self.isLive: - self.liveHandler.last(message) + def last(self, isLive): + if isLive: + self.liveHandler.last() else: - self.previewHandler.last(message) + self.previewHandler.last() - def next(self, message): - if self.isLive: - self.liveHandler.next(message) + def next(self, isLive): + if isLive: + self.liveHandler.next() else: - self.previewHandler.next(message) + self.previewHandler.next() - def previous(self, message): - if self.isLive: - self.liveHandler.previous(message) + def previous(self, isLive): + if isLive: + self.liveHandler.previous() else: - self.previewHandler.previous(message) + self.previewHandler.previous() - def shutdown(self, message): - if self.isLive: - self.liveHandler.shutdown(message) + def shutdown(self, isLive): + if isLive: + self.liveHandler.shutdown() Receiver.send_message(u'live_slide_show') else: - self.previewHandler.shutdown(message) + self.previewHandler.shutdown() def blank(self): - if self.isLive: - self.liveHandler.blank() - else: - self.previewHandler.blank() + self.liveHandler.blank() def unblank(self): - if self.isLive: - self.liveHandler.unblank() - else: - self.previewHandler.unblank() + self.liveHandler.unblank() def splitMessage(self, message): """ @@ -265,7 +289,7 @@ class MessageListener(object): Message containing Presentaion handler name and file to be presented. """ file = os.path.join(message[1], message[2]) - return message[0], file, message[4] + return message[0], file, message[4], message[5] def timeout(self): - self.controller.poll_slidenumber(self.is_live) \ No newline at end of file + self.liveHandler.poll() diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index d64314c76..2137496a5 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -31,7 +31,9 @@ if os.name == u'nt': import _winreg import win32ui -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument + +log = logging.getLogger(__name__) # PPT API documentation: # http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx @@ -42,9 +44,7 @@ class PowerpointController(PresentationController): It creates the runtime Environment , Loads the and Closes the Presentation As well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'PowerpointController') - log.info(u'loaded') + log.info(u'PowerpointController loaded') def __init__(self, plugin): """ @@ -52,8 +52,8 @@ class PowerpointController(PresentationController): """ log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Powerpoint') + self.supports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] self.process = None - self.presentation = None def check_available(self): """ @@ -77,121 +77,145 @@ class PowerpointController(PresentationController): self.process.Visible = True self.process.WindowState = 2 - def is_loaded(self): - """ - Returns true if a presentation is loaded - """ - try: - if not self.process.Visible: - return False - if self.process.Windows.Count == 0: - return False - if self.process.Presentations.Count == 0: - return False - except: - return False - return True - def kill(self): """ Called at system exit to clean up any running presentations """ + for doc in self.docs: + doc.close_presentation() if self.process is None: return + if self.process.Presentations.Count > 0: + return try: self.process.Quit() except: pass self.process = None - def load_presentation(self, presentation): - """ - Called when a presentation is added to the SlideController. - It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded - and started. + def add_doc(self, name): + log.debug(u'Add Doc PowerPoint') + doc = PowerpointDocument(self, name) + self.docs.append(doc) + return doc - ``presentation`` - The file name of the presentations to run. - """ - log.debug(u'LoadPresentation') - self.store_filename(presentation) - try: - if not self.process.Visible: - self.start_process() - except: - self.start_process() - try: - self.process.Presentations.Open(presentation, False, False, True) - except: - return - self.presentation = self.process.Presentations(self.process.Presentations.Count) - self.create_thumbnails() +class PowerpointDocument(PresentationDocument): - def create_thumbnails(self): - """ - Create the thumbnail images for the current presentation. - Note an alternative and quicker method would be do - self.presentation.Slides[n].Copy() - thumbnail = QApplication.clipboard.image() - But for now we want a physical file since it makes - life easier elsewhere - """ - if self.check_thumbnails(): - return - self.presentation.Export(os.path.join(self.thumbnailpath, '') - , 'png', 600, 480) + def __init__(self, controller, presentation): + log.debug(u'Init Presentation Powerpoint') + self.presentation = None + self.controller = controller + self.store_filename(presentation) - def close_presentation(self): - """ - Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down - """ - if self.presentation == None: - return - try: - self.presentation.Close() - except: - pass - self.presentation = None + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. + It builds the environment, starts communcations with the background + OpenOffice task started earlier. If OpenOffice is not present is is + started. Once the environment is available the presentation is loaded + and started. - def is_active(self): - """ - Returns true if a presentation is currently active - """ - if not self.is_loaded(): + ``presentation`` + The file name of the presentations to run. + """ + log.debug(u'LoadPresentation') + if not self.controller.process.Visible: + self.controller.start_process() + #try: + self.controller.process.Presentations.Open(self.filepath, False, False, True) + #except: + # return + self.presentation = self.controller.process.Presentations( + self.controller.process.Presentations.Count) + self.create_thumbnails() + + def create_thumbnails(self): + """ + Create the thumbnail images for the current presentation. + Note an alternative and quicker method would be do + self.presentation.Slides[n].Copy() + thumbnail = QApplication.clipboard.image() + But for now we want a physical file since it makes + life easier elsewhere + """ + if self.check_thumbnails(): + return + self.presentation.Export(os.path.join(self.thumbnailpath, '') + , 'png', 640, 480) + + def close_presentation(self): + """ + Close presentation and clean up objects + Triggerent by new object being added to SlideController orOpenLP + being shut down + """ + if self.presentation is None: + return + try: + self.presentation.Close() + except: + pass + self.presentation = None + self.controller.remove_doc(self) + + def is_loaded(self): + """ + Returns true if a presentation is loaded + """ + try: + if not self.controller.process.Visible: return False - try: - if self.presentation.SlideShowWindow == None: - return False - if self.presentation.SlideShowWindow.View == None: - return False - except: + if self.controller.process.Windows.Count == 0: return False - return True + if self.controller.process.Presentations.Count == 0: + return False + except: + return False + return True - def unblank_screen(self): - """ - Unblanks (restores) the presentationn - """ - self.presentation.SlideShowSettings.Run() - self.presentation.SlideShowWindow.View.State = 1 - self.presentation.SlideShowWindow.Activate() - def blank_screen(self): - """ - Blanks the screen - """ - self.presentation.SlideShowWindow.View.State = 3 + def is_active(self): + """ + Returns true if a presentation is currently active + """ + if not self.is_loaded(): + return False + try: + if self.presentation.SlideShowWindow is None: + return False + if self.presentation.SlideShowWindow.View is None: + return False + except: + return False + return True - def stop_presentation(self): - """ - Stops the current presentation and hides the output - """ - self.presentation.SlideShowWindow.View.Exit() + def unblank_screen(self): + """ + Unblanks (restores) the presentationn + """ + self.presentation.SlideShowSettings.Run() + self.presentation.SlideShowWindow.View.State = 1 + self.presentation.SlideShowWindow.Activate() + def blank_screen(self): + """ + Blanks the screen + """ + self.presentation.SlideShowWindow.View.State = 3 + + def is_blank(self): + """ + Returns true if screen is blank + """ + return self.presentation.SlideShowWindow.View.State == 3 + + def stop_presentation(self): + """ + Stops the current presentation and hides the output + """ + self.presentation.SlideShowWindow.View.Exit() + + if os.name == u'nt': def start_presentation(self): """ Starts a presentation from the beginning @@ -206,53 +230,83 @@ class PowerpointController(PresentationController): dpi = 96 self.presentation.SlideShowSettings.Run() self.presentation.SlideShowWindow.View.GotoSlide(1) - rendermanager = self.plugin.render_manager - rect = rendermanager.screen_list[rendermanager.current_display][u'size'] + rendermanager = self.controller.plugin.render_manager + rect = rendermanager.screens.current[u'size'] self.presentation.SlideShowWindow.Top = rect.y() * 72 / dpi self.presentation.SlideShowWindow.Height = rect.height() * 72 / dpi self.presentation.SlideShowWindow.Left = rect.x() * 72 / dpi self.presentation.SlideShowWindow.Width = rect.width() * 72 / dpi - def get_slide_number(self): - """ - Returns the current slide number - """ - return self.presentation.SlideShowWindow.View.CurrentShowPosition + def get_slide_number(self): + """ + Returns the current slide number + """ + return self.presentation.SlideShowWindow.View.CurrentShowPosition - def get_slide_count(self): - """ - Returns total number of slides - """ - return self.presentation.Slides.Count + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.presentation.Slides.Count - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation - """ - self.presentation.SlideShowWindow.View.GotoSlide(slideno) + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation + """ + self.presentation.SlideShowWindow.View.GotoSlide(slideno) - def next_step(self): - """ - Triggers the next effect of slide on the running presentation - """ - self.presentation.SlideShowWindow.View.Next() + def next_step(self): + """ + Triggers the next effect of slide on the running presentation + """ + self.presentation.SlideShowWindow.View.Next() - def previous_step(self): - """ - Triggers the previous slide on the running presentation - """ - self.presentation.SlideShowWindow.View.Previous() + def previous_step(self): + """ + Triggers the previous slide on the running presentation + """ + self.presentation.SlideShowWindow.View.Previous() - def get_slide_preview_file(self, slide_no): - """ - Returns an image path containing a preview for the requested slide + def get_slide_preview_file(self, slide_no): + """ + Returns an image path containing a preview for the requested slide - ``slide_no`` - The slide an image is required for, starting at 1 - """ - path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.png') - if os.path.isfile(path): - return path - else: - return None \ No newline at end of file + ``slide_no`` + The slide an image is required for, starting at 1 + """ + path = os.path.join(self.thumbnailpath, + self.controller.thumbnailprefix + unicode(slide_no) + u'.png') + if os.path.isfile(path): + return path + else: + return None + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + text = '' + shapes = self.presentation.Slides(slide_no).Shapes + for idx in range(shapes.Count): + shape = shapes(idx + 1) + if shape.HasTextFrame: + text += shape.TextFrame.TextRange.Text + '\n' + return text + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + text = '' + shapes = self.presentation.Slides(slide_no).NotesPage.Shapes + for idx in range(shapes.Count): + shape = shapes(idx + 1) + if shape.HasTextFrame: + text += shape.TextFrame.TextRange.Text + '\n' + return text diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 02b280540..6bec8dbf1 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -30,7 +30,9 @@ if os.name == u'nt': from ctypes import * from ctypes.wintypes import RECT -from presentationcontroller import PresentationController +from presentationcontroller import PresentationController, PresentationDocument + +log = logging.getLogger(__name__) class PptviewController(PresentationController): """ @@ -38,9 +40,7 @@ class PptviewController(PresentationController): It creates the runtime Environment , Loads the and Closes the Presentation As well as triggering the correct activities based on the users input """ - global log - log = logging.getLogger(u'PptviewController') - log.info(u'loaded') + log.info(u'PPTViewController loaded') def __init__(self, plugin): """ @@ -49,7 +49,7 @@ class PptviewController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Powerpoint Viewer') - self.pptid = None + self.supports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx'] def check_available(self): """ @@ -89,123 +89,147 @@ class PptviewController(PresentationController): Called at system exit to clean up any running presentations """ log.debug(u'Kill') - self.close_presentation() + for doc in self.docs: + doc.close_presentation() - def load_presentation(self, presentation): - """ - Called when a presentation is added to the SlideController. - It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded - and started. + def add_doc(self, name): + log.debug(u'Add Doc PPTView') + doc = PptviewDocument(self, name) + self.docs.append(doc) + return doc - ``presentation`` - The file name of the presentations to run. - """ - log.debug(u'LoadPresentation') - self.store_filename(presentation) - if self.pptid >= 0: - self.close_presentation() - rendermanager = self.plugin.render_manager - rect = rendermanager.screen_list[rendermanager.current_display][u'size'] - rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) - filepath = str(presentation.replace(u'/', u'\\')); - try: - self.pptid = self.process.OpenPPT(filepath, None, rect, - str(os.path.join(self.thumbnailpath, self.thumbnailprefix))) - self.stop_presentation() - except: - log.exception(u'Failed to load presentation') +class PptviewDocument(PresentationDocument): - def close_presentation(self): - """ - Close presentation and clean up objects - Triggerent by new object being added to SlideController orOpenLP - being shut down - """ - self.process.ClosePPT(self.pptid) - self.pptid = -1 + def __init__(self, controller, presentation): + log.debug(u'Init Presentation PowerPoint') + self.presentation = None + self.pptid = None + self.blanked = False + self.controller = controller + self.store_filename(presentation) - def is_loaded(self): - """ - Returns true if a presentation is loaded - """ - if self.pptid < 0: - return False - if self.get_slide_count() < 0: - return False - return True + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. + It builds the environment, starts communcations with the background + PptView task started earlier. - def is_active(self): - """ - Returns true if a presentation is currently active - """ - return self.is_loaded() + ``presentation`` + The file name of the presentations to run. + """ + log.debug(u'LoadPresentation') + #if self.pptid >= 0: + # self.close_presentation() + rendermanager = self.controller.plugin.render_manager + rect = rendermanager.screens.current[u'size'] + rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) + filepath = str(self.filepath.replace(u'/', u'\\')) + try: + self.pptid = self.controller.process.OpenPPT(filepath, None, rect, + str(os.path.join(self.thumbnailpath, self.controller.thumbnailprefix))) + self.stop_presentation() + except: + log.exception(u'Failed to load presentation') - def blank_screen(self): - """ - Blanks the screen - """ - self.process.Blank(self.pptid) + def close_presentation(self): + """ + Close presentation and clean up objects + Triggerent by new object being added to SlideController orOpenLP + being shut down + """ + self.controller.process.ClosePPT(self.pptid) + self.pptid = -1 + self.controller.remove_doc(self) - def unblank_screen(self): - """ - Unblanks (restores) the presentationn - """ - self.process.Unblank(self.pptid) + def is_loaded(self): + """ + Returns true if a presentation is loaded + """ + if self.pptid < 0: + return False + if self.get_slide_count() < 0: + return False + return True - def stop_presentation(self): - """ - Stops the current presentation and hides the output - """ - self.process.Stop(self.pptid) + def is_active(self): + """ + Returns true if a presentation is currently active + """ + return self.is_loaded() - def start_presentation(self): - """ - Starts a presentation from the beginning - """ - self.process.RestartShow(self.pptid) + def blank_screen(self): + """ + Blanks the screen + """ + self.controller.process.Blank(self.pptid) + self.blanked = True - def get_slide_number(self): - """ - Returns the current slide number - """ - return self.process.GetCurrentSlide(self.pptid) + def unblank_screen(self): + """ + Unblanks (restores) the presentationn + """ + self.controller.process.Unblank(self.pptid) + self.blanked = False - def get_slide_count(self): - """ - Returns total number of slides - """ - return self.process.GetSlideCount(self.pptid) + def is_blank(self): + """ + Returns true if screen is blank + """ + log.debug(u'is blank OpenOffice') + return self.blanked - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation - """ - self.process.GotoSlide(self.pptid, slideno) + def stop_presentation(self): + """ + Stops the current presentation and hides the output + """ + self.controller.process.Stop(self.pptid) - def next_step(self): - """ - Triggers the next effect of slide on the running presentation - """ - self.process.NextStep(self.pptid) + def start_presentation(self): + """ + Starts a presentation from the beginning + """ + self.controller.process.RestartShow(self.pptid) - def previous_step(self): - """ - Triggers the previous slide on the running presentation - """ - self.process.PrevStep(self.pptid) + def get_slide_number(self): + """ + Returns the current slide number + """ + return self.controller.process.GetCurrentSlide(self.pptid) - def get_slide_preview_file(self, slide_no): - """ - Returns an image path containing a preview for the requested slide + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.controller.process.GetSlideCount(self.pptid) - ``slide_no`` - The slide an image is required for, starting at 1 - """ - path = os.path.join(self.thumbnailpath, - self.thumbnailprefix + unicode(slide_no) + u'.bmp') - if os.path.isfile(path): - return path - else: - return None + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation + """ + self.controller.process.GotoSlide(self.pptid, slideno) + + def next_step(self): + """ + Triggers the next effect of slide on the running presentation + """ + self.controller.process.NextStep(self.pptid) + + def previous_step(self): + """ + Triggers the previous slide on the running presentation + """ + self.controller.process.PrevStep(self.pptid) + + def get_slide_preview_file(self, slide_no): + """ + Returns an image path containing a preview for the requested slide + + ``slide_no`` + The slide an image is required for, starting at 1 + """ + path = os.path.join(self.thumbnailpath, + self.controller.thumbnailprefix + unicode(slide_no) + u'.bmp') + if os.path.isfile(path): + return path + else: + return None diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 82c014b64..ae41a56b1 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -31,16 +31,16 @@ from PyQt4 import QtCore from openlp.core.lib import Receiver +log = logging.getLogger(__name__) + class PresentationController(object): """ Base class for presentation controllers to inherit from Class to control interactions with presentations. - It creates the runtime environment, loads and closes the presentation as - well as triggering the correct activities based on the users input - + It creates the runtime environment To create a new controller, take a copy of this file and name it so it ends in controller.py, i.e. foobarcontroller.py - Make sure it inhetits PresentationController + Make sure it inherits PresentationController Then fill in the blanks. If possible try and make sure it loads on all platforms, using for example os.name checks, although __init__, check_available and presentation_deleted should always work. @@ -73,6 +73,87 @@ class PresentationController(object): ``presentation_deleted()`` Deletes presentation specific files, e.g. thumbnails + """ + log.info(u'PresentationController loaded') + + def __init__(self, plugin=None, name=u'PresentationController'): + """ + This is the constructor for the presentationcontroller object. + This provides an easy way for descendent plugins to populate common data. + This method *must* be overridden, like so:: + + class MyPresentationController(PresentationController): + def __init__(self, plugin): + PresentationController.__init(self, plugin, u'My Presenter App') + + ``plugin`` + Defaults to *None*. The presentationplugin object + + ``name`` + Name of the application, to appear in the application + """ + self.supports = [] + self.alsosupports = [] + self.docs = [] + self.plugin = plugin + self.name = name + self.available = self.check_available() + if self.available: + self.enabled = int(plugin.config.get_config( + name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked + else: + self.enabled = False + self.thumbnailroot = os.path.join(plugin.config.get_data_path(), + name, u'thumbnails') + self.thumbnailprefix = u'slide' + if not os.path.isdir(self.thumbnailroot): + os.makedirs(self.thumbnailroot) + + def check_available(self): + """ + Presentation app is able to run on this machine + """ + return False + + + def start_process(self): + """ + Loads a running version of the presentation application in the background. + """ + pass + + def kill(self): + """ + Called at system exit to clean up any running presentations and + close the application + """ + log.debug(u'Kill') + self.close_presentation() + + def add_doc(self, name): + """ + Called when a new presentation document is opened + """ + doc = PresentationDocument(self, name) + self.docs.append(doc) + return doc + + def remove_doc(self, doc): + """ + Called to remove an open document from the collection + """ + log.debug(u'remove_doc Presentation') + self.docs.remove(doc) + + +class PresentationDocument(object): + """ + Base class for presentation documents to inherit from. + Loads and closes the presentation as well as triggering the correct + activities based on the users input + + **Hook Functions** + ``load_presentation(presentation)`` Load a presentation file @@ -91,6 +172,9 @@ class PresentationController(object): ``unblank_screen()`` Unblanks the screen, restoring the output + ``is_blank`` + Returns true if screen is blank + ``stop_presentation()`` Stops the presentation, removing it from the output display @@ -116,70 +200,12 @@ class PresentationController(object): Returns a path to an image containing a preview for the requested slide """ - global log - log = logging.getLogger(u'PresentationController') - log.info(u'loaded') - - def __init__(self, plugin=None, name=u'PresentationController'): - """ - This is the constructor for the presentationcontroller object. - This provides an easy way for descendent plugins to populate common data. - This method *must* be overridden, like so:: - - class MyPresentationController(PresentationController): - def __init__(self, plugin): - PresentationController.__init(self, plugin, u'My Presenter App') - - ``plugin`` - Defaults to *None*. The presentationplugin object - - ``name`` - Name of the application, to appear in the application - """ - self.plugin = plugin - self.name = name - self.available = self.check_available() + def __init__(self, controller, name): self.slidenumber = 0 - if self.available: - self.enabled = int(plugin.config.get_config( - name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked - else: - self.enabled = False - self.thumbnailroot = os.path.join(plugin.config.get_data_path(), - name, u'thumbnails') - self.thumbnailprefix = u'slide' - if not os.path.isdir(self.thumbnailroot): - os.makedirs(self.thumbnailroot) + self.controller = controller + self.store_filename(name) - def check_available(self): - """ - Presentation app is able to run on this machine - """ - return False - - def presentation_deleted(self, presentation): - """ - Cleans up/deletes any controller specific files created for - a file, e.g. thumbnails - """ - self.store_filename(presentation) - shutil.rmtree(self.thumbnailpath) - - def start_process(self): - """ - Loads a running version of the presentation application in the background. - """ - pass - - def kill(self): - """ - Called at system exit to clean up any running presentations and - close the application - """ - log.debug(u'Kill') - self.close_presentation() - - def load_presentation(self, presentation): + def load_presentation(self): """ Called when a presentation is added to the SlideController. Loads the presentation and starts it @@ -190,16 +216,29 @@ class PresentationController(object): """ pass + def presentation_deleted(self): + """ + Cleans up/deletes any controller specific files created for + a file, e.g. thumbnails + """ + shutil.rmtree(self.thumbnailpath) + def store_filename(self, presentation): """ Set properties for the filename and thumbnail paths """ self.filepath = presentation - self.filename = os.path.split(presentation)[1] - self.thumbnailpath = os.path.join(self.thumbnailroot, self.filename) + self.filename = self.get_file_name(presentation) + self.thumbnailpath = self.get_thumbnail_path(presentation) if not os.path.isdir(self.thumbnailpath): os.mkdir(self.thumbnailpath) + def get_file_name(self, presentation): + return os.path.split(presentation)[1] + + def get_thumbnail_path(self, presentation): + return os.path.join(self.controller.thumbnailroot, self.get_file_name(presentation)) + def check_thumbnails(self): """ Returns true if the thumbnail images look to exist and are more @@ -217,10 +256,10 @@ class PresentationController(object): Close presentation and clean up objects Triggered by new object being added to SlideController """ - pass + self.controller.delete_doc(self) def is_active(self): - """ + """ Returns True if a presentation is currently running """ return False @@ -243,6 +282,12 @@ class PresentationController(object): """ pass + def is_blank(self): + """ + Returns true if screen is blank + """ + return False + def stop_presentation(self): """ Stops the presentation, removing it from the output display @@ -313,4 +358,22 @@ class PresentationController(object): else: prefix = u'preview' Receiver.send_message(u'%s_slidecontroller_change' % prefix, - self.slidenumber - 1) \ No newline at end of file + self.slidenumber - 1) + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the text is required for, starting at 1 + """ + return '' + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide + + ``slide_no`` + The slide the notes are required for, starting at 1 + """ + return '' diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 40ab15e69..0ef7e17d1 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -51,17 +51,10 @@ class PresentationTab(SettingsTab): self.PresentationLeftLayout.setMargin(0) self.VerseDisplayGroupBox = QtGui.QGroupBox(self) self.VerseDisplayGroupBox.setObjectName(u'VerseDisplayGroupBox') - self.VerseDisplayLayout = QtGui.QGridLayout(self.VerseDisplayGroupBox) + self.VerseDisplayLayout = QtGui.QVBoxLayout(self.VerseDisplayGroupBox) self.VerseDisplayLayout.setMargin(8) self.VerseDisplayLayout.setObjectName(u'VerseDisplayLayout') - self.VerseTypeWidget = QtGui.QWidget(self.VerseDisplayGroupBox) - self.VerseTypeWidget.setObjectName(u'VerseTypeWidget') - self.VerseTypeLayout = QtGui.QHBoxLayout(self.VerseTypeWidget) - self.VerseTypeLayout.setSpacing(8) - self.VerseTypeLayout.setMargin(0) - self.VerseTypeLayout.setObjectName(u'VerseTypeLayout') self.PresenterCheckboxes = {} - index = 0 for key in self.controllers: controller = self.controllers[key] checkbox = QtGui.QCheckBox(self.VerseDisplayGroupBox) @@ -69,8 +62,7 @@ class PresentationTab(SettingsTab): checkbox.setEnabled(controller.available) checkbox.setObjectName(controller.name + u'CheckBox') self.PresenterCheckboxes[controller.name] = checkbox - index = index + 1 - self.VerseDisplayLayout.addWidget(checkbox, index, 0, 1, 1) + self.VerseDisplayLayout.addWidget(checkbox) self.PresentationThemeWidget = QtGui.QWidget(self.VerseDisplayGroupBox) self.PresentationThemeWidget.setObjectName(u'PresentationThemeWidget') self.PresentationThemeLayout = QtGui.QHBoxLayout( @@ -96,6 +88,7 @@ class PresentationTab(SettingsTab): self.PresentationLayout.addWidget(self.PresentationRightWidget) def retranslateUi(self): + self.VerseDisplayGroupBox.setTitle(self.trUtf8('Available Controllers')) for key in self.controllers: controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] @@ -115,4 +108,4 @@ class PresentationTab(SettingsTab): controller = self.controllers[key] checkbox = self.PresenterCheckboxes[controller.name] self.config.set_config( - controller.name, unicode(checkbox.checkState())) \ No newline at end of file + controller.name, unicode(checkbox.checkState())) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a380e9bb0..8353611ab 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -26,20 +26,21 @@ import os import logging -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, Receiver, PluginStatus from openlp.plugins.presentations.lib import * -class PresentationPlugin(Plugin): +log = logging.getLogger(__name__) - global log +class PresentationPlugin(Plugin): log = logging.getLogger(u'PresentationPlugin') def __init__(self, plugin_helpers): log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Presentations', u'1.9.1', plugin_helpers) self.weight = -8 self.icon = build_icon(u':/media/media_presentation.png') + self.status = PluginStatus.Active def get_settings_tab(self): """ @@ -51,6 +52,12 @@ class PresentationPlugin(Plugin): log.info(u'Presentations Initialising') Plugin.initialise(self) self.insert_toolbox_item() + presentation_types = [] + for controller in self.controllers: + if self.controllers[controller].enabled: + presentation_types.append({u'%s' % controller : self.controllers[controller].supports}) + Receiver.send_message( + u'presentation types', presentation_types) def finalise(self): log.info(u'Plugin Finalise') @@ -96,7 +103,7 @@ class PresentationPlugin(Plugin): self.registerControllers(controller) if controller.enabled: controller.start_process() - if len(self.controllers) > 0: + if self.controllers: return True else: return False @@ -106,4 +113,4 @@ class PresentationPlugin(Plugin): 'the ability to show presentations using a number of different ' 'programs. The choice of available presentation programs is ' 'available to the user in a drop down box.') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/remotes/remoteclient-cli.py b/openlp/plugins/remotes/remoteclient.py similarity index 86% rename from openlp/plugins/remotes/remoteclient-cli.py rename to openlp/plugins/remotes/remoteclient.py index a55aafe50..857a7fc7e 100755 --- a/openlp/plugins/remotes/remoteclient-cli.py +++ b/openlp/plugins/remotes/remoteclient.py @@ -28,7 +28,6 @@ import socket import sys from optparse import OptionParser - def sendData(options, message): addr = (options.address, options.port) try: @@ -47,34 +46,23 @@ def main(): parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=True, help="make lots of noise [%default]") - parser.add_option("-p", "--port", - default=4316, + parser.add_option("-p", "--port", default=4316, help="IP Port number %default ") parser.add_option("-a", "--address", help="Recipient address ") - parser.add_option("-e", "--event", - default=u'Alert', - help="Action to be undertaken") parser.add_option("-m", "--message", help="Message to be passed for the action") - parser.add_option("-n", "--slidenext", - help="Trigger the next slide") (options, args) = parser.parse_args() - if len(args) > 0: + if args: parser.print_help() parser.error("incorrect number of arguments") - elif options.message is None: - parser.print_help() - parser.error("No message passed") elif options.address is None: parser.print_help() parser.error("IP address missing") - elif options.slidenext: - options.event = u'next_slide' - options.message = u'' - text = format_message(options) - sendData(options, text) + elif options.message is None: + parser.print_help() + parser.error("No message passed") else: text = format_message(options) sendData(options, text) diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 428cdbf90..acfa6f97d 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -30,14 +30,13 @@ from PyQt4 import QtNetwork, QtCore from openlp.core.lib import Plugin, Receiver from openlp.plugins.remotes.lib import RemoteTab -class RemotesPlugin(Plugin): +log = logging.getLogger(__name__) - global log - log = logging.getLogger(u'RemotesPlugin') +class RemotesPlugin(Plugin): log.info(u'Remote Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Remotes', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Remotes', u'1.9.1', plugin_helpers) self.weight = -1 self.server = None @@ -83,4 +82,4 @@ class RemotesPlugin(Plugin): 'provides the ability to send messages to a running version of ' 'openlp on a different computer.<br>The Primary use for this ' 'would be to send alerts from a creche') - return about_text \ No newline at end of file + return about_text diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index fb7e0eecd..183af6a44 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -33,13 +33,13 @@ from openlp.plugins.songs.forms import EditVerseForm from openlp.plugins.songs.lib.models import Song from editsongdialog import Ui_EditSongDialog +log = logging.getLogger(__name__) + class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Class to manage the editing of a song """ - global log - log = logging.getLogger(u'EditSongForm') - log.info(u'Song Editor loaded') + log.info(u'%s EditSongForm loaded', __name__) def __init__(self, songmanager, parent=None): """ @@ -169,6 +169,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.loadAuthors() self.loadTopics() self.loadBooks() + #it's a new song to preview is not possible + self.previewButton.setVisible(False) def loadSong(self, id, preview): log.debug(u'Load Song') @@ -316,13 +318,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def onVerseAddButtonClicked(self): self.verse_form.setVerse(u'', self.VerseListWidget.count() + 1, True) - self.verse_form.exec_() - afterText, verse, subVerse = self.verse_form.getVerse() - data = u'%s:%s' %(verse, subVerse) - item = QtGui.QListWidgetItem(afterText) - item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) - item.setText(afterText) - self.VerseListWidget.addItem(item) + if self.verse_form.exec_(): + afterText, verse, subVerse = self.verse_form.getVerse() + data = u'%s:%s' %(verse, subVerse) + item = QtGui.QListWidgetItem(afterText) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) + item.setText(afterText) + self.VerseListWidget.addItem(item) def onVerseEditButtonClicked(self): item = self.VerseListWidget.currentItem() @@ -331,25 +333,25 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): verseId = unicode((item.data(QtCore.Qt.UserRole)).toString()) self.verse_form.setVerse(tempText, \ self.VerseListWidget.count(), True, verseId) - self.verse_form.exec_() - afterText, verse, subVerse = self.verse_form.getVerse() - data = u'%s:%s' %(verse, subVerse) - item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) - item.setText(afterText) - #number of lines has change so repaint the list moving the data - if len(tempText.split(u'\n')) != len(afterText.split(u'\n')): - tempList = {} - tempId = {} - for row in range(0, self.VerseListWidget.count()): - tempList[row] = self.VerseListWidget.item(row).text() - tempId[row] = self.VerseListWidget.item(row).\ - data(QtCore.Qt.UserRole) - self.VerseListWidget.clear() - for row in range (0, len(tempList)): - item = QtGui.QListWidgetItem(tempList[row]) - item.setData(QtCore.Qt.UserRole, tempId[row]) - self.VerseListWidget.addItem(item) - self.VerseListWidget.repaint() + if self.verse_form.exec_(): + afterText, verse, subVerse = self.verse_form.getVerse() + data = u'%s:%s' %(verse, subVerse) + item.setData(QtCore.Qt.UserRole, QtCore.QVariant(data)) + item.setText(afterText) + #number of lines has change so repaint the list moving the data + if len(tempText.split(u'\n')) != len(afterText.split(u'\n')): + tempList = {} + tempId = {} + for row in range(0, self.VerseListWidget.count()): + tempList[row] = self.VerseListWidget.item(row).text() + tempId[row] = self.VerseListWidget.item(row).\ + data(QtCore.Qt.UserRole) + self.VerseListWidget.clear() + for row in range (0, len(tempList)): + item = QtGui.QListWidgetItem(tempList[row]) + item.setData(QtCore.Qt.UserRole, tempId[row]) + self.VerseListWidget.addItem(item) + self.VerseListWidget.repaint() self.VerseEditButton.setEnabled(False) self.VerseDeleteButton.setEnabled(False) @@ -410,7 +412,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.AuthorsListView.setFocus() #split the verse list by space and mark lower case for testing for verse in unicode(self.VerseOrderEdit.text()).lower().split(u' '): - if len(verse) == 2: + if len(verse) > 1: if verse[0:1] == u'v' and verse[1:].isdigit(): pass else: @@ -533,4 +535,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.search_title = self.song.search_title.replace(u'{', u'') self.song.search_title = self.song.search_title.replace(u'}', u'') self.song.search_title = self.song.search_title.replace(u'?', u'') - self.song.search_title = unicode(self.song.search_title) \ No newline at end of file + self.song.search_title = unicode(self.song.search_title) diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index 8f532ac3f..89e704c87 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -1,113 +1,129 @@ # -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 -# Form implementation generated from reading ui file 'editversedialog.ui' -# -# Created: Wed Dec 2 08:14:47 2009 -# by: PyQt4 UI code generator 4.6.2 -# -# WARNING! All changes made in this file will be lost! +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 class Ui_EditVerseDialog(object): def setupUi(self, EditVerseDialog): - EditVerseDialog.setObjectName("EditVerseDialog") + EditVerseDialog.setObjectName(u'EditVerseDialog') EditVerseDialog.resize(500, 521) EditVerseDialog.setModal(True) self.layoutWidget = QtGui.QWidget(EditVerseDialog) self.layoutWidget.setGeometry(QtCore.QRect(11, 1, 471, 491)) - self.layoutWidget.setObjectName("layoutWidget") + self.layoutWidget.setObjectName(u'layoutWidget') self.verticalLayout_3 = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_3.setObjectName(u'verticalLayout_3') self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") + self.horizontalLayout.setObjectName(u'horizontalLayout') self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout.setObjectName(u'verticalLayout') self.VerseTypeLabel = QtGui.QLabel(self.layoutWidget) self.VerseTypeLabel.setTextFormat(QtCore.Qt.PlainText) self.VerseTypeLabel.setAlignment(QtCore.Qt.AlignCenter) - self.VerseTypeLabel.setObjectName("VerseTypeLabel") + self.VerseTypeLabel.setObjectName(u'VerseTypeLabel') self.verticalLayout.addWidget(self.VerseTypeLabel) self.VerseListComboBox = QtGui.QComboBox(self.layoutWidget) - self.VerseListComboBox.setObjectName("VerseListComboBox") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") - self.VerseListComboBox.addItem("") + self.VerseListComboBox.setObjectName(u'VerseListComboBox') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') + self.VerseListComboBox.addItem(u'') self.verticalLayout.addWidget(self.VerseListComboBox) self.horizontalLayout.addLayout(self.verticalLayout) self.verticalLayout_2 = QtGui.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout_2.setObjectName(u'verticalLayout_2') self.VerseNumberLabel = QtGui.QLabel(self.layoutWidget) self.VerseNumberLabel.setAlignment(QtCore.Qt.AlignCenter) - self.VerseNumberLabel.setObjectName("VerseNumberLabel") + self.VerseNumberLabel.setObjectName(u'VerseNumberLabel') self.verticalLayout_2.addWidget(self.VerseNumberLabel) self.SubVerseListComboBox = QtGui.QComboBox(self.layoutWidget) - self.SubVerseListComboBox.setObjectName("SubVerseListComboBox") + self.SubVerseListComboBox.setObjectName(u'SubVerseListComboBox') self.verticalLayout_2.addWidget(self.SubVerseListComboBox) self.horizontalLayout.addLayout(self.verticalLayout_2) self.verticalLayout_3.addLayout(self.horizontalLayout) self.VerseTextEdit = QtGui.QTextEdit(self.layoutWidget) self.VerseTextEdit.setAcceptRichText(False) - self.VerseTextEdit.setObjectName("VerseTextEdit") + self.VerseTextEdit.setObjectName(u'VerseTextEdit') self.verticalLayout_3.addWidget(self.VerseTextEdit) self.horizontalLayout_2 = QtGui.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout_2.setObjectName(u'horizontalLayout_2') self.addBridge = QtGui.QPushButton(self.layoutWidget) - self.addBridge.setObjectName("addBridge") + self.addBridge.setObjectName(u'addBridge') self.horizontalLayout_2.addWidget(self.addBridge) self.addVerse = QtGui.QPushButton(self.layoutWidget) - self.addVerse.setObjectName("addVerse") + self.addVerse.setObjectName(u'addVerse') self.horizontalLayout_2.addWidget(self.addVerse) self.addChorus = QtGui.QPushButton(self.layoutWidget) - self.addChorus.setObjectName("addChorus") + self.addChorus.setObjectName(u'addChorus') self.horizontalLayout_2.addWidget(self.addChorus) self.verticalLayout_3.addLayout(self.horizontalLayout_2) self.horizontalLayout_3 = QtGui.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.horizontalLayout_3.setObjectName(u'horizontalLayout_3') self.addPreChorus = QtGui.QPushButton(self.layoutWidget) - self.addPreChorus.setObjectName("addPreChorus") + self.addPreChorus.setObjectName(u'addPreChorus') self.horizontalLayout_3.addWidget(self.addPreChorus) self.addIntro = QtGui.QPushButton(self.layoutWidget) - self.addIntro.setObjectName("addIntro") + self.addIntro.setObjectName(u'addIntro') self.horizontalLayout_3.addWidget(self.addIntro) self.addOther = QtGui.QPushButton(self.layoutWidget) - self.addOther.setObjectName("addOther") + self.addOther.setObjectName(u'addOther') self.horizontalLayout_3.addWidget(self.addOther) self.addEnding = QtGui.QPushButton(self.layoutWidget) - self.addEnding.setObjectName("addEnding") + self.addEnding.setObjectName(u'addEnding') self.horizontalLayout_3.addWidget(self.addEnding) self.verticalLayout_3.addLayout(self.horizontalLayout_3) self.ButtonBox = QtGui.QDialogButtonBox(self.layoutWidget) self.ButtonBox.setOrientation(QtCore.Qt.Horizontal) self.ButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) - self.ButtonBox.setObjectName("ButtonBox") + self.ButtonBox.setObjectName(u'ButtonBox') self.verticalLayout_3.addWidget(self.ButtonBox) self.retranslateUi(EditVerseDialog) - QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL("accepted()"), EditVerseDialog.accept) - QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL("rejected()"), EditVerseDialog.reject) + QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL(u'accepted()'), EditVerseDialog.accept) + QtCore.QObject.connect(self.ButtonBox, QtCore.SIGNAL(u'rejected()'), EditVerseDialog.reject) QtCore.QMetaObject.connectSlotsByName(EditVerseDialog) def retranslateUi(self, EditVerseDialog): - EditVerseDialog.setWindowTitle(QtGui.QApplication.translate("EditVerseDialog", "Edit Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseTypeLabel.setText(QtGui.QApplication.translate("EditVerseDialog", "Verse Type", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(0, QtGui.QApplication.translate("EditVerseDialog", "Intro", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(1, QtGui.QApplication.translate("EditVerseDialog", "Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(2, QtGui.QApplication.translate("EditVerseDialog", "Pre-Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(3, QtGui.QApplication.translate("EditVerseDialog", "Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(4, QtGui.QApplication.translate("EditVerseDialog", "Bridge", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(5, QtGui.QApplication.translate("EditVerseDialog", "Ending", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseListComboBox.setItemText(6, QtGui.QApplication.translate("EditVerseDialog", "Other", None, QtGui.QApplication.UnicodeUTF8)) - self.VerseNumberLabel.setText(QtGui.QApplication.translate("EditVerseDialog", "Number", None, QtGui.QApplication.UnicodeUTF8)) - self.addBridge.setText(QtGui.QApplication.translate("EditVerseDialog", "Bridge", None, QtGui.QApplication.UnicodeUTF8)) - self.addVerse.setText(QtGui.QApplication.translate("EditVerseDialog", "Verse", None, QtGui.QApplication.UnicodeUTF8)) - self.addChorus.setText(QtGui.QApplication.translate("EditVerseDialog", "Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.addPreChorus.setText(QtGui.QApplication.translate("EditVerseDialog", "Pre-Chorus", None, QtGui.QApplication.UnicodeUTF8)) - self.addIntro.setText(QtGui.QApplication.translate("EditVerseDialog", "Intro", None, QtGui.QApplication.UnicodeUTF8)) - self.addOther.setText(QtGui.QApplication.translate("EditVerseDialog", "Other", None, QtGui.QApplication.UnicodeUTF8)) - self.addEnding.setText(QtGui.QApplication.translate("EditVerseDialog", "Ending", None, QtGui.QApplication.UnicodeUTF8)) + EditVerseDialog.setWindowTitle(self.trUtf8('Edit Verse')) + self.VerseTypeLabel.setText(self.trUtf8('Verse Type')) + self.VerseListComboBox.setItemText(0, self.trUtf8('Intro')) + self.VerseListComboBox.setItemText(1, self.trUtf8('Verse')) + self.VerseListComboBox.setItemText(2, self.trUtf8('Pre-Chorus')) + self.VerseListComboBox.setItemText(3, self.trUtf8('Chorus')) + self.VerseListComboBox.setItemText(4, self.trUtf8('Bridge')) + self.VerseListComboBox.setItemText(5, self.trUtf8('Ending')) + self.VerseListComboBox.setItemText(6, self.trUtf8('Other')) + self.VerseNumberLabel.setText(self.trUtf8('Number')) + self.addBridge.setText(self.trUtf8('Bridge')) + self.addVerse.setText(self.trUtf8('Verse')) + self.addChorus.setText(self.trUtf8('Chorus')) + self.addPreChorus.setText(self.trUtf8('Pre-Chorus')) + self.addIntro.setText(self.trUtf8('Intro')) + self.addOther.setText(self.trUtf8('Other')) + self.addEnding.setText(self.trUtf8('Ending')) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 148fd164a..99dfd6d69 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -77,6 +77,8 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): def setVerse(self, text, verseCount=0, single=False, tag=u'Verse:1'): posVerse = 0 posSub = 0 + if len(text) == 0 and not single: + text = u'---[Verse:1]---\n' if single: id = tag.split(u':') posVerse = self.VerseListComboBox.findText(id[0], QtCore.Qt.MatchExactly) @@ -112,6 +114,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.VerseTextEdit.setPlainText(text) self.VerseTextEdit.setFocus(QtCore.Qt.OtherFocusReason) self.onVerseComboChanged(0) + self.VerseTextEdit.moveCursor(QtGui.QTextCursor.Down) def getVerse(self): return self.VerseTextEdit.toPlainText(), \ @@ -119,11 +122,14 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): unicode(self.SubVerseListComboBox.currentText()) def getVerseAll(self): - return self.VerseTextEdit.toPlainText() + text = self.VerseTextEdit.toPlainText() + if not text.startsWith(u'---['): + text = u'---[Verse:1]---\n%s' % text + return text def onVerseComboChanged(self, id): if unicode(self.VerseListComboBox.currentText()) == u'Verse': self.SubVerseListComboBox.setEnabled(True) else: self.SubVerseListComboBox.setEnabled(False) - self.SubVerseListComboBox.setCurrentIndex(0) \ No newline at end of file + self.SubVerseListComboBox.setCurrentIndex(0) diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py index 5730e8c56..643676ff6 100644 --- a/openlp/plugins/songs/forms/songmaintenancedialog.py +++ b/openlp/plugins/songs/forms/songmaintenancedialog.py @@ -51,10 +51,10 @@ class Ui_SongMaintenanceDialog(object): self.TypeListWidget.sizePolicy().hasHeightForWidth()) self.TypeListWidget.setSizePolicy(sizePolicy) self.TypeListWidget.setViewMode(QtGui.QListView.IconMode) - self.TypeListWidget.setIconSize(QtCore.QSize(112, 100)); - self.TypeListWidget.setMovement(QtGui.QListView.Static); - self.TypeListWidget.setMaximumWidth(118); - self.TypeListWidget.setSpacing(0); + self.TypeListWidget.setIconSize(QtCore.QSize(112, 100)) + self.TypeListWidget.setMovement(QtGui.QListView.Static) + self.TypeListWidget.setMaximumWidth(118) + self.TypeListWidget.setSpacing(0) self.TypeListWidget.setSortingEnabled(False) self.TypeListWidget.setUniformItemSizes(True) self.TypeListWidget.setObjectName(u'TypeListWidget') diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 4d1d39cce..d8d6bb5b2 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -139,7 +139,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your author!'), + self.trUtf8('Couldn\'t add your author.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicAddButtonClick(self): @@ -150,7 +150,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your topic!'), + self.trUtf8('Couldn\'t add your topic.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookAddButtonClick(self): @@ -162,7 +162,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t add your book!'), + self.trUtf8('Couldn\'t add your book.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorEditButtonClick(self): @@ -182,7 +182,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your author!'), + self.trUtf8('Couldn\'t save your author.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onTopicEditButtonClick(self): @@ -197,7 +197,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your topic!'), + self.trUtf8('Couldn\'t save your topic.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onBookEditButtonClick(self): @@ -214,7 +214,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): else: QtGui.QMessageBox.critical( self, self.trUtf8('Error'), - self.trUtf8('Couldn\'t save your book!'), + self.trUtf8('Couldn\'t save your book.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def onAuthorDeleteButtonClick(self): @@ -227,7 +227,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Author'), self.trUtf8('Are you sure you want to delete the selected author?'), self.trUtf8('This author can\'t be deleted, they are currently ' - 'assigned to at least one song!'), + 'assigned to at least one song.'), self.trUtf8('No author selected!')) def onTopicDeleteButtonClick(self): @@ -240,7 +240,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Topic'), self.trUtf8('Are you sure you want to delete the selected topic?'), self.trUtf8('This topic can\'t be deleted, it is currently ' - 'assigned to at least one song!'), + 'assigned to at least one song.'), self.trUtf8('No topic selected!')) def onBookDeleteButtonClick(self): @@ -253,5 +253,5 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.trUtf8('Delete Book'), self.trUtf8('Are you sure you want to delete the selected book?'), self.trUtf8('This book can\'t be deleted, it is currently ' - 'assigned to at least one song!'), - self.trUtf8('No book selected!')) \ No newline at end of file + 'assigned to at least one song.'), + self.trUtf8('No book selected!')) diff --git a/openlp/plugins/songs/lib/manager.py b/openlp/plugins/songs/lib/manager.py index ceb596942..0e662dcbc 100644 --- a/openlp/plugins/songs/lib/manager.py +++ b/openlp/plugins/songs/lib/manager.py @@ -28,14 +28,13 @@ import logging from openlp.plugins.songs.lib.models import init_models, metadata, Song, \ Author, Topic, Book +log = logging.getLogger(__name__) + class SongManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'SongManager') log.info(u'Song manager loaded') def __init__(self, config): @@ -238,3 +237,6 @@ class SongManager(): self.session.rollback() log.exception(u'Could not delete book from song database') return False + + def get_songs_for_theme(self, theme): + return self.session.query(Song).filter(Song.theme_name == theme).all() diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 9aef321e5..cd440e54f 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -31,6 +31,8 @@ from openlp.core.lib import MediaManagerItem, SongXMLParser, \ BaseListWithDnD, Receiver, str_to_bool from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm +log = logging.getLogger(__name__) + class SongListView(BaseListWithDnD): def __init__(self, parent=None): self.PluginName = u'Songs' @@ -40,8 +42,6 @@ class SongMediaItem(MediaManagerItem): """ This is the custom media manager item for Songs. """ - global log - log = logging.getLogger(u'SongMediaItem') log.info(u'Song Media Item loaded') def __init__(self, parent, icon, title): @@ -185,8 +185,13 @@ class SongMediaItem(MediaManagerItem): if author_list != u'': author_list = author_list + u', ' author_list = author_list + author.display_name - song_detail = unicode(self.trUtf8('%s (%s)' % \ - (unicode(song.title), unicode(author_list)))) + if not isinstance(author_list, unicode): + author_list = unicode(author_list, u'utf8') + if isinstance(song.title, unicode): + song_title = song.title + else: + song_title = unicode(song.title, u'utf8') + song_detail = u'%s (%s)' % (song_title, author_list) song_name = QtGui.QListWidgetItem(song_detail) song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) self.ListView.addItem(song_name) @@ -285,6 +290,7 @@ class SongMediaItem(MediaManagerItem): item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] else: item_id = self.remoteSong + service_item.autoPreviewAllowed = True song = self.parent.songmanager.get_song(item_id) service_item.theme = song.theme_name service_item.edit_enabled = True @@ -305,7 +311,7 @@ class SongMediaItem(MediaManagerItem): for verse in verseList: if verse[1]: if verse[0][u'type'] == "Verse": - if verse[0][u'label'][0] == order[1:]: + if verse[0][u'label'] == order[1:]: verseTag = u'%s:%s' % \ (verse[0][u'type'], verse[0][u'label']) service_item.add_from_text\ @@ -339,4 +345,4 @@ class SongMediaItem(MediaManagerItem): service_item.audit = [ song.title, author_audit, song.copyright, song.ccli_number ] - return True \ No newline at end of file + return True diff --git a/openlp/plugins/songs/lib/songxml.py b/openlp/plugins/songs/lib/songxml.py index 7d356d848..f9cef7fce 100644 --- a/openlp/plugins/songs/lib/songxml.py +++ b/openlp/plugins/songs/lib/songxml.py @@ -153,12 +153,12 @@ class _OpenSong(XmlRootClass): tmpVerse = [] finalLyrics = [] tag = "" - for l in lyrics: - line = l.rstrip() + for lyric in lyrics: + line = lyric.rstrip() if not line.startswith(u'.'): # drop all chords tmpVerse.append(line) - if len(line) > 0: + if line: if line.startswith(u'['): tag = line else: @@ -298,9 +298,9 @@ class Song(object): chars are: .,:;!?&%#/\@`$'|"^~*- """ punctuation = ".,:;!?&%#'\"/\\@`$|^~*-" - s = title - for c in punctuation: - s = s.replace(c, '') + string = title + for char in punctuation: + string = string.replace(char, '') return s def set_title(self, title): @@ -582,17 +582,16 @@ class Song(object): self.slideList = [] tmpSlide = [] metContent = False - for l in self.lyrics: - if len(l) > 0: + for lyric in self.lyrics: + if lyric: metContent = True - tmpSlide.append(l) + tmpSlide.append(lyric) else: if metContent: metContent = False self.slideList.append(tmpSlide) tmpSlide = [] - # - if len(tmpSlide) > 0: + if tmpSlide: self.slideList.append(tmpSlide) def get_number_of_slides(self): diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index d22cf4fd4..9ebaa99b2 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -27,11 +27,13 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import Plugin, build_icon +from openlp.core.lib import Plugin, build_icon, PluginStatus from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \ OpenSongImportForm, OpenLPExportForm +log = logging.getLogger(__name__) + class SongsPlugin(Plugin): """ This is the number 1 plugin, if importance were placed on any @@ -40,16 +42,13 @@ class SongsPlugin(Plugin): specified. Authors, topics and song books can be assigned to songs as well. """ - - global log - log = logging.getLogger(u'SongsPlugin') log.info(u'Song Plugin loaded') def __init__(self, plugin_helpers): """ Create and set up the Songs plugin. """ - Plugin.__init__(self, u'Songs', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'Songs', u'1.9.1', plugin_helpers) self.weight = -10 self.songmanager = SongManager(self.config) self.openlp_import_form = OpenLPImportForm() @@ -57,6 +56,7 @@ class SongsPlugin(Plugin): self.openlp_export_form = OpenLPExportForm() self.opensong_export_form = OpenSongExportForm() self.icon = build_icon(u':/media/media_song.png') + self.status = PluginStatus.Active def get_settings_tab(self): return SongsTab(self.name) @@ -178,4 +178,9 @@ class SongsPlugin(Plugin): def about(self): about_text = self.trUtf8('<b>Song Plugin</b> <br>This plugin allows ' 'Songs to be managed and displayed.<br>') - return about_text \ No newline at end of file + return about_text + + def can_delete_theme(self, theme): + if len(self.songmanager.get_songs_for_theme(theme)) == 0: + return True + return False diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py index 03c9f63ec..e9a9a8603 100644 --- a/openlp/plugins/songusage/forms/songusagedeletedialog.py +++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py @@ -57,4 +57,4 @@ class Ui_SongUsageDeleteDialog(object): QtCore.QMetaObject.connectSlotsByName(AuditDeleteDialog) def retranslateUi(self, AuditDeleteDialog): - AuditDeleteDialog.setWindowTitle(self.trUtf8('Audit Delete')) + AuditDeleteDialog.setWindowTitle(self.trUtf8('Song Usage Delete')) diff --git a/openlp/plugins/songusage/forms/songusagedeleteform.py b/openlp/plugins/songusage/forms/songusagedeleteform.py index b20f13c6b..56eb1954a 100644 --- a/openlp/plugins/songusage/forms/songusagedeleteform.py +++ b/openlp/plugins/songusage/forms/songusagedeleteform.py @@ -23,8 +23,6 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from datetime import date - from PyQt4 import QtGui from songusagedeletedialog import Ui_SongUsageDeleteDialog @@ -33,11 +31,11 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog): """ Class documentation goes here. """ - def __init__(self, auditmanager, parent=None): + def __init__(self, songusagemanager, parent=None): """ Constructor """ - self.auditmanager = auditmanager + self.songusagemanager = songusagemanager QtGui.QDialog.__init__(self, parent) self.setupUi(self) @@ -50,7 +48,6 @@ class SongUsageDeleteForm(QtGui.QDialog, Ui_SongUsageDeleteDialog): QtGui.QMessageBox.Cancel), QtGui.QMessageBox.Cancel) if ret == QtGui.QMessageBox.Ok: - qDeleteDate = self.DeleteCalendar.selectedDate() - deleteDate = date(qDeleteDate.year(), qDeleteDate.month(), qDeleteDate.day()) - self.auditmanager.delete_to_date(deleteDate) - self.close() \ No newline at end of file + deleteDate = self.DeleteCalendar.selectedDate().toPyDate() + self.songusagemanager.delete_to_date(deleteDate) + self.close() diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index bfb2efcaf..411187086 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -28,14 +28,28 @@ from PyQt4 import QtCore, QtGui class Ui_SongUsageDetailDialog(object): def setupUi(self, AuditDetailDialog): AuditDetailDialog.setObjectName(u'AuditDetailDialog') - AuditDetailDialog.resize(593, 501) - self.buttonBox = QtGui.QDialogButtonBox(AuditDetailDialog) - self.buttonBox.setGeometry(QtCore.QRect(420, 470, 170, 25)) - self.buttonBox.setStandardButtons( - QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName(u'buttonBox') - self.FileGroupBox = QtGui.QGroupBox(AuditDetailDialog) - self.FileGroupBox.setGeometry(QtCore.QRect(10, 370, 571, 70)) + AuditDetailDialog.resize(609, 413) + self.verticalLayout = QtGui.QVBoxLayout(AuditDetailDialog) + self.verticalLayout.setObjectName(u'verticalLayout') + self.DateRangeGroupBox = QtGui.QGroupBox(AuditDetailDialog) + self.DateRangeGroupBox.setObjectName(u'DateRangeGroupBox') + self.verticalLayout_2 = QtGui.QVBoxLayout(self.DateRangeGroupBox) + self.verticalLayout_2.setObjectName(u'verticalLayout_2') + self.DateHorizontalLayout = QtGui.QHBoxLayout() + self.DateHorizontalLayout.setObjectName(u'DateHorizontalLayout') + self.FromDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) + self.FromDate.setObjectName(u'FromDate') + self.DateHorizontalLayout.addWidget(self.FromDate) + self.ToLabel = QtGui.QLabel(self.DateRangeGroupBox) + self.ToLabel.setScaledContents(False) + self.ToLabel.setAlignment(QtCore.Qt.AlignCenter) + self.ToLabel.setObjectName(u'ToLabel') + self.DateHorizontalLayout.addWidget(self.ToLabel) + self.ToDate = QtGui.QCalendarWidget(self.DateRangeGroupBox) + self.ToDate.setObjectName(u'ToDate') + self.DateHorizontalLayout.addWidget(self.ToDate) + self.verticalLayout_2.addLayout(self.DateHorizontalLayout) + self.FileGroupBox = QtGui.QGroupBox(self.DateRangeGroupBox) self.FileGroupBox.setObjectName(u'FileGroupBox') self.verticalLayout_4 = QtGui.QVBoxLayout(self.FileGroupBox) self.verticalLayout_4.setObjectName(u'verticalLayout_4') @@ -46,152 +60,32 @@ class Ui_SongUsageDetailDialog(object): self.horizontalLayout.addWidget(self.FileLineEdit) self.SaveFilePushButton = QtGui.QPushButton(self.FileGroupBox) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), - QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap(u':/exports/export_load.png'), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.SaveFilePushButton.setIcon(icon) self.SaveFilePushButton.setObjectName(u'SaveFilePushButton') self.horizontalLayout.addWidget(self.SaveFilePushButton) self.verticalLayout_4.addLayout(self.horizontalLayout) - self.layoutWidget = QtGui.QWidget(AuditDetailDialog) - self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 561, 361)) - self.layoutWidget.setObjectName(u'layoutWidget') - self.verticalLayout_3 = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout_3.setObjectName(u'verticalLayout_3') - self.ReportTypeGroup = QtGui.QGroupBox(self.layoutWidget) - self.ReportTypeGroup.setObjectName(u'ReportTypeGroup') - self.layoutWidget1 = QtGui.QWidget(self.ReportTypeGroup) - self.layoutWidget1.setGeometry(QtCore.QRect(50, 40, 481, 23)) - self.layoutWidget1.setObjectName(u'layoutWidget1') - self.ReportHorizontalLayout = QtGui.QHBoxLayout(self.layoutWidget1) - self.ReportHorizontalLayout.setObjectName(u'ReportHorizontalLayout') - self.SummaryReport = QtGui.QRadioButton(self.layoutWidget1) - self.SummaryReport.setObjectName(u'SummaryReport') - self.ReportHorizontalLayout.addWidget(self.SummaryReport) - self.DetailedReport = QtGui.QRadioButton(self.layoutWidget1) - self.DetailedReport.setChecked(True) - self.DetailedReport.setObjectName(u'DetailedReport') - self.ReportHorizontalLayout.addWidget(self.DetailedReport) - self.verticalLayout_3.addWidget(self.ReportTypeGroup) - self.DateRangeGroupBox = QtGui.QGroupBox(self.layoutWidget) - self.DateRangeGroupBox.setObjectName(u'DateRangeGroupBox') - self.verticalLayout_2 = QtGui.QVBoxLayout(self.DateRangeGroupBox) - self.verticalLayout_2.setObjectName(u'verticalLayout_2') - self.DateHorizontalLayout = QtGui.QHBoxLayout() - self.DateHorizontalLayout.setObjectName(u'DateHorizontalLayout') - self.FromDateEdit = QtGui.QDateEdit(self.DateRangeGroupBox) - self.FromDateEdit.setCalendarPopup(True) - self.FromDateEdit.setObjectName(u'FromDateEdit') - self.DateHorizontalLayout.addWidget(self.FromDateEdit) - self.To = QtGui.QLabel(self.DateRangeGroupBox) - self.To.setObjectName(u'To') - self.DateHorizontalLayout.addWidget(self.To) - self.ToDateEdit = QtGui.QDateEdit(self.DateRangeGroupBox) - self.ToDateEdit.setCalendarPopup(True) - self.ToDateEdit.setObjectName(u'ToDateEdit') - self.DateHorizontalLayout.addWidget(self.ToDateEdit) - self.verticalLayout_2.addLayout(self.DateHorizontalLayout) - self.verticalLayout_3.addWidget(self.DateRangeGroupBox) - self.TimePeriodGroupBox = QtGui.QGroupBox(self.layoutWidget) - self.TimePeriodGroupBox.setObjectName(u'TimePeriodGroupBox') - self.verticalLayout = QtGui.QVBoxLayout(self.TimePeriodGroupBox) - self.verticalLayout.setObjectName(u'verticalLayout') - self.FirstHorizontalLayout = QtGui.QHBoxLayout() - self.FirstHorizontalLayout.setObjectName(u'FirstHorizontalLayout') - self.FirstCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.FirstCheckBox.setChecked(True) - self.FirstCheckBox.setObjectName(u'FirstCheckBox') - self.FirstHorizontalLayout.addWidget(self.FirstCheckBox) - self.FirstFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.FirstFromTimeEdit.setTime(QtCore.QTime(9, 0, 0)) - self.FirstFromTimeEdit.setObjectName(u'FirstFromTimeEdit') - self.FirstHorizontalLayout.addWidget(self.FirstFromTimeEdit) - self.FirstTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.FirstTo.setObjectName(u'FirstTo') - self.FirstHorizontalLayout.addWidget(self.FirstTo) - self.FirstToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.FirstToTimeEdit.setCalendarPopup(True) - self.FirstToTimeEdit.setTime(QtCore.QTime(10, 0, 0)) - self.FirstToTimeEdit.setObjectName(u'FirstToTimeEdit') - self.FirstHorizontalLayout.addWidget(self.FirstToTimeEdit) - self.verticalLayout.addLayout(self.FirstHorizontalLayout) - self.SecondHorizontalLayout = QtGui.QHBoxLayout() - self.SecondHorizontalLayout.setObjectName(u'SecondHorizontalLayout') - self.SecondCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.SecondCheckBox.setChecked(True) - self.SecondCheckBox.setObjectName(u'SecondCheckBox') - self.SecondHorizontalLayout.addWidget(self.SecondCheckBox) - self.SecondFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.SecondFromTimeEdit.setTime(QtCore.QTime(10, 45, 0)) - self.SecondFromTimeEdit.setObjectName(u'SecondFromTimeEdit') - self.SecondHorizontalLayout.addWidget(self.SecondFromTimeEdit) - self.SecondTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.SecondTo.setObjectName(u'SecondTo') - self.SecondHorizontalLayout.addWidget(self.SecondTo) - self.SecondToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.SecondToTimeEdit.setObjectName(u'SecondToTimeEdit') - self.SecondHorizontalLayout.addWidget(self.SecondToTimeEdit) - self.verticalLayout.addLayout(self.SecondHorizontalLayout) - self.ThirdHorizontalLayout = QtGui.QHBoxLayout() - self.ThirdHorizontalLayout.setObjectName(u'ThirdHorizontalLayout') - self.ThirdCheckBox = QtGui.QCheckBox(self.TimePeriodGroupBox) - self.ThirdCheckBox.setChecked(True) - self.ThirdCheckBox.setObjectName(u'ThirdCheckBox') - self.ThirdHorizontalLayout.addWidget(self.ThirdCheckBox) - self.ThirdFromTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.ThirdFromTimeEdit.setTime(QtCore.QTime(18, 30, 0)) - self.ThirdFromTimeEdit.setObjectName(u'ThirdFromTimeEdit') - self.ThirdHorizontalLayout.addWidget(self.ThirdFromTimeEdit) - self.ThirdTo = QtGui.QLabel(self.TimePeriodGroupBox) - self.ThirdTo.setObjectName(u'ThirdTo') - self.ThirdHorizontalLayout.addWidget(self.ThirdTo) - self.ThirdToTimeEdit = QtGui.QTimeEdit(self.TimePeriodGroupBox) - self.ThirdToTimeEdit.setTime(QtCore.QTime(19, 30, 0)) - self.ThirdToTimeEdit.setObjectName(u'ThirdToTimeEdit') - self.ThirdHorizontalLayout.addWidget(self.ThirdToTimeEdit) - self.verticalLayout.addLayout(self.ThirdHorizontalLayout) - self.verticalLayout_3.addWidget(self.TimePeriodGroupBox) + self.verticalLayout_2.addWidget(self.FileGroupBox) + self.verticalLayout.addWidget(self.DateRangeGroupBox) + self.buttonBox = QtGui.QDialogButtonBox(AuditDetailDialog) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'buttonBox') + self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(AuditDetailDialog) - QtCore.QObject.connect( - self.buttonBox, QtCore.SIGNAL(u'accepted()'), - AuditDetailDialog.accept) - QtCore.QObject.connect( - self.buttonBox, QtCore.SIGNAL(u'rejected()'), - AuditDetailDialog.close) - QtCore.QObject.connect( - self.FirstCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeFirstService) - QtCore.QObject.connect( - self.SecondCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeSecondService) - QtCore.QObject.connect( - self.ThirdCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), - AuditDetailDialog.changeThirdService) - QtCore.QObject.connect( - self.SaveFilePushButton, QtCore.SIGNAL(u'pressed()'), - AuditDetailDialog.defineOutputLocation) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'accepted()'), + AuditDetailDialog.accept) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), + AuditDetailDialog.close) + QtCore.QObject.connect(self.SaveFilePushButton, + QtCore.SIGNAL(u'pressed()'), + AuditDetailDialog.defineOutputLocation) QtCore.QMetaObject.connectSlotsByName(AuditDetailDialog) def retranslateUi(self, AuditDetailDialog): - AuditDetailDialog.setWindowTitle(self.trUtf8('Audit Detail Extraction')) - self.FileGroupBox.setTitle(self.trUtf8('Report Location')) - self.ReportTypeGroup.setTitle(self.trUtf8('Report Type')) - self.SummaryReport.setText(self.trUtf8('Summary')) - self.DetailedReport.setText(self.trUtf8('Detailed')) + AuditDetailDialog.setWindowTitle(self.trUtf8('Song Usage Extraction')) self.DateRangeGroupBox.setTitle(self.trUtf8('Select Date Range')) - self.FromDateEdit.setDisplayFormat(self.trUtf8('dd/MM/yyyy')) - self.To.setText(self.trUtf8('to')) - self.ToDateEdit.setDisplayFormat(self.trUtf8('dd/MM/yyyy')) - self.TimePeriodGroupBox.setTitle(self.trUtf8('Select Time Periods')) - self.FirstCheckBox.setText(self.trUtf8('First Service')) - self.FirstFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.FirstTo.setText(self.trUtf8('to')) - self.FirstToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.SecondCheckBox.setText(self.trUtf8('Second Service')) - self.SecondFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.SecondTo.setText(self.trUtf8('to')) - self.SecondToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.ThirdCheckBox.setText(self.trUtf8('Third Service')) - self.ThirdFromTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) - self.ThirdTo.setText(self.trUtf8('to')) - self.ThirdToTimeEdit.setDisplayFormat(self.trUtf8('hh:mm AP')) \ No newline at end of file + self.ToLabel.setText(self.trUtf8('to')) + self.FileGroupBox.setTitle(self.trUtf8('Report Location')) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index 93b6d2e98..be548ac35 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -25,10 +25,14 @@ import os from PyQt4 import QtCore, QtGui +import logging from songusagedetaildialog import Ui_SongUsageDetailDialog +log = logging.getLogger(__name__) + class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): + log.info(u'SongUsage Detail Form loaded') """ Class documentation goes here. """ @@ -41,33 +45,14 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): self.setupUi(self) def initialise(self): - self.FirstCheckBox.setCheckState( - int(self.parent.config.get_config(u'first service', QtCore.Qt.Checked))) - self.SecondCheckBox.setCheckState( - int(self.parent.config.get_config(u'second service', QtCore.Qt.Checked))) - self.ThirdCheckBox.setCheckState( - int(self.parent.config.get_config(u'third service', QtCore.Qt.Checked))) year = QtCore.QDate().currentDate().year() if QtCore.QDate().currentDate().month() < 9: year -= 1 toDate = QtCore.QDate(year, 8, 31) fromDate = QtCore.QDate(year - 1, 9, 1) - self.FromDateEdit.setDate(fromDate) - self.ToDateEdit.setDate(toDate) + self.FromDate.setSelectedDate(fromDate) + self.ToDate.setSelectedDate(toDate) self.FileLineEdit.setText(self.parent.config.get_last_dir(1)) - self.resetWindow() - - def changeFirstService(self, value): - self.parent.config.set_config(u'first service', value) - self.resetWindow() - - def changeSecondService(self, value): - self.parent.config.set_config(u'second service', value) - self.resetWindow() - - def changeThirdService(self, value): - self.parent.config.set_config(u'third service', value) - self.resetWindow() def defineOutputLocation(self): path = QtGui.QFileDialog.getExistingDirectory(self, @@ -78,47 +63,22 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): self.parent.config.set_last_dir(path, 1) self.FileLineEdit.setText(path) - def resetWindow(self): - if self.FirstCheckBox.checkState() == QtCore.Qt.Unchecked: - self.FirstFromTimeEdit.setEnabled(False) - self.FirstToTimeEdit.setEnabled(False) - else: - self.FirstFromTimeEdit.setEnabled(True) - self.FirstToTimeEdit.setEnabled(True) - if self.SecondCheckBox.checkState() == QtCore.Qt.Unchecked: - self.SecondFromTimeEdit.setEnabled(False) - self.SecondToTimeEdit.setEnabled(False) - else: - self.SecondFromTimeEdit.setEnabled(True) - self.SecondToTimeEdit.setEnabled(True) - if self.ThirdCheckBox.checkState() == QtCore.Qt.Unchecked: - self.ThirdFromTimeEdit.setEnabled(False) - self.ThirdToTimeEdit.setEnabled(False) - else: - self.ThirdFromTimeEdit.setEnabled(True) - self.ThirdToTimeEdit.setEnabled(True) - def accept(self): - if self.DetailedReport.isChecked(): - self.detailedReport() - else: - self.summaryReport() - self.close() - - def detailedReport(self): - print "detailed" - filename = u'audit_det_%s_%s.txt' % \ - (self.FromDateEdit.date().toString(u'ddMMyyyy'), - self.ToDateEdit.date().toString(u'ddMMyyyy')) - audits = self.parent.auditmanager.get_all_audits() + log.debug(u'Detailed report generated') + filename = u'usage_detail_%s_%s.txt' % \ + (self.FromDate.selectedDate().toString(u'ddMMyyyy'), + self.ToDate.selectedDate().toString(u'ddMMyyyy')) + usage = self.parent.songusagemanager.get_all_songusage(\ + self.FromDate.selectedDate(), \ + self.ToDate.selectedDate()) outname = os.path.join(unicode(self.FileLineEdit.text()), filename) file = None try: file = open(outname, u'w') - for audit in audits: + for instance in usage: record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % \ - (audit.auditdate,audit.audittime, audit.title, - audit.copyright, audit.ccl_number , audit.authors) + (instance.usagedate,instance.usagetime, instance.title, + instance.copyright, instance.ccl_number , instance.authors) file.write(record) except: log.exception(u'Failed to write out audit records') @@ -126,9 +86,3 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog): if file: file.close() - def summaryReport(self): - print "summary" - filename = u'audit_sum_%s_%s.txt' % \ - (self.FromDateEdit.date().toString(u'ddMMyyyy'), - self.ToDateEdit.date().toString(u'ddMMyyyy')) - print filename \ No newline at end of file diff --git a/openlp/plugins/songusage/lib/manager.py b/openlp/plugins/songusage/lib/manager.py index cf286d37f..d9f7feb6f 100644 --- a/openlp/plugins/songusage/lib/manager.py +++ b/openlp/plugins/songusage/lib/manager.py @@ -27,14 +27,13 @@ import logging from openlp.plugins.songusage.lib.models import init_models, metadata, SongUsageItem +log = logging.getLogger(__name__) + class SongUsageManager(): """ The Song Manager provides a central location for all database code. This class takes care of connecting to the database and running all the queries. """ - - global log - log = logging.getLogger(u'SongUsageManager') log.info(u'SongUsage manager loaded') def __init__(self, config): @@ -60,12 +59,14 @@ class SongUsageManager(): log.debug(u'SongUsage Initialised') - def get_all_songusage(self): + def get_all_songusage(self, start_date, end_date): """ Returns the details of SongUsage """ - return self.session.query(SongUsageItem).\ - order_by(SongUsageItem.usagedate, SongUsageItem.usagetime ).all() + return self.session.query(SongUsageItem) \ + .filter(SongUsageItem.usagedate >= start_date.toPyDate()) \ + .filter(SongUsageItem.usagedate < end_date.toPyDate()) \ + .order_by(SongUsageItem.usagedate, SongUsageItem.usagetime ).all() def insert_songusage(self, songusageitem): """ @@ -94,7 +95,7 @@ class SongUsageManager(): """ Delete a SongUsage record """ - if id !=0: + if id != 0: songusageitem = self.get_songusage(id) try: self.session.delete(songusageitem) @@ -133,4 +134,4 @@ class SongUsageManager(): except: self.session.rollback() log.exception(u'Failed to delete all Song Usage items to %s' % date) - return False \ No newline at end of file + return False diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index effc06657..d30bd4bea 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -33,13 +33,13 @@ from openlp.plugins.songusage.lib import SongUsageManager from openlp.plugins.songusage.forms import SongUsageDetailForm, SongUsageDeleteForm from openlp.plugins.songusage.lib.models import SongUsageItem +log = logging.getLogger(__name__) + class SongUsagePlugin(Plugin): - global log - log = logging.getLogger(u'SongUsagePlugin') log.info(u'SongUsage Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'SongUsage', u'1.9.0', plugin_helpers) + Plugin.__init__(self, u'SongUsage', u'1.9.1', plugin_helpers) self.weight = -4 self.icon = build_icon(u':/media/media_image.png') self.songusagemanager = None @@ -141,7 +141,7 @@ class SongUsagePlugin(Plugin): SongUsageitem.authors = u'' for author in SongUsageData[1]: SongUsageitem.authors += author + u' ' - self.songusagemanager.insert_SongUsage(SongUsageitem) + self.songusagemanager.insert_songusage(SongUsageitem) def onSongUsageDelete(self): self.SongUsagedeleteform.exec_() @@ -154,4 +154,4 @@ class SongUsagePlugin(Plugin): about_text = self.trUtf8('<b>SongUsage Plugin</b><br>This plugin ' 'records the use of songs and when they have been used during ' 'a live service') - return about_text \ No newline at end of file + return about_text diff --git a/openlpcnv.pyw b/openlpcnv.pyw index 877e74744..8c3a8bcf5 100755 --- a/openlpcnv.pyw +++ b/openlpcnv.pyw @@ -147,4 +147,4 @@ if __name__ == u'__main__': newdb = os.path.join(newpath, u'songs.sqlite') mig.convert_sqlite2_to_3(olddb, newdb) mig.process() - #mig.move_log_file() \ No newline at end of file + #mig.move_log_file() diff --git a/resources/.config/openlp/openlp.conf b/resources/.config/openlp/openlp.conf index 876bea5cf..a1501ff73 100644 --- a/resources/.config/openlp/openlp.conf +++ b/resources/.config/openlp/openlp.conf @@ -1,77 +1,73 @@ -[audit] -first service = 2 -db type = sqlite -audit active = False -second service = 2 -audit_status = 0 -data path = audit - [bibles] display new chapter = False display brackets = 0 -verse layout style = 0 -bible theme = 0 -search as type = True -bibles_status = 0 +dual bibles = False +db type = sqlite +bible theme = +verse layout style = 1 +status = 1 data path = bibles [media] -use mode layout = False -media_status = 1 - -[image] -loop delay = 5 +status = 1 [alerts] font color = #ffffff +background color = #660000 font face = Sans Serif timeout = 5 -background color = #660000 -[user interface] -display previewpanel = True -display thememanager = True -display servicemanager = True -display mediamanager = True +[remotes] +remote port = 4316 [presentations] -data path = presentations +status = 1 impress = 0 +data path = presentations +powerpoint = 0 +powerpoint viewer = 0 [custom] +status = 1 +display footer = True data path = custom db type = sqlite -custom_status = 0 [themes] +global theme = data path = themes -theme global theme = -theme global style = Global +theme level = 1 + +[images] +status = 1 +data path = images +loop delay = 5 + +[user interface] +theme manager = True +media manager = True +preview panel = True +service manager = True [servicemanager] data path = servicemanager -theme service theme = - -[remotes] -remotes_status = 1 -remote port = 4316 - -[images] -images_status = 1 [general] monitor = 0 +run environment = dev +ccli number = +blank warning = False show splash = True -application version test = 2009-10-14 -user name = -application version = 1.9.0-600 -warning = False +last version test = 2010-02-05 +songselect username = +save prompt = False +songselect password = auto open = False -password = -ccl number = XXX [songs] -songs_status = 0 +status = 1 +search as type = False +display songbar = True data path = songs db type = sqlite diff --git a/resources/forms/alertdialog.ui b/resources/forms/alertdialog.ui new file mode 100644 index 000000000..da56f3847 --- /dev/null +++ b/resources/forms/alertdialog.ui @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AlertForm</class> + <widget class="QWidget" name="AlertForm"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>430</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Alert Message</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/icon/openlp.org-icon-32.bmp</normaloff>:/icon/openlp.org-icon-32.bmp</iconset> + </property> + <layout class="QVBoxLayout" name="AlertFormLayout"> + <property name="spacing"> + <number>8</number> + </property> + <property name="margin"> + <number>8</number> + </property> + <item> + <widget class="QWidget" name="AlertEntryWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="AlertEntryLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Alert Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="AlertEntryEditItem"/> + </item> + <item> + <widget class="QListWidget" name="AlertListWidget"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="ButtonBoxWidgetSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>181</width> + <height>38</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="DisplayButton"> + <property name="text"> + <string>Display</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="CancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../images/openlp-2.qrc"/> + </resources> + <connections> + <connection> + <sender>CancelButton</sender> + <signal>clicked()</signal> + <receiver>AlertForm</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>66</y> + </hint> + <hint type="destinationlabel"> + <x>257</x> + <y>3</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/resources/forms/alerteditdialog.ui b/resources/forms/alerteditdialog.ui new file mode 100644 index 000000000..352e3d7b1 --- /dev/null +++ b/resources/forms/alerteditdialog.ui @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AlertEditDialog</class> + <widget class="QWidget" name="AlertEditDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Maintain Alerts</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>220</x> + <y>270</y> + <width>173</width> + <height>27</height> + </rect> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel</set> + </property> + </widget> + <widget class="QWidget" name="layoutWidget"> + <property name="geometry"> + <rect> + <x>20</x> + <y>10</y> + <width>361</width> + <height>251</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLineEdit" name="AlertLineEdit"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QListWidget" name="AlertListWidget"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="SaveButton"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="ClearButton"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="AddButton"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="EdirButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="DeleteButton"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/resources/forms/alertform.ui b/resources/forms/alertform.ui deleted file mode 100644 index 1caf4a356..000000000 --- a/resources/forms/alertform.ui +++ /dev/null @@ -1,133 +0,0 @@ -<ui version="4.0" > - <class>AlertForm</class> - <widget class="QWidget" name="AlertForm" > - <property name="geometry" > - <rect> - <x>0</x> - <y>0</y> - <width>370</width> - <height>105</height> - </rect> - </property> - <property name="windowTitle" > - <string>Alert Message</string> - </property> - <property name="windowIcon" > - <iconset resource="../images/openlp-2.qrc" > - <normaloff>:/icon/openlp.org-icon-32.bmp</normaloff>:/icon/openlp.org-icon-32.bmp</iconset> - </property> - <layout class="QVBoxLayout" name="AlertFormLayout" > - <property name="spacing" > - <number>8</number> - </property> - <property name="margin" > - <number>8</number> - </property> - <item> - <widget class="QWidget" native="1" name="AlertEntryWidget" > - <property name="sizePolicy" > - <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <widget class="QLabel" name="AlertEntryLabel" > - <property name="geometry" > - <rect> - <x>0</x> - <y>0</y> - <width>353</width> - <height>16</height> - </rect> - </property> - <property name="sizePolicy" > - <sizepolicy vsizetype="Fixed" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text" > - <string>Alert Text:</string> - </property> - </widget> - <widget class="QLineEdit" name="AlertEntryEditItem" > - <property name="geometry" > - <rect> - <x>0</x> - <y>20</y> - <width>353</width> - <height>21</height> - </rect> - </property> - </widget> - </widget> - </item> - <item> - <widget class="QWidget" native="1" name="ButtonBoxWidget" > - <property name="sizePolicy" > - <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QHBoxLayout" name="horizontalLayout" > - <property name="spacing" > - <number>8</number> - </property> - <property name="margin" > - <number>0</number> - </property> - <item> - <spacer name="ButtonBoxWidgetSpacer" > - <property name="orientation" > - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0" > - <size> - <width>267</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="DisplayButton" > - <property name="text" > - <string>Display</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="CancelButton" > - <property name="text" > - <string>Cancel</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../images/openlp-2.qrc" /> - </resources> - <connections> - <connection> - <sender>CancelButton</sender> - <signal>clicked()</signal> - <receiver>AlertForm</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel" > - <x>294</x> - <y>66</y> - </hint> - <hint type="destinationlabel" > - <x>257</x> - <y>3</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/resources/forms/auditdetaildialog.ui b/resources/forms/auditdetaildialog.ui deleted file mode 100644 index bafcfd535..000000000 --- a/resources/forms/auditdetaildialog.ui +++ /dev/null @@ -1,411 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>AuditDetailDialog</class> - <widget class="QWidget" name="AuditDetailDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>593</width> - <height>501</height> - </rect> - </property> - <property name="windowTitle"> - <string>Audit Detail Extraction</string> - </property> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="geometry"> - <rect> - <x>420</x> - <y>470</y> - <width>170</width> - <height>25</height> - </rect> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - <widget class="QGroupBox" name="FileGroupBox"> - <property name="geometry"> - <rect> - <x>10</x> - <y>370</y> - <width>571</width> - <height>70</height> - </rect> - </property> - <property name="title"> - <string>Report Location</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLineEdit" name="FileLineEdit"/> - </item> - <item> - <widget class="QPushButton" name="SaveFilePushButton"> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../images/openlp-2.qrc"> - <normaloff>:/exports/export_load.png</normaloff>:/exports/export_load.png</iconset> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <widget class="QWidget" name="layoutWidget"> - <property name="geometry"> - <rect> - <x>10</x> - <y>10</y> - <width>561</width> - <height>361</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QGroupBox" name="ReportTypeGroup"> - <property name="title"> - <string>Report Type</string> - </property> - <widget class="QWidget" name="layoutWidget"> - <property name="geometry"> - <rect> - <x>50</x> - <y>40</y> - <width>481</width> - <height>23</height> - </rect> - </property> - <layout class="QHBoxLayout" name="ReportHorizontalLayout"> - <item> - <widget class="QRadioButton" name="SummaryReport"> - <property name="text"> - <string>Summary</string> - </property> - </widget> - </item> - <item> - <widget class="QRadioButton" name="DetailedReport"> - <property name="text"> - <string>Detailed</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QGroupBox" name="DateRangeGroupBox"> - <property name="title"> - <string>Select Date Range</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="DateHorizontalLayout"> - <item> - <widget class="QDateEdit" name="FromDateEdit"> - <property name="displayFormat"> - <string>dd/MM/yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="To"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QDateEdit" name="ToDateEdit"> - <property name="displayFormat"> - <string>dd/MM/yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="TimePeriodGroupBox"> - <property name="title"> - <string>Select Time Periods</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="FirstHorizontalLayout"> - <item> - <widget class="QCheckBox" name="FirstCheckBox"> - <property name="text"> - <string>First Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="FirstFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>9</hour> - <minute>0</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="FirstTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="FirstToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - <property name="time"> - <time> - <hour>10</hour> - <minute>0</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="SecondHorizontalLayout"> - <item> - <widget class="QCheckBox" name="SecondCheckBox"> - <property name="text"> - <string>Second Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="SecondFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>10</hour> - <minute>45</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="SecondTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="SecondToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="ThirdHorizontalLayout"> - <item> - <widget class="QCheckBox" name="ThirdCheckBox"> - <property name="text"> - <string>Third Service</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="ThirdFromTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>18</hour> - <minute>30</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="ThirdTo"> - <property name="text"> - <string>to</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="ThirdToTimeEdit"> - <property name="displayFormat"> - <string>hh:mm AP</string> - </property> - <property name="time"> - <time> - <hour>19</hour> - <minute>30</minute> - <second>0</second> - </time> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - </widget> - <resources> - <include location="../images/openlp-2.qrc"/> - </resources> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>AuditDetailDialog</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>455</x> - <y>483</y> - </hint> - <hint type="destinationlabel"> - <x>445</x> - <y>575</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>AuditDetailDialog</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel"> - <x>528</x> - <y>484</y> - </hint> - <hint type="destinationlabel"> - <x>526</x> - <y>531</y> - </hint> - </hints> - </connection> - <connection> - <sender>FirstCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeFirstService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>26</x> - <y>285</y> - </hint> - <hint type="destinationlabel"> - <x>136</x> - <y>483</y> - </hint> - </hints> - </connection> - <connection> - <sender>SecondCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeSecondService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>41</x> - <y>323</y> - </hint> - <hint type="destinationlabel"> - <x>103</x> - <y>494</y> - </hint> - </hints> - </connection> - <connection> - <sender>ThirdCheckBox</sender> - <signal>stateChanged(int)</signal> - <receiver>AuditDetailDialog</receiver> - <slot>changeThirdService(int)</slot> - <hints> - <hint type="sourcelabel"> - <x>38</x> - <y>351</y> - </hint> - <hint type="destinationlabel"> - <x>155</x> - <y>463</y> - </hint> - </hints> - </connection> - <connection> - <sender>SaveFilePushButton</sender> - <signal>pressed()</signal> - <receiver>AuditDetailDialog</receiver> - <slot>defineOutputLocation()</slot> - <hints> - <hint type="sourcelabel"> - <x>538</x> - <y>419</y> - </hint> - <hint type="destinationlabel"> - <x>385</x> - <y>480</y> - </hint> - </hints> - </connection> - </connections> - <slots> - <slot>accept()</slot> - <slot>changeFirstService(int)</slot> - <slot>changeSecondService(int)</slot> - <slot>changeThirdService(int)</slot> - <slot>defineOutputLocation()</slot> - </slots> -</ui> diff --git a/resources/forms/serviceitemdialog.ui b/resources/forms/serviceitemdialog.ui new file mode 100644 index 000000000..6615b08aa --- /dev/null +++ b/resources/forms/serviceitemdialog.ui @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ServiceNoteEdit</class> + <widget class="QWidget" name="ServiceNoteEdit"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>243</height> + </rect> + </property> + <property name="windowTitle"> + <string>Service Item Notes</string> + </property> + <widget class="QWidget" name=""> + <property name="geometry"> + <rect> + <x>20</x> + <y>10</y> + <width>361</width> + <height>223</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTextEdit" name="textEdit"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/resources/forms/auditdeletedialog.ui b/resources/forms/songusagedeletedialog.ui similarity index 100% rename from resources/forms/auditdeletedialog.ui rename to resources/forms/songusagedeletedialog.ui diff --git a/resources/forms/songusagedetaildialog.ui b/resources/forms/songusagedetaildialog.ui new file mode 100644 index 000000000..a32b63899 --- /dev/null +++ b/resources/forms/songusagedetaildialog.ui @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AuditDetailDialog</class> + <widget class="QWidget" name="AuditDetailDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>609</width> + <height>413</height> + </rect> + </property> + <property name="windowTitle"> + <string>Audit Detail Extraction</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="DateRangeGroupBox"> + <property name="title"> + <string>Select Date Range</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="DateHorizontalLayout"> + <item> + <widget class="QCalendarWidget" name="FromDate"/> + </item> + <item> + <widget class="QLabel" name="ToLabel"> + <property name="text"> + <string>to</string> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QCalendarWidget" name="ToDate"/> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="FileGroupBox"> + <property name="title"> + <string>Report Location</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="FileLineEdit"/> + </item> + <item> + <widget class="QPushButton" name="SaveFilePushButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../images/openlp-2.qrc"> + <normaloff>:/exports/export_load.png</normaloff>:/exports/export_load.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <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>AuditDetailDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>455</x> + <y>483</y> + </hint> + <hint type="destinationlabel"> + <x>445</x> + <y>575</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>AuditDetailDialog</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>528</x> + <y>484</y> + </hint> + <hint type="destinationlabel"> + <x>526</x> + <y>531</y> + </hint> + </hints> + </connection> + <connection> + <sender>SaveFilePushButton</sender> + <signal>pressed()</signal> + <receiver>AuditDetailDialog</receiver> + <slot>defineOutputLocation()</slot> + <hints> + <hint type="sourcelabel"> + <x>538</x> + <y>419</y> + </hint> + <hint type="destinationlabel"> + <x>385</x> + <y>480</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>accept()</slot> + <slot>changeFirstService(int)</slot> + <slot>changeSecondService(int)</slot> + <slot>changeThirdService(int)</slot> + <slot>defineOutputLocation()</slot> + </slots> +</ui> diff --git a/resources/i18n/openlp_en.ts b/resources/i18n/openlp_en.ts index d92273c1d..6f9d4a26a 100644 --- a/resources/i18n/openlp_en.ts +++ b/resources/i18n/openlp_en.ts @@ -4,879 +4,2296 @@ <context> <name>BibleMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="131"/> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="141"/> <source>Quick</source> <translation type="unfinished"></translation> </message> </context> - <context> - <name>AlertsTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="36"/> - <source>Alerts</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="151"/> - <source>Font</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="291"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="579"/> - <source>Vertical</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertform.py" line="97"/> - <source>Display</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SplashScreen</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/splashscreen.py" line="32"/> - <source>Starting</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="215"/> - <source>New</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>EditCustomForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomform.py" line="119"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="640"/> - <source>Center</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TestMediaManager:</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/test/test_mediamanageritem.py" line="74"/> - <source>Item2</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="167"/> - <source>License</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TestMediaManager:</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/test/test_mediamanageritem.py" line="72"/> - <source>Item1</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="394"/> - <source>English</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="588"/> - <source>pt</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongimportdialog.py" line="107"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="643"/> - <source>Middle</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="568"/> - <source>Opaque</source> - <translation type="unfinished"></translation> - </message> - </context> <context> <name>Ui_customEditDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="154"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="150"/> - <source>Save</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleImportForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportform.py" line="187"/> - <source>Information</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="293"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_customEditDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="152"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ImageTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/images/lib/imagetab.py" line="66"/> - <source>sec</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="263"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="291"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_PluginViewDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="108"/> - <source>Inactive</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="303"/> - <source>Import</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="372"/> - <source>F9</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="361"/> - <source>F8</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="212"/> - <source>Topics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="638"/> - <source>Left</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMaintenanceForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenanceform.py" line="134"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="642"/> - <source>Top</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="380"/> - <source>F7</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="636"/> - <source>Alignment</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="249"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="250"/> - <source>Search</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="215"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="441"/> - <source>Theme</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongsPlugin</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/songsplugin.py" line="116"/> - <source>OpenSong</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="222"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="208"/> - <source>Load</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="644"/> - <source>Bottom</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="250"/> - <source>NIV</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="573"/> - <source>Image</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>GeneralTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="170"/> - <source>Screen</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ThemeManager</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/thememanager.py" line="166"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="590"/> - <source>Normal</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertsTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="156"/> - <source>s</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="304"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="291"/> - <source>Author</source> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="162"/> + <source>Delete selected slide</source> <translation type="unfinished"></translation> </message> </context> <context> <name>BiblesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/biblestab.py" line="45"/> - <source>Bibles</source> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="161"/> + <source>( and )</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_AuditDetailDialog</name> + <name>RemoteTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="163"/> - <source>Summary</source> + <location filename="openlp/plugins/remotes/lib/remotetab.py" line="39"/> + <source>Remotes</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_BibleImportDialog</name> + <name>ServiceManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="234"/> - <source>Import</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongBookForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songbookform.py" line="51"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="294"/> - <source>Title</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_PluginViewDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="104"/> - <source>TextLabel</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="182"/> - <source>Contribute</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>GeneralTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="136"/> - <source>Monitors</source> + <location filename="openlp/core/ui/servicemanager.py" line="123"/> + <source>Save Service</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="591"/> - <source>Bold</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>TopicsForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/topicsform.py" line="50"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SlideController</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="107"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="303"/> - <source>Export</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="264"/> - <source>Keep</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="639"/> - <source>Right</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="446"/> - <source>Comments</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="181"/> - <source>Credits</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AlertForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertform.py" line="98"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AuthorsForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/authorsform.py" line="78"/> - <source>Error</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="670"/> + <source>Shadow Size:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_OpenSongExportDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="303"/> - <source>Export</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="237"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SettingsDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/settingsdialog.py" line="57"/> - <source>Settings</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="597"/> - <source>Information</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="293"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ImageTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/images/lib/imagetab.py" line="34"/> - <source>Images</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AmendThemeDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="578"/> - <source>Horizontal</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="580"/> - <source>Circular</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_EditSongDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="434"/> - <source>Topic</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>BibleMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/mediaitem.py" line="209"/> - <source>Advanced</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/media/lib/mediatab.py" line="35"/> - <source>Media</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>AboutForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/about.py" line="183"/> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="305"/> <source>Close</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>ThemeManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="423"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenLPImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpimportdialog.py" line="293"/> - <source>Lyrics</source> + <location filename="openlp/core/ui/thememanager.py" line="66"/> + <source>Import Theme</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="601"/> - <source>px</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="683"/> + <source>Slide Transition</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>ImportWizardForm</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="425"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_OpenSongExportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongexportdialog.py" line="294"/> - <source>Title</source> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="166"/> + <source>Bible Exists</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ThemesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/themestab.py" line="37"/> - <source>Themes</source> + <location filename="openlp/core/ui/themestab.py" line="110"/> + <source>Theme level</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>BibleMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="429"/> - <source>Authors</source> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="68"/> + <source>Bible</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_BibleImportDialog</name> + <name>ServiceManager</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="251"/> - <source>KJV</source> + <location filename="openlp/core/ui/servicemanager.py" line="391"/> + <source>Save Changes to Service?</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_SongMaintenanceDialog</name> + <name>SongUsagePlugin</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="211"/> - <source>Authors</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AuditDetailDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="164"/> - <source>Detailed</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="216"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SlideController</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="175"/> - <source>s</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="136"/> - <source>Titles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="132"/> - <source>Clear</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="248"/> - <source>Crosswalk</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_customEditDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/custom/forms/editcustomdialog.py" line="146"/> - <source>Edit</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>PresentationTab</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/presentations/lib/presentationtab.py" line="101"/> - <source>available</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>ThemeManager</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/thememanager.py" line="120"/> - <source>default</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>EditSongForm</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongform.py" line="399"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_SongMaintenanceDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/songmaintenancedialog.py" line="214"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_BibleImportDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/forms/bibleimportdialog.py" line="235"/> - <source>Cancel</source> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="65"/> + <source>&Delete recorded data</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_OpenLPExportDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/openlpexportdialog.py" line="294"/> - <source>Title</source> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="298"/> + <source>Song Title</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>GeneralTab</name> + <name>Ui_customEditDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="37"/> - <source>General</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/generaltab.py" line="172"/> - <source>primary</source> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="156"/> + <source>Edit selected slide</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_AmendThemeDialog</name> + <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="648"/> - <source>Preview</source> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="343"/> + <source>CCLI Licence: </source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDeleteDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeletedialog.py" line="60"/> + <source>Audit Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="299"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="310"/> + <source>Bible Import Wizard</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="157"/> + <source>Edit All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_ServiceNoteEdit</name> + <message> + <location filename="openlp/core/ui/serviceitemdialog.py" line="49"/> + <source>Service Item Notes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="185"/> + <source>Couldn't save your author!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="163"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="109"/> + <source>Global theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="84"/> + <source>Start/Stop live song usage recording</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="585"/> + <source>The Main Display has been blanked out</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="70"/> + <source>Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="161"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="229"/> + <source>This author can't be deleted, they are currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="57"/> + <source>Create a new theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="356"/> + <source>Open an existing service</source> <translation type="unfinished"></translation> </message> </context> <context> <name>SlideController</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/slidecontroller.py" line="105"/> - <source>Live</source> + <location filename="openlp/core/ui/slidecontroller.py" line="163"/> + <source>Move to previous</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="186"/> + <source>Edit and re-preview Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="102"/> + <source>Plugin Details</source> <translation type="unfinished"></translation> </message> </context> <context> <name>AlertsTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/alertstab.py" line="157"/> - <source>Preview</source> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="230"/> + <source>pt</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="235"/> + <source>Edit History:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="201"/> + <source>Delay between slides in seconds</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="165"/> + <source>Couldn't add your book!</source> <translation type="unfinished"></translation> </message> </context> <context> <name>BiblesTab</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/bibles/lib/biblestab.py" line="159"/> - <source>continuous</source> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="158"/> + <source>verse per line</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="165"/> + <source>Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="216"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="333"/> + <source>Bible:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="121"/> + <source>You need to specify a file with books of the Bible to use in the import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="62"/> + <source>Delete Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SplashScreen</name> + <message> + <location filename="openlp/core/ui/splashscreen.py" line="61"/> + <source>Splash Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="62"/> + <source>Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDeleteForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeleteform.py" line="44"/> + <source>Delete Selected Audit Events?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="298"/> + <source>Song Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="294"/> + <source>Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="399"/> + <source>List the Plugins</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="231"/> + <source>No author selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="154"/> + <source><b>SongUsage Plugin</b><br>This plugin records the use of songs and when they have been used during a live service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="149"/> + <source>Move slide Up 1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="156"/> + <source>OpenSong</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsManager</name> + <message> + <location filename="openlp/plugins/alerts/lib/alertsmanager.py" line="83"/> + <source>Alert message created and delayed</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="420"/> + <source>Alternative Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="526"/> + <source>Open Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="155"/> + <source>Display Style:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="569"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="606"/> + <source>Image</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="403"/> + <source>You need to enter a song title.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="361"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="111"/> + <source>Invalid Bible Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="120"/> + <source>Global level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="91"/> + <source>Make Global</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="387"/> + <source>&Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="656"/> + <source>Height:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="327"/> + <source>Books Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="63"/> + <source>Delete a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="331"/> + <source>Crosswalk</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongBookForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookform.py" line="51"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="79"/> + <source>Last name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="152"/> + <source>Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="157"/> + <source>You need to set a copyright for your Bible! Bibles in the Public Domain need to be marked as such.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="72"/> + <source>Maintain the lists of authors, topics and books</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="62"/> + <source>Save</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="259"/> + <source>You have unsaved data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="292"/> + <source>To:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="664"/> + <source>Outline</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="296"/> + <source>Text Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="288"/> + <source>openlp.org Song Exporter</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="67"/> + <source>Delete song usage to specified date</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="91"/> + <source>Report Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="324"/> + <source>OpenSong</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="355"/> + <source>Open Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="146"/> + <source>Titles</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="62"/> + <source>Select Image(s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="282"/> + <source>Search Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="345"/> + <source>Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="64"/> + <source>Images (*.jpg *jpeg *.gif *.png *.bmp);; All files (*)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="370"/> + <source>Alt+F4</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="278"/> + <source>&Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="163"/> + <source>CCLI Details</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/generaltab.py" line="166"/> + <source>SongSelect Password:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="396"/> + <source>Toggle the visibility of the Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="254"/> + <source>Are you sure you want to delete the selected book?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="401"/> + <source>&User Guide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDeleteForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedeleteform.py" line="45"/> + <source>Are you sure you want to delete selected Audit Data?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="413"/> + <source>Set the interface language to English</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="617"/> + <source>Main Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="156"/> + <source>Empty Copyright</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomPlugin</name> + <message> + <location filename="openlp/plugins/custom/customplugin.py" line="70"/> + <source><b>Custom Plugin</b><br>This plugin allows slides to be displayed on the screen in the same way songs are. This plugin provides greater freedom over the songs plugin.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="80"/> + <source>You need to type in the first name of the author.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="68"/> + <source>Display Verses on Live Tool bar:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="162"/> + <source>Move to top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="102"/> + <source>Override background</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="222"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="112"/> + <source>Use the theme from each song in the database. If a song doesn't have a theme associated with it, then use the service's theme. If the service doesn't have a theme, then use the global theme.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="62"/> + <source>Presentation</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="604"/> + <source>Solid Color</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="39"/> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="303"/> + <source>Ready to import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="549"/> + <source>OpenLP version %s has been updated to version %s + +You can obtain the latest version from http://openlp.org</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="326"/> + <source>File Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="226"/> + <source>Go to Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="335"/> + <source>&Import</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="369"/> + <source>Quit OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="315"/> + <source>This wizard will help you to import Bibles from a variety of formats. Click the next button below to start the process by selecting a format to import from.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="148"/> + <source>Empty Version Name</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="393"/> + <source>&Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="191"/> + <source>Start continuous loop</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="548"/> + <source>License</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="199"/> + <source>primary</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="443"/> + <source>Add a Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="350"/> + <source>&New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="167"/> + <source>Credits:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="111"/> + <source>Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="129"/> + <source>You need to specify a file of Bible verses to import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="159"/> + <source>continuous</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="121"/> + <source>Number</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="156"/> + <source>Application Startup</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="652"/> + <source>Use Default Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="107"/> + <source>Import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="353"/> + <source>Ctrl+N</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="422"/> + <source>Verse Order:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="89"/> + <source>ASelect Date Range</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="331"/> + <source>Default Theme: </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="394"/> + <source>Toggle Preview Panel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="147"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="669"/> + <source>Shadow</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="155"/> + <source>Select monitor for output display:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="648"/> + <source>Italics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="118"/> + <source>Create a new service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="600"/> + <source>Background:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="288"/> + <source>openlp.org Song Importer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="347"/> + <source>Copyright:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="116"/> + <source>Service level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="163"/> + <source>[ and ]</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="159"/> + <source>Save</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="381"/> + <source>You must select one or more items</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="160"/> + <source>Application Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="124"/> + <source>Save this service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="187"/> + <source>Open Books CSV file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="165"/> + <source>SongSelect Username:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="653"/> + <source>X Position:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="353"/> + <source>No matching book could be found in this Bible.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="337"/> + <source>Server:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="336"/> + <source>Download Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="137"/> + <source>Invalid OpenSong Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="164"/> + <source>CCLI Number:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="678"/> + <source>Center</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="126"/> + <source>Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="418"/> + <source>&Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="240"/> + <source>Delete Topic</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="411"/> + <source>English</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="169"/> + <source>You must select one or more items</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="78"/> + <source>First name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="348"/> + <source>Permission:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="108"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="601"/> + <source>Opaque</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="255"/> + <source>This book can't be deleted, it is currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="367"/> + <source>Your Bible import failed.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="213"/> + <source>Start playing media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="141"/> + <source>Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="550"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TopicsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsform.py" line="51"/> + <source>You need to type in a topic name!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="297"/> + <source>Song Export List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="287"/> + <source>Dual:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="68"/> + <source>sec</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="175"/> + <source>Delete From Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="158"/> + <source>Automatically open the last service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="297"/> + <source>Song Import List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="667"/> + <source>Outline Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="318"/> + <source>Select Import Source</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="392"/> + <source>F9</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="381"/> + <source>F8</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="213"/> + <source>&Change Item Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="213"/> + <source>Topics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="290"/> + <source>Import File Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="151"/> + <source>Edit Custom Slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="432"/> + <source>&Remove</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="345"/> + <source>Set up the Bible's license details.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="674"/> + <source>Alignment</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="253"/> + <source>Delete Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="80"/> + <source>Edit a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="332"/> + <source>BibleGateway</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="162"/> + <source>Preview Next Song from Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="429"/> + <source>Title && Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="257"/> + <source>No book selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="182"/> + <source>Move to live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="127"/> + <source>Other</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="442"/> + <source>Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="123"/> + <source>Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="361"/> + <source>Save the current service to disk</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="289"/> + <source>Chapter:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="682"/> + <source>Bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="91"/> + <source>Available Controllers</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="191"/> + <source>Open Verses CSV file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TopicsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsform.py" line="50"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>RemoteTab</name> + <message> + <location filename="openlp/plugins/remotes/lib/remotetab.py" line="56"/> + <source>Remotes Receiver Port</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="338"/> + <source>&View</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="646"/> + <source>Normal</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="305"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="338"/> + <source>Username:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="59"/> + <source>Edit Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="207"/> + <source>&Preview Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="68"/> + <source>Alert Message</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="364"/> + <source>Finished import.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="157"/> + <source>Show blank screen warning</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="233"/> + <source>Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="441"/> + <source>Authors, Topics && Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="407"/> + <source>You need to enter some verses.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="543"/> + <source>Bible not fully loaded</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="60"/> + <source>Display Footer:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblePlugin</name> + <message> + <location filename="openlp/plugins/bibles/bibleplugin.py" line="91"/> + <source><strong>Bible Plugin</strong><br />This plugin allows bible verses from different sources to be displayed on the screen during the service.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="444"/> + <source>Copyright Information</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="336"/> + <source>&Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="647"/> + <source>Bold</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="122"/> + <source>Export songs in OpenLP 2.0 format</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="214"/> + <source>Load a new</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="128"/> + <source>Missing data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="179"/> + <source><b>Song Plugin</b> <br>This plugin allows Songs to be managed and displayed.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="641"/> + <source>Footer Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="420"/> + <source>Invalid verse entry - vX</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="352"/> + <source>No Book Found</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="304"/> + <source>Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="330"/> + <source>Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="300"/> + <source>Keep</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="74"/> + <source>Generate report on Song Usage</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="435"/> + <source>Topic</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="354"/> + <source>&Open</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="99"/> + <source>Present using:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="209"/> + <source>&Live Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="125"/> + <source>Pre-Chorus</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="421"/> + <source>Lyrics:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="135"/> + <source>Project Lead + Raoul "superfly" Snyman + +Developers + Tim "TRB143" Bentley + Jonathan "gushie" Corwin + Michael "cocooncrash" Gorven + Scott "sguerrieri" Guerrieri + Raoul "superfly" Snyman + Maikel Stuivenberg + Martin "mijiti" Thompson + Jon "Meths" Tibble + Carsten "catini" Tingaard + +Testers + Wesley "wrst" Stout</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="94"/> + <source>You haven't set a display name for the author, would you like me to combine the first and last names for you?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="142"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="708"/> + <source>Slide Height is %s rows</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="105"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="383"/> + <source>Toggle Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="69"/> + <source>Alert Text:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="424"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="227"/> + <source>Font Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="598"/> + <source>Theme Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomTab</name> + <message> + <location filename="openlp/plugins/custom/lib/customtab.py" line="58"/> + <source>Custom Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="607"/> + <source><Color1></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="430"/> + <source>Authors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="69"/> + <source>Export Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="168"/> + <source>No items selected...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="69"/> + <source>Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="76"/> + <source>Author Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="663"/> + <source>Font Footer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="376"/> + <source>&Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="337"/> + <source>&Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="293"/> + <source>Results:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="290"/> + <source>Full Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="104"/> + <source>OpenSong Folder:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="170"/> + <source>Move to last</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="221"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="228"/> + <source>Are you sure you want to delete the selected author?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="82"/> + <source>Song Usage Status</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="295"/> + <source>Verse Search</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="68"/> + <source>Edit Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="460"/> + <source>Save && Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongBookDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookdialog.py" line="70"/> + <source>Publisher:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="650"/> + <source>Font Weight:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="329"/> + <source>Bible Filename:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="602"/> <source>Transparent</source> <translation type="unfinished"></translation> </message> @@ -884,84 +2301,63 @@ <context> <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="133"/> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="143"/> <source>Search</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_OpenSongImportDialog</name> + <name>Ui_BibleImportWizard</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/opensongimportdialog.py" line="106"/> - <source>Import</source> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="321"/> + <source>Format:</source> <translation type="unfinished"></translation> </message> </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="583"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="616"/> <source>Background</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>Ui_EditSongDialog</name> + <name>Ui_BibleImportWizard</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/forms/editsongdialog.py" line="422"/> - <source>Add</source> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="349"/> + <source>Importing</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="158"/> + <source>Edit all slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaMediaItem</name> + <message> + <location filename="openlp/plugins/media/lib/mediaitem.py" line="63"/> + <source>Select Media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="65"/> + <source>Select Presentation(s)</source> <translation type="unfinished"></translation> </message> </context> <context> <name>SongMediaItem</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="137"/> - <source>Lyrics</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>MediaManagerItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/lib/mediamanageritem.py" line="229"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="377"/> - <source>F11</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="366"/> - <source>F10</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_AuditDetailDialog</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/audit/forms/auditdetaildialog.py" line="167"/> - <source>to</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>Ui_MainWindow</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/mainwindow.py" line="347"/> - <source>F12</source> - <translation type="unfinished"></translation> - </message> - </context> - <context> - <name>SongMediaItem</name> - <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/plugins/songs/lib/mediaitem.py" line="138"/> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="148"/> <source>Authors</source> <translation type="unfinished"></translation> </message> @@ -969,23 +2365,2479 @@ <context> <name>Ui_PluginViewDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/plugindialog.py" line="107"/> + <location filename="openlp/core/ui/plugindialog.py" line="107"/> <source>Active</source> <translation type="unfinished"></translation> </message> </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="366"/> + <source>Save the current service under a new name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="357"/> + <source>Ctrl+O</source> + <translation type="unfinished"></translation> + </message> + </context> <context> <name>Ui_AmendThemeDialog</name> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="592"/> - <source>Italics</source> + <location filename="openlp/core/ui/amendthemedialog.py" line="687"/> + <source>Other Options</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="142"/> + <source>Couldn't add your author!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="65"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="418"/> + <source>Song Editor</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="225"/> + <source>Font</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="155"/> + <source>&Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="334"/> + <source>&File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="272"/> + <source>&Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="612"/> + <source>Vertical</source> <translation type="unfinished"></translation> </message> <message> - <location filename="/home/raoul/Projects/openlp/i18n/openlp/core/ui/amendthemedialog.py" line="572"/> + <location filename="openlp/core/ui/amendthemedialog.py" line="655"/> + <source>Width:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="180"/> + <source>You are unable to delete the default theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="121"/> + <source>Use the global theme, overriding any themes associated with either the service or the songs.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="286"/> + <source>Version:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="117"/> + <source>OpenLP <version> build <revision> - Open Source Lyrics Projection + +OpenLP is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if OpenOffice.org, PowerPoint or PowerPoint Viewer is installed) for church worship using a computer and a data projector. + +Find out more about OpenLP: http://openlp.org/ + +OpenLP is written and maintained by volunteers. If you would like to see more free Christian software being written, please consider contributing by using the button below.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="158"/> + <source>OpenLP 2.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="117"/> + <source>New Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_TopicsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsdialog.py" line="63"/> + <source>Topic name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="362"/> + <source>File is not a valid theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="343"/> + <source>License Details</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="167"/> + <source>Move down</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="437"/> + <source>R&emove</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="681"/> + <source>Middle</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="328"/> + <source>Verse Location:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="87"/> + <source>Item selected to Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="291"/> + <source>From:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="672"/> + <source>Shadow Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="203"/> + <source>&Notes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="368"/> + <source>E&xit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="305"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="552"/> + <source>OpenLP Version Updated</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="160"/> + <source>Replace edited slide</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="154"/> + <source>Add new slide at bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="252"/> + <source>You need to enter a title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="444"/> + <source>Theme Exists</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="343"/> + <source>&Help</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="288"/> + <source>OpenSong Song Exporter</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="679"/> + <source>Vertical Align:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>TestMediaManager</name> + <message> + <location filename="openlp/core/test/test_mediamanageritem.py" line="74"/> + <source>Item2</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/test/test_mediamanageritem.py" line="72"/> + <source>Item1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="680"/> + <source>Top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="166"/> + <source>Display Dual Bible Verses</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="389"/> + <source>Toggle Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="287"/> + <source>&Add to Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="639"/> + <source>First Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="111"/> + <source>Song level</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="68"/> + <source>Show an alert message</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="405"/> + <source>Ctrl+F1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="200"/> + <source>Couldn't save your topic!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="86"/> + <source>Delete theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="66"/> + <source>Image Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="103"/> + <source>OpenSong Song Importer</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="48"/> + <source>Bibles</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="72"/> + <source>&Extract recorded data</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="226"/> + <source>Font Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="407"/> + <source>&Web Site</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="245"/> + <source>Send the selected item live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="339"/> + <source>M&ode</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="410"/> + <source>Translate the interface to your language</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="347"/> + <source>Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>CustomMediaItem</name> + <message> + <location filename="openlp/plugins/custom/lib/mediaitem.py" line="70"/> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="322"/> + <source>OSIS</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="157"/> + <source>openlp.org 1.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="373"/> + <source>&Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="112"/> + <source>Edit Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="374"/> + <source>&Language</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="450"/> + <source>Verse</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="138"/> + <source>You need to specify an OpenSong Bible file to import!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="392"/> + <source>Your service is unsaved, do you want to save those changes before creating a new one ?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="140"/> + <source>Search:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="626"/> + <source>Save Changes to Service?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="627"/> + <source>Your service has changed, do you want to save those changes?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="428"/> + <source>Invalid verse entry - values must be Numeric, I,B,C,T,P,E,O</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="431"/> + <source>&Add to Song</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="402"/> + <source>&About</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="153"/> + <source>Only show new chapter numbers</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="149"/> + <source>You need to specify a version name for your Bible!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="66"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="145"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>RemotesPlugin</name> + <message> + <location filename="openlp/plugins/remotes/remoteplugin.py" line="81"/> + <source><b>Remote Plugin</b><br>This plugin provides the ability to send messages to a running version of openlp on a different computer.<br>The Primary use for this would be to send alerts from a creche</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="242"/> + <source>This topic can't be deleted, it is currently assigned to at least one song!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="283"/> + <source>Find:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="127"/> + <source>Item selected to Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="677"/> + <source>Right</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="224"/> + <source>Save Theme - (%s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="104"/> + <source>Allow background of live slide to be overridden</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="250"/> + <source>Add the selected item(s) to the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="93"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="288"/> + <source>Book:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="643"/> + <source>Font Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="289"/> + <source>Select openlp.org export filename:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SettingsDialog</name> + <message> + <location filename="openlp/core/ui/settingsdialog.py" line="62"/> + <source>Settings</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="152"/> + <source>Verse Display</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="226"/> + <source>Edit the selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="166"/> + <source>Move to next</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="398"/> + <source>&Plugin List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblePlugin</name> + <message> + <location filename="openlp/plugins/bibles/bibleplugin.py" line="83"/> + <source>&Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="325"/> + <source>Web Download</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="611"/> + <source>Horizontal</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="183"/> + <source>Open OSIS file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="217"/> + <source>Couldn't save your book!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="153"/> + <source>Couldn't add your topic!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="645"/> + <source>pt</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="414"/> + <source>&Add Tool...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="608"/> + <source><Color2></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="164"/> + <source>Move up</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="294"/> + <source>Lyrics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="160"/> + <source>No brackets</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="61"/> + <source>Maintain Alerts</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="671"/> + <source>px</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="131"/> + <source>Select a theme for the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="40"/> + <source>Themes</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="170"/> + <source>Move to bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="106"/> + <source>Status:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="446"/> + <source>CCLI Number:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="167"/> + <source>This Bible already exists! Please import a different Bible or first delete the existing one.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="408"/> + <source>&Translate</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertEditForm</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditform.py" line="88"/> + <source>Please Save or Clear seletced item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="364"/> + <source>Save Service As</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="212"/> + <source>Authors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsageDetailForm</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetailform.py" line="59"/> + <source>Output File Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="162"/> + <source>{ and }</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="161"/> + <source>Prompt to save Service before starting New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="313"/> + <source>Starting import...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="165"/> + <source>Note: +Changes don't affect verses already in the service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="126"/> + <source>Intro</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="165"/> + <source>Move up order</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="96"/> + <source>available</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="275"/> + <source>default</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="227"/> + <source>Delete Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="651"/> + <source>Display Location</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="103"/> + <source>Version:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="64"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="40"/> + <source>General</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="654"/> + <source>Y Position:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="168"/> + <source>Move down order</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="157"/> + <source>verse per slide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="313"/> + <source>Welcome to the Bible Import Wizard</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="673"/> + <source>Show Shadow:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="236"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="98"/> + <source><b>Alerts Plugin</b><br>This plugin controls the displaying of alerts on the presentations screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="159"/> + <source>Show the splash screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="351"/> + <source>New Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="160"/> + <source>Move to first</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="406"/> + <source>&Online Help</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="175"/> + <source>Blank Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="359"/> + <source>Save Service</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="363"/> + <source>Save &As...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="380"/> + <source>Toggle the visibility of the Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="233"/> + <source>Delete the selected item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="423"/> + <source>Add</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="67"/> + <source>&Alert</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="225"/> + <source>Advanced</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="146"/> + <source>Image(s)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="397"/> + <source>F11</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="386"/> + <source>F10</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="367"/> + <source>F12</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="320"/> + <source>Select the import format, and where to import from.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="400"/> + <source>Alt+F7</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="416"/> + <source>Add an application to the list of tools</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaPlugin</name> + <message> + <location filename="openlp/plugins/media/mediaplugin.py" line="78"/> + <source><b>Media Plugin</b><br>This plugin allows the playing of audio and video media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="156"/> + <source>Bible Theme:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsPlugin</name> + <message> + <location filename="openlp/plugins/songs/songsplugin.py" line="117"/> + <source>Export songs in openlp.org 1.0 format</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="349"/> + <source>Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="40"/> + <source>Alerts</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="150"/> + <source>Move slide down 1</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="642"/> + <source>Font:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="121"/> + <source>Load an existing service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="385"/> + <source>Toggle the visibility of the Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationTab</name> + <message> + <location filename="openlp/plugins/presentations/lib/presentationtab.py" line="40"/> + <source>Presentations</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SplashScreen</name> + <message> + <location filename="openlp/core/ui/splashscreen.py" line="33"/> + <source>Starting</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="67"/> + <source>Slide Loop Delay:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="171"/> + <source>Move to end</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="231"/> + <source>Alert timeout:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="417"/> + <source>&Preview Pane</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="220"/> + <source>Add a new</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="246"/> + <source>Select Theme Import File</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/thememanager.py" line="504"/> + <source>New Theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaMediaItem</name> + <message> + <location filename="openlp/plugins/media/lib/mediaitem.py" line="84"/> + <source>Media</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="339"/> + <source>Password:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="665"/> + <source>Outline Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="302"/> + <source>Progress:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="640"/> + <source>Second Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="450"/> + <source>Theme, Copyright Info && Comments</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="382"/> + <source>&Theme Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="289"/> + <source>Select openlp.org songfile to import:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="438"/> + <source>Song Book</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>alertsPlugin</name> + <message> + <location filename="openlp/plugins/alerts/alertsplugin.py" line="69"/> + <source>F7</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="299"/> + <source>Author</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="622"/> + <source>Wrap Indentation</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="67"/> + <source>Import a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationPlugin</name> + <message> + <location filename="openlp/plugins/presentations/presentationplugin.py" line="112"/> + <source><b>Presentation Plugin</b> <br> Delivers the ability to show presentations using a number of different programs. The choice of available presentation programs is available to the user in a drop down box.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageMediaItem</name> + <message> + <location filename="openlp/plugins/images/lib/mediaitem.py" line="59"/> + <source>Image</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="66"/> + <source>Enable search as you type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertdialog.py" line="71"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="304"/> + <source>Import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="124"/> + <source>Chorus</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="425"/> + <source>Edit All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AuthorsForm</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsform.py" line="87"/> + <source>You need to type in the last name of the author.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="64"/> + <source>Songs Mode</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="676"/> + <source>Left</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemesTab</name> + <message> + <location filename="openlp/core/ui/themestab.py" line="117"/> + <source>Use the theme from the service, overriding any of the individual songs' themes. If the service doesn't have a theme, then use the global theme.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImageTab</name> + <message> + <location filename="openlp/plugins/images/lib/imagetab.py" line="39"/> + <source>Images</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BibleMediaItem</name> + <message> + <location filename="openlp/plugins/bibles/lib/mediaitem.py" line="290"/> + <source>Verse:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="323"/> + <source>CSV</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="297"/> + <source>Song Export List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="96"/> + <source>Export theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="223"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="599"/> + <source>Theme Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="116"/> + <source>About OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="391"/> + <source>Toggle the visibility of the Service Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="125"/> + <source>A presentation with that filename already exists.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="237"/> + <source>openlp.org</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="120"/> + <source>Invalid Books File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="298"/> + <source>Song Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="283"/> + <source>&Show Live</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="234"/> + <source>Keep History:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="609"/> + <source>Image:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="166"/> + <source>Set Theme for Slides</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="404"/> + <source>More information about OpenLP</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="228"/> + <source>Background Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="244"/> + <source>No topic selected!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="377"/> + <source>&Media Manager</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="342"/> + <source>&Tools</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="624"/> + <source>Background Color:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="436"/> + <source>A&dd to Song</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="419"/> + <source>Title:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="197"/> + <source>Screen</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="232"/> + <source>s</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImagePlugin</name> + <message> + <location filename="openlp/plugins/images/imageplugin.py" line="59"/> + <source><b>Image Plugin</b><br>Allows images of all types to be displayed. If a number of images are selected together and presented on the live controller it is possible to turn them into a timed loop.<br<br>From the plugin if the <i>Override background</i> is chosen and an image is selected any somgs which are rendered will use the selected image from the background instead of the one provied by the theme.<br></source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AlertEditDialog</name> + <message> + <location filename="openlp/plugins/alerts/forms/alerteditdialog.py" line="63"/> + <source>Clear</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="351"/> + <source>Please wait while your Bible is imported.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="380"/> + <source>No items selected...</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="640"/> + <source>Font Main</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="295"/> + <source>Title</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="289"/> + <source>Select OpenSong song folder:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="378"/> + <source>Toggle Media Manager</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongUsagePlugin</name> + <message> + <location filename="openlp/plugins/songusage/songusageplugin.py" line="61"/> + <source>&Song Usage</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>GeneralTab</name> + <message> + <location filename="openlp/core/ui/generaltab.py" line="154"/> + <source>Monitors</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="256"/> + <source>You need to enter a slide</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="112"/> + <source>You need to specify a file to import your Bible from!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="113"/> + <source>Verse Type</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="219"/> + <source>You have not selected a theme!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="447"/> + <source>Comments</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="239"/> + <source>Bottom</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="352"/> + <source>Create a new Service</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="238"/> + <source>Top</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="116"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="104"/> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="105"/> + <source>About:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="108"/> + <source>Inactive</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="303"/> + <source>Ready to export</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="304"/> + <source>Export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_PluginViewDialog</name> + <message> + <location filename="openlp/core/ui/plugindialog.py" line="101"/> + <source>Plugin List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="684"/> + <source>Transition Active:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="342"/> + <source>Proxy Server (Optional)</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="434"/> + <source>&Manage Authors, Topics, Books</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="88"/> + <source>Audit Detail Extraction</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="303"/> + <source>Ready to export</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditCustomForm</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomform.py" line="84"/> + <source>Save && Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpexportdialog.py" line="300"/> + <source>Select All</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongUsageDetailDialog</name> + <message> + <location filename="openlp/plugins/songusage/forms/songusagedetaildialog.py" line="90"/> + <source>to</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="644"/> + <source>Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="584"/> + <source>OpenLP Main Display Blanked</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenLPImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/openlpimportdialog.py" line="301"/> + <source>Remove Selected</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>OpenSongBible</name> + <message> + <location filename="openlp/plugins/bibles/lib/opensong.py" line="96"/> + <source>Importing</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditSongDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongdialog.py" line="426"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="362"/> + <source>Ctrl+S</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>PresentationMediaItem</name> + <message> + <location filename="openlp/plugins/presentations/lib/mediaitem.py" line="125"/> + <source>File exists</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongImportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongimportdialog.py" line="106"/> + <source>Ready to import</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SlideController</name> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="194"/> + <source>Stop continuous loop</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/slidecontroller.py" line="200"/> + <source>s</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMediaItem</name> + <message> + <location filename="openlp/plugins/songs/lib/mediaitem.py" line="71"/> + <source>Song Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="155"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="610"/> + <source>Gradient :</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>BiblesTab</name> + <message> + <location filename="openlp/plugins/bibles/lib/biblestab.py" line="154"/> + <source>Layout Style:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="128"/> + <source>Invalid Verse File</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>EditSongForm</name> + <message> + <location filename="openlp/plugins/songs/forms/editsongform.py" line="478"/> + <source>Error</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="153"/> + <source>Add New</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AuthorsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/authorsdialog.py" line="77"/> + <source>Display name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongMaintenanceForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenanceform.py" line="241"/> + <source>Are you sure you want to delete the selected topic?</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="649"/> + <source>Bold/Italics</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="211"/> + <source>Song Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="122"/> + <source>Bridge</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongsTab</name> + <message> + <location filename="openlp/plugins/songs/lib/songstab.py" line="39"/> + <source>Songs</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="688"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="155"/> + <source>Credits</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>MediaManagerItem</name> + <message> + <location filename="openlp/core/lib/mediamanageritem.py" line="240"/> + <source>Preview the selected item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="346"/> + <source>Version Name:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="134"/> + <source>About</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_EditVerseDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/editversedialog.py" line="128"/> + <source>Ending</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="675"/> + <source>Horizontal Align:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ServiceManager</name> + <message> + <location filename="openlp/core/ui/servicemanager.py" line="201"/> + <source>&Edit Item</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="603"/> + <source>Background Type:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="358"/> + <source>&Save</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="330"/> + <source>OpenLP 2.0</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ThemeManager</name> + <message> + <location filename="openlp/core/ui/thememanager.py" line="445"/> + <source>A theme with this name already exists, would you like to overwrite it?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="openlp/core/ui/thememanager.py" line="70"/> + <source>Export a theme</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>AmendThemeForm</name> + <message> + <location filename="openlp/core/ui/amendthemeform.py" line="208"/> + <source>Open file</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_TopicsDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/topicsdialog.py" line="62"/> + <source>Topic Maintenance</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_customEditDialog</name> + <message> + <location filename="openlp/plugins/custom/forms/editcustomdialog.py" line="164"/> + <source>Clear edit area</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="668"/> + <source>Show Outline:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>SongBookForm</name> + <message> + <location filename="openlp/plugins/songs/forms/songbookform.py" line="52"/> + <source>You need to type in a book name!</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>ImportWizardForm</name> + <message> + <location filename="openlp/plugins/bibles/forms/importwizardform.py" line="195"/> + <source>Open OpenSong Bible</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_MainWindow</name> + <message> + <location filename="openlp/core/ui/mainwindow.py" line="375"/> + <source>Look && &Feel</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_BibleImportWizard</name> + <message> + <location filename="openlp/plugins/bibles/forms/bibleimportwizard.py" line="352"/> + <source>Ready.</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_SongMaintenanceDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/songmaintenancedialog.py" line="214"/> + <source>Books/Hymnals</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AboutDialog</name> + <message> + <location filename="openlp/core/ui/aboutdialog.py" line="549"/> + <source>Contribute</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="605"/> <source>Gradient</source> <translation type="unfinished"></translation> </message> </context> + <context> + <name>AlertsTab</name> + <message> + <location filename="openlp/plugins/alerts/forms/alertstab.py" line="229"/> + <source>Font Size:</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_OpenSongExportDialog</name> + <message> + <location filename="openlp/plugins/songs/forms/opensongexportdialog.py" line="290"/> + <source>Full Song List</source> + <translation type="unfinished"></translation> + </message> + </context> + <context> + <name>Ui_AmendThemeDialog</name> + <message> + <location filename="openlp/core/ui/amendthemedialog.py" line="613"/> + <source>Circular</source> + <translation type="unfinished"></translation> + </message> + </context> </TS> diff --git a/resources/images/OpenLP.ico b/resources/images/OpenLP.ico new file mode 100644 index 000000000..b275542c3 Binary files /dev/null and b/resources/images/OpenLP.ico differ diff --git a/resources/images/image_clapperboard.png b/resources/images/image_clapperboard.png new file mode 100644 index 000000000..e46056914 Binary files /dev/null and b/resources/images/image_clapperboard.png differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 8fa38c42b..28af4c31a 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -1,5 +1,5 @@ <RCC> - <qresource prefix="songs" > + <qresource prefix="songs"> <file>topic_edit.png</file> <file>author_add.png</file> <file>author_delete.png</file> @@ -21,7 +21,7 @@ <file>song_topic_edit.png</file> <file>song_book_edit.png</file> </qresource> - <qresource prefix="slides" > + <qresource prefix="slides"> <file>slide_close.png</file> <file>slide_first.png</file> <file>slide_last.png</file> @@ -31,7 +31,7 @@ <file>media_playback_stop.png</file> <file>media_playback_pause.png</file> </qresource> - <qresource prefix="icon" > + <qresource prefix="icon"> <file>openlp-logo-16x16.png</file> <file>openlp-logo-32x32.png</file> <file>openlp-logo-48x48.png</file> @@ -39,43 +39,46 @@ <file>openlp-logo-128x128.png</file> <file>openlp-logo-256x256.png</file> </qresource> - <qresource prefix="graphics" > + <qresource prefix="graphics"> <file>openlp-about-logo.png</file> <file>openlp-splash-screen.png</file> </qresource> - <qresource prefix="imports" > + <qresource prefix="imports"> <file>import_selectall.png</file> <file>import_move_to_list.png</file> <file>import_remove.png</file> <file>import_load.png</file> </qresource> - <qresource prefix="exports" > + <qresource prefix="exports"> <file>export_selectall.png</file> <file>export_remove.png</file> <file>export_load.png</file> <file>export_move_to_list.png</file> </qresource> - <qresource prefix="custom" > + <qresource prefix="custom"> <file>custom_new.png</file> <file>custom_edit.png</file> <file>custom_delete.png</file> </qresource> - <qresource prefix="wizards" > + <qresource prefix="wizards"> <file>wizard_importbible.bmp</file> </qresource> - <qresource prefix="presentations" > + <qresource prefix="presentations"> <file>presentation_delete.png</file> <file>presentation_load.png</file> </qresource> - <qresource prefix="videos" > + <qresource prefix="videos"> <file>video_delete.png</file> <file>video_load.png</file> </qresource> - <qresource prefix="images" > + <qresource prefix="images"> <file>image_delete.png</file> <file>image_load.png</file> </qresource> - <qresource prefix="services" > + <qresource prefix="services"> + <file>service_edit.png</file> + <file>service_notes.png</file> + <file>service_item_notes.png</file> <file>service_bottom.png</file> <file>service_down.png</file> <file>service_top.png</file> @@ -85,7 +88,7 @@ <file>service_open.png</file> <file>service_save.png</file> </qresource> - <qresource prefix="system" > + <qresource prefix="system"> <file>system_close.png</file> <file>system_about.png</file> <file>system_help_contents.png</file> @@ -99,7 +102,7 @@ <file>system_exit.png</file> <file>system_settings.png</file> </qresource> - <qresource prefix="media" > + <qresource prefix="media"> <file>media_custom.png</file> <file>media_presentation.png</file> <file>media_image.png</file> @@ -108,17 +111,18 @@ <file>media_video.png</file> <file>media_time.png</file> <file>media_stop.png</file> + <file>image_clapperboard.png</file> </qresource> - <qresource prefix="messagebox" > + <qresource prefix="messagebox"> <file>messagebox_critical.png</file> <file>messagebox_info.png</file> <file>messagebox_warning.png</file> </qresource> - <qresource prefix="tools" > + <qresource prefix="tools"> <file>tools_add.png</file> <file>tools_alert.png</file> </qresource> - <qresource prefix="themes" > + <qresource prefix="themes"> <file>theme_delete.png</file> <file>theme_new.png</file> <file>theme_edit.png</file> diff --git a/resources/images/service_edit.png b/resources/images/service_edit.png new file mode 100644 index 000000000..84e345d22 Binary files /dev/null and b/resources/images/service_edit.png differ diff --git a/resources/images/service_item_notes.png b/resources/images/service_item_notes.png new file mode 100644 index 000000000..59991dba8 Binary files /dev/null and b/resources/images/service_item_notes.png differ diff --git a/resources/images/service_notes.png b/resources/images/service_notes.png new file mode 100644 index 000000000..d79aa5151 Binary files /dev/null and b/resources/images/service_notes.png differ diff --git a/resources/images/song_edit.png b/resources/images/song_edit.png index d79aa5151..84e345d22 100644 Binary files a/resources/images/song_edit.png and b/resources/images/song_edit.png differ diff --git a/resources/innosetup/LICENSE.txt b/resources/innosetup/LICENSE.txt new file mode 100644 index 000000000..d511905c1 --- /dev/null +++ b/resources/innosetup/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/resources/innosetup/OpenLP-2.0.iss b/resources/innosetup/OpenLP-2.0.iss new file mode 100644 index 000000000..4b25d6a28 --- /dev/null +++ b/resources/innosetup/OpenLP-2.0.iss @@ -0,0 +1,72 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "OpenLP" +#define MyAppVerName "OpenLP 2.0" +#define MyAppPublisher "OpenLP Developers" +#define MyAppURL "http://openlp.org/" +#define MyAppExeName "openlp.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{AA7699FA-B2D2-43F4-8A70-D497D03C9485} +AppName={#MyAppName} +AppVerName={#MyAppVerName} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={pf}\{#MyAppName} +DefaultGroupName=OpenLP 2.0 +AllowNoIcons=yes +LicenseFile=LICENSE.txt +OutputBaseFilename=OpenLP-2.0-setup +Compression=lzma +SolidCompression=true +SetupIconFile=OpenLP.ico + +[Languages] +Name: english; MessagesFile: compiler:Default.isl +Name: basque; MessagesFile: compiler:Languages\Basque.isl +Name: brazilianportuguese; MessagesFile: compiler:Languages\BrazilianPortuguese.isl +Name: catalan; MessagesFile: compiler:Languages\Catalan.isl +Name: czech; MessagesFile: compiler:Languages\Czech.isl +Name: danish; MessagesFile: compiler:Languages\Danish.isl +Name: dutch; MessagesFile: compiler:Languages\Dutch.isl +Name: finnish; MessagesFile: compiler:Languages\Finnish.isl +Name: french; MessagesFile: compiler:Languages\French.isl +Name: german; MessagesFile: compiler:Languages\German.isl +Name: hebrew; MessagesFile: compiler:Languages\Hebrew.isl +Name: hungarian; MessagesFile: compiler:Languages\Hungarian.isl +Name: italian; MessagesFile: compiler:Languages\Italian.isl +Name: japanese; MessagesFile: compiler:Languages\Japanese.isl +Name: norwegian; MessagesFile: compiler:Languages\Norwegian.isl +Name: polish; MessagesFile: compiler:Languages\Polish.isl +Name: portuguese; MessagesFile: compiler:Languages\Portuguese.isl +Name: russian; MessagesFile: compiler:Languages\Russian.isl +Name: slovak; MessagesFile: compiler:Languages\Slovak.isl +Name: slovenian; MessagesFile: compiler:Languages\Slovenian.isl +Name: spanish; MessagesFile: compiler:Languages\Spanish.isl + +[Tasks] +Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked +Name: quicklaunchicon; Description: {cm:CreateQuickLaunchIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked + +[Files] +Source: C:\Documents and Settings\raoul\My Documents\My Projects\openlp\pyinstaller\dist\openlp\*; DestDir: {app}; Flags: ignoreversion +Source: C:\Documents and Settings\raoul\My Documents\My Projects\openlp\pyinstaller\dist\openlp\plugins\*; DestDir: {app}\plugins; Flags: ignoreversion recursesubdirs createallsubdirs +Source: C:\Documents and Settings\raoul\My Documents\My Projects\openlp\pyinstaller\dist\openlp\Microsoft.VC90.CRT\*; DestDir: {app}\Microsoft.VC90.CRT; Flags: ignoreversion recursesubdirs createallsubdirs +Source: C:\Documents and Settings\raoul\My Documents\My Projects\openlp\pyinstaller\dist\openlp\qt4_plugins\*; DestDir: {app}\qt4_plugins; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: {group}\{#MyAppName}; Filename: {app}\{#MyAppExeName} +Name: {group}\{cm:ProgramOnTheWeb,{#MyAppName}}; Filename: {#MyAppURL} +Name: {group}\{cm:UninstallProgram,{#MyAppName}}; Filename: {uninstallexe} +Name: {commondesktop}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: desktopicon +Name: {userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: quicklaunchicon + +[Run] +Filename: {app}\{#MyAppExeName}; Description: {cm:LaunchProgram,{#MyAppName}}; Flags: nowait postinstall skipifsilent diff --git a/resources/innosetup/OpenLP.ico b/resources/innosetup/OpenLP.ico new file mode 100644 index 000000000..b275542c3 Binary files /dev/null and b/resources/innosetup/OpenLP.ico differ diff --git a/resources/openlp.desktop b/resources/openlp.desktop new file mode 100644 index 000000000..8791c2d8f --- /dev/null +++ b/resources/openlp.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=OpenLP +GenericName=Church lyrics projection +Exec=openlp +Icon=openlp +StartupNotify=true +Terminal=False +Type=Application +Categories=AudioVideo diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py new file mode 100644 index 000000000..8b7d6b8a2 --- /dev/null +++ b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py @@ -0,0 +1,3 @@ +hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', + 'openlp.plugins.presentations.lib.powerpointcontroller', + 'openlp.plugins.presentations.lib.pptviewcontroller'] \ No newline at end of file diff --git a/resources/pyinstaller/hook-openlp.py b/resources/pyinstaller/hook-openlp.py new file mode 100644 index 000000000..bd97e4aec --- /dev/null +++ b/resources/pyinstaller/hook-openlp.py @@ -0,0 +1,9 @@ +hiddenimports = ['plugins.songs.songsplugin', + 'plugins.bibles.bibleplugin', + 'plugins.presentations.presentationplugin', + 'plugins.media.mediaplugin', + 'plugins.images.imageplugin', + 'plugins.custom.customplugin', + 'plugins.songusage.songusageplugin', + 'plugins.remotes.remoteplugin', + 'plugins.alerts.alertsplugin'] \ No newline at end of file diff --git a/scripts/bible-1to2-converter.py b/scripts/bible-1to2-converter.py new file mode 100755 index 000000000..b1e9b6897 --- /dev/null +++ b/scripts/bible-1to2-converter.py @@ -0,0 +1,306 @@ +#!/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-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard # +# --------------------------------------------------------------------------- # +# 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 sys +import os +import sqlite +import sqlite3 + +from optparse import OptionParser +from traceback import format_tb as get_traceback + +# Some global options to be used throughout the import process +verbose = False +debug = False +old_cursor = None +new_cursor = None + +# SQL create statments +create_statements = [ + (u'table "book"', u"""CREATE TABLE book ( + id INTEGER NOT NULL, + testament_id INTEGER, + name VARCHAR(30), + abbreviation VARCHAR(5), + PRIMARY KEY (id), + FOREIGN KEY(testament_id) REFERENCES testament (id) +)"""), + (u'table "metadata"', u"""CREATE TABLE metadata ( + "key" VARCHAR(255) NOT NULL, + value VARCHAR(255), + PRIMARY KEY ("key") +)"""), + (u'table "testament"', u"""CREATE TABLE testament ( + id INTEGER NOT NULL, + name VARCHAR(30), + PRIMARY KEY (id) +)"""), + (u'table "verse"', u"""CREATE TABLE verse ( + id INTEGER NOT NULL, + book_id INTEGER, + chapter INTEGER, + verse INTEGER, + text TEXT, + PRIMARY KEY (id), + FOREIGN KEY(book_id) REFERENCES book (id) +)"""), + (u'index "idx_abbrev"', + u"""CREATE INDEX idx_abbrev ON book (abbreviation, id)"""), + (u'index "idx_chapter_verse_book', + u"""CREATE INDEX idx_chapter_verse_book ON verse (chapter, verse, book_id, id)"""), + (u'index "idx_chapter_verse_text"', + u"""CREATE INDEX idx_chapter_verse_text ON verse (text, verse, book_id, id)"""), + (u'index "idx_name"', + u"""CREATE INDEX idx_name ON book (name, id)""") +] + +def display_sql(sql, params): + prepared_params = [] + for param in params: + if isinstance(param, basestring): + prepared_params.append(u'"%s"' % param) + elif isinstance(param, (int, long)): + prepared_params.append(u'%d' % param) + elif isinstance(param, (float, complex)): + prepared_params.append(u'%f' % param) + else: + prepared_params.append(u'"%s"' % str(param)) + for prepared_param in prepared_params: + sql = sql.replace(u'?', prepared_param, 1) + return sql + +def create_database(): + global new_cursor, create_statements + if debug or verbose: + print 'Creating new database:' + else: + print 'Creating new database...', + for statement_type, sql_create in create_statements: + if debug: + print '... ', sql_create.replace('\n', ' ').replace(' ', ' ') + elif verbose: + print '... creating %s...' % statement_type, + new_cursor.execute(sql_create) + if verbose and not debug: + print 'done.' + if not verbose and not debug: + print 'done.' + +def import_bible(): + global old_cursor, new_cursor, debug, verbose + if debug or verbose: + print 'Importing metadata:' + else: + print 'Importing metadata...', + if debug: + print '... SELECT "key", "value" FROM metadata' + elif verbose: + print '... fetching metadata from old database...', + old_cursor.execute(u'SELECT "key", "value" FROM metadata') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + for row in rows: + key = unicode(row[0], u'cp1252') + value = unicode(row[1], u'cp1252') + sql_insert = u'INSERT INTO metadata '\ + '("key", "value") '\ + 'VALUES (?, ?)' + sql_params = (key, value) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % key + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing testaments:' + else: + print 'Importing testaments...', + if debug: + print '... SELECT id, name FROM testament' + elif verbose: + print '... fetching testaments from old database...', + old_cursor.execute(u'SELECT id, name FROM testament') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + for row in rows: + id = int(row[0]) + name = unicode(row[1], u'cp1252') + sql_insert = u'INSERT INTO testament '\ + '(id, name) '\ + 'VALUES (?, ?)' + sql_params = (id, name) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % name + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing books:' + else: + print 'Importing books...', + if debug: + print '... SELECT id, testament_id, name, abbreviation FROM book' + elif verbose: + print '... fetching books from old database...', + old_cursor.execute(u'SELECT id, testament_id, name, abbreviation FROM book') + rows = old_cursor.fetchall() + if not debug and verbose: + print 'done.' + book_map = {} + for row in rows: + testament_id = int(row[1]) + name = unicode(row[2], u'cp1252') + abbreviation = unicode(row[3], u'cp1252') + sql_insert = u'INSERT INTO book '\ + '(id, testament_id, name, abbreviation) '\ + 'VALUES (NULL, ?, ?, ?)' + sql_params = (testament_id, name, abbreviation) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s"' % name + new_cursor.execute(sql_insert, sql_params) + book_map[row[0]] = new_cursor.lastrowid + if debug: + print ' >>> (old) books.id =', row[0], ' (new) books.id =', book_map[row[0]] + if not verbose and not debug: + print 'done.' + if debug or verbose: + print 'Importing verses:' + else: + print 'Importing verses...', + if debug: + print '... SELECT id, book_id, chapter, verse, text || \'\' AS text FROM verse...', + elif verbose: + print '... fetching verses from old database...', + old_cursor.execute(u'SELECT id, book_id, chapter, verse, text || \'\' AS text FROM verse') + rows = old_cursor.fetchall() + if debug or verbose: + print 'done.' + song_map = {} + for row in rows: + book_id = int(row[1]) + chapter = int(row[2]) + verse = int(row[3]) + text = unicode(row[4], u'cp1252') + sql_insert = u'INSERT INTO verse '\ + '(id, book_id, chapter, verse, text) '\ + 'VALUES (NULL, ?, ?, ?, ?)' + sql_params = (book_map[book_id], chapter, verse, text) + if debug: + print '...', display_sql(sql_insert, sql_params) + elif verbose: + print '... importing "%s..."' % text[:17] + new_cursor.execute(sql_insert, sql_params) + if not verbose and not debug: + print 'done.' + +def main(old_db, new_db): + global old_cursor, new_cursor, debug + old_connection = None + new_connection = None + try: + old_connection = sqlite.connect(old_db) + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem connecting to the old database:', errormsg + return 1 + try: + new_connection = sqlite3.connect(new_db) + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem creating the new database:', errormsg + return 1 + old_cursor = old_connection.cursor() + new_cursor = new_connection.cursor() + try: + create_database() + except: + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem creating the database:', errormsg + return 1 + try: + import_bible() + new_connection.commit() + except: + new_connection.rollback() + if debug: + errormsg = '\n' + ''.join(get_traceback(sys.exc_info()[2]))\ + + str(sys.exc_info()[1]) + else: + errormsg = sys.exc_info()[1] + print 'There was a problem importing songs:', errormsg + return 1 + print 'Import complete.' + +if __name__ == u'__main__': + option_parser = OptionParser(usage='Usage: %prog [options] OLDDATABASE NEWDATABASE') + option_parser.add_option('-o', '--overwrite', dest='overwrite', default=False, + action=u'store_true', help='Overwrite database file if it already exists.') + option_parser.add_option('-v', '--verbose', dest='verbose', default=False, + action=u'store_true', help='Outputs additional progress data.') + option_parser.add_option('-d', '--debug', dest='debug', default=False, + action=u'store_true', help='Outputs raw SQL statements (overrides verbose).') + options, arguments = option_parser.parse_args() + if len(arguments) < 2: + if len(arguments) == 0: + option_parser.error('Please specify an old database and a new database.') + else: + option_parser.error('Please specify a new database.') + old_db = os.path.abspath(arguments[0]) + new_db = os.path.abspath(arguments[1]) + if not os.path.isfile(old_db): + option_parser.error('Old database file ("%s") is not a file.' % old_db) + if not os.path.exists(old_db): + option_parser.error('Old database file ("%s") does not exist.' % old_db) + if os.path.exists(new_db): + if not options.overwrite: + option_parser.error('New database file ("%s") exists. If you want to overwrite it, specify the --overwrite option.' % new_db) + else: + if not os.path.isfile(new_db): + option_parser.error('New database file ("%s") is not a file.' % new_db) + os.unlink(new_db) + verbose = options.verbose + debug = options.debug + main(old_db, new_db) diff --git a/scripts/get-strings.py b/scripts/get-strings.py index 8d9dad174..ed3cdcb41 100755 --- a/scripts/get-strings.py +++ b/scripts/get-strings.py @@ -24,6 +24,7 @@ ############################################################################### import os +from cgi import escape from ast import parse, NodeVisitor, Str ts_file = u"""<?xml version="1.0" encoding="utf-8"?> @@ -45,7 +46,8 @@ ts_message = u""" <message> class StringExtractor(NodeVisitor): - def __init__(self, strings, filename): + def __init__(self, strings, filename, base_path): + self.base_path = base_path self.filename = filename self.strings = strings self.classname = 'unknown' @@ -58,10 +60,10 @@ class StringExtractor(NodeVisitor): if hasattr(node.func, 'attr') and node.func.attr == 'trUtf8' and isinstance(node.args[0], Str): string = node.args[0].s key = '%s-%s' % (self.classname, string) - self.strings[key] = [self.classname, self.filename, node.lineno, string] + self.strings[key] = [self.classname, self.filename[len(self.base_path) + 1:], node.lineno, escape(string)] self.generic_visit(node) -def parse_file(filename, strings): +def parse_file(base_path, filename, strings): file = open(filename, u'r') try: ast = parse(file.read()) @@ -70,7 +72,7 @@ def parse_file(filename, strings): return file.close() - StringExtractor(strings, filename).visit(ast) + StringExtractor(strings, filename, base_path).visit(ast) def write_file(filename, strings): translation_file = u'' @@ -94,15 +96,18 @@ def write_file(filename, strings): def main(): strings = {} - start_dir = os.path.abspath(u'.') + start_dir = os.path.abspath(u'..') for root, dirs, files in os.walk(start_dir): for file in files: if file.endswith(u'.py'): print u'Parsing "%s"' % file - parse_file(os.path.join(root, file), strings) + parse_file(start_dir, os.path.join(root, file), strings) print u'Generating TS file...', - write_file(os.path.join(start_dir, u'i18n', u'openlp_en.ts'), strings) + write_file(os.path.join(start_dir, u'resources', u'i18n', u'openlp_en.ts'), strings) print u'done.' if __name__ == u'__main__': - main() \ No newline at end of file + if os.path.split(os.path.abspath(u'.'))[1] != u'scripts': + print u'You need to run this script from the scripts directory.' + else: + main() diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 8c34238ff..d6f4b7503 --- a/setup.py +++ b/setup.py @@ -1,38 +1,56 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 +#!/usr/bin/env python -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2010 Raoul Snyman # -# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # -# Carsten Tinggaard # -# --------------------------------------------------------------------------- # -# 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 setuptools import setup, find_packages -from setuptools import setup +VERSION_FILE = 'openlp/.version' + +try: + from bzrlib.branch import Branch + b = Branch.open_containing('.')[0] + b.lock_read() + try: + # Get the branch's latest revision number. + revno = b.revno() + # Convert said revision number into a bzr revision id. + revision_id = b.dotted_revno_to_revision_id((revno,)) + # Get a dict of tags, with the revision id as the key. + tags = b.tags.get_reverse_tag_dict() + # Check if the latest + if revision_id in tags: + version = u'%s' % tags[revision_id][0] + else: + version = '%s-bzr%s' % (sorted(b.tags.get_tag_dict().keys())[-1], revno) + ver_file = open(VERSION_FILE, u'w') + ver_file.write(version) + ver_file.close() + finally: + b.unlock() +except: + ver_file = open(VERSION_FILE, u'r') + version = ver_file.read().strip() + ver_file.close() -APP = ['openlp.pyw'] -OPTIONS = {'argv_emulation': True, 'includes': ['sip', 'PyQt4']} setup( - name='openlp.org', - version='1.9.0', - url='http://www.openlp.org/', - app=APP, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) \ No newline at end of file + name='OpenLP', + version=version, + description="Open source Church presentation and lyrics projection application.", + long_description="""\ +OpenLP (previously openlp.org) is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if PowerPoint is installed) for church worship using a computer and a data projector.""", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='open source church presentation lyrics projection song bible display project', + author='Raoul Snyman', + author_email='raoulsnyman@openlp.org', + url='http://openlp.org/', + license='GNU General Public License', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + scripts=['openlp.pyw', 'scripts/openlp-1to2-converter.py', 'scripts/bible-1to2-converter.py'], + include_package_data=True, + zip_safe=False, + install_requires=[ + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """ +) diff --git a/version.txt b/version.txt deleted file mode 100644 index fbb45f3be..000000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -1.9.0-694