diff --git a/copyright.txt b/copyright.txt index 0fb988622..df5563844 100644 --- a/copyright.txt +++ b/copyright.txt @@ -7,7 +7,7 @@ # Copyright (c) 2008-2011 Raoul Snyman # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # -# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # --------------------------------------------------------------------------- # diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index e24045883..e104f4022 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -144,6 +144,59 @@ def image_to_byte(image): # convert to base64 encoding so does not get missed! return byte_array.toBase64() +def create_thumb(image_path, thumb_path, return_icon=True, size=None): + """ + Create a thumbnail from the given image path and depending on + ``return_icon`` it returns an icon from this thumb. + + ``image_path`` + The image file to create the icon from. + + ``thumb_path`` + The filename to save the thumbnail to. + + ``return_icon`` + States if an icon should be build and returned from the thumb. Defaults + to ``True``. + + ``size`` + Allows to state a own size to use. Defaults to ``None``, which means + that a default height of 88 is used. + """ + ext = os.path.splitext(thumb_path)[1].lower() + reader = QtGui.QImageReader(image_path) + if size is None: + ratio = float(reader.size().width()) / float(reader.size().height()) + reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) + else: + reader.setScaledSize(size) + thumb = reader.read() + thumb.save(thumb_path, ext[1:]) + if not return_icon: + return + if os.path.exists(thumb_path): + return build_icon(unicode(thumb_path)) + # Fallback for files with animation support. + return build_icon(unicode(image_path)) + +def validate_thumb(file_path, thumb_path): + """ + Validates whether an file's thumb still exists and if is up to date. + **Note**, you must **not** call this function, before checking the + existence of the file. + + ``file_path`` + The path to the file. The file **must** exist! + + ``thumb_path`` + The path to the thumb. + """ + if not os.path.exists(unicode(thumb_path)): + return False + image_date = os.stat(unicode(file_path)).st_mtime + thumb_date = os.stat(unicode(thumb_path)).st_mtime + return image_date <= thumb_date + def resize_image(image_path, width, height, background=u'#000000'): """ Resize an image to fit on the current screen. @@ -158,7 +211,7 @@ def resize_image(image_path, width, height, background=u'#000000'): The new image height. ``background`` - The background colour defaults to black. + The background colour. Defaults to black. DO NOT REMOVE THE DEFAULT BACKGROUND VALUE! """ diff --git a/openlp/core/lib/eventreceiver.py b/openlp/core/lib/eventreceiver.py index 7c0115f89..1b957e5df 100644 --- a/openlp/core/lib/eventreceiver.py +++ b/openlp/core/lib/eventreceiver.py @@ -51,9 +51,6 @@ class EventReceiver(QtCore.QObject): ``config_screen_changed`` The display monitor has been changed - ``slidecontroller_{live|preview}_first`` - Moves to the first slide - ``slidecontroller_{live|preview}_next`` Moves to the next slide @@ -66,9 +63,6 @@ class EventReceiver(QtCore.QObject): ``slidecontroller_{live|preview}_previous_noloop`` Moves to the previous slide, without auto advance - ``slidecontroller_{live|preview}_last`` - Moves to the last slide - ``slidecontroller_{live|preview}_set`` Moves to a specific slide, by index @@ -82,11 +76,6 @@ class EventReceiver(QtCore.QObject): ``slidecontroller_{live|preview}_changed`` Broadcasts that the slidecontroller has changed the current slide - ``slidecontroller_{live|preview}_text_request`` - Request the text for the current item in the controller - Returns a slidecontroller_{live|preview}_text_response with an - array of dictionaries with the tag and verse text - ``slidecontroller_{live|preview}_blank`` Request that the output screen is blanked diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 6f56cf8b2..8e31d8950 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -34,8 +34,8 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \ log = logging.getLogger(__name__) -# FIXME: Add html5 doctype. However, do not break theme gradients. HTMLSRC = u""" + OpenLP Display @@ -404,7 +404,7 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left top, left bottom, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string( \ @@ -412,7 +412,7 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left top, right bottom, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string \ @@ -420,20 +420,21 @@ def build_background_css(item, width, height): background = \ u'background: ' \ u'-webkit-gradient(linear, left bottom, right top, ' \ - 'from(%s), to(%s))' % (theme.background_start_color, + 'from(%s), to(%s)) fixed' % (theme.background_start_color, theme.background_end_color) elif theme.background_direction == \ BackgroundGradientType.to_string \ (BackgroundGradientType.Vertical): background = \ u'background: -webkit-gradient(linear, left top, ' \ - u'right top, from(%s), to(%s))' % \ + u'right top, from(%s), to(%s)) fixed' % \ (theme.background_start_color, theme.background_end_color) else: background = \ u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \ - u'50%%, %s, from(%s), to(%s))' % (width, width, width, - theme.background_start_color, theme.background_end_color) + u'50%%, %s, from(%s), to(%s)) fixed' % (width, width, + width, theme.background_start_color, + theme.background_end_color) return background def build_lyrics_css(item, webkitvers): @@ -557,11 +558,15 @@ def build_lyrics_format_css(theme, width, height): left_margin = int(theme.font_main_outline_size) * 2 else: left_margin = 0 - lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \ + justify = u'white-space:pre-wrap;' + # fix tag incompatibilities + if theme.display_horizontal_align == HorizontalType.Justify: + justify = u'' + lyrics = u'%s word-wrap: break-word; ' \ 'text-align: %s; vertical-align: %s; font-family: %s; ' \ 'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \ 'padding: 0; padding-left: %spx; width: %spx; height: %spx; ' % \ - (align, valign, theme.font_main_name, theme.font_main_size, + (justify, align, valign, theme.font_main_name, theme.font_main_size, theme.font_main_color, 100 + int(theme.font_main_line_adjustment), left_margin, width, height) if theme.font_main_outline: diff --git a/openlp/core/lib/mailto/LICENSE b/openlp/core/lib/mailto/LICENSE deleted file mode 100644 index d8ab2d8d2..000000000 --- a/openlp/core/lib/mailto/LICENSE +++ /dev/null @@ -1,38 +0,0 @@ -PSF LICENSE AGREEMENT FOR PYTHON 2.7.1 - - 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), - and the Individual or Organization ("Licensee") accessing and otherwise - using Python 2.7.1 software in source or binary form and its associated - documentation. - 2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to - reproduce, analyze, test, perform and/or display publicly, prepare - derivative works, distribute, and otherwise use Python 2.7.1 alone or in any - derivative version, provided, however, that PSF's License Agreement and - PSF's notice of copyright, i.e., "Copyright (c) 2001-2010 Python Software - Foundation; All Rights Reserved" are retained in Python 2.7.1 alone or in - any derivative version prepared by Licensee. - 3. In the event Licensee prepares a derivative work that is based on or - incorporates Python 2.7.1 or any part thereof, and wants to make the - derivative work available to others as provided herein, then Licensee hereby - agrees to include in any such work a brief summary of the changes made to - Python 2.7.1. - 4. PSF is making Python 2.7.1 available to Licensee on an "AS IS" basis. PSF - MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF - EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION - OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT - THE USE OF PYTHON 2.7.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.1 FOR - ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.1, OR ANY DERIVATIVE - THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - 6. This License Agreement will automatically terminate upon a material breach - of its terms and conditions. - 7. Nothing in this License Agreement shall be deemed to create any relationship - of agency, partnership, or joint venture between PSF and Licensee. This - License Agreement does not grant permission to use PSF trademarks or trade - name in a trademark sense to endorse or promote products or services of - Licensee, or any third party. - 8. By copying, installing or otherwise using Python 2.7.1, Licensee agrees to - be bound by the terms and conditions of this License Agreement. - diff --git a/openlp/core/lib/mailto/__init__.py b/openlp/core/lib/mailto/__init__.py deleted file mode 100644 index f05ebfdee..000000000 --- a/openlp/core/lib/mailto/__init__.py +++ /dev/null @@ -1,321 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - -############################################################################### -# Utilities for opening files or URLs in the registered default application # -# and for sending e-mail using the user's preferred composer. # -# --------------------------------------------------------------------------- # -# Copyright (c) 2007 Antonio Valentino # -# All rights reserved. # -# --------------------------------------------------------------------------- # -# This program offered under the PSF License as published by the Python # -# Software Foundation. # -# # -# The license text can be found at http://docs.python.org/license.html # -# # -# This code is taken from: http://code.activestate.com/recipes/511443 # -# Modified for use in OpenLP # -############################################################################### - -__version__ = u'1.1' -__all__ = [u'open', u'mailto'] - -import os -import sys -import webbrowser -import subprocess - -from email.Utils import encode_rfc2231 - -_controllers = {} -_open = None - - -class BaseController(object): - """ - Base class for open program controllers. - """ - - def __init__(self, name): - self.name = name - - def open(self, filename): - raise NotImplementedError - - -class Controller(BaseController): - """ - Controller for a generic open program. - """ - - def __init__(self, *args): - super(Controller, self).__init__(os.path.basename(args[0])) - self.args = list(args) - - def _invoke(self, cmdline): - if sys.platform[:3] == u'win': - closefds = False - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - else: - closefds = True - startupinfo = None - - if (os.environ.get(u'DISPLAY') or sys.platform[:3] == u'win' or \ - sys.platform == u'darwin'): - inout = file(os.devnull, u'r+') - else: - # for TTY programs, we need stdin/out - inout = None - - # if possible, put the child precess in separate process group, - # so keyboard interrupts don't affect child precess as well as - # Python - setsid = getattr(os, u'setsid', None) - if not setsid: - setsid = getattr(os, u'setpgrp', None) - - pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout, - stderr=inout, close_fds=closefds, preexec_fn=setsid, - startupinfo=startupinfo) - - # It is assumed that this kind of tools (gnome-open, kfmclient, - # exo-open, xdg-open and open for OSX) immediately exit after lauching - # the specific application - returncode = pipe.wait() - if hasattr(self, u'fixreturncode'): - returncode = self.fixreturncode(returncode) - return not returncode - - def open(self, filename): - if isinstance(filename, basestring): - cmdline = self.args + [filename] - else: - # assume it is a sequence - cmdline = self.args + filename - try: - return self._invoke(cmdline) - except OSError: - return False - - -# Platform support for Windows -if sys.platform[:3] == u'win': - - class Start(BaseController): - """ - Controller for the win32 start progam through os.startfile. - """ - - def open(self, filename): - try: - os.startfile(filename) - except WindowsError: - # [Error 22] No application is associated with the specified - # file for this operation: '' - return False - else: - return True - - _controllers[u'windows-default'] = Start(u'start') - _open = _controllers[u'windows-default'].open - - -# Platform support for MacOS -elif sys.platform == u'darwin': - _controllers[u'open'] = Controller(u'open') - _open = _controllers[u'open'].open - - -# Platform support for Unix -else: - - import commands - - # @WARNING: use the private API of the webbrowser module - from webbrowser import _iscommand - - class KfmClient(Controller): - """ - Controller for the KDE kfmclient program. - """ - - def __init__(self, kfmclient=u'kfmclient'): - super(KfmClient, self).__init__(kfmclient, u'exec') - self.kde_version = self.detect_kde_version() - - def detect_kde_version(self): - kde_version = None - try: - info = commands.getoutput(u'kfmclient --version') - - for line in info.splitlines(): - if line.startswith(u'KDE'): - kde_version = line.split(u':')[-1].strip() - break - except (OSError, RuntimeError): - pass - - return kde_version - - def fixreturncode(self, returncode): - if returncode is not None and self.kde_version > u'3.5.4': - return returncode - else: - return os.EX_OK - - def detect_desktop_environment(): - """ - Checks for known desktop environments - - Return the desktop environments name, lowercase (kde, gnome, xfce) - or "generic" - """ - - desktop_environment = u'generic' - - if os.environ.get(u'KDE_FULL_SESSION') == u'true': - desktop_environment = u'kde' - elif os.environ.get(u'GNOME_DESKTOP_SESSION_ID'): - desktop_environment = u'gnome' - else: - try: - info = commands.getoutput(u'xprop -root _DT_SAVE_MODE') - if u' = "xfce4"' in info: - desktop_environment = u'xfce' - except (OSError, RuntimeError): - pass - - return desktop_environment - - - def register_X_controllers(): - if _iscommand(u'kfmclient'): - _controllers[u'kde-open'] = KfmClient() - - for command in (u'gnome-open', u'exo-open', u'xdg-open'): - if _iscommand(command): - _controllers[command] = Controller(command) - - - def get(): - controllers_map = { - u'gnome': u'gnome-open', - u'kde': u'kde-open', - u'xfce': u'exo-open', - } - - desktop_environment = detect_desktop_environment() - - try: - controller_name = controllers_map[desktop_environment] - return _controllers[controller_name].open - - except KeyError: - if _controllers.has_key(u'xdg-open'): - return _controllers[u'xdg-open'].open - else: - return webbrowser.open - - if os.environ.get(u'DISPLAY'): - register_X_controllers() - _open = get() - - -def open(filename): - """ - Open a file or an URL in the registered default application. - """ - - return _open(filename) - - -def _fix_addresses(**kwargs): - for headername in (u'address', u'to', u'cc', u'bcc'): - try: - headervalue = kwargs[headername] - if not headervalue: - del kwargs[headername] - continue - elif not isinstance(headervalue, basestring): - # assume it is a sequence - headervalue = u','.join(headervalue) - except KeyError: - pass - except TypeError: - raise TypeError(u'string or sequence expected for "%s", %s ' - u'found' % (headername, type(headervalue).__name__)) - else: - translation_map = {u'%': u'%25', u'&': u'%26', u'?': u'%3F'} - for char, replacement in translation_map.items(): - headervalue = headervalue.replace(char, replacement) - kwargs[headername] = headervalue - - return kwargs - - -def mailto_format(**kwargs): - """ - Compile mailto string from call parameters - """ - # @TODO: implement utf8 option - - kwargs = _fix_addresses(**kwargs) - parts = [] - for headername in (u'to', u'cc', u'bcc', u'subject', u'body', u'attach'): - if kwargs.has_key(headername): - headervalue = kwargs[headername] - if not headervalue: - continue - if headername in (u'address', u'to', u'cc', u'bcc'): - parts.append(u'%s=%s' % (headername, headervalue)) - else: - headervalue = encode_rfc2231(headervalue) # @TODO: check - parts.append(u'%s=%s' % (headername, headervalue)) - - mailto_string = u'mailto:%s' % kwargs.get(u'address', '') - if parts: - mailto_string = u'%s?%s' % (mailto_string, u'&'.join(parts)) - - return mailto_string - - -def mailto(address, to=None, cc=None, bcc=None, subject=None, body=None, - attach=None): - """ - Send an e-mail using the user's preferred composer. - - Open the user's preferred e-mail composer in order to send a mail to - address(es) that must follow the syntax of RFC822. Multiple addresses - may be provided (for address, cc and bcc parameters) as separate - arguments. - - All parameters provided are used to prefill corresponding fields in - the user's e-mail composer. The user will have the opportunity to - change any of this information before actually sending the e-mail. - - ``address`` - specify the destination recipient - - ``cc`` - specify a recipient to be copied on the e-mail - - ``bcc`` - specify a recipient to be blindly copied on the e-mail - - ``subject`` - specify a subject for the e-mail - - ``body`` - specify a body for the e-mail. Since the user will be able to make - changes before actually sending the e-mail, this can be used to provide - the user with a template for the e-mail text may contain linebreaks - - ``attach`` - specify an attachment for the e-mail. file must point to an existing - file - """ - - mailto_string = mailto_format(**locals()) - return open(mailto_string) - diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 5366f3f68..1bddb2d93 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -425,44 +425,6 @@ class MediaManagerItem(QtGui.QWidget): count += 1 return filelist - def validate(self, image, thumb): - """ - Validates whether an image still exists and, if it does, is the - thumbnail representation of the image up to date. - """ - if not os.path.exists(unicode(image)): - return False - if os.path.exists(thumb): - imageDate = os.stat(unicode(image)).st_mtime - thumbDate = os.stat(unicode(thumb)).st_mtime - # If image has been updated rebuild icon - if imageDate > thumbDate: - self.iconFromFile(image, thumb) - else: - self.iconFromFile(image, thumb) - return True - - def iconFromFile(self, image_path, thumb_path): - """ - Create a thumbnail icon from a given image. - - ``image_path`` - The image file to create the icon from. - - ``thumb_path`` - The filename to save the thumbnail to. - """ - ext = os.path.splitext(thumb_path)[1].lower() - reader = QtGui.QImageReader(image_path) - ratio = float(reader.size().width()) / float(reader.size().height()) - reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) - thumb = reader.read() - thumb.save(thumb_path, ext[1:]) - if os.path.exists(thumb_path): - return build_icon(unicode(thumb_path)) - # Fallback for files with animation support. - return build_icon(unicode(image_path)) - def loadList(self, list): raise NotImplementedError(u'MediaManagerItem.loadList needs to be ' u'defined by the plugin') diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index abfd658ba..02f6970ac 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -44,6 +44,7 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n' \ 'Get those children out of the muddy, muddy \n' \ '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' +VERSE_FOR_LINE_COUNT = u'\n'.join(map(unicode, xrange(50))) FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456'] class Renderer(object): @@ -56,14 +57,14 @@ class Renderer(object): def __init__(self, imageManager, themeManager): """ - Initialise the render manager. + Initialise the renderer. ``imageManager`` - A ImageManager instance which takes care of e. g. caching and resizing + A imageManager instance which takes care of e. g. caching and resizing images. ``themeManager`` - The ThemeManager instance, used to get the current theme details. + The themeManager instance, used to get the current theme details. """ log.debug(u'Initialisation started') self.themeManager = themeManager @@ -80,7 +81,7 @@ class Renderer(object): def update_display(self): """ - Updates the render manager's information about the current screen. + Updates the renderer's information about the current screen. """ log.debug(u'Update Display') self._calculate_default() @@ -190,7 +191,7 @@ class Renderer(object): serviceItem.theme = theme_data if self.force_page: # make big page for theme edit dialog to get line count - serviceItem.add_from_text(u'', VERSE + VERSE + VERSE) + serviceItem.add_from_text(u'', VERSE_FOR_LINE_COUNT) else: self.imageManager.del_image(theme_data.theme_name) serviceItem.add_from_text(u'', VERSE) @@ -200,7 +201,8 @@ class Renderer(object): if not self.force_page: self.display.buildHtml(serviceItem) raw_html = serviceItem.get_rendered_frame(0) - preview = self.display.text(raw_html) + self.display.text(raw_html) + preview = self.display.preview() # Reset the real screen size for subsequent render requests self._calculate_default() return preview @@ -224,14 +226,10 @@ class Renderer(object): # Bibles if item.is_capable(ItemCapabilities.CanWordSplit): pages = self._paginate_slide_words(text.split(u'\n'), line_end) - else: - # Clean up line endings. - lines = self._lines_split(text) - pages = self._paginate_slide(lines, line_end) - # Songs and Custom - if item.is_capable(ItemCapabilities.CanSoftBreak) and \ - len(pages) > 1 and u'[---]' in text: - pages = [] + # Songs and Custom + elif item.is_capable(ItemCapabilities.CanSoftBreak): + pages = [] + if u'[---]' in text: while True: slides = text.split(u'\n[---]\n', 2) # If there are (at least) two occurrences of [---] we use @@ -272,6 +270,11 @@ class Renderer(object): lines = text.strip(u'\n').split(u'\n') pages.extend(self._paginate_slide(lines, line_end)) break + else: + # Clean up line endings. + pages = self._paginate_slide(text.split(u'\n'), line_end) + else: + pages = self._paginate_slide(text.split(u'\n'), line_end) new_pages = [] for page in pages: while page.endswith(u'
'): @@ -302,21 +305,37 @@ class Renderer(object): The theme to build a text block for. """ log.debug(u'_build_text_rectangle') - main_rect = None - footer_rect = None + main_rect = self.get_main_rectangle(theme) + footer_rect = self.get_footer_rectangle(theme) + self._set_text_rectangle(main_rect, footer_rect) + + def get_main_rectangle(self, theme): + """ + Calculates the placement and size of the main rectangle. + + ``theme`` + The theme information + """ if not theme.font_main_override: - main_rect = QtCore.QRect(10, 0, self.width - 20, self.footer_start) + return QtCore.QRect(10, 0, self.width - 20, self.footer_start) else: - main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y, + return QtCore.QRect(theme.font_main_x, theme.font_main_y, theme.font_main_width - 1, theme.font_main_height - 1) + + def get_footer_rectangle(self, theme): + """ + Calculates the placement and size of the footer rectangle. + + ``theme`` + The theme information + """ if not theme.font_footer_override: - footer_rect = QtCore.QRect(10, self.footer_start, self.width - 20, + return QtCore.QRect(10, self.footer_start, self.width - 20, self.height - self.footer_start) else: - footer_rect = QtCore.QRect(theme.font_footer_x, + return QtCore.QRect(theme.font_footer_x, theme.font_footer_y, theme.font_footer_width - 1, theme.font_footer_height - 1) - self._set_text_rectangle(main_rect, footer_rect) def _set_text_rectangle(self, rect_main, rect_footer): """ @@ -585,12 +604,3 @@ class Renderer(object): # this parse we are to be wordy line = line.replace(u'\n', u' ') return line.split(u' ') - - def _lines_split(self, text): - """ - Split the slide up by physical line - """ - # this parse we do not want to use this so remove it - text = text.replace(u'\n[---]', u'') - text = text.replace(u'[---]', u'') - return text.split(u'\n') diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 3170e0a93..0eb0c866f 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -122,9 +122,8 @@ class ServiceItem(object): def _new_item(self): """ - Method to set the internal id of the item - This is used to compare service items to see if they are - the same + Method to set the internal id of the item. This is used to compare + service items to see if they are the same. """ self._uuid = unicode(uuid.uuid1()) @@ -160,9 +159,8 @@ class ServiceItem(object): def render(self, use_override=False): """ The render method is what generates the frames for the screen and - obtains the display information from the renderemanager. - At this point all the slides are built for the given - display size. + obtains the display information from the renderer. At this point all + slides are built for the given display size. """ log.debug(u'Render called') self._display_frames = [] @@ -364,6 +362,11 @@ class ServiceItem(object): """ self._uuid = other._uuid self.notes = other.notes + # Copy theme over if present. + if other.theme is not None: + self.theme = other.theme + self._new_item() + self.render() if self.is_capable(ItemCapabilities.HasBackgroundAudio): log.debug(self.background_audio) diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 3b0a62f5b..34a3b9d98 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -176,8 +176,9 @@ class HorizontalType(object): Left = 0 Right = 1 Center = 2 + Justify = 3 - Names = [u'left', u'right', u'center'] + Names = [u'left', u'right', u'center', u'justify'] class VerticalType(object): diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index e754480e0..7d2dfa0ba 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -54,6 +54,7 @@ class HideMode(object): from firsttimeform import FirstTimeForm from firsttimelanguageform import FirstTimeLanguageForm +from themelayoutform import ThemeLayoutForm from themeform import ThemeForm from filerenameform import FileRenameForm from starttimeform import StartTimeForm diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 7a42d99cc..c5fc678e2 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -56,7 +56,6 @@ except ImportError: SQLITE_VERSION = u'-' from openlp.core.lib import translate, SettingsManager -from openlp.core.lib.mailto import mailto from openlp.core.lib.ui import UiStrings from openlp.core.utils import get_application_version @@ -159,12 +158,12 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): if u':' in line: exception = line.split(u'\n')[-1].split(u':')[0] subject = u'Bug report: %s in %s' % (exception, source) + mailto_url = QtCore.QUrl(u'mailto:bugs@openlp.org') + mailto_url.addQueryItem(u'subject', subject) + mailto_url.addQueryItem(u'body', body % content) if self.fileAttachment: - mailto(address=u'bugs@openlp.org', subject=subject, - body=body % content, attach=self.fileAttachment) - else: - mailto(address=u'bugs@openlp.org', subject=subject, - body=body % content) + mailto_url.addQueryItem(u'attach', self.fileAttachment) + QtGui.QDesktopServices.openUrl(mailto_url) def onDescriptionUpdated(self): count = int(20 - len(self.descriptionTextEdit.toPlainText())) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 06afb0313..36f911df5 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -194,7 +194,6 @@ class MainDisplay(QtGui.QGraphicsView): self.setGeometry(self.screen[u'size']) self.frame.evaluateJavaScript(u'show_text("%s")' % slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) - return self.preview() def alert(self, text): """ @@ -256,7 +255,6 @@ class MainDisplay(QtGui.QGraphicsView): image = self.imageManager.get_image_bytes(name) self.resetVideo() self.displayImage(image) - return self.preview() def displayImage(self, image): """ @@ -357,7 +355,7 @@ class MainDisplay(QtGui.QGraphicsView): """ # We request a background video but have no service Item if isBackground and not hasattr(self, u'serviceItem'): - return None + return False if not self.mediaObject: self.createMediaObject() log.debug(u'video') @@ -387,7 +385,7 @@ class MainDisplay(QtGui.QGraphicsView): # Update the preview frame. if self.isLive: Receiver.send_message(u'maindisplay_active') - return self.preview() + return True def videoState(self, newState, oldState): """ @@ -455,9 +453,8 @@ class MainDisplay(QtGui.QGraphicsView): self.setVisible(True) else: self.setVisible(True) - preview = QtGui.QImage(self.screen[u'size'].width(), - self.screen[u'size'].height(), - QtGui.QImage.Format_ARGB32_Premultiplied) + preview = QtGui.QPixmap(self.screen[u'size'].width(), + self.screen[u'size'].height()) painter = QtGui.QPainter(preview) painter.setRenderHint(QtGui.QPainter.Antialiasing) self.frame.render(painter) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index c6ffaaccc..8ad2db9c2 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -30,6 +30,7 @@ import logging import os import shutil import zipfile +from tempfile import mkstemp log = logging.getLogger(__name__) @@ -467,15 +468,24 @@ class ServiceManager(QtGui.QWidget): def saveFile(self): """ - Save the current Service file. + Save the current service file. + + A temporary file is created so that we don't overwrite the existing one + and leave a mangled service file should there be an error when saving. + Audio files are also copied into the service manager directory, and + then packaged into the zip file. """ if not self.fileName(): return self.saveFileAs() + temp_file, temp_file_name = mkstemp(u'.osz', u'openlp_') + # We don't need the file handle. + os.close(temp_file) + log.debug(temp_file_name) path_file_name = unicode(self.fileName()) path, file_name = os.path.split(path_file_name) basename, extension = os.path.splitext(file_name) service_file_name = '%s.osd' % basename - log.debug(u'ServiceManager.saveFile - %s' % path_file_name) + log.debug(u'ServiceManager.saveFile - %s', path_file_name) SettingsManager.set_last_dir( self.mainwindow.servicemanagerSettingsSection, path) @@ -494,7 +504,8 @@ class ServiceManager(QtGui.QWidget): if len(service_item[u'header'][u'background_audio']) > 0: for i, filename in \ enumerate(service_item[u'header'][u'background_audio']): - new_file = os.path.join(u'audio', item[u'service_item']._uuid, + new_file = os.path.join(u'audio', + item[u'service_item']._uuid, os.path.split(filename)[1]) audio_files.append((filename, new_file)) service_item[u'header'][u'background_audio'][i] = new_file @@ -545,30 +556,38 @@ class ServiceManager(QtGui.QWidget): success = True self.mainwindow.incrementProgressBar() try: - zip = zipfile.ZipFile(path_file_name, 'w', zipfile.ZIP_STORED, + zip = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, allow_zip_64) # First we add service contents. # We save ALL filenames into ZIP using UTF-8. zip.writestr(service_file_name.encode(u'utf-8'), service_content) # Finally add all the listed media files. - for path_from in write_list: - zip.write(path_from, path_from.encode(u'utf-8')) - for path_from, path_to in audio_files: - if path_from == path_to: - # If this file has already been saved, let's use set the - # from path to the real location of the files - path_from = os.path.join(self.servicePath, path_from) - else: - # If this file has not yet been saved, let's copy the file - # to the service manager path - save_file = os.path.join(self.servicePath, path_to) - save_path = os.path.split(save_file)[0] - if not os.path.exists(save_path): - os.makedirs(save_path) - shutil.copy(path_from, save_file) - zip.write(path_from, path_to.encode(u'utf-8')) + for write_from in write_list: + zip.write(write_from, write_from.encode(u'utf-8')) + for audio_from, audio_to in audio_files: + if audio_from.startswith(u'audio'): + # When items are saved, they get new UUID's. Let's copy the + # file to the new location. Unused files can be ignored, + # OpenLP automatically cleans up the service manager dir on + # exit. + audio_from = os.path.join(self.servicePath, audio_from) + save_file = os.path.join(self.servicePath, audio_to) + save_path = os.path.split(save_file)[0] + if not os.path.exists(save_path): + os.makedirs(save_path) + if not os.path.exists(save_file): + shutil.copy(audio_from, save_file) + zip.write(audio_from, audio_to.encode(u'utf-8')) except IOError: - log.exception(u'Failed to save service to disk') + log.exception(u'Failed to save service to disk: %s', temp_file_name) + # Add this line in after the release to notify the user that saving + # their file failed. Commented out due to string freeze. + #Receiver.send_message(u'openlp_error_message', { + # u'title': translate(u'OpenLP.ServiceManager', + # u'Error Saving File'), + # u'message': translate(u'OpenLP.ServiceManager', + # u'There was an error saving your file.') + #}) success = False finally: if zip: @@ -576,10 +595,13 @@ class ServiceManager(QtGui.QWidget): self.mainwindow.finishedProgressBar() Receiver.send_message(u'cursor_normal') if success: + shutil.copy(temp_file_name, path_file_name) self.mainwindow.addRecentFile(path_file_name) self.setModified(False) - else: - delete_file(path_file_name) + try: + delete_file(temp_file_name) + except: + pass return success def saveFileAs(self): @@ -623,6 +645,7 @@ class ServiceManager(QtGui.QWidget): osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) if not osfile.startswith(u'audio'): osfile = os.path.split(osfile)[1] + log.debug(u'Extract file: %s', osfile) zipinfo.filename = osfile zip.extract(zipinfo, self.servicePath) if osfile.endswith(u'osd'): @@ -637,14 +660,17 @@ class ServiceManager(QtGui.QWidget): for item in items: self.mainwindow.incrementProgressBar() serviceItem = ServiceItem() - serviceItem.from_service = True serviceItem.renderer = self.mainwindow.renderer serviceItem.set_from_service(item, self.servicePath) self.validateItem(serviceItem) - self.addServiceItem(serviceItem, repaint=False) + self.loadItem_uuid = 0 if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate): Receiver.send_message(u'%s_service_load' % serviceItem.name.lower(), serviceItem) + # if the item has been processed + if serviceItem._uuid == self.loadItem_uuid: + serviceItem.edit_id = int(self.loadItem_editId) + self.addServiceItem(serviceItem, repaint=False) delete_file(p_file) self.setFileName(fileName) self.mainwindow.addRecentFile(fileName) @@ -1022,11 +1048,12 @@ class ServiceManager(QtGui.QWidget): """ Empties the servicePath of temporary files. """ + log.debug(u'Cleaning up servicePath') for file in os.listdir(self.servicePath): file_path = os.path.join(self.servicePath, file) delete_file(file_path) if os.path.exists(os.path.join(self.servicePath, u'audio')): - shutil.rmtree(os.path.join(self.servicePath, u'audio'), False) + shutil.rmtree(os.path.join(self.servicePath, u'audio'), True) def onThemeComboBoxSelected(self, currentIndex): """ @@ -1098,12 +1125,10 @@ class ServiceManager(QtGui.QWidget): def serviceItemUpdate(self, message): """ Triggered from plugins to update service items. + Save the values as they will be used as part of the service load """ - editId, uuid = message.split(u':') - for item in self.serviceItems: - if item[u'service_item']._uuid == uuid: - item[u'service_item'].edit_id = int(editId) - self.setModified() + editId, self.loadItem_uuid = message.split(u':') + self.loadItem_editId = int(editId) def replaceServiceItem(self, newItem): """ @@ -1135,7 +1160,6 @@ class ServiceManager(QtGui.QWidget): # if not passed set to config value if expand is None: expand = self.expandTabs - item.render() item.from_service = True if replace: sitem, child = self.findServiceItem() @@ -1144,6 +1168,7 @@ class ServiceManager(QtGui.QWidget): self.repaintServiceList(sitem, child) self.mainwindow.liveController.replaceServiceManagerItem(item) else: + item.render() # nothing selected for dnd if self.dropPosition == 0: if isinstance(item, list): diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 6615d0e35..354cfa168 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -118,7 +118,7 @@ class SlideController(QtGui.QWidget): self.previewListWidget.horizontalHeader().setVisible(False) self.previewListWidget.setColumnWidth(0, self.controller.width()) self.previewListWidget.isLive = self.isLive - self.previewListWidget.setObjectName(u'PreviewListWidget') + self.previewListWidget.setObjectName(u'previewListWidget') self.previewListWidget.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.previewListWidget.setSelectionMode( @@ -288,14 +288,14 @@ class SlideController(QtGui.QWidget): QtGui.QSizePolicy.Label)) self.previewFrame.setFrameShape(QtGui.QFrame.StyledPanel) self.previewFrame.setFrameShadow(QtGui.QFrame.Sunken) - self.previewFrame.setObjectName(u'PreviewFrame') + self.previewFrame.setObjectName(u'previewFrame') self.grid = QtGui.QGridLayout(self.previewFrame) self.grid.setMargin(8) self.grid.setObjectName(u'grid') self.slideLayout = QtGui.QVBoxLayout() self.slideLayout.setSpacing(0) self.slideLayout.setMargin(0) - self.slideLayout.setObjectName(u'SlideLayout') + self.slideLayout.setObjectName(u'slideLayout') if not self.isLive: self.mediaObject = Phonon.MediaObject(self) self.video = Phonon.VideoWidget() @@ -319,7 +319,7 @@ class SlideController(QtGui.QWidget): self.slidePreview.setFrameShadow(QtGui.QFrame.Plain) self.slidePreview.setLineWidth(1) self.slidePreview.setScaledContents(True) - self.slidePreview.setObjectName(u'SlidePreview') + self.slidePreview.setObjectName(u'slidePreview') self.slideLayout.insertWidget(0, self.slidePreview) self.grid.addLayout(self.slideLayout, 0, 0, 1, 1) # Signals @@ -328,8 +328,6 @@ class SlideController(QtGui.QWidget): if self.isLive: QtCore.QObject.connect(self.volumeSlider, QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'maindisplay_active'), self.updatePreview) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_live_spin_delay'), self.receiveSpinDelay) @@ -351,18 +349,12 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_stop_loop' % self.typePrefix), self.onStopLoop) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_first' % self.typePrefix), - self.onSlideSelectedFirst) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_next' % self.typePrefix), self.onSlideSelectedNext) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_previous' % self.typePrefix), self.onSlideSelectedPrevious) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_last' % self.typePrefix), - self.onSlideSelectedLast) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_change' % self.typePrefix), self.onSlideChange) @@ -375,9 +367,6 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.typePrefix), self.onSlideUnblank) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'slidecontroller_%s_text_request' % self.typePrefix), - self.onTextRequest) def setPreviewHotkeys(self, parent=None): self.previousItem.setObjectName(u'previousItemPreview') @@ -669,14 +658,14 @@ class SlideController(QtGui.QWidget): label.setMargin(4) label.setScaledContents(True) if self.serviceItem.is_command(): - image = QtGui.QImage(frame[u'image']) + label.setPixmap(QtGui.QPixmap(frame[u'image'])) else: # If current slide set background to image if framenumber == slideno: self.serviceItem.bg_image_bytes = \ self.imageManager.get_image_bytes(frame[u'title']) image = self.imageManager.get_image(frame[u'title']) - label.setPixmap(QtGui.QPixmap.fromImage(image)) + label.setPixmap(QtGui.QPixmap.fromImage(image)) self.previewListWidget.setCellWidget(framenumber, 0, label) slideHeight = width * self.parent().renderer.screen_ratio row += 1 @@ -723,41 +712,7 @@ class SlideController(QtGui.QWidget): else: self.__checkUpdateSelectedSlide(slideno) - def onTextRequest(self): - """ - Return the text for the current item in controller - """ - data = [] - if self.serviceItem: - for framenumber, frame in enumerate(self.serviceItem.get_frames()): - dataItem = {} - if self.serviceItem.is_text(): - dataItem[u'tag'] = unicode(frame[u'verseTag']) - dataItem[u'text'] = unicode(frame[u'html']) - else: - dataItem[u'tag'] = unicode(framenumber) - dataItem[u'text'] = u'' - dataItem[u'selected'] = \ - (self.previewListWidget.currentRow() == framenumber) - data.append(dataItem) - Receiver.send_message(u'slidecontroller_%s_text_response' - % self.typePrefix, data) - # Screen event methods - def onSlideSelectedFirst(self): - """ - Go to the first slide. - """ - if not self.serviceItem: - return - if self.serviceItem.is_command(): - Receiver.send_message(u'%s_first' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) - self.updatePreview() - else: - self.previewListWidget.selectRow(0) - self.slideSelected() - def onSlideSelectedIndex(self, message): """ Go to the requested slide @@ -936,20 +891,18 @@ class SlideController(QtGui.QWidget): Receiver.send_message( u'%s_slide' % self.serviceItem.name.lower(), [self.serviceItem, self.isLive, row]) - self.updatePreview() else: toDisplay = self.serviceItem.get_rendered_frame(row) if self.serviceItem.is_text(): - frame = self.display.text(toDisplay) + self.display.text(toDisplay) else: if start: self.display.buildHtml(self.serviceItem, toDisplay) - frame = self.display.preview() else: - frame = self.display.image(toDisplay) + self.display.image(toDisplay) # reset the store used to display first image self.serviceItem.bg_image_bytes = None - self.slidePreview.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.updatePreview() self.selectedRow = row self.__checkUpdateSelectedSlide(row) Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix, @@ -977,8 +930,7 @@ class SlideController(QtGui.QWidget): QtCore.QTimer.singleShot(0.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) else: - self.slidePreview.setPixmap( - QtGui.QPixmap.fromImage(self.display.preview())) + self.slidePreview.setPixmap(self.display.preview()) def grabMainDisplay(self): """ @@ -1041,21 +993,6 @@ class SlideController(QtGui.QWidget): self.previewListWidget.item(row + 1, 0)) self.previewListWidget.selectRow(row) - def onSlideSelectedLast(self): - """ - Go to the last slide. - """ - if not self.serviceItem: - return - Receiver.send_message(u'%s_last' % self.serviceItem.name.lower(), - [self.serviceItem, self.isLive]) - if self.serviceItem.is_command(): - self.updatePreview() - else: - self.previewListWidget.selectRow( - self.previewListWidget.rowCount() - 1) - self.slideSelected() - def onToggleLoop(self): """ Toggles the loop state. diff --git a/openlp/core/ui/splashscreen.py b/openlp/core/ui/splashscreen.py index 8b2ba5d95..036daf968 100644 --- a/openlp/core/ui/splashscreen.py +++ b/openlp/core/ui/splashscreen.py @@ -36,8 +36,7 @@ class SplashScreen(QtGui.QSplashScreen): QtCore.SIGNAL(u'close_splash'), self.close) def setupUi(self): - self.setObjectName(u'splash_screen') - self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) + self.setObjectName(u'splashScreen') self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) splash_image = QtGui.QPixmap(u':/graphics/openlp-splash-screen.png') self.setPixmap(splash_image) diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index dc3c23d0d..9ccd91d08 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -33,6 +33,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, translate from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.ui import ThemeLayoutForm from openlp.core.utils import get_images_filter from themewizard import Ui_ThemeWizard @@ -58,6 +59,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.registerFields() self.updateThemeAllowed = True self.temp_background_filename = u'' + self.themeLayoutForm = ThemeLayoutForm(self) QtCore.QObject.connect(self.backgroundComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onBackgroundComboBoxCurrentIndexChanged) @@ -88,6 +90,9 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.onShadowCheckCheckBoxStateChanged) QtCore.QObject.connect(self.footerColorButton, QtCore.SIGNAL(u'clicked()'), self.onFooterColorButtonClicked) + QtCore.QObject.connect(self, + QtCore.SIGNAL(u'customButtonClicked(int)'), + self.onCustom1ButtonClicked) QtCore.QObject.connect(self.mainPositionCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onMainPositionCheckBoxStateChanged) @@ -229,13 +234,36 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): """ Detects Page changes and updates as approprate. """ + if self.page(pageId) == self.areaPositionPage: + self.setOption(QtGui.QWizard.HaveCustomButton1, True) + else: + self.setOption(QtGui.QWizard.HaveCustomButton1, False) if self.page(pageId) == self.previewPage: self.updateTheme() frame = self.thememanager.generateImage(self.theme) - self.previewBoxLabel.setPixmap(QtGui.QPixmap.fromImage(frame)) + self.previewBoxLabel.setPixmap(frame) self.displayAspectRatio = float(frame.width()) / frame.height() self.resizeEvent() + def onCustom1ButtonClicked(self, number): + """ + Generate layout preview and display the form. + """ + self.updateTheme() + width = self.thememanager.mainwindow.renderer.width + height = self.thememanager.mainwindow.renderer.height + pixmap = QtGui.QPixmap(width, height) + pixmap.fill(QtCore.Qt.white) + paint = QtGui.QPainter(pixmap) + paint.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) + paint.drawRect(self.thememanager.mainwindow.renderer. + get_main_rectangle(self.theme)) + paint.setPen(QtGui.QPen(QtCore.Qt.red, 2)) + paint.drawRect(self.thememanager.mainwindow.renderer. + get_footer_rectangle(self.theme)) + paint.end() + self.themeLayoutForm.exec_(pixmap) + def onOutlineCheckCheckBoxStateChanged(self, state): """ Change state as Outline check box changed diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py new file mode 100644 index 000000000..5be08ad65 --- /dev/null +++ b/openlp/core/ui/themelayoutdialog.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate +from openlp.core.lib.ui import create_accept_reject_button_box + + +class Ui_ThemeLayoutDialog(object): + def setupUi(self, themeLayoutDialog): + themeLayoutDialog.setObjectName(u'themeLayoutDialogDialog') + #themeLayoutDialog.resize(300, 200) + self.previewLayout = QtGui.QVBoxLayout(themeLayoutDialog) + self.previewLayout.setObjectName(u'PreviewLayout') + self.previewArea = QtGui.QWidget(themeLayoutDialog) + self.previewArea.setObjectName(u'PreviewArea') + self.previewAreaLayout = QtGui.QGridLayout(self.previewArea) + self.previewAreaLayout.setMargin(0) + self.previewAreaLayout.setColumnStretch(0, 1) + self.previewAreaLayout.setRowStretch(0, 1) + self.previewAreaLayout.setObjectName(u'PreviewAreaLayout') + self.themeDisplayLabel = QtGui.QLabel(self.previewArea) + self.themeDisplayLabel.setFrameShape(QtGui.QFrame.Box) + self.themeDisplayLabel.setScaledContents(True) + self.themeDisplayLabel.setObjectName(u'ThemeDisplayLabel') + self.previewAreaLayout.addWidget(self.themeDisplayLabel) + self.previewLayout.addWidget(self.previewArea) + self.mainColourLabel = QtGui.QLabel(self.previewArea) + self.mainColourLabel.setObjectName(u'MainColourLabel') + self.previewLayout.addWidget(self.mainColourLabel) + self.footerColourLabel = QtGui.QLabel(self.previewArea) + self.footerColourLabel.setObjectName(u'FooterColourLabel') + self.previewLayout.addWidget(self.footerColourLabel) + self.buttonBox = QtGui.QDialogButtonBox(themeLayoutDialog) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'ButtonBox') + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'accepted()'), + themeLayoutDialog.accept) + self.previewLayout.addWidget(self.buttonBox) + self.retranslateUi(themeLayoutDialog) + QtCore.QMetaObject.connectSlotsByName(themeLayoutDialog) + + def retranslateUi(self, themeLayoutDialog): + themeLayoutDialog.setWindowTitle( + translate('OpenLP.StartTimeForm', 'Theme Layout')) + self.mainColourLabel.setText(translate('OpenLP.StartTimeForm', + 'The blue box shows the main area.')) + self.footerColourLabel.setText(translate('OpenLP.StartTimeForm', + 'The red box shows the footer.')) + diff --git a/openlp/core/ui/themelayoutform.py b/openlp/core/ui/themelayoutform.py new file mode 100644 index 000000000..6f77d31da --- /dev/null +++ b/openlp/core/ui/themelayoutform.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtGui, QtCore + +from themelayoutdialog import Ui_ThemeLayoutDialog + +from openlp.core.lib import translate +from openlp.core.lib.ui import UiStrings, critical_error_message_box + +class ThemeLayoutForm(QtGui.QDialog, Ui_ThemeLayoutDialog): + """ + The exception dialog + """ + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + + def exec_(self, image): + """ + Run the Dialog with correct heading. + """ + pixmap = image.scaledToHeight(400, QtCore.Qt.SmoothTransformation) + self.themeDisplayLabel.setPixmap(image) + displayAspectRatio = float(image.width()) / image.height() + self.themeDisplayLabel.setFixedSize(400, 400 / displayAspectRatio ) + return QtGui.QDialog.exec_(self) + + def accept(self): + return QtGui.QDialog.accept(self) + diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index fdd0d74f3..b472ccef0 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -36,7 +36,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ Receiver, SettingsManager, translate, check_item_selected, \ - check_directory_exists + check_directory_exists, create_thumb, validate_thumb from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ BackgroundGradientType from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ @@ -359,7 +359,7 @@ class ThemeManager(QtGui.QWidget): The theme to delete. """ self.themelist.remove(theme) - thumb = theme + u'.png' + thumb = u'%s.png' % theme delete_file(os.path.join(self.path, thumb)) delete_file(os.path.join(self.thumbPath, thumb)) try: @@ -473,15 +473,12 @@ class ThemeManager(QtGui.QWidget): name = textName thumb = os.path.join(self.thumbPath, u'%s.png' % textName) item_name = QtGui.QListWidgetItem(name) - if os.path.exists(thumb): + if validate_thumb(theme, thumb): icon = build_icon(thumb) else: - icon = build_icon(theme) - pixmap = icon.pixmap(QtCore.QSize(88, 50)) - pixmap.save(thumb, u'png') + icon = create_thumb(theme, thumb) item_name.setIcon(icon) - item_name.setData(QtCore.Qt.UserRole, - QtCore.QVariant(textName)) + item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName)) self.themeListWidget.addItem(item_name) self.themelist.append(textName) self._pushThemes() @@ -658,9 +655,7 @@ class ThemeManager(QtGui.QWidget): 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') + create_thumb(samplepathname, thumb, False) log.debug(u'Theme image written to %s', samplepathname) def updatePreviewImages(self): diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 6001c83d6..1135db274 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -38,7 +38,8 @@ class Ui_ThemeWizard(object): themeWizard.setModal(True) themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) themeWizard.setOptions(QtGui.QWizard.IndependentPages | - QtGui.QWizard.NoBackButtonOnStartPage) + QtGui.QWizard.NoBackButtonOnStartPage | + QtGui.QWizard.HaveCustomButton1) self.spacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) # Welcome Page @@ -246,7 +247,7 @@ class Ui_ThemeWizard(object): self.horizontalLabel = QtGui.QLabel(self.alignmentPage) self.horizontalLabel.setObjectName(u'HorizontalLabel') self.horizontalComboBox = QtGui.QComboBox(self.alignmentPage) - self.horizontalComboBox.addItems([u'', u'', u'']) + self.horizontalComboBox.addItems([u'', u'', u'', u'']) self.horizontalComboBox.setObjectName(u'HorizontalComboBox') self.alignmentLayout.addRow(self.horizontalLabel, self.horizontalComboBox) @@ -495,6 +496,8 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'Right')) self.horizontalComboBox.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center')) + self.horizontalComboBox.setItemText(HorizontalType.Justify, + translate('OpenLP.ThemeWizard', 'Justify')) self.transitionsLabel.setText( translate('OpenLP.ThemeWizard', 'Transitions:')) self.areaPositionPage.setTitle( @@ -533,6 +536,9 @@ class Ui_ThemeWizard(object): translate('OpenLP.ThemeWizard', 'px')) self.footerPositionCheckBox.setText( translate('OpenLP.ThemeWizard', 'Use default location')) + themeWizard.setOption(QtGui.QWizard.HaveCustomButton1, False) + themeWizard.setButtonText(QtGui.QWizard.CustomButton1, + translate('OpenLP.ThemeWizard', 'Layout Preview')) self.previewPage.setTitle( translate('OpenLP.ThemeWizard', 'Save and Preview')) self.previewPage.setSubTitle( diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index b5478e514..7577e86a3 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -461,6 +461,11 @@ class BibleImportForm(OpenLPWizard): WizardStrings.YouSpecifyFile % WizardStrings.OS) self.openSongFileEdit.setFocus() return False + elif self.field(u'source_format').toInt()[0] == \ + BibleFormat.WebDownload: + self.versionNameEdit.setText( + self.webTranslationComboBox.currentText()) + return True elif self.field(u'source_format').toInt()[0] == BibleFormat.OpenLP1: if not self.field(u'openlp1_location').toString(): critical_error_message_box(UiStrings().NFSs, @@ -674,7 +679,7 @@ class BibleImportForm(OpenLPWizard): elif bible_type == BibleFormat.CSV: # Import a CSV bible. importer = self.manager.import_bible(BibleFormat.CSV, - name=license_version, + name=license_version, booksfile=unicode(self.field(u'csv_booksfile').toString()), versefile=unicode(self.field(u'csv_versefile').toString()) ) diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index e5962664b..9ec5b45b2 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -28,7 +28,6 @@ import logging import chardet import os -import re import sqlite3 from PyQt4 import QtCore diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 2d8e16c4c..228d4758c 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -82,13 +82,16 @@ class BGExtract(object): Receiver.send_message(u'openlp_process_events') footnotes = soup.findAll(u'sup', u'footnote') if footnotes: - [footnote.extract() for footnote in footnotes] + for footnote in footnotes: + footnote.extract() crossrefs = soup.findAll(u'sup', u'xref') if crossrefs: - [crossref.extract() for crossref in crossrefs] + for crossref in crossrefs: + crossref.extract() headings = soup.findAll(u'h5') if headings: - [heading.extract() for heading in headings] + for heading in headings: + heading.extract() cleanup = [(re.compile('\s+'), lambda match: ' ')] verses = BeautifulSoup(str(soup), markupMassage=cleanup) verse_list = {} diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 98fbd203f..1aa39b63c 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -28,7 +28,6 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import SettingsTab, translate, Receiver -from openlp.core.lib.ui import UiStrings, create_valign_combo class ImageTab(SettingsTab): """ diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index cb73a132f..049e2da18 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -33,7 +33,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ SettingsManager, translate, check_item_selected, check_directory_exists, \ - Receiver + Receiver, create_thumb, validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.utils import AppLocation, delete_file, get_images_filter @@ -127,13 +127,13 @@ class ImageMediaItem(MediaManagerItem): self.plugin.formparent.incrementProgressBar() filename = os.path.split(unicode(imageFile))[1] thumb = os.path.join(self.servicePath, filename) - if os.path.exists(thumb): - if self.validate(imageFile, thumb): + if not os.path.exists(imageFile): + icon = build_icon(u':/general/general_delete.png') + else: + if validate_thumb(imageFile, thumb): icon = build_icon(thumb) else: - icon = build_icon(u':/general/general_delete.png') - else: - icon = self.iconFromFile(imageFile, thumb) + icon = create_thumb(imageFile, thumb) item_name = QtGui.QListWidgetItem(filename) item_name.setIcon(icon) item_name.setToolTip(imageFile) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index f2e0bbc06..c8f746851 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -39,7 +39,7 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box log = logging.getLogger(__name__) -CLAPPERBOARD = QtGui.QPixmap(u':/media/media_video.png').toImage() +CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png') class MediaMediaItem(MediaManagerItem): """ @@ -95,14 +95,14 @@ class MediaMediaItem(MediaManagerItem): def onResetClick(self): """ - Called to reset the Live backgound with the media selected, + Called to reset the Live backgound with the media selected. """ self.resetAction.setVisible(False) self.plugin.liveController.display.resetVideo() def videobackgroundReplaced(self): """ - Triggered by main display on change of serviceitem + Triggered by main display on change of serviceitem. """ self.resetAction.setVisible(False) @@ -179,8 +179,7 @@ class MediaMediaItem(MediaManagerItem): def mediaStateWait(self, mediaState): """ - Wait for the video to change its state - Wait no longer than 5 seconds. + Wait for the video to change its state. Wait no longer than 5 seconds. """ start = datetime.now() while self.mediaObject.state() != mediaState: @@ -198,7 +197,7 @@ class MediaMediaItem(MediaManagerItem): def onDeleteClick(self): """ - Remove a media item from the list + Remove a media item from the list. """ if check_item_selected(self.listView, translate('MediaPlugin.MediaItem', 'You must select a media file to delete.')): diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index c6455a03a..e1dd57271 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -32,7 +32,8 @@ import locale from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \ - translate, check_item_selected, Receiver, ItemCapabilities + translate, check_item_selected, Receiver, ItemCapabilities, create_thumb, \ + validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ media_item_combo_box from openlp.plugins.presentations.lib import MessageListener @@ -193,10 +194,13 @@ class PresentationMediaItem(MediaManagerItem): doc.load_presentation() preview = doc.get_thumbnail_path(1, True) doc.close_presentation() - if preview and self.validate(preview, thumb): - icon = build_icon(thumb) - else: + if not (preview and os.path.exists(preview)): icon = build_icon(u':/general/general_delete.png') + else: + if validate_thumb(preview, thumb): + icon = build_icon(thumb) + else: + icon = create_thumb(preview, thumb) else: if initialLoad: icon = build_icon(u':/general/general_delete.png') diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 738974add..a9d384c81 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -31,7 +31,8 @@ import shutil from PyQt4 import QtCore -from openlp.core.lib import Receiver, resize_image +from openlp.core.lib import Receiver, check_directory_exists, create_thumb, \ + validate_thumb from openlp.core.utils import AppLocation log = logging.getLogger(__name__) @@ -97,8 +98,7 @@ class PresentationDocument(object): self.slidenumber = 0 self.controller = controller self.filepath = name - if not os.path.isdir(self.get_thumbnail_folder()): - os.mkdir(self.get_thumbnail_folder()) + check_directory_exists(self.get_thumbnail_folder()) def load_presentation(self): """ @@ -145,15 +145,13 @@ class PresentationDocument(object): def check_thumbnails(self): """ - Returns true if the thumbnail images look to exist and are more - recent than the powerpoint + Returns ``True`` if the thumbnail images exist and are more recent than + the powerpoint file. """ lastimage = self.get_thumbnail_path(self.get_slide_count(), True) if not (lastimage and os.path.isfile(lastimage)): return False - imgdate = os.stat(lastimage).st_mtime - pptdate = os.stat(self.filepath).st_mtime - return imgdate >= pptdate + return validate_thumb(self.filepath, lastimage) def close_presentation(self): """ @@ -246,8 +244,8 @@ class PresentationDocument(object): if self.check_thumbnails(): return if os.path.isfile(file): - img = resize_image(file, 320, 240) - img.save(self.get_thumbnail_path(idx, False)) + thumb_path = self.get_thumbnail_path(idx, False) + create_thumb(file, thumb_path, False, QtCore.QSize(320, 240)) def get_thumbnail_path(self, slide_no, check_exists): """ @@ -387,10 +385,8 @@ class PresentationController(object): AppLocation.get_section_data_path(self.settings_section), u'thumbnails') self.thumbnail_prefix = u'slide' - if not os.path.isdir(self.thumbnail_folder): - os.makedirs(self.thumbnail_folder) - if not os.path.isdir(self.temp_folder): - os.makedirs(self.temp_folder) + check_directory_exists(self.thumbnail_folder) + check_directory_exists(self.temp_folder) def enabled(self): """ diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a97f82159..643ad14ad 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -87,7 +87,7 @@ class PresentationPlugin(Plugin): to close down their applications and release resources. """ log.info(u'Plugin Finalise') - #Ask each controller to tidy up + # Ask each controller to tidy up. for key in self.controllers: controller = self.controllers[key] if controller.enabled(): diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index 09c82c49b..8db92b39a 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -121,11 +121,11 @@ window.OpenLP = { $("#nextslide").html(text); } }, - updateClock: function() { + updateClock: function(data) { var div = $("#clock"); var t = new Date(); var h = t.getHours(); - if (h > 12) + if (data.results.twelve && h > 12) h = h - 12; var m = t.getMinutes(); if (m < 10) @@ -136,7 +136,7 @@ window.OpenLP = { $.getJSON( "/api/poll", function (data, status) { - OpenLP.updateClock(); + OpenLP.updateClock(data); if (OpenLP.currentItem != data.results.item) { OpenLP.currentItem = data.results.item; OpenLP.loadSlides(); diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index c81c83d92..522c354b8 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -315,7 +315,7 @@ class HttpConnection(object): """ log.debug(u'ready to read socket') if self.socket.canReadLine(): - data = unicode(self.socket.readLine()) + data = unicode(self.socket.readLine()).encode(u'utf-8') log.debug(u'received: ' + data) words = data.split(u' ') response = None @@ -397,7 +397,9 @@ class HttpConnection(object): result = { u'slide': self.parent.current_slide or 0, u'item': self.parent.current_item._uuid \ - if self.parent.current_item else u'' + if self.parent.current_item else u'', + u'twelve':QtCore.QSettings().value( + u'remotes/twelve hour', QtCore.QVariant(True)).toBool() } return HttpResponse(json.dumps({u'results': result}), {u'Content-Type': u'application/json'}) diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index 03781ae06..95bb27f1c 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -57,6 +57,9 @@ class RemoteTab(SettingsTab): QtCore.QObject.connect(self.addressEdit, QtCore.SIGNAL(u'textChanged(const QString&)'), self.setUrls) self.serverSettingsLayout.addRow(self.addressLabel, self.addressEdit) + self.twelveHourCheckBox = QtGui.QCheckBox(self.serverSettingsGroupBox) + self.twelveHourCheckBox.setObjectName(u'twelveHourCheckBox') + self.serverSettingsLayout.addRow(self.twelveHourCheckBox) self.portLabel = QtGui.QLabel(self.serverSettingsGroupBox) self.portLabel.setObjectName(u'portLabel') self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox) @@ -80,6 +83,9 @@ class RemoteTab(SettingsTab): self.leftLayout.addWidget(self.serverSettingsGroupBox) self.leftLayout.addStretch() self.rightLayout.addStretch() + QtCore.QObject.connect(self.twelveHourCheckBox, + QtCore.SIGNAL(u'stateChanged(int)'), + self.onTwelveHourCheckBoxChanged) def retranslateUi(self): self.serverSettingsGroupBox.setTitle( @@ -92,6 +98,9 @@ class RemoteTab(SettingsTab): 'Remote URL:')) self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:')) + self.twelveHourCheckBox.setText( + translate('RemotePlugin.RemoteTab', + 'Display stage time in 12h format')) def setUrls(self): ipAddress = u'localhost' @@ -123,6 +132,10 @@ class RemoteTab(SettingsTab): self.addressEdit.setText( QtCore.QSettings().value(self.settingsSection + u'/ip address', QtCore.QVariant(ZERO_URL)).toString()) + self.twelveHour = QtCore.QSettings().value( + self.settingsSection + u'/twelve hour', + QtCore.QVariant(True)).toBool() + self.twelveHourCheckBox.setChecked(self.twelveHour) self.setUrls() def save(self): @@ -130,3 +143,11 @@ class RemoteTab(SettingsTab): QtCore.QVariant(self.portSpinBox.value())) QtCore.QSettings().setValue(self.settingsSection + u'/ip address', QtCore.QVariant(self.addressEdit.text())) + QtCore.QSettings().setValue(self.settingsSection + u'/twelve hour', + QtCore.QVariant(self.twelveHour)) + + def onTwelveHourCheckBoxChanged(self, check_state): + self.twelveHour = False + # we have a set value convert to True/False + if check_state == QtCore.Qt.Checked: + self.twelveHour = True diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 6f09c9b8c..b79e56f7b 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -240,8 +240,9 @@ class SongImportForm(OpenLPWizard): self.formatLabel.setText(WizardStrings.FormatLabel) self.formatComboBox.setItemText(SongFormat.OpenLP2, UiStrings().OLPV2) self.formatComboBox.setItemText(SongFormat.OpenLP1, UiStrings().OLPV1) - self.formatComboBox.setItemText( - SongFormat.OpenLyrics, WizardStrings.OL) + self.formatComboBox.setItemText(SongFormat.OpenLyrics, + translate('SongsPlugin.ImportWizardForm', + 'OpenLyrics or OpenLP 2.0 Exported Song')) self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS) self.formatComboBox.setItemText( SongFormat.WordsOfWorship, WizardStrings.WoW) @@ -508,7 +509,8 @@ class SongImportForm(OpenLPWizard): Get OpenLyrics song database files """ self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.OL, - self.openLyricsFileListWidget) + self.openLyricsFileListWidget, u'%s (*.xml)' % + translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files')) def onOpenLyricsRemoveButtonClicked(self): """ diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py index e15f898ab..624256290 100644 --- a/openlp/plugins/songs/lib/cclifileimport.py +++ b/openlp/plugins/songs/lib/cclifileimport.py @@ -333,5 +333,6 @@ class CCLIFileImport(SongImport): if len(author_list) < 2: author_list = song_author.split(u'|') # Clean spaces before and after author names. - [self.addAuthor(author_name.strip()) for author_name in author_list] + for author_name in author_list: + self.addAuthor(author_name.strip()) return self.finish() diff --git a/openlp/plugins/songs/lib/easislidesimport.py b/openlp/plugins/songs/lib/easislidesimport.py index 6d3bde025..a3dd553f1 100644 --- a/openlp/plugins/songs/lib/easislidesimport.py +++ b/openlp/plugins/songs/lib/easislidesimport.py @@ -258,7 +258,7 @@ class EasiSlidesImport(SongImport): verses[reg][vt][vn] = {} if not verses[reg][vt][vn].has_key(inst): verses[reg][vt][vn][inst] = [] - words = self.tidy_text(line) + words = self.tidyText(line) verses[reg][vt][vn][inst].append(words) # done parsing diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 950eff870..d98a55c00 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -397,7 +397,8 @@ class SongMediaItem(MediaManagerItem): try: os.remove(media_file.file_name) except: - log.exception('Could not remove file: %s', audio) + log.exception('Could not remove file: %s', + media_file.file_name) try: save_path = os.path.join(AppLocation.get_section_data_path( self.plugin.name), 'audio', str(item_id)) @@ -428,11 +429,9 @@ class SongMediaItem(MediaManagerItem): def generateSlideData(self, service_item, item=None, xmlVersion=False, remote=False): - log.debug(u'generateSlideData: %s, %s, %s' % (service_item, item, self.remoteSong)) - # The ``None`` below is a workaround for bug #812289 - I think that Qt - # deletes the item somewhere along the line because the user is taking - # so long to update their item (or something weird like that). - item_id = self._getIdOfItemToGenerate(None, self.remoteSong) + log.debug(u'generateSlideData: %s, %s, %s' % + (service_item, item, self.remoteSong)) + item_id = self._getIdOfItemToGenerate(item, self.remoteSong) service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) @@ -512,7 +511,8 @@ class SongMediaItem(MediaManagerItem): # Add the audio file to the service item. if len(song.media_files) > 0: service_item.add_capability(ItemCapabilities.HasBackgroundAudio) - service_item.background_audio = [m.file_name for m in song.media_files] + service_item.background_audio = \ + [m.file_name for m in song.media_files] return True def serviceLoad(self, item): diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index ec5677ea4..515674618 100644 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -30,7 +30,6 @@ songs from the database to the OpenLyrics format. """ import logging import os -import re from lxml import etree diff --git a/openlp/plugins/songs/lib/sofimport.py b/openlp/plugins/songs/lib/sofimport.py index 6294f211e..5f310dba0 100644 --- a/openlp/plugins/songs/lib/sofimport.py +++ b/openlp/plugins/songs/lib/sofimport.py @@ -194,7 +194,7 @@ class SofImport(OooImport): into line """ text = textportion.getString() - text = self.tidy_text(text) + text = self.tidyText(text) if text.strip() == u'': return text if textportion.CharWeight == BOLD: diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index fae3400c2..21988c267 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -29,7 +29,7 @@ The :mod:`upgrade` module provides a way for the database and schema that is the backend for the Songs plugin """ -from sqlalchemy import Column, ForeignKey, Table, types +from sqlalchemy import Column, Table, types from sqlalchemy.sql.expression import func from migrate import changeset from migrate.changeset.constraint import ForeignKeyConstraint diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 5b36a7cb9..aaf82b395 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -60,7 +60,7 @@ The XML of an `OpenLyrics `_ song looks like this:: """ - +import cgi import logging import re @@ -257,11 +257,12 @@ class OpenLyrics(object): """ IMPLEMENTED_VERSION = u'0.8' + START_TAGS_REGEX = re.compile(r'\{(\w+)\}') + END_TAGS_REGEX = re.compile(r'\{\/(\w+)\}') + VERSE_NUMBER_REGEX = re.compile(u'[a-zA-Z]*') def __init__(self, manager): self.manager = manager - self.start_tags_regex = re.compile(r'\{(\w+)\}') # {abc} -> abc - self.end_tags_regex = re.compile(r'\{\/(\w+)\}') # {/abc} -> abc def song_to_xml(self, song): """ @@ -334,7 +335,8 @@ class OpenLyrics(object): if u'lang' in verse[0]: verse_element.set(u'lang', verse[0][u'lang']) # Create a list with all "virtual" verses. - virtual_verses = verse[1].split(u'[---]') + virtual_verses = cgi.escape(verse[1]) + virtual_verses = virtual_verses.split(u'[---]') for index, virtual_verse in enumerate(virtual_verses): # Add formatting tags to text lines_element = self._add_text_with_tags_to_lines(verse_element, @@ -402,32 +404,32 @@ class OpenLyrics(object): def _add_tag_to_formatting(self, tag_name, tags_element): """ - Add new formatting tag to the element ```` - if the tag is not present yet. + Add new formatting tag to the element ```` if the tag is not + present yet. """ available_tags = FormattingTags.get_html_tags() start_tag = '{%s}' % tag_name - for t in available_tags: - if t[u'start tag'] == start_tag: + for tag in available_tags: + if tag[u'start tag'] == start_tag: # Create new formatting tag in openlyrics xml. - el = self._add_text_to_element(u'tag', tags_element) - el.set(u'name', tag_name) - el_open = self._add_text_to_element(u'open', el) - el_open.text = etree.CDATA(t[u'start html']) + element = self._add_text_to_element(u'tag', tags_element) + element.set(u'name', tag_name) + element_open = self._add_text_to_element(u'open', element) + element_open.text = etree.CDATA(tag[u'start html']) # Check if formatting tag contains end tag. Some formatting # tags e.g. {br} has only start tag. If no end tag is present # element has not to be in OpenLyrics xml. - if t['end tag']: - el_close = self._add_text_to_element(u'close', el) - el_close.text = etree.CDATA(t[u'end html']) + if tag['end tag']: + element_close = self._add_text_to_element(u'close', element) + element_close.text = etree.CDATA(tag[u'end html']) def _add_text_with_tags_to_lines(self, verse_element, text, tags_element): """ Convert text with formatting tags from OpenLP format to OpenLyrics format and append it to element ````. """ - start_tags = self.start_tags_regex.findall(text) - end_tags = self.end_tags_regex.findall(text) + start_tags = OpenLyrics.START_TAGS_REGEX.findall(text) + end_tags = OpenLyrics.END_TAGS_REGEX.findall(text) # Replace start tags with xml syntax. for tag in start_tags: # Tags already converted to xml structure. @@ -442,12 +444,11 @@ class OpenLyrics(object): if tag not in xml_tags: self._add_tag_to_formatting(tag, tags_element) # Replace end tags. - for t in end_tags: - text = text.replace(u'{/%s}' % t, u'') + for tag in end_tags: + text = text.replace(u'{/%s}' % tag, u'') # Replace \n with
. text = text.replace(u'\n', u'
') - text = u'' + text + u'' - element = etree.XML(text) + element = etree.XML(u'%s' % text) verse_element.append(element) return element @@ -692,7 +693,7 @@ class OpenLyrics(object): verse_tag = verse_def[0] else: verse_tag = VerseType.Tags[VerseType.Other] - verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_def) + verse_number = OpenLyrics.VERSE_NUMBER_REGEX.sub(u'', verse_def) # OpenLyrics allows e. g. "c", but we need "c1". However, this does # not correct the verse order. if not verse_number: diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py index 28f18d578..a6f7fb2b0 100644 --- a/openlp/plugins/songusage/forms/songusagedeletedialog.py +++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py @@ -28,7 +28,6 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import translate -from openlp.core.lib.ui import create_accept_reject_button_box class Ui_SongUsageDeleteDialog(object): def setupUi(self, songUsageDeleteDialog): diff --git a/resources/forms/themelayout.ui b/resources/forms/themelayout.ui new file mode 100644 index 000000000..2152fc5d0 --- /dev/null +++ b/resources/forms/themelayout.ui @@ -0,0 +1,81 @@ + + + ThemeLayout + + + + 0 + 0 + 400 + 300 + + + + Theme Layout + + + + + 50 + 260 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 20 + 10 + 361 + 231 + + + + + + + + + + + buttonBox + accepted() + ThemeLayout + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ThemeLayout + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index fff1f75b8..c12b67002 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -60,8 +60,6 @@ slide_close.png - slide_first.png - slide_last.png slide_next.png slide_blank.png slide_desktop.png diff --git a/resources/images/slide_first.png b/resources/images/slide_first.png deleted file mode 100644 index a9d66123e..000000000 Binary files a/resources/images/slide_first.png and /dev/null differ diff --git a/resources/images/slide_last.png b/resources/images/slide_last.png deleted file mode 100644 index 2702a3ddd..000000000 Binary files a/resources/images/slide_last.png and /dev/null differ diff --git a/resources/windows/OpenLP-2.0.iss b/resources/windows/OpenLP-2.0.iss index 9d2e6bfaa..d773a22fd 100644 --- a/resources/windows/OpenLP-2.0.iss +++ b/resources/windows/OpenLP-2.0.iss @@ -87,7 +87,7 @@ Root: HKCR; Subkey: "OpenLP\DefaultIcon"; ValueType: string; ValueName: ""; Valu Root: HKCR; Subkey: "OpenLP\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\OpenLP.exe"" ""%1""" [Code] -function IsModuleLoaded(modulename: String ): Boolean; +function IsModuleLoaded(modulename: AnsiString ): Boolean; external 'IsModuleLoaded@files:psvince.dll stdcall'; function GetUninstallString(): String; diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 14d27fb81..dd2907ba9 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -8,7 +8,7 @@ # Copyright (c) 2008-2011 Raoul Snyman # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # -# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # --------------------------------------------------------------------------- # diff --git a/scripts/generate_resources.sh b/scripts/generate_resources.sh index cf6f8f2f8..5778f771a 100755 --- a/scripts/generate_resources.sh +++ b/scripts/generate_resources.sh @@ -5,11 +5,11 @@ # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # # Copyright (c) 2008-2011 Raoul Snyman # -# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael # -# Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, # -# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # -# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode # -# Woldsund # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # --------------------------------------------------------------------------- # # This program is free software; you can redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the Free # diff --git a/testing/conftest.py b/testing/conftest.py index f38018c17..001f8979a 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -8,7 +8,7 @@ # Copyright (c) 2008-2011 Raoul Snyman # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # -# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # --------------------------------------------------------------------------- # diff --git a/testing/test_app.py b/testing/test_app.py index 00cd744ba..3ff053479 100644 --- a/testing/test_app.py +++ b/testing/test_app.py @@ -8,7 +8,7 @@ # Copyright (c) 2008-2011 Raoul Snyman # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # -# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # --------------------------------------------------------------------------- #