This commit is contained in:
Andreas Preikschat 2011-10-19 19:59:28 +02:00
commit 3c0ec6d7b3
51 changed files with 571 additions and 681 deletions

View File

@ -7,7 +7,7 @@
# Copyright (c) 2008-2011 Raoul Snyman # # Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # # 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, # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #

View File

@ -144,6 +144,59 @@ def image_to_byte(image):
# convert to base64 encoding so does not get missed! # convert to base64 encoding so does not get missed!
return byte_array.toBase64() return byte_array.toBase64()
def 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'): def resize_image(image_path, width, height, background=u'#000000'):
""" """
Resize an image to fit on the current screen. 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. The new image height.
``background`` ``background``
The background colour defaults to black. The background colour. Defaults to black.
DO NOT REMOVE THE DEFAULT BACKGROUND VALUE! DO NOT REMOVE THE DEFAULT BACKGROUND VALUE!
""" """

View File

@ -51,9 +51,6 @@ class EventReceiver(QtCore.QObject):
``config_screen_changed`` ``config_screen_changed``
The display monitor has been changed The display monitor has been changed
``slidecontroller_{live|preview}_first``
Moves to the first slide
``slidecontroller_{live|preview}_next`` ``slidecontroller_{live|preview}_next``
Moves to the next slide Moves to the next slide
@ -66,9 +63,6 @@ class EventReceiver(QtCore.QObject):
``slidecontroller_{live|preview}_previous_noloop`` ``slidecontroller_{live|preview}_previous_noloop``
Moves to the previous slide, without auto advance Moves to the previous slide, without auto advance
``slidecontroller_{live|preview}_last``
Moves to the last slide
``slidecontroller_{live|preview}_set`` ``slidecontroller_{live|preview}_set``
Moves to a specific slide, by index Moves to a specific slide, by index
@ -82,11 +76,6 @@ class EventReceiver(QtCore.QObject):
``slidecontroller_{live|preview}_changed`` ``slidecontroller_{live|preview}_changed``
Broadcasts that the slidecontroller has changed the current slide 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`` ``slidecontroller_{live|preview}_blank``
Request that the output screen is blanked Request that the output screen is blanked

View File

@ -34,8 +34,8 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# FIXME: Add html5 doctype. However, do not break theme gradients.
HTMLSRC = u""" HTMLSRC = u"""
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>OpenLP Display</title> <title>OpenLP Display</title>
@ -404,7 +404,7 @@ def build_background_css(item, width, height):
background = \ background = \
u'background: ' \ u'background: ' \
u'-webkit-gradient(linear, left top, left bottom, ' \ 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) theme.background_end_color)
elif theme.background_direction == \ elif theme.background_direction == \
BackgroundGradientType.to_string( \ BackgroundGradientType.to_string( \
@ -412,7 +412,7 @@ def build_background_css(item, width, height):
background = \ background = \
u'background: ' \ u'background: ' \
u'-webkit-gradient(linear, left top, right bottom, ' \ 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) theme.background_end_color)
elif theme.background_direction == \ elif theme.background_direction == \
BackgroundGradientType.to_string \ BackgroundGradientType.to_string \
@ -420,20 +420,21 @@ def build_background_css(item, width, height):
background = \ background = \
u'background: ' \ u'background: ' \
u'-webkit-gradient(linear, left bottom, right top, ' \ 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) theme.background_end_color)
elif theme.background_direction == \ elif theme.background_direction == \
BackgroundGradientType.to_string \ BackgroundGradientType.to_string \
(BackgroundGradientType.Vertical): (BackgroundGradientType.Vertical):
background = \ background = \
u'background: -webkit-gradient(linear, left top, ' \ 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) (theme.background_start_color, theme.background_end_color)
else: else:
background = \ background = \
u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \ u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \
u'50%%, %s, from(%s), to(%s))' % (width, width, width, u'50%%, %s, from(%s), to(%s)) fixed' % (width, width,
theme.background_start_color, theme.background_end_color) width, theme.background_start_color,
theme.background_end_color)
return background return background
def build_lyrics_css(item, webkitvers): 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 left_margin = int(theme.font_main_outline_size) * 2
else: else:
left_margin = 0 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; ' \ 'text-align: %s; vertical-align: %s; font-family: %s; ' \
'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \ 'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \
'padding: 0; padding-left: %spx; width: %spx; height: %spx; ' % \ '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), theme.font_main_color, 100 + int(theme.font_main_line_adjustment),
left_margin, width, height) left_margin, width, height)
if theme.font_main_outline: if theme.font_main_outline:

View File

@ -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.

View File

@ -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: '<URL>'
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)

View File

@ -425,44 +425,6 @@ class MediaManagerItem(QtGui.QWidget):
count += 1 count += 1
return filelist 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): def loadList(self, list):
raise NotImplementedError(u'MediaManagerItem.loadList needs to be ' raise NotImplementedError(u'MediaManagerItem.loadList needs to be '
u'defined by the plugin') u'defined by the plugin')

View File

@ -44,6 +44,7 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n' \
'Get those children out of the muddy, muddy \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}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' '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'] FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
class Renderer(object): class Renderer(object):
@ -56,14 +57,14 @@ class Renderer(object):
def __init__(self, imageManager, themeManager): def __init__(self, imageManager, themeManager):
""" """
Initialise the render manager. Initialise the renderer.
``imageManager`` ``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. images.
``themeManager`` ``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') log.debug(u'Initialisation started')
self.themeManager = themeManager self.themeManager = themeManager
@ -80,7 +81,7 @@ class Renderer(object):
def update_display(self): 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') log.debug(u'Update Display')
self._calculate_default() self._calculate_default()
@ -190,7 +191,7 @@ class Renderer(object):
serviceItem.theme = theme_data serviceItem.theme = theme_data
if self.force_page: if self.force_page:
# make big page for theme edit dialog to get line count # 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: else:
self.imageManager.del_image(theme_data.theme_name) self.imageManager.del_image(theme_data.theme_name)
serviceItem.add_from_text(u'', VERSE) serviceItem.add_from_text(u'', VERSE)
@ -200,7 +201,8 @@ class Renderer(object):
if not self.force_page: if not self.force_page:
self.display.buildHtml(serviceItem) self.display.buildHtml(serviceItem)
raw_html = serviceItem.get_rendered_frame(0) 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 # Reset the real screen size for subsequent render requests
self._calculate_default() self._calculate_default()
return preview return preview
@ -224,14 +226,10 @@ class Renderer(object):
# Bibles # Bibles
if item.is_capable(ItemCapabilities.CanWordSplit): if item.is_capable(ItemCapabilities.CanWordSplit):
pages = self._paginate_slide_words(text.split(u'\n'), line_end) pages = self._paginate_slide_words(text.split(u'\n'), line_end)
else: # Songs and Custom
# Clean up line endings. elif item.is_capable(ItemCapabilities.CanSoftBreak):
lines = self._lines_split(text) pages = []
pages = self._paginate_slide(lines, line_end) if u'[---]' in text:
# Songs and Custom
if item.is_capable(ItemCapabilities.CanSoftBreak) and \
len(pages) > 1 and u'[---]' in text:
pages = []
while True: while True:
slides = text.split(u'\n[---]\n', 2) slides = text.split(u'\n[---]\n', 2)
# If there are (at least) two occurrences of [---] we use # 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') lines = text.strip(u'\n').split(u'\n')
pages.extend(self._paginate_slide(lines, line_end)) pages.extend(self._paginate_slide(lines, line_end))
break 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 = [] new_pages = []
for page in pages: for page in pages:
while page.endswith(u'<br>'): while page.endswith(u'<br>'):
@ -302,21 +305,37 @@ class Renderer(object):
The theme to build a text block for. The theme to build a text block for.
""" """
log.debug(u'_build_text_rectangle') log.debug(u'_build_text_rectangle')
main_rect = None main_rect = self.get_main_rectangle(theme)
footer_rect = None 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: 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: 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) 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: 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) self.height - self.footer_start)
else: 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_y, theme.font_footer_width - 1,
theme.font_footer_height - 1) theme.font_footer_height - 1)
self._set_text_rectangle(main_rect, footer_rect)
def _set_text_rectangle(self, rect_main, rect_footer): def _set_text_rectangle(self, rect_main, rect_footer):
""" """
@ -585,12 +604,3 @@ class Renderer(object):
# this parse we are to be wordy # this parse we are to be wordy
line = line.replace(u'\n', u' ') line = line.replace(u'\n', u' ')
return line.split(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')

View File

@ -122,9 +122,8 @@ class ServiceItem(object):
def _new_item(self): def _new_item(self):
""" """
Method to set the internal id of the item Method to set the internal id of the item. This is used to compare
This is used to compare service items to see if they are service items to see if they are the same.
the same
""" """
self._uuid = unicode(uuid.uuid1()) self._uuid = unicode(uuid.uuid1())
@ -160,9 +159,8 @@ class ServiceItem(object):
def render(self, use_override=False): def render(self, use_override=False):
""" """
The render method is what generates the frames for the screen and The render method is what generates the frames for the screen and
obtains the display information from the renderemanager. obtains the display information from the renderer. At this point all
At this point all the slides are built for the given slides are built for the given display size.
display size.
""" """
log.debug(u'Render called') log.debug(u'Render called')
self._display_frames = [] self._display_frames = []
@ -364,6 +362,11 @@ class ServiceItem(object):
""" """
self._uuid = other._uuid self._uuid = other._uuid
self.notes = other.notes 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): if self.is_capable(ItemCapabilities.HasBackgroundAudio):
log.debug(self.background_audio) log.debug(self.background_audio)

View File

@ -176,8 +176,9 @@ class HorizontalType(object):
Left = 0 Left = 0
Right = 1 Right = 1
Center = 2 Center = 2
Justify = 3
Names = [u'left', u'right', u'center'] Names = [u'left', u'right', u'center', u'justify']
class VerticalType(object): class VerticalType(object):

View File

@ -54,6 +54,7 @@ class HideMode(object):
from firsttimeform import FirstTimeForm from firsttimeform import FirstTimeForm
from firsttimelanguageform import FirstTimeLanguageForm from firsttimelanguageform import FirstTimeLanguageForm
from themelayoutform import ThemeLayoutForm
from themeform import ThemeForm from themeform import ThemeForm
from filerenameform import FileRenameForm from filerenameform import FileRenameForm
from starttimeform import StartTimeForm from starttimeform import StartTimeForm

View File

@ -56,7 +56,6 @@ except ImportError:
SQLITE_VERSION = u'-' SQLITE_VERSION = u'-'
from openlp.core.lib import translate, SettingsManager from openlp.core.lib import translate, SettingsManager
from openlp.core.lib.mailto import mailto
from openlp.core.lib.ui import UiStrings from openlp.core.lib.ui import UiStrings
from openlp.core.utils import get_application_version from openlp.core.utils import get_application_version
@ -159,12 +158,12 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
if u':' in line: if u':' in line:
exception = line.split(u'\n')[-1].split(u':')[0] exception = line.split(u'\n')[-1].split(u':')[0]
subject = u'Bug report: %s in %s' % (exception, source) 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: if self.fileAttachment:
mailto(address=u'bugs@openlp.org', subject=subject, mailto_url.addQueryItem(u'attach', self.fileAttachment)
body=body % content, attach=self.fileAttachment) QtGui.QDesktopServices.openUrl(mailto_url)
else:
mailto(address=u'bugs@openlp.org', subject=subject,
body=body % content)
def onDescriptionUpdated(self): def onDescriptionUpdated(self):
count = int(20 - len(self.descriptionTextEdit.toPlainText())) count = int(20 - len(self.descriptionTextEdit.toPlainText()))

View File

@ -194,7 +194,6 @@ class MainDisplay(QtGui.QGraphicsView):
self.setGeometry(self.screen[u'size']) self.setGeometry(self.screen[u'size'])
self.frame.evaluateJavaScript(u'show_text("%s")' % self.frame.evaluateJavaScript(u'show_text("%s")' %
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
return self.preview()
def alert(self, text): def alert(self, text):
""" """
@ -256,7 +255,6 @@ class MainDisplay(QtGui.QGraphicsView):
image = self.imageManager.get_image_bytes(name) image = self.imageManager.get_image_bytes(name)
self.resetVideo() self.resetVideo()
self.displayImage(image) self.displayImage(image)
return self.preview()
def displayImage(self, image): def displayImage(self, image):
""" """
@ -357,7 +355,7 @@ class MainDisplay(QtGui.QGraphicsView):
""" """
# We request a background video but have no service Item # We request a background video but have no service Item
if isBackground and not hasattr(self, u'serviceItem'): if isBackground and not hasattr(self, u'serviceItem'):
return None return False
if not self.mediaObject: if not self.mediaObject:
self.createMediaObject() self.createMediaObject()
log.debug(u'video') log.debug(u'video')
@ -387,7 +385,7 @@ class MainDisplay(QtGui.QGraphicsView):
# Update the preview frame. # Update the preview frame.
if self.isLive: if self.isLive:
Receiver.send_message(u'maindisplay_active') Receiver.send_message(u'maindisplay_active')
return self.preview() return True
def videoState(self, newState, oldState): def videoState(self, newState, oldState):
""" """
@ -455,9 +453,8 @@ class MainDisplay(QtGui.QGraphicsView):
self.setVisible(True) self.setVisible(True)
else: else:
self.setVisible(True) self.setVisible(True)
preview = QtGui.QImage(self.screen[u'size'].width(), preview = QtGui.QPixmap(self.screen[u'size'].width(),
self.screen[u'size'].height(), self.screen[u'size'].height())
QtGui.QImage.Format_ARGB32_Premultiplied)
painter = QtGui.QPainter(preview) painter = QtGui.QPainter(preview)
painter.setRenderHint(QtGui.QPainter.Antialiasing) painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.frame.render(painter) self.frame.render(painter)

View File

@ -30,6 +30,7 @@ import logging
import os import os
import shutil import shutil
import zipfile import zipfile
from tempfile import mkstemp
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -467,15 +468,24 @@ class ServiceManager(QtGui.QWidget):
def saveFile(self): 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(): if not self.fileName():
return self.saveFileAs() 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 = unicode(self.fileName())
path, file_name = os.path.split(path_file_name) path, file_name = os.path.split(path_file_name)
basename, extension = os.path.splitext(file_name) basename, extension = os.path.splitext(file_name)
service_file_name = '%s.osd' % basename 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( SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection, self.mainwindow.servicemanagerSettingsSection,
path) path)
@ -494,7 +504,8 @@ class ServiceManager(QtGui.QWidget):
if len(service_item[u'header'][u'background_audio']) > 0: if len(service_item[u'header'][u'background_audio']) > 0:
for i, filename in \ for i, filename in \
enumerate(service_item[u'header'][u'background_audio']): 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]) os.path.split(filename)[1])
audio_files.append((filename, new_file)) audio_files.append((filename, new_file))
service_item[u'header'][u'background_audio'][i] = new_file service_item[u'header'][u'background_audio'][i] = new_file
@ -545,30 +556,38 @@ class ServiceManager(QtGui.QWidget):
success = True success = True
self.mainwindow.incrementProgressBar() self.mainwindow.incrementProgressBar()
try: try:
zip = zipfile.ZipFile(path_file_name, 'w', zipfile.ZIP_STORED, zip = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED,
allow_zip_64) allow_zip_64)
# First we add service contents. # First we add service contents.
# We save ALL filenames into ZIP using UTF-8. # We save ALL filenames into ZIP using UTF-8.
zip.writestr(service_file_name.encode(u'utf-8'), service_content) zip.writestr(service_file_name.encode(u'utf-8'), service_content)
# Finally add all the listed media files. # Finally add all the listed media files.
for path_from in write_list: for write_from in write_list:
zip.write(path_from, path_from.encode(u'utf-8')) zip.write(write_from, write_from.encode(u'utf-8'))
for path_from, path_to in audio_files: for audio_from, audio_to in audio_files:
if path_from == path_to: if audio_from.startswith(u'audio'):
# If this file has already been saved, let's use set the # When items are saved, they get new UUID's. Let's copy the
# from path to the real location of the files # file to the new location. Unused files can be ignored,
path_from = os.path.join(self.servicePath, path_from) # OpenLP automatically cleans up the service manager dir on
else: # exit.
# If this file has not yet been saved, let's copy the file audio_from = os.path.join(self.servicePath, audio_from)
# to the service manager path save_file = os.path.join(self.servicePath, audio_to)
save_file = os.path.join(self.servicePath, path_to) save_path = os.path.split(save_file)[0]
save_path = os.path.split(save_file)[0] if not os.path.exists(save_path):
if not os.path.exists(save_path): os.makedirs(save_path)
os.makedirs(save_path) if not os.path.exists(save_file):
shutil.copy(path_from, save_file) shutil.copy(audio_from, save_file)
zip.write(path_from, path_to.encode(u'utf-8')) zip.write(audio_from, audio_to.encode(u'utf-8'))
except IOError: except IOError:
log.exception(u'Failed to save service to disk') log.exception(u'Failed to save service to disk: %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 success = False
finally: finally:
if zip: if zip:
@ -576,10 +595,13 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.finishedProgressBar() self.mainwindow.finishedProgressBar()
Receiver.send_message(u'cursor_normal') Receiver.send_message(u'cursor_normal')
if success: if success:
shutil.copy(temp_file_name, path_file_name)
self.mainwindow.addRecentFile(path_file_name) self.mainwindow.addRecentFile(path_file_name)
self.setModified(False) self.setModified(False)
else: try:
delete_file(path_file_name) delete_file(temp_file_name)
except:
pass
return success return success
def saveFileAs(self): def saveFileAs(self):
@ -623,6 +645,7 @@ class ServiceManager(QtGui.QWidget):
osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile))
if not osfile.startswith(u'audio'): if not osfile.startswith(u'audio'):
osfile = os.path.split(osfile)[1] osfile = os.path.split(osfile)[1]
log.debug(u'Extract file: %s', osfile)
zipinfo.filename = osfile zipinfo.filename = osfile
zip.extract(zipinfo, self.servicePath) zip.extract(zipinfo, self.servicePath)
if osfile.endswith(u'osd'): if osfile.endswith(u'osd'):
@ -637,14 +660,17 @@ class ServiceManager(QtGui.QWidget):
for item in items: for item in items:
self.mainwindow.incrementProgressBar() self.mainwindow.incrementProgressBar()
serviceItem = ServiceItem() serviceItem = ServiceItem()
serviceItem.from_service = True
serviceItem.renderer = self.mainwindow.renderer serviceItem.renderer = self.mainwindow.renderer
serviceItem.set_from_service(item, self.servicePath) serviceItem.set_from_service(item, self.servicePath)
self.validateItem(serviceItem) self.validateItem(serviceItem)
self.addServiceItem(serviceItem, repaint=False) self.loadItem_uuid = 0
if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate): if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate):
Receiver.send_message(u'%s_service_load' % Receiver.send_message(u'%s_service_load' %
serviceItem.name.lower(), serviceItem) 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) delete_file(p_file)
self.setFileName(fileName) self.setFileName(fileName)
self.mainwindow.addRecentFile(fileName) self.mainwindow.addRecentFile(fileName)
@ -1022,11 +1048,12 @@ class ServiceManager(QtGui.QWidget):
""" """
Empties the servicePath of temporary files. Empties the servicePath of temporary files.
""" """
log.debug(u'Cleaning up servicePath')
for file in os.listdir(self.servicePath): for file in os.listdir(self.servicePath):
file_path = os.path.join(self.servicePath, file) file_path = os.path.join(self.servicePath, file)
delete_file(file_path) delete_file(file_path)
if os.path.exists(os.path.join(self.servicePath, u'audio')): 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): def onThemeComboBoxSelected(self, currentIndex):
""" """
@ -1098,12 +1125,10 @@ class ServiceManager(QtGui.QWidget):
def serviceItemUpdate(self, message): def serviceItemUpdate(self, message):
""" """
Triggered from plugins to update service items. 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':') editId, self.loadItem_uuid = message.split(u':')
for item in self.serviceItems: self.loadItem_editId = int(editId)
if item[u'service_item']._uuid == uuid:
item[u'service_item'].edit_id = int(editId)
self.setModified()
def replaceServiceItem(self, newItem): def replaceServiceItem(self, newItem):
""" """
@ -1135,7 +1160,6 @@ class ServiceManager(QtGui.QWidget):
# if not passed set to config value # if not passed set to config value
if expand is None: if expand is None:
expand = self.expandTabs expand = self.expandTabs
item.render()
item.from_service = True item.from_service = True
if replace: if replace:
sitem, child = self.findServiceItem() sitem, child = self.findServiceItem()
@ -1144,6 +1168,7 @@ class ServiceManager(QtGui.QWidget):
self.repaintServiceList(sitem, child) self.repaintServiceList(sitem, child)
self.mainwindow.liveController.replaceServiceManagerItem(item) self.mainwindow.liveController.replaceServiceManagerItem(item)
else: else:
item.render()
# nothing selected for dnd # nothing selected for dnd
if self.dropPosition == 0: if self.dropPosition == 0:
if isinstance(item, list): if isinstance(item, list):

View File

@ -118,7 +118,7 @@ class SlideController(QtGui.QWidget):
self.previewListWidget.horizontalHeader().setVisible(False) self.previewListWidget.horizontalHeader().setVisible(False)
self.previewListWidget.setColumnWidth(0, self.controller.width()) self.previewListWidget.setColumnWidth(0, self.controller.width())
self.previewListWidget.isLive = self.isLive self.previewListWidget.isLive = self.isLive
self.previewListWidget.setObjectName(u'PreviewListWidget') self.previewListWidget.setObjectName(u'previewListWidget')
self.previewListWidget.setSelectionBehavior( self.previewListWidget.setSelectionBehavior(
QtGui.QAbstractItemView.SelectRows) QtGui.QAbstractItemView.SelectRows)
self.previewListWidget.setSelectionMode( self.previewListWidget.setSelectionMode(
@ -288,14 +288,14 @@ class SlideController(QtGui.QWidget):
QtGui.QSizePolicy.Label)) QtGui.QSizePolicy.Label))
self.previewFrame.setFrameShape(QtGui.QFrame.StyledPanel) self.previewFrame.setFrameShape(QtGui.QFrame.StyledPanel)
self.previewFrame.setFrameShadow(QtGui.QFrame.Sunken) self.previewFrame.setFrameShadow(QtGui.QFrame.Sunken)
self.previewFrame.setObjectName(u'PreviewFrame') self.previewFrame.setObjectName(u'previewFrame')
self.grid = QtGui.QGridLayout(self.previewFrame) self.grid = QtGui.QGridLayout(self.previewFrame)
self.grid.setMargin(8) self.grid.setMargin(8)
self.grid.setObjectName(u'grid') self.grid.setObjectName(u'grid')
self.slideLayout = QtGui.QVBoxLayout() self.slideLayout = QtGui.QVBoxLayout()
self.slideLayout.setSpacing(0) self.slideLayout.setSpacing(0)
self.slideLayout.setMargin(0) self.slideLayout.setMargin(0)
self.slideLayout.setObjectName(u'SlideLayout') self.slideLayout.setObjectName(u'slideLayout')
if not self.isLive: if not self.isLive:
self.mediaObject = Phonon.MediaObject(self) self.mediaObject = Phonon.MediaObject(self)
self.video = Phonon.VideoWidget() self.video = Phonon.VideoWidget()
@ -319,7 +319,7 @@ class SlideController(QtGui.QWidget):
self.slidePreview.setFrameShadow(QtGui.QFrame.Plain) self.slidePreview.setFrameShadow(QtGui.QFrame.Plain)
self.slidePreview.setLineWidth(1) self.slidePreview.setLineWidth(1)
self.slidePreview.setScaledContents(True) self.slidePreview.setScaledContents(True)
self.slidePreview.setObjectName(u'SlidePreview') self.slidePreview.setObjectName(u'slidePreview')
self.slideLayout.insertWidget(0, self.slidePreview) self.slideLayout.insertWidget(0, self.slidePreview)
self.grid.addLayout(self.slideLayout, 0, 0, 1, 1) self.grid.addLayout(self.slideLayout, 0, 0, 1, 1)
# Signals # Signals
@ -328,8 +328,6 @@ class SlideController(QtGui.QWidget):
if self.isLive: if self.isLive:
QtCore.QObject.connect(self.volumeSlider, QtCore.QObject.connect(self.volumeSlider,
QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) 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.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_live_spin_delay'), QtCore.SIGNAL(u'slidecontroller_live_spin_delay'),
self.receiveSpinDelay) self.receiveSpinDelay)
@ -351,18 +349,12 @@ class SlideController(QtGui.QWidget):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_stop_loop' % self.typePrefix), QtCore.SIGNAL(u'slidecontroller_%s_stop_loop' % self.typePrefix),
self.onStopLoop) 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.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_next' % self.typePrefix), QtCore.SIGNAL(u'slidecontroller_%s_next' % self.typePrefix),
self.onSlideSelectedNext) self.onSlideSelectedNext)
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_previous' % self.typePrefix), QtCore.SIGNAL(u'slidecontroller_%s_previous' % self.typePrefix),
self.onSlideSelectedPrevious) 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.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_change' % self.typePrefix), QtCore.SIGNAL(u'slidecontroller_%s_change' % self.typePrefix),
self.onSlideChange) self.onSlideChange)
@ -375,9 +367,6 @@ class SlideController(QtGui.QWidget):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.typePrefix), QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.typePrefix),
self.onSlideUnblank) self.onSlideUnblank)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_%s_text_request' % self.typePrefix),
self.onTextRequest)
def setPreviewHotkeys(self, parent=None): def setPreviewHotkeys(self, parent=None):
self.previousItem.setObjectName(u'previousItemPreview') self.previousItem.setObjectName(u'previousItemPreview')
@ -669,14 +658,14 @@ class SlideController(QtGui.QWidget):
label.setMargin(4) label.setMargin(4)
label.setScaledContents(True) label.setScaledContents(True)
if self.serviceItem.is_command(): if self.serviceItem.is_command():
image = QtGui.QImage(frame[u'image']) label.setPixmap(QtGui.QPixmap(frame[u'image']))
else: else:
# If current slide set background to image # If current slide set background to image
if framenumber == slideno: if framenumber == slideno:
self.serviceItem.bg_image_bytes = \ self.serviceItem.bg_image_bytes = \
self.imageManager.get_image_bytes(frame[u'title']) self.imageManager.get_image_bytes(frame[u'title'])
image = self.imageManager.get_image(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) self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * self.parent().renderer.screen_ratio slideHeight = width * self.parent().renderer.screen_ratio
row += 1 row += 1
@ -723,41 +712,7 @@ class SlideController(QtGui.QWidget):
else: else:
self.__checkUpdateSelectedSlide(slideno) 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 # 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): def onSlideSelectedIndex(self, message):
""" """
Go to the requested slide Go to the requested slide
@ -936,20 +891,18 @@ class SlideController(QtGui.QWidget):
Receiver.send_message( Receiver.send_message(
u'%s_slide' % self.serviceItem.name.lower(), u'%s_slide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive, row]) [self.serviceItem, self.isLive, row])
self.updatePreview()
else: else:
toDisplay = self.serviceItem.get_rendered_frame(row) toDisplay = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text(): if self.serviceItem.is_text():
frame = self.display.text(toDisplay) self.display.text(toDisplay)
else: else:
if start: if start:
self.display.buildHtml(self.serviceItem, toDisplay) self.display.buildHtml(self.serviceItem, toDisplay)
frame = self.display.preview()
else: else:
frame = self.display.image(toDisplay) self.display.image(toDisplay)
# reset the store used to display first image # reset the store used to display first image
self.serviceItem.bg_image_bytes = None self.serviceItem.bg_image_bytes = None
self.slidePreview.setPixmap(QtGui.QPixmap.fromImage(frame)) self.updatePreview()
self.selectedRow = row self.selectedRow = row
self.__checkUpdateSelectedSlide(row) self.__checkUpdateSelectedSlide(row)
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix, 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(0.5, self.grabMainDisplay)
QtCore.QTimer.singleShot(2.5, self.grabMainDisplay) QtCore.QTimer.singleShot(2.5, self.grabMainDisplay)
else: else:
self.slidePreview.setPixmap( self.slidePreview.setPixmap(self.display.preview())
QtGui.QPixmap.fromImage(self.display.preview()))
def grabMainDisplay(self): def grabMainDisplay(self):
""" """
@ -1041,21 +993,6 @@ class SlideController(QtGui.QWidget):
self.previewListWidget.item(row + 1, 0)) self.previewListWidget.item(row + 1, 0))
self.previewListWidget.selectRow(row) 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): def onToggleLoop(self):
""" """
Toggles the loop state. Toggles the loop state.

View File

@ -36,8 +36,7 @@ class SplashScreen(QtGui.QSplashScreen):
QtCore.SIGNAL(u'close_splash'), self.close) QtCore.SIGNAL(u'close_splash'), self.close)
def setupUi(self): def setupUi(self):
self.setObjectName(u'splash_screen') self.setObjectName(u'splashScreen')
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
splash_image = QtGui.QPixmap(u':/graphics/openlp-splash-screen.png') splash_image = QtGui.QPixmap(u':/graphics/openlp-splash-screen.png')
self.setPixmap(splash_image) self.setPixmap(splash_image)

View File

@ -33,6 +33,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver, translate from openlp.core.lib import Receiver, translate
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import UiStrings, critical_error_message_box 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 openlp.core.utils import get_images_filter
from themewizard import Ui_ThemeWizard from themewizard import Ui_ThemeWizard
@ -58,6 +59,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.registerFields() self.registerFields()
self.updateThemeAllowed = True self.updateThemeAllowed = True
self.temp_background_filename = u'' self.temp_background_filename = u''
self.themeLayoutForm = ThemeLayoutForm(self)
QtCore.QObject.connect(self.backgroundComboBox, QtCore.QObject.connect(self.backgroundComboBox,
QtCore.SIGNAL(u'currentIndexChanged(int)'), QtCore.SIGNAL(u'currentIndexChanged(int)'),
self.onBackgroundComboBoxCurrentIndexChanged) self.onBackgroundComboBoxCurrentIndexChanged)
@ -88,6 +90,9 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.onShadowCheckCheckBoxStateChanged) self.onShadowCheckCheckBoxStateChanged)
QtCore.QObject.connect(self.footerColorButton, QtCore.QObject.connect(self.footerColorButton,
QtCore.SIGNAL(u'clicked()'), self.onFooterColorButtonClicked) QtCore.SIGNAL(u'clicked()'), self.onFooterColorButtonClicked)
QtCore.QObject.connect(self,
QtCore.SIGNAL(u'customButtonClicked(int)'),
self.onCustom1ButtonClicked)
QtCore.QObject.connect(self.mainPositionCheckBox, QtCore.QObject.connect(self.mainPositionCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'), QtCore.SIGNAL(u'stateChanged(int)'),
self.onMainPositionCheckBoxStateChanged) self.onMainPositionCheckBoxStateChanged)
@ -229,13 +234,36 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
""" """
Detects Page changes and updates as approprate. 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: if self.page(pageId) == self.previewPage:
self.updateTheme() self.updateTheme()
frame = self.thememanager.generateImage(self.theme) 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.displayAspectRatio = float(frame.width()) / frame.height()
self.resizeEvent() 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): def onOutlineCheckCheckBoxStateChanged(self, state):
""" """
Change state as Outline check box changed Change state as Outline check box changed

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate
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.'))

View File

@ -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)

View File

@ -36,7 +36,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \
Receiver, SettingsManager, translate, check_item_selected, \ 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, \ from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType BackgroundGradientType
from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
@ -359,7 +359,7 @@ class ThemeManager(QtGui.QWidget):
The theme to delete. The theme to delete.
""" """
self.themelist.remove(theme) 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.path, thumb))
delete_file(os.path.join(self.thumbPath, thumb)) delete_file(os.path.join(self.thumbPath, thumb))
try: try:
@ -473,15 +473,12 @@ class ThemeManager(QtGui.QWidget):
name = textName name = textName
thumb = os.path.join(self.thumbPath, u'%s.png' % textName) thumb = os.path.join(self.thumbPath, u'%s.png' % textName)
item_name = QtGui.QListWidgetItem(name) item_name = QtGui.QListWidgetItem(name)
if os.path.exists(thumb): if validate_thumb(theme, thumb):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = build_icon(theme) icon = create_thumb(theme, thumb)
pixmap = icon.pixmap(QtCore.QSize(88, 50))
pixmap.save(thumb, u'png')
item_name.setIcon(icon) item_name.setIcon(icon)
item_name.setData(QtCore.Qt.UserRole, item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName))
QtCore.QVariant(textName))
self.themeListWidget.addItem(item_name) self.themeListWidget.addItem(item_name)
self.themelist.append(textName) self.themelist.append(textName)
self._pushThemes() self._pushThemes()
@ -658,9 +655,7 @@ class ThemeManager(QtGui.QWidget):
os.unlink(samplepathname) os.unlink(samplepathname)
frame.save(samplepathname, u'png') frame.save(samplepathname, u'png')
thumb = os.path.join(self.thumbPath, u'%s.png' % name) thumb = os.path.join(self.thumbPath, u'%s.png' % name)
icon = build_icon(frame) create_thumb(samplepathname, thumb, False)
pixmap = icon.pixmap(QtCore.QSize(88, 50))
pixmap.save(thumb, u'png')
log.debug(u'Theme image written to %s', samplepathname) log.debug(u'Theme image written to %s', samplepathname)
def updatePreviewImages(self): def updatePreviewImages(self):

View File

@ -38,7 +38,8 @@ class Ui_ThemeWizard(object):
themeWizard.setModal(True) themeWizard.setModal(True)
themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
themeWizard.setOptions(QtGui.QWizard.IndependentPages | themeWizard.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage) QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.HaveCustomButton1)
self.spacer = QtGui.QSpacerItem(10, 0, self.spacer = QtGui.QSpacerItem(10, 0,
QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
# Welcome Page # Welcome Page
@ -246,7 +247,7 @@ class Ui_ThemeWizard(object):
self.horizontalLabel = QtGui.QLabel(self.alignmentPage) self.horizontalLabel = QtGui.QLabel(self.alignmentPage)
self.horizontalLabel.setObjectName(u'HorizontalLabel') self.horizontalLabel.setObjectName(u'HorizontalLabel')
self.horizontalComboBox = QtGui.QComboBox(self.alignmentPage) 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.horizontalComboBox.setObjectName(u'HorizontalComboBox')
self.alignmentLayout.addRow(self.horizontalLabel, self.alignmentLayout.addRow(self.horizontalLabel,
self.horizontalComboBox) self.horizontalComboBox)
@ -495,6 +496,8 @@ class Ui_ThemeWizard(object):
translate('OpenLP.ThemeWizard', 'Right')) translate('OpenLP.ThemeWizard', 'Right'))
self.horizontalComboBox.setItemText(HorizontalType.Center, self.horizontalComboBox.setItemText(HorizontalType.Center,
translate('OpenLP.ThemeWizard', 'Center')) translate('OpenLP.ThemeWizard', 'Center'))
self.horizontalComboBox.setItemText(HorizontalType.Justify,
translate('OpenLP.ThemeWizard', 'Justify'))
self.transitionsLabel.setText( self.transitionsLabel.setText(
translate('OpenLP.ThemeWizard', 'Transitions:')) translate('OpenLP.ThemeWizard', 'Transitions:'))
self.areaPositionPage.setTitle( self.areaPositionPage.setTitle(
@ -533,6 +536,9 @@ class Ui_ThemeWizard(object):
translate('OpenLP.ThemeWizard', 'px')) translate('OpenLP.ThemeWizard', 'px'))
self.footerPositionCheckBox.setText( self.footerPositionCheckBox.setText(
translate('OpenLP.ThemeWizard', 'Use default location')) 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( self.previewPage.setTitle(
translate('OpenLP.ThemeWizard', 'Save and Preview')) translate('OpenLP.ThemeWizard', 'Save and Preview'))
self.previewPage.setSubTitle( self.previewPage.setSubTitle(

View File

@ -461,6 +461,11 @@ class BibleImportForm(OpenLPWizard):
WizardStrings.YouSpecifyFile % WizardStrings.OS) WizardStrings.YouSpecifyFile % WizardStrings.OS)
self.openSongFileEdit.setFocus() self.openSongFileEdit.setFocus()
return False 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: elif self.field(u'source_format').toInt()[0] == BibleFormat.OpenLP1:
if not self.field(u'openlp1_location').toString(): if not self.field(u'openlp1_location').toString():
critical_error_message_box(UiStrings().NFSs, critical_error_message_box(UiStrings().NFSs,
@ -674,7 +679,7 @@ class BibleImportForm(OpenLPWizard):
elif bible_type == BibleFormat.CSV: elif bible_type == BibleFormat.CSV:
# Import a CSV bible. # Import a CSV bible.
importer = self.manager.import_bible(BibleFormat.CSV, importer = self.manager.import_bible(BibleFormat.CSV,
name=license_version, name=license_version,
booksfile=unicode(self.field(u'csv_booksfile').toString()), booksfile=unicode(self.field(u'csv_booksfile').toString()),
versefile=unicode(self.field(u'csv_versefile').toString()) versefile=unicode(self.field(u'csv_versefile').toString())
) )

View File

@ -28,7 +28,6 @@
import logging import logging
import chardet import chardet
import os import os
import re
import sqlite3 import sqlite3
from PyQt4 import QtCore from PyQt4 import QtCore

View File

@ -82,13 +82,16 @@ class BGExtract(object):
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
footnotes = soup.findAll(u'sup', u'footnote') footnotes = soup.findAll(u'sup', u'footnote')
if footnotes: if footnotes:
[footnote.extract() for footnote in footnotes] for footnote in footnotes:
footnote.extract()
crossrefs = soup.findAll(u'sup', u'xref') crossrefs = soup.findAll(u'sup', u'xref')
if crossrefs: if crossrefs:
[crossref.extract() for crossref in crossrefs] for crossref in crossrefs:
crossref.extract()
headings = soup.findAll(u'h5') headings = soup.findAll(u'h5')
if headings: if headings:
[heading.extract() for heading in headings] for heading in headings:
heading.extract()
cleanup = [(re.compile('\s+'), lambda match: ' ')] cleanup = [(re.compile('\s+'), lambda match: ' ')]
verses = BeautifulSoup(str(soup), markupMassage=cleanup) verses = BeautifulSoup(str(soup), markupMassage=cleanup)
verse_list = {} verse_list = {}

View File

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

View File

@ -33,7 +33,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
SettingsManager, translate, check_item_selected, check_directory_exists, \ 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.lib.ui import UiStrings, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, get_images_filter from openlp.core.utils import AppLocation, delete_file, get_images_filter
@ -127,13 +127,13 @@ class ImageMediaItem(MediaManagerItem):
self.plugin.formparent.incrementProgressBar() self.plugin.formparent.incrementProgressBar()
filename = os.path.split(unicode(imageFile))[1] filename = os.path.split(unicode(imageFile))[1]
thumb = os.path.join(self.servicePath, filename) thumb = os.path.join(self.servicePath, filename)
if os.path.exists(thumb): if not os.path.exists(imageFile):
if self.validate(imageFile, thumb): icon = build_icon(u':/general/general_delete.png')
else:
if validate_thumb(imageFile, thumb):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = build_icon(u':/general/general_delete.png') icon = create_thumb(imageFile, thumb)
else:
icon = self.iconFromFile(imageFile, thumb)
item_name = QtGui.QListWidgetItem(filename) item_name = QtGui.QListWidgetItem(filename)
item_name.setIcon(icon) item_name.setIcon(icon)
item_name.setToolTip(imageFile) item_name.setToolTip(imageFile)

View File

@ -39,7 +39,7 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
CLAPPERBOARD = QtGui.QPixmap(u':/media/media_video.png').toImage() CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png')
class MediaMediaItem(MediaManagerItem): class MediaMediaItem(MediaManagerItem):
""" """
@ -95,14 +95,14 @@ class MediaMediaItem(MediaManagerItem):
def onResetClick(self): 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.resetAction.setVisible(False)
self.plugin.liveController.display.resetVideo() self.plugin.liveController.display.resetVideo()
def videobackgroundReplaced(self): def videobackgroundReplaced(self):
""" """
Triggered by main display on change of serviceitem Triggered by main display on change of serviceitem.
""" """
self.resetAction.setVisible(False) self.resetAction.setVisible(False)
@ -179,8 +179,7 @@ class MediaMediaItem(MediaManagerItem):
def mediaStateWait(self, mediaState): def mediaStateWait(self, mediaState):
""" """
Wait for the video to change its state Wait for the video to change its state. Wait no longer than 5 seconds.
Wait no longer than 5 seconds.
""" """
start = datetime.now() start = datetime.now()
while self.mediaObject.state() != mediaState: while self.mediaObject.state() != mediaState:
@ -198,7 +197,7 @@ class MediaMediaItem(MediaManagerItem):
def onDeleteClick(self): 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', if check_item_selected(self.listView, translate('MediaPlugin.MediaItem',
'You must select a media file to delete.')): 'You must select a media file to delete.')):

View File

@ -32,7 +32,8 @@ import locale
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \ 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, \ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
media_item_combo_box media_item_combo_box
from openlp.plugins.presentations.lib import MessageListener from openlp.plugins.presentations.lib import MessageListener
@ -193,10 +194,13 @@ class PresentationMediaItem(MediaManagerItem):
doc.load_presentation() doc.load_presentation()
preview = doc.get_thumbnail_path(1, True) preview = doc.get_thumbnail_path(1, True)
doc.close_presentation() doc.close_presentation()
if preview and self.validate(preview, thumb): if not (preview and os.path.exists(preview)):
icon = build_icon(thumb)
else:
icon = build_icon(u':/general/general_delete.png') 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: else:
if initialLoad: if initialLoad:
icon = build_icon(u':/general/general_delete.png') icon = build_icon(u':/general/general_delete.png')

View File

@ -31,7 +31,8 @@ import shutil
from PyQt4 import QtCore 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 from openlp.core.utils import AppLocation
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -97,8 +98,7 @@ class PresentationDocument(object):
self.slidenumber = 0 self.slidenumber = 0
self.controller = controller self.controller = controller
self.filepath = name self.filepath = name
if not os.path.isdir(self.get_thumbnail_folder()): check_directory_exists(self.get_thumbnail_folder())
os.mkdir(self.get_thumbnail_folder())
def load_presentation(self): def load_presentation(self):
""" """
@ -145,15 +145,13 @@ class PresentationDocument(object):
def check_thumbnails(self): def check_thumbnails(self):
""" """
Returns true if the thumbnail images look to exist and are more Returns ``True`` if the thumbnail images exist and are more recent than
recent than the powerpoint the powerpoint file.
""" """
lastimage = self.get_thumbnail_path(self.get_slide_count(), True) lastimage = self.get_thumbnail_path(self.get_slide_count(), True)
if not (lastimage and os.path.isfile(lastimage)): if not (lastimage and os.path.isfile(lastimage)):
return False return False
imgdate = os.stat(lastimage).st_mtime return validate_thumb(self.filepath, lastimage)
pptdate = os.stat(self.filepath).st_mtime
return imgdate >= pptdate
def close_presentation(self): def close_presentation(self):
""" """
@ -246,8 +244,8 @@ class PresentationDocument(object):
if self.check_thumbnails(): if self.check_thumbnails():
return return
if os.path.isfile(file): if os.path.isfile(file):
img = resize_image(file, 320, 240) thumb_path = self.get_thumbnail_path(idx, False)
img.save(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): def get_thumbnail_path(self, slide_no, check_exists):
""" """
@ -387,10 +385,8 @@ class PresentationController(object):
AppLocation.get_section_data_path(self.settings_section), AppLocation.get_section_data_path(self.settings_section),
u'thumbnails') u'thumbnails')
self.thumbnail_prefix = u'slide' self.thumbnail_prefix = u'slide'
if not os.path.isdir(self.thumbnail_folder): check_directory_exists(self.thumbnail_folder)
os.makedirs(self.thumbnail_folder) check_directory_exists(self.temp_folder)
if not os.path.isdir(self.temp_folder):
os.makedirs(self.temp_folder)
def enabled(self): def enabled(self):
""" """

View File

@ -87,7 +87,7 @@ class PresentationPlugin(Plugin):
to close down their applications and release resources. to close down their applications and release resources.
""" """
log.info(u'Plugin Finalise') log.info(u'Plugin Finalise')
#Ask each controller to tidy up # Ask each controller to tidy up.
for key in self.controllers: for key in self.controllers:
controller = self.controllers[key] controller = self.controllers[key]
if controller.enabled(): if controller.enabled():

View File

@ -121,11 +121,11 @@ window.OpenLP = {
$("#nextslide").html(text); $("#nextslide").html(text);
} }
}, },
updateClock: function() { updateClock: function(data) {
var div = $("#clock"); var div = $("#clock");
var t = new Date(); var t = new Date();
var h = t.getHours(); var h = t.getHours();
if (h > 12) if (data.results.twelve && h > 12)
h = h - 12; h = h - 12;
var m = t.getMinutes(); var m = t.getMinutes();
if (m < 10) if (m < 10)
@ -136,7 +136,7 @@ window.OpenLP = {
$.getJSON( $.getJSON(
"/api/poll", "/api/poll",
function (data, status) { function (data, status) {
OpenLP.updateClock(); OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item) { if (OpenLP.currentItem != data.results.item) {
OpenLP.currentItem = data.results.item; OpenLP.currentItem = data.results.item;
OpenLP.loadSlides(); OpenLP.loadSlides();

View File

@ -315,7 +315,7 @@ class HttpConnection(object):
""" """
log.debug(u'ready to read socket') log.debug(u'ready to read socket')
if self.socket.canReadLine(): if self.socket.canReadLine():
data = unicode(self.socket.readLine()) data = unicode(self.socket.readLine()).encode(u'utf-8')
log.debug(u'received: ' + data) log.debug(u'received: ' + data)
words = data.split(u' ') words = data.split(u' ')
response = None response = None
@ -397,7 +397,9 @@ class HttpConnection(object):
result = { result = {
u'slide': self.parent.current_slide or 0, u'slide': self.parent.current_slide or 0,
u'item': self.parent.current_item._uuid \ 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}), return HttpResponse(json.dumps({u'results': result}),
{u'Content-Type': u'application/json'}) {u'Content-Type': u'application/json'})

View File

@ -57,6 +57,9 @@ class RemoteTab(SettingsTab):
QtCore.QObject.connect(self.addressEdit, QtCore.QObject.connect(self.addressEdit,
QtCore.SIGNAL(u'textChanged(const QString&)'), self.setUrls) QtCore.SIGNAL(u'textChanged(const QString&)'), self.setUrls)
self.serverSettingsLayout.addRow(self.addressLabel, self.addressEdit) 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 = QtGui.QLabel(self.serverSettingsGroupBox)
self.portLabel.setObjectName(u'portLabel') self.portLabel.setObjectName(u'portLabel')
self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox) self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox)
@ -80,6 +83,9 @@ class RemoteTab(SettingsTab):
self.leftLayout.addWidget(self.serverSettingsGroupBox) self.leftLayout.addWidget(self.serverSettingsGroupBox)
self.leftLayout.addStretch() self.leftLayout.addStretch()
self.rightLayout.addStretch() self.rightLayout.addStretch()
QtCore.QObject.connect(self.twelveHourCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onTwelveHourCheckBoxChanged)
def retranslateUi(self): def retranslateUi(self):
self.serverSettingsGroupBox.setTitle( self.serverSettingsGroupBox.setTitle(
@ -92,6 +98,9 @@ class RemoteTab(SettingsTab):
'Remote URL:')) 'Remote URL:'))
self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab', self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab',
'Stage view URL:')) 'Stage view URL:'))
self.twelveHourCheckBox.setText(
translate('RemotePlugin.RemoteTab',
'Display stage time in 12h format'))
def setUrls(self): def setUrls(self):
ipAddress = u'localhost' ipAddress = u'localhost'
@ -123,6 +132,10 @@ class RemoteTab(SettingsTab):
self.addressEdit.setText( self.addressEdit.setText(
QtCore.QSettings().value(self.settingsSection + u'/ip address', QtCore.QSettings().value(self.settingsSection + u'/ip address',
QtCore.QVariant(ZERO_URL)).toString()) 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() self.setUrls()
def save(self): def save(self):
@ -130,3 +143,11 @@ class RemoteTab(SettingsTab):
QtCore.QVariant(self.portSpinBox.value())) QtCore.QVariant(self.portSpinBox.value()))
QtCore.QSettings().setValue(self.settingsSection + u'/ip address', QtCore.QSettings().setValue(self.settingsSection + u'/ip address',
QtCore.QVariant(self.addressEdit.text())) 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

View File

@ -240,8 +240,9 @@ class SongImportForm(OpenLPWizard):
self.formatLabel.setText(WizardStrings.FormatLabel) self.formatLabel.setText(WizardStrings.FormatLabel)
self.formatComboBox.setItemText(SongFormat.OpenLP2, UiStrings().OLPV2) self.formatComboBox.setItemText(SongFormat.OpenLP2, UiStrings().OLPV2)
self.formatComboBox.setItemText(SongFormat.OpenLP1, UiStrings().OLPV1) self.formatComboBox.setItemText(SongFormat.OpenLP1, UiStrings().OLPV1)
self.formatComboBox.setItemText( self.formatComboBox.setItemText(SongFormat.OpenLyrics,
SongFormat.OpenLyrics, WizardStrings.OL) translate('SongsPlugin.ImportWizardForm',
'OpenLyrics or OpenLP 2.0 Exported Song'))
self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS) self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS)
self.formatComboBox.setItemText( self.formatComboBox.setItemText(
SongFormat.WordsOfWorship, WizardStrings.WoW) SongFormat.WordsOfWorship, WizardStrings.WoW)
@ -508,7 +509,8 @@ class SongImportForm(OpenLPWizard):
Get OpenLyrics song database files Get OpenLyrics song database files
""" """
self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.OL, self.getFiles(WizardStrings.OpenTypeFile % WizardStrings.OL,
self.openLyricsFileListWidget) self.openLyricsFileListWidget, u'%s (*.xml)' %
translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'))
def onOpenLyricsRemoveButtonClicked(self): def onOpenLyricsRemoveButtonClicked(self):
""" """

View File

@ -333,5 +333,6 @@ class CCLIFileImport(SongImport):
if len(author_list) < 2: if len(author_list) < 2:
author_list = song_author.split(u'|') author_list = song_author.split(u'|')
# Clean spaces before and after author names. # Clean spaces before and after author names.
[self.addAuthor(author_name.strip()) for author_name in author_list] for author_name in author_list:
self.addAuthor(author_name.strip())
return self.finish() return self.finish()

View File

@ -258,7 +258,7 @@ class EasiSlidesImport(SongImport):
verses[reg][vt][vn] = {} verses[reg][vt][vn] = {}
if not verses[reg][vt][vn].has_key(inst): if not verses[reg][vt][vn].has_key(inst):
verses[reg][vt][vn][inst] = [] verses[reg][vt][vn][inst] = []
words = self.tidy_text(line) words = self.tidyText(line)
verses[reg][vt][vn][inst].append(words) verses[reg][vt][vn][inst].append(words)
# done parsing # done parsing

View File

@ -397,7 +397,8 @@ class SongMediaItem(MediaManagerItem):
try: try:
os.remove(media_file.file_name) os.remove(media_file.file_name)
except: except:
log.exception('Could not remove file: %s', audio) log.exception('Could not remove file: %s',
media_file.file_name)
try: try:
save_path = os.path.join(AppLocation.get_section_data_path( save_path = os.path.join(AppLocation.get_section_data_path(
self.plugin.name), 'audio', str(item_id)) self.plugin.name), 'audio', str(item_id))
@ -428,11 +429,9 @@ class SongMediaItem(MediaManagerItem):
def generateSlideData(self, service_item, item=None, xmlVersion=False, def generateSlideData(self, service_item, item=None, xmlVersion=False,
remote=False): remote=False):
log.debug(u'generateSlideData: %s, %s, %s' % (service_item, item, self.remoteSong)) log.debug(u'generateSlideData: %s, %s, %s' %
# The ``None`` below is a workaround for bug #812289 - I think that Qt (service_item, item, self.remoteSong))
# deletes the item somewhere along the line because the user is taking item_id = self._getIdOfItemToGenerate(item, self.remoteSong)
# so long to update their item (or something weird like that).
item_id = self._getIdOfItemToGenerate(None, self.remoteSong)
service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanEdit)
service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanLoop)
@ -512,7 +511,8 @@ class SongMediaItem(MediaManagerItem):
# Add the audio file to the service item. # Add the audio file to the service item.
if len(song.media_files) > 0: if len(song.media_files) > 0:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio) 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 return True
def serviceLoad(self, item): def serviceLoad(self, item):

View File

@ -30,7 +30,6 @@ songs from the database to the OpenLyrics format.
""" """
import logging import logging
import os import os
import re
from lxml import etree from lxml import etree

View File

@ -194,7 +194,7 @@ class SofImport(OooImport):
into line into line
""" """
text = textportion.getString() text = textportion.getString()
text = self.tidy_text(text) text = self.tidyText(text)
if text.strip() == u'': if text.strip() == u'':
return text return text
if textportion.CharWeight == BOLD: if textportion.CharWeight == BOLD:

View File

@ -29,7 +29,7 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin 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 sqlalchemy.sql.expression import func
from migrate import changeset from migrate import changeset
from migrate.changeset.constraint import ForeignKeyConstraint from migrate.changeset.constraint import ForeignKeyConstraint

View File

@ -60,7 +60,7 @@ The XML of an `OpenLyrics <http://openlyrics.info/>`_ song looks like this::
</lyrics> </lyrics>
</song> </song>
""" """
import cgi
import logging import logging
import re import re
@ -257,11 +257,12 @@ class OpenLyrics(object):
""" """
IMPLEMENTED_VERSION = u'0.8' 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): def __init__(self, manager):
self.manager = 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): def song_to_xml(self, song):
""" """
@ -334,7 +335,8 @@ class OpenLyrics(object):
if u'lang' in verse[0]: if u'lang' in verse[0]:
verse_element.set(u'lang', verse[0][u'lang']) verse_element.set(u'lang', verse[0][u'lang'])
# Create a list with all "virtual" verses. # 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): for index, virtual_verse in enumerate(virtual_verses):
# Add formatting tags to text # Add formatting tags to text
lines_element = self._add_text_with_tags_to_lines(verse_element, 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): def _add_tag_to_formatting(self, tag_name, tags_element):
""" """
Add new formatting tag to the element ``<format>`` Add new formatting tag to the element ``<format>`` if the tag is not
if the tag is not present yet. present yet.
""" """
available_tags = FormattingTags.get_html_tags() available_tags = FormattingTags.get_html_tags()
start_tag = '{%s}' % tag_name start_tag = '{%s}' % tag_name
for t in available_tags: for tag in available_tags:
if t[u'start tag'] == start_tag: if tag[u'start tag'] == start_tag:
# Create new formatting tag in openlyrics xml. # Create new formatting tag in openlyrics xml.
el = self._add_text_to_element(u'tag', tags_element) element = self._add_text_to_element(u'tag', tags_element)
el.set(u'name', tag_name) element.set(u'name', tag_name)
el_open = self._add_text_to_element(u'open', el) element_open = self._add_text_to_element(u'open', element)
el_open.text = etree.CDATA(t[u'start html']) element_open.text = etree.CDATA(tag[u'start html'])
# Check if formatting tag contains end tag. Some formatting # Check if formatting tag contains end tag. Some formatting
# tags e.g. {br} has only start tag. If no end tag is present # tags e.g. {br} has only start tag. If no end tag is present
# <close> element has not to be in OpenLyrics xml. # <close> element has not to be in OpenLyrics xml.
if t['end tag']: if tag['end tag']:
el_close = self._add_text_to_element(u'close', el) element_close = self._add_text_to_element(u'close', element)
el_close.text = etree.CDATA(t[u'end html']) element_close.text = etree.CDATA(tag[u'end html'])
def _add_text_with_tags_to_lines(self, verse_element, text, tags_element): def _add_text_with_tags_to_lines(self, verse_element, text, tags_element):
""" """
Convert text with formatting tags from OpenLP format to OpenLyrics Convert text with formatting tags from OpenLP format to OpenLyrics
format and append it to element ``<lines>``. format and append it to element ``<lines>``.
""" """
start_tags = self.start_tags_regex.findall(text) start_tags = OpenLyrics.START_TAGS_REGEX.findall(text)
end_tags = self.end_tags_regex.findall(text) end_tags = OpenLyrics.END_TAGS_REGEX.findall(text)
# Replace start tags with xml syntax. # Replace start tags with xml syntax.
for tag in start_tags: for tag in start_tags:
# Tags already converted to xml structure. # Tags already converted to xml structure.
@ -442,12 +444,11 @@ class OpenLyrics(object):
if tag not in xml_tags: if tag not in xml_tags:
self._add_tag_to_formatting(tag, tags_element) self._add_tag_to_formatting(tag, tags_element)
# Replace end tags. # Replace end tags.
for t in end_tags: for tag in end_tags:
text = text.replace(u'{/%s}' % t, u'</tag>') text = text.replace(u'{/%s}' % tag, u'</tag>')
# Replace \n with <br/>. # Replace \n with <br/>.
text = text.replace(u'\n', u'<br/>') text = text.replace(u'\n', u'<br/>')
text = u'<lines>' + text + u'</lines>' element = etree.XML(u'<lines>%s</lines>' % text)
element = etree.XML(text)
verse_element.append(element) verse_element.append(element)
return element return element
@ -692,7 +693,7 @@ class OpenLyrics(object):
verse_tag = verse_def[0] verse_tag = verse_def[0]
else: else:
verse_tag = VerseType.Tags[VerseType.Other] 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 # OpenLyrics allows e. g. "c", but we need "c1". However, this does
# not correct the verse order. # not correct the verse order.
if not verse_number: if not verse_number:

View File

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

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ThemeLayout</class>
<widget class="QDialog" name="ThemeLayout">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Theme Layout</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>50</x>
<y>260</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QLabel" name="themeDisplayLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>361</width>
<height>231</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ThemeLayout</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ThemeLayout</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -60,8 +60,6 @@
</qresource> </qresource>
<qresource prefix="slides"> <qresource prefix="slides">
<file>slide_close.png</file> <file>slide_close.png</file>
<file>slide_first.png</file>
<file>slide_last.png</file>
<file>slide_next.png</file> <file>slide_next.png</file>
<file>slide_blank.png</file> <file>slide_blank.png</file>
<file>slide_desktop.png</file> <file>slide_desktop.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

View File

@ -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""" Root: HKCR; Subkey: "OpenLP\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\OpenLP.exe"" ""%1"""
[Code] [Code]
function IsModuleLoaded(modulename: String ): Boolean; function IsModuleLoaded(modulename: AnsiString ): Boolean;
external 'IsModuleLoaded@files:psvince.dll stdcall'; external 'IsModuleLoaded@files:psvince.dll stdcall';
function GetUninstallString(): String; function GetUninstallString(): String;

View File

@ -8,7 +8,7 @@
# Copyright (c) 2008-2011 Raoul Snyman # # Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # # 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, # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #

View File

@ -5,11 +5,11 @@
# OpenLP - Open Source Lyrics Projection # # OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman # # Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, # # Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, # # Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Woldsund # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it # # 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 # # under the terms of the GNU General Public License as published by the Free #

View File

@ -8,7 +8,7 @@
# Copyright (c) 2008-2011 Raoul Snyman # # Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # # 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, # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #

View File

@ -8,7 +8,7 @@
# Copyright (c) 2008-2011 Raoul Snyman # # Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # # Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # # 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, # # Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # # Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #