This commit is contained in:
Andreas Preikschat 2012-07-07 16:55:23 +02:00
commit bbc6b0d7b6
45 changed files with 34003 additions and 33488 deletions

View File

@ -36,6 +36,23 @@ from PyQt4 import QtCore, QtGui, Qt
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ImageSource(object):
"""
This enumeration class represents different image sources. An image sources
states where an image is used. This enumeration class is need in the context
of the :class:~openlp.core.lib.imagemanager`.
``ImagePlugin``
This states that an image is being used by the image plugin.
``Theme``
This says, that the image is used by a theme.
"""
ImagePlugin = 1
Theme = 2
class MediaType(object): class MediaType(object):
""" """
An enumeration class for types of media. An enumeration class for types of media.

View File

@ -32,6 +32,7 @@ A Thread is used to convert the image to a byte array so the user does not need
to wait for the conversion to happen. to wait for the conversion to happen.
""" """
import logging import logging
import os
import time import time
import Queue import Queue
@ -97,19 +98,34 @@ class Priority(object):
class Image(object): class Image(object):
""" """
This class represents an image. To mark an image as *dirty* set the instance This class represents an image. To mark an image as *dirty* call the
variables ``image`` and ``image_bytes`` to ``None`` and add the image object :class:`ImageManager`'s ``_resetImage`` method with the Image instance as
to the queue of images to process. argument.
""" """
secondary_priority = 0 secondary_priority = 0
def __init__(self, name, path, source, background):
self.name = name def __init__(self, path, source, background):
"""
Create an image for the :class:`ImageManager`'s cache.
``path``
The image's file path. This should be an existing file path.
``source``
The source describes the image's origin. Possible values are
described in the :class:`~openlp.core.lib.ImageSource` class.
``background``
A ``QtGui.QColor`` object specifying the colour to be used to fill
the gabs if the image's ratio does not match with the display ratio.
"""
self.path = path self.path = path
self.image = None self.image = None
self.image_bytes = None self.image_bytes = None
self.priority = Priority.Normal self.priority = Priority.Normal
self.source = source self.source = source
self.background = background self.background = background
self.timestamp = os.stat(path).st_mtime
self.secondary_priority = Image.secondary_priority self.secondary_priority = Image.secondary_priority
Image.secondary_priority += 1 Image.secondary_priority += 1
@ -118,7 +134,7 @@ class PriorityQueue(Queue.PriorityQueue):
""" """
Customised ``Queue.PriorityQueue``. Customised ``Queue.PriorityQueue``.
Each item in the queue must be tuple with three values. The first value Each item in the queue must be a tuple with three values. The first value
is the :class:`Image`'s ``priority`` attribute, the second value is the :class:`Image`'s ``priority`` attribute, the second value
the :class:`Image`'s ``secondary_priority`` attribute. The last value the the :class:`Image`'s ``secondary_priority`` attribute. The last value the
:class:`Image` instance itself:: :class:`Image` instance itself::
@ -187,7 +203,7 @@ class ImageManager(QtCore.QObject):
for image in self._cache.values(): for image in self._cache.values():
self._resetImage(image) self._resetImage(image)
def updateImages(self, imageType, background): def updateImagesBorder(self, source, background):
""" """
Border has changed so update all the images affected. Border has changed so update all the images affected.
""" """
@ -195,23 +211,27 @@ class ImageManager(QtCore.QObject):
# Mark the images as dirty for a rebuild by setting the image and byte # Mark the images as dirty for a rebuild by setting the image and byte
# stream to None. # stream to None.
for image in self._cache.values(): for image in self._cache.values():
if image.source == imageType: if image.source == source:
image.background = background image.background = background
self._resetImage(image) self._resetImage(image)
def updateImage(self, name, imageType, background): def updateImageBorder(self, path, source, background):
""" """
Border has changed so update the image affected. Border has changed so update the image affected.
""" """
log.debug(u'updateImage') log.debug(u'updateImage')
# Mark the images as dirty for a rebuild by setting the image and byte # Mark the image as dirty for a rebuild by setting the image and byte
# stream to None. # stream to None.
for image in self._cache.values(): image = self._cache[(path, source)]
if image.source == imageType and image.name == name: if image.source == source:
image.background = background image.background = background
self._resetImage(image) self._resetImage(image)
def _resetImage(self, image): def _resetImage(self, image):
"""
Mark the given :class:`Image` instance as dirty by setting its ``image``
and ``image_bytes`` attributes to None.
"""
image.image = None image.image = None
image.image_bytes = None image.image_bytes = None
self._conversionQueue.modify_priority(image, Priority.Normal) self._conversionQueue.modify_priority(image, Priority.Normal)
@ -224,13 +244,13 @@ class ImageManager(QtCore.QObject):
if not self.imageThread.isRunning(): if not self.imageThread.isRunning():
self.imageThread.start() self.imageThread.start()
def getImage(self, name): def getImage(self, path, source):
""" """
Return the ``QImage`` from the cache. If not present wait for the Return the ``QImage`` from the cache. If not present wait for the
background thread to process it. background thread to process it.
""" """
log.debug(u'getImage %s' % name) log.debug(u'getImage %s' % path)
image = self._cache[name] image = self._cache[(path, source)]
if image.image is None: if image.image is None:
self._conversionQueue.modify_priority(image, Priority.High) self._conversionQueue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick # make sure we are running and if not give it a kick
@ -246,13 +266,13 @@ class ImageManager(QtCore.QObject):
self._conversionQueue.modify_priority(image, Priority.Low) self._conversionQueue.modify_priority(image, Priority.Low)
return image.image return image.image
def getImageBytes(self, name): def getImageBytes(self, path, source):
""" """
Returns the byte string for an image. If not present wait for the Returns the byte string for an image. If not present wait for the
background thread to process it. background thread to process it.
""" """
log.debug(u'getImageBytes %s' % name) log.debug(u'getImageBytes %s' % path)
image = self._cache[name] image = self._cache[(path, source)]
if image.image_bytes is None: if image.image_bytes is None:
self._conversionQueue.modify_priority(image, Priority.Urgent) self._conversionQueue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick # make sure we are running and if not give it a kick
@ -262,27 +282,22 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1) time.sleep(0.1)
return image.image_bytes return image.image_bytes
def deleteImage(self, name): def addImage(self, path, source, background):
"""
Delete the Image from the cache.
"""
log.debug(u'deleteImage %s' % name)
if name in self._cache:
self._conversionQueue.remove(self._cache[name])
del self._cache[name]
def addImage(self, name, path, source, background):
""" """
Add image to cache if it is not already there. Add image to cache if it is not already there.
""" """
log.debug(u'addImage %s:%s' % (name, path)) log.debug(u'addImage %s' % path)
if not name in self._cache: if not (path, source) in self._cache:
image = Image(name, path, source, background) image = Image(path, source, background)
self._cache[name] = image self._cache[(path, source)] = image
self._conversionQueue.put( self._conversionQueue.put(
(image.priority, image.secondary_priority, image)) (image.priority, image.secondary_priority, image))
else: # Check if the there are any images with the same path and check if the
log.debug(u'Image in cache %s:%s' % (name, path)) # timestamp has changed.
for image in self._cache.values():
if image.path == path and image.timestamp != os.stat(path).st_mtime:
image.timestamp = os.stat(path).st_mtime
self._resetImage(image)
# We want only one thread. # We want only one thread.
if not self.imageThread.isRunning(): if not self.imageThread.isRunning():
self.imageThread.start() self.imageThread.start()

View File

@ -352,24 +352,23 @@ class MediaManagerItem(QtGui.QWidget):
``files`` ``files``
The list of files to be loaded The list of files to be loaded
""" """
#FIXME: change local variables to words_separated_by_underscores. new_files = []
newFiles = [] error_shown = False
errorShown = False
for file in files: for file in files:
type = file.split(u'.')[-1] type = file.split(u'.')[-1]
if type.lower() not in self.onNewFileMasks: if type.lower() not in self.onNewFileMasks:
if not errorShown: if not error_shown:
critical_error_message_box( critical_error_message_box(
translate('OpenLP.MediaManagerItem', translate('OpenLP.MediaManagerItem',
'Invalid File Type'), 'Invalid File Type'),
unicode(translate('OpenLP.MediaManagerItem', unicode(translate('OpenLP.MediaManagerItem',
'Invalid File %s.\nSuffix not supported')) 'Invalid File %s.\nSuffix not supported'))
% file) % file)
errorShown = True error_shown = True
else: else:
newFiles.append(file) new_files.append(file)
if files: if new_files:
self.validateAndLoad(newFiles) self.validateAndLoad(new_files)
def validateAndLoad(self, files): def validateAndLoad(self, files):
""" """
@ -379,30 +378,29 @@ class MediaManagerItem(QtGui.QWidget):
``files`` ``files``
The files to be loaded. The files to be loaded.
""" """
#FIXME: change local variables to words_separated_by_underscores.
names = [] names = []
fullList = [] full_list = []
for count in range(self.listView.count()): for count in range(self.listView.count()):
names.append(unicode(self.listView.item(count).text())) names.append(unicode(self.listView.item(count).text()))
fullList.append(unicode(self.listView.item(count). full_list.append(unicode(self.listView.item(count).
data(QtCore.Qt.UserRole).toString())) data(QtCore.Qt.UserRole).toString()))
duplicatesFound = False duplicates_found = False
filesAdded = False files_added = False
for file in files: for file in files:
filename = os.path.split(unicode(file))[1] filename = os.path.split(unicode(file))[1]
if filename in names: if filename in names:
duplicatesFound = True duplicates_found = True
else: else:
filesAdded = True files_added = True
fullList.append(file) full_list.append(file)
if fullList and filesAdded: if full_list and files_added:
self.listView.clear() self.listView.clear()
self.loadList(fullList) self.loadList(full_list)
lastDir = os.path.split(unicode(files[0]))[0] last_dir = os.path.split(unicode(files[0]))[0]
SettingsManager.set_last_dir(self.settingsSection, lastDir) SettingsManager.set_last_dir(self.settingsSection, last_dir)
SettingsManager.set_list(self.settingsSection, SettingsManager.set_list(self.settingsSection,
self.settingsSection, self.getFileList()) self.settingsSection, self.getFileList())
if duplicatesFound: if duplicates_found:
critical_error_message_box( critical_error_message_box(
UiStrings().Duplicate, UiStrings().Duplicate,
unicode(translate('OpenLP.MediaManagerItem', unicode(translate('OpenLP.MediaManagerItem',
@ -422,13 +420,13 @@ class MediaManagerItem(QtGui.QWidget):
Return the current list of files Return the current list of files
""" """
count = 0 count = 0
filelist = [] file_list = []
while count < self.listView.count(): while count < self.listView.count():
bitem = self.listView.item(count) bitem = self.listView.item(count)
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
filelist.append(filename) file_list.append(filename)
count += 1 count += 1
return filelist return file_list
def loadList(self, list): def loadList(self, list):
raise NotImplementedError(u'MediaManagerItem.loadList needs to be ' raise NotImplementedError(u'MediaManagerItem.loadList needs to be '
@ -477,9 +475,8 @@ class MediaManagerItem(QtGui.QWidget):
Allows the change of current item in the list to be actioned Allows the change of current item in the list to be actioned
""" """
if Settings().value(u'advanced/single click preview', if Settings().value(u'advanced/single click preview',
QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \ QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed and \
and self.listView.selectedIndexes() \ self.listView.selectedIndexes() and self.autoSelectId == -1:
and self.autoSelectId == -1:
self.onPreviewClick(True) self.onPreviewClick(True)
def onPreviewClick(self, keepFocus=False): def onPreviewClick(self, keepFocus=False):

View File

@ -32,7 +32,7 @@ from PyQt4 import QtGui, QtCore, QtWebKit
from openlp.core.lib import ServiceItem, expand_tags, \ from openlp.core.lib import ServiceItem, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \ build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
ItemCapabilities, FormattingTags ItemCapabilities, FormattingTags, ImageSource
from openlp.core.lib.theme import ThemeLevel from openlp.core.lib.theme import ThemeLevel
from openlp.core.ui import MainDisplay, ScreenList from openlp.core.ui import MainDisplay, ScreenList
@ -82,6 +82,9 @@ class Renderer(object):
self._calculate_default() self._calculate_default()
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'theme_update_global'), self.set_global_theme) QtCore.SIGNAL(u'theme_update_global'), self.set_global_theme)
self.web = QtWebKit.QWebView()
self.web.setVisible(False)
self.web_frame = self.web.page().mainFrame()
def update_display(self): def update_display(self):
""" """
@ -137,8 +140,8 @@ class Renderer(object):
self._theme_dimensions[theme_name] self._theme_dimensions[theme_name]
# if No file do not update cache # if No file do not update cache
if theme_data.background_filename: if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name, self.image_manager.addImage(theme_data.background_filename,
theme_data.background_filename, u'theme', ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color)) QtGui.QColor(theme_data.background_border_color))
def pre_render(self, override_theme_data=None): def pre_render(self, override_theme_data=None):
@ -237,14 +240,13 @@ class Renderer(object):
# 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(VERSE_FOR_LINE_COUNT) serviceItem.add_from_text(VERSE_FOR_LINE_COUNT)
else: else:
self.image_manager.deleteImage(theme_data.theme_name)
serviceItem.add_from_text(VERSE) serviceItem.add_from_text(VERSE)
serviceItem.renderer = self serviceItem.renderer = self
serviceItem.raw_footer = FOOTER serviceItem.raw_footer = FOOTER
# if No file do not update cache # if No file do not update cache
if theme_data.background_filename: if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name, self.image_manager.addImage(theme_data.background_filename,
theme_data.background_filename, u'theme', ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color)) QtGui.QColor(theme_data.background_border_color))
theme_data, main, footer = self.pre_render(theme_data) theme_data, main, footer = self.pre_render(theme_data)
serviceItem.themedata = theme_data serviceItem.themedata = theme_data
@ -404,10 +406,7 @@ class Renderer(object):
if theme_data.font_main_shadow: if theme_data.font_main_shadow:
self.page_width -= int(theme_data.font_main_shadow_size) self.page_width -= int(theme_data.font_main_shadow_size)
self.page_height -= int(theme_data.font_main_shadow_size) self.page_height -= int(theme_data.font_main_shadow_size)
self.web = QtWebKit.QWebView()
self.web.setVisible(False)
self.web.resize(self.page_width, self.page_height) self.web.resize(self.page_width, self.page_height)
self.web_frame = self.web.page().mainFrame()
# Adjust width and height to account for shadow. outline done in css. # Adjust width and height to account for shadow. outline done in css.
html = u"""<!DOCTYPE html><html><head><script> html = u"""<!DOCTYPE html><html><head><script>
function show_text(newtext) { function show_text(newtext) {

View File

@ -36,7 +36,8 @@ import logging
import os import os
import uuid import uuid
from openlp.core.lib import build_icon, clean_tags, expand_tags, translate from openlp.core.lib import build_icon, clean_tags, expand_tags, translate, \
ImageSource
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -178,7 +179,7 @@ class ServiceItem(object):
self.renderer.set_item_theme(self.theme) self.renderer.set_item_theme(self.theme)
self.themedata, self.main, self.footer = self.renderer.pre_render() self.themedata, self.main, self.footer = self.renderer.pre_render()
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
log.debug(u'Formatting slides') log.debug(u'Formatting slides: %s' % self.title)
for slide in self._raw_frames: for slide in self._raw_frames:
pages = self.renderer.format_slide(slide[u'raw_slide'], self) pages = self.renderer.format_slide(slide[u'raw_slide'], self)
for page in pages: for page in pages:
@ -217,8 +218,8 @@ class ServiceItem(object):
self.image_border = background self.image_border = background
self.service_item_type = ServiceItemType.Image self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path}) self._raw_frames.append({u'title': title, u'path': path})
self.renderer.image_manager.addImage(title, path, u'image', self.renderer.image_manager.addImage(
self.image_border) path, ImageSource.ImagePlugin, self.image_border)
self._new_item() self._new_item()
def add_from_text(self, raw_slide, verse_tag=None): def add_from_text(self, raw_slide, verse_tag=None):
@ -432,13 +433,12 @@ class ServiceItem(object):
def get_rendered_frame(self, row): def get_rendered_frame(self, row):
""" """
Returns the correct frame for a given list and Returns the correct frame for a given list and renders it if required.
renders it if required.
""" """
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
return self._display_frames[row][u'html'].split(u'\n')[0] return self._display_frames[row][u'html'].split(u'\n')[0]
elif self.service_item_type == ServiceItemType.Image: elif self.service_item_type == ServiceItemType.Image:
return self._raw_frames[row][u'title'] return self._raw_frames[row][u'path']
else: else:
return self._raw_frames[row][u'image'] return self._raw_frames[row][u'image']

View File

@ -37,7 +37,7 @@ from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
from PyQt4.phonon import Phonon from PyQt4.phonon import Phonon
from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \ from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \
translate, PluginManager, expand_tags translate, PluginManager, expand_tags, ImageSource
from openlp.core.lib.theme import BackgroundType from openlp.core.lib.theme import BackgroundType
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
@ -274,31 +274,33 @@ class MainDisplay(Display):
self.setVisible(False) self.setVisible(False)
self.setGeometry(self.screen[u'size']) self.setGeometry(self.screen[u'size'])
def directImage(self, name, path, background): def directImage(self, path, background):
""" """
API for replacement backgrounds so Images are added directly to cache. API for replacement backgrounds so Images are added directly to cache.
""" """
self.imageManager.addImage(name, path, u'image', background) self.imageManager.addImage(path, ImageSource.ImagePlugin, background)
if hasattr(self, u'serviceItem'): if not hasattr(self, u'serviceItem'):
self.override[u'image'] = name return False
self.override[u'theme'] = self.serviceItem.themedata.theme_name self.override[u'image'] = path
self.image(name) self.override[u'theme'] = self.serviceItem.themedata.background_filename
# Update the preview frame. self.image(path)
if self.isLive: # Update the preview frame.
self.parent().updatePreview() if self.isLive:
return True self.parent().updatePreview()
return False return True
def image(self, name): def image(self, path):
""" """
Add an image as the background. The image has already been added to the Add an image as the background. The image has already been added to the
cache. cache.
``name`` ``path``
The name of the image to be displayed. The path to the image to be displayed. **Note**, the path is only
passed to identify the image. If the image has changed it has to be
re-added to the image manager.
""" """
log.debug(u'image to display') log.debug(u'image to display')
image = self.imageManager.getImageBytes(name) image = self.imageManager.getImageBytes(path, ImageSource.ImagePlugin)
self.controller.mediaController.video_reset(self.controller) self.controller.mediaController.video_reset(self.controller)
self.displayImage(image) self.displayImage(image)
@ -360,7 +362,7 @@ class MainDisplay(Display):
self.setVisible(True) self.setVisible(True)
return QtGui.QPixmap.grabWidget(self) return QtGui.QPixmap.grabWidget(self)
def buildHtml(self, serviceItem, image=None): def buildHtml(self, serviceItem, image_path=u''):
""" """
Store the serviceItem and build the new HTML from it. Add the Store the serviceItem and build the new HTML from it. Add the
HTML to the display HTML to the display
@ -377,20 +379,23 @@ class MainDisplay(Display):
Receiver.send_message(u'video_background_replaced') Receiver.send_message(u'video_background_replaced')
self.override = {} self.override = {}
# We have a different theme. # We have a different theme.
elif self.override[u'theme'] != serviceItem.themedata.theme_name: elif self.override[u'theme'] != \
serviceItem.themedata.background_filename:
Receiver.send_message(u'live_theme_changed') Receiver.send_message(u'live_theme_changed')
self.override = {} self.override = {}
else: else:
# replace the background # replace the background
background = self.imageManager. \ background = self.imageManager.getImageBytes(
getImageBytes(self.override[u'image']) self.override[u'image'], ImageSource.ImagePlugin)
self.setTransparency(self.serviceItem.themedata.background_type == self.setTransparency(self.serviceItem.themedata.background_type ==
BackgroundType.to_string(BackgroundType.Transparent)) BackgroundType.to_string(BackgroundType.Transparent))
if self.serviceItem.themedata.background_filename: if self.serviceItem.themedata.background_filename:
self.serviceItem.bg_image_bytes = self.imageManager. \ self.serviceItem.bg_image_bytes = self.imageManager.getImageBytes(
getImageBytes(self.serviceItem.themedata.theme_name) self.serviceItem.themedata.background_filename,
if image: ImageSource.Theme)
image_bytes = self.imageManager.getImageBytes(image) if image_path:
image_bytes = self.imageManager.getImageBytes(
image_path, ImageSource.ImagePlugin)
else: else:
image_bytes = None image_bytes = None
html = build_html(self.serviceItem, self.screen, self.isLive, html = build_html(self.serviceItem, self.screen, self.isLive,

View File

@ -48,7 +48,7 @@ from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
ShortcutListForm, FormattingTagForm ShortcutListForm, FormattingTagForm
from openlp.core.ui.media import MediaController from openlp.core.ui.media import MediaController
from openlp.core.utils import AppLocation, add_actions, LanguageManager, \ from openlp.core.utils import AppLocation, add_actions, LanguageManager, \
get_application_version get_application_version, get_filesystem_encoding
from openlp.core.utils.actions import ActionList, CategoryOrder from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui import ScreenList from openlp.core.ui import ScreenList

View File

@ -34,7 +34,7 @@ from collections import deque
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \
translate, build_icon, build_html, PluginManager, ServiceItem translate, build_icon, build_html, PluginManager, ServiceItem, ImageSource
from openlp.core.lib.ui import UiStrings, create_action from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.core.lib import SlideLimits, ServiceItemAction from openlp.core.lib import SlideLimits, ServiceItemAction
@ -861,8 +861,10 @@ class SlideController(Controller):
# 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.getImageBytes(frame[u'title']) self.imageManager.getImageBytes(frame[u'path'],
image = self.imageManager.getImage(frame[u'title']) ImageSource.ImagePlugin)
image = self.imageManager.getImage(frame[u'path'],
ImageSource.ImagePlugin)
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 * (1 / self.ratio) slideHeight = width * (1 / self.ratio)
@ -1092,14 +1094,14 @@ class SlideController(Controller):
u'%s_slide' % self.serviceItem.name.lower(), u'%s_slide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive, row]) [self.serviceItem, self.isLive, row])
else: else:
toDisplay = self.serviceItem.get_rendered_frame(row) to_display = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text(): if self.serviceItem.is_text():
self.display.text(toDisplay) self.display.text(to_display)
else: else:
if start: if start:
self.display.buildHtml(self.serviceItem, toDisplay) self.display.buildHtml(self.serviceItem, to_display)
else: else:
self.display.image(toDisplay) self.display.image(to_display)
# 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.updatePreview() self.updatePreview()

View File

@ -228,10 +228,8 @@ 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: enabled = self.page(pageId) == self.areaPositionPage
self.setOption(QtGui.QWizard.HaveCustomButton1, True) self.setOption(QtGui.QWizard.HaveCustomButton1, enabled)
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)

View File

@ -38,7 +38,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, create_thumb, validate_thumb check_directory_exists, create_thumb, validate_thumb, ImageSource
from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType BackgroundGradientType
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
@ -139,14 +139,14 @@ class ThemeManager(QtGui.QWidget):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'config_updated'), self.configUpdated) QtCore.SIGNAL(u'config_updated'), self.configUpdated)
# Variables # Variables
self.theme_list = [] self.themeList = []
self.path = AppLocation.get_section_data_path(self.settingsSection) self.path = AppLocation.get_section_data_path(self.settingsSection)
check_directory_exists(self.path) check_directory_exists(self.path)
self.thumb_path = os.path.join(self.path, u'thumbnails') self.thumbPath = os.path.join(self.path, u'thumbnails')
check_directory_exists(self.thumb_path) check_directory_exists(self.thumbPath)
self.themeForm.path = self.path self.themeForm.path = self.path
self.old_background_image = None self.oldBackgroundImage = None
self.bad_v1_name_chars = re.compile(r'[%+\[\]]') self.badV1NameChars = re.compile(r'[%+\[\]]')
# Last little bits of setting up # Last little bits of setting up
self.configUpdated() self.configUpdated()
@ -194,14 +194,10 @@ class ThemeManager(QtGui.QWidget):
return return
real_theme_name = unicode(item.data(QtCore.Qt.UserRole).toString()) real_theme_name = unicode(item.data(QtCore.Qt.UserRole).toString())
theme_name = unicode(item.text()) theme_name = unicode(item.text())
self.deleteAction.setVisible(False) visible = real_theme_name == theme_name
self.renameAction.setVisible(False) self.deleteAction.setVisible(visible)
self.globalAction.setVisible(False) self.renameAction.setVisible(visible)
# If default theme restrict actions self.globalAction.setVisible(visible)
if real_theme_name == theme_name:
self.deleteAction.setVisible(True)
self.renameAction.setVisible(True)
self.globalAction.setVisible(True)
self.menu.exec_(self.themeListWidget.mapToGlobal(point)) self.menu.exec_(self.themeListWidget.mapToGlobal(point))
def changeGlobalFromTab(self, theme_name): def changeGlobalFromTab(self, theme_name):
@ -330,10 +326,10 @@ class ThemeManager(QtGui.QWidget):
theme = self.getThemeData( theme = self.getThemeData(
unicode(item.data(QtCore.Qt.UserRole).toString())) unicode(item.data(QtCore.Qt.UserRole).toString()))
if theme.background_type == u'image': if theme.background_type == u'image':
self.old_background_image = theme.background_filename self.oldBackgroundImage = theme.background_filename
self.themeForm.theme = theme self.themeForm.theme = theme
self.themeForm.exec_(True) self.themeForm.exec_(True)
self.old_background_image = None self.oldBackgroundImage = None
self.mainwindow.renderer.update_theme(theme.theme_name) self.mainwindow.renderer.update_theme(theme.theme_name)
def onDeleteTheme(self): def onDeleteTheme(self):
@ -361,10 +357,10 @@ class ThemeManager(QtGui.QWidget):
``theme`` ``theme``
The theme to delete. The theme to delete.
""" """
self.theme_list.remove(theme) self.themeList.remove(theme)
thumb = u'%s.png' % theme 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.thumb_path, thumb)) delete_file(os.path.join(self.thumbPath, thumb))
try: try:
encoding = get_filesystem_encoding() encoding = get_filesystem_encoding()
shutil.rmtree(os.path.join(self.path, theme).encode(encoding)) shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
@ -442,7 +438,7 @@ class ThemeManager(QtGui.QWidget):
The plugins will call back in to get the real list if they want it. The plugins will call back in to get the real list if they want it.
""" """
log.debug(u'Load themes from dir') log.debug(u'Load themes from dir')
self.theme_list = [] self.themeList = []
self.themeListWidget.clear() self.themeListWidget.clear()
files = SettingsManager.get_files(self.settingsSection, u'.png') files = SettingsManager.get_files(self.settingsSection, u'.png')
if firstTime: if firstTime:
@ -473,16 +469,17 @@ class ThemeManager(QtGui.QWidget):
'%s (default)')) % text_name '%s (default)')) % text_name
else: else:
name = text_name name = text_name
thumb = os.path.join(self.thumb_path, u'%s.png' % text_name) thumb = os.path.join(self.thumbPath, u'%s.png' % text_name)
item_name = QtGui.QListWidgetItem(name) item_name = QtGui.QListWidgetItem(name)
if validate_thumb(theme, thumb): if validate_thumb(theme, thumb):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = create_thumb(theme, thumb) icon = create_thumb(theme, thumb)
item_name.setIcon(icon) item_name.setIcon(icon)
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(text_name)) item_name.setData(
QtCore.Qt.UserRole, QtCore.QVariant(text_name))
self.themeListWidget.addItem(item_name) self.themeListWidget.addItem(item_name)
self.theme_list.append(text_name) self.themeList.append(text_name)
self._pushThemes() self._pushThemes()
def _pushThemes(self): def _pushThemes(self):
@ -495,7 +492,7 @@ class ThemeManager(QtGui.QWidget):
""" """
Return the list of loaded themes Return the list of loaded themes
""" """
return self.theme_list return self.themeList
def getThemeData(self, theme_name): def getThemeData(self, theme_name):
""" """
@ -509,7 +506,7 @@ class ThemeManager(QtGui.QWidget):
unicode(theme_name) + u'.xml') unicode(theme_name) + u'.xml')
xml = get_text_file_string(xml_file) xml = get_text_file_string(xml_file)
if not xml: if not xml:
log.debug("No theme data - using default theme") log.debug(u'No theme data - using default theme')
return ThemeXML() return ThemeXML()
else: else:
return self._createThemeFromXml(xml, self.path) return self._createThemeFromXml(xml, self.path)
@ -547,8 +544,9 @@ class ThemeManager(QtGui.QWidget):
xml_tree = ElementTree(element=XML(zip.read(xml_file[0]))).getroot() xml_tree = ElementTree(element=XML(zip.read(xml_file[0]))).getroot()
v1_background = xml_tree.find(u'BackgroundType') v1_background = xml_tree.find(u'BackgroundType')
if v1_background is not None: if v1_background is not None:
theme_name, file_xml, out_file, abort_import = self.unzipVersion122(dir, zip, theme_name, file_xml, out_file, abort_import = \
xml_file[0], xml_tree, v1_background, out_file) self.unzipVersion122(
dir, zip, xml_file[0], xml_tree, v1_background, out_file)
else: else:
theme_name = xml_tree.find(u'name').text.strip() theme_name = xml_tree.find(u'name').text.strip()
theme_folder = os.path.join(dir, theme_name) theme_folder = os.path.join(dir, theme_name)
@ -601,8 +599,8 @@ class ThemeManager(QtGui.QWidget):
if file_xml: if file_xml:
theme = self._createThemeFromXml(file_xml, self.path) theme = self._createThemeFromXml(file_xml, self.path)
self.generateAndSaveImage(dir, theme_name, theme) self.generateAndSaveImage(dir, theme_name, theme)
# Only show the error message, when IOError was not raised (in this # Only show the error message, when IOError was not raised (in
# case the error message has already been shown). # this case the error message has already been shown).
elif zip is not None: elif zip is not None:
critical_error_message_box( critical_error_message_box(
translate('OpenLP.ThemeManager', 'Validation Error'), translate('OpenLP.ThemeManager', 'Validation Error'),
@ -611,13 +609,14 @@ class ThemeManager(QtGui.QWidget):
log.exception(u'Theme file does not contain XML data %s' % log.exception(u'Theme file does not contain XML data %s' %
file_name) file_name)
def unzipVersion122(self, dir, zip, xml_file, xml_tree, background, out_file): def unzipVersion122(self, dir, zip, xml_file, xml_tree, background,
out_file):
""" """
Unzip openlp.org 1.2x theme file and upgrade the theme xml. When calling Unzip openlp.org 1.2x theme file and upgrade the theme xml. When calling
this method, please keep in mind, that some parameters are redundant. this method, please keep in mind, that some parameters are redundant.
""" """
theme_name = xml_tree.find(u'Name').text.strip() theme_name = xml_tree.find(u'Name').text.strip()
theme_name = self.bad_v1_name_chars.sub(u'', theme_name) theme_name = self.badV1NameChars.sub(u'', theme_name)
theme_folder = os.path.join(dir, theme_name) theme_folder = os.path.join(dir, theme_name)
theme_exists = os.path.exists(theme_folder) theme_exists = os.path.exists(theme_folder)
if theme_exists and not self.overWriteMessageBox(theme_name): if theme_exists and not self.overWriteMessageBox(theme_name):
@ -632,12 +631,12 @@ class ThemeManager(QtGui.QWidget):
if background.text.strip() == u'2': if background.text.strip() == u'2':
image_name = xml_tree.find(u'BackgroundParameter1').text.strip() image_name = xml_tree.find(u'BackgroundParameter1').text.strip()
# image file has same extension and is in subfolder # image file has same extension and is in subfolder
imagefile = filter(lambda name: os.path.splitext(name)[1].lower() image_file = filter(lambda name: os.path.splitext(name)[1].lower()
== os.path.splitext(image_name)[1].lower() and name.find(r'/'), == os.path.splitext(image_name)[1].lower() and name.find(r'/'),
zip.namelist()) zip.namelist())
if len(imagefile) >= 1: if len(image_file) >= 1:
out_file = open(os.path.join(themedir, image_name), u'wb') out_file = open(os.path.join(themedir, image_name), u'wb')
out_file.write(zip.read(imagefile[0])) out_file.write(zip.read(image_file[0]))
out_file.close() out_file.close()
else: else:
log.exception(u'Theme file does not contain image file "%s"' % log.exception(u'Theme file does not contain image file "%s"' %
@ -669,8 +668,9 @@ class ThemeManager(QtGui.QWidget):
self._writeTheme(theme, image_from, image_to) self._writeTheme(theme, image_from, image_to)
if theme.background_type == \ if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image): BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.updateImage(theme.theme_name, self.mainwindow.imageManager.updateImageBorder(
u'theme', QtGui.QColor(theme.background_border_color)) theme.background_filename,
ImageSource.Theme, QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.processUpdates() self.mainwindow.imageManager.processUpdates()
self.loadThemes() self.loadThemes()
@ -685,9 +685,8 @@ class ThemeManager(QtGui.QWidget):
theme_dir = os.path.join(self.path, name) theme_dir = os.path.join(self.path, name)
check_directory_exists(theme_dir) check_directory_exists(theme_dir)
theme_file = os.path.join(theme_dir, name + u'.xml') theme_file = os.path.join(theme_dir, name + u'.xml')
if self.old_background_image and \ if self.oldBackgroundImage and image_to != self.oldBackgroundImage:
image_to != self.old_background_image: delete_file(self.oldBackgroundImage)
delete_file(self.old_background_image)
out_file = None out_file = None
try: try:
out_file = open(theme_file, u'w') out_file = open(theme_file, u'w')
@ -714,7 +713,7 @@ class ThemeManager(QtGui.QWidget):
if os.path.exists(sample_path_name): if os.path.exists(sample_path_name):
os.unlink(sample_path_name) os.unlink(sample_path_name)
frame.save(sample_path_name, u'png') frame.save(sample_path_name, u'png')
thumb = os.path.join(self.thumb_path, u'%s.png' % name) thumb = os.path.join(self.thumbPath, u'%s.png' % name)
create_thumb(sample_path_name, thumb, False) create_thumb(sample_path_name, thumb, False)
log.debug(u'Theme image written to %s', sample_path_name) log.debug(u'Theme image written to %s', sample_path_name)
@ -722,8 +721,8 @@ class ThemeManager(QtGui.QWidget):
""" """
Called to update the themes' preview images. Called to update the themes' preview images.
""" """
self.mainwindow.displayProgressBar(len(self.theme_list)) self.mainwindow.displayProgressBar(len(self.themeList))
for theme in self.theme_list: for theme in self.themeList:
self.mainwindow.incrementProgressBar() self.mainwindow.incrementProgressBar()
self.generateAndSaveImage( self.generateAndSaveImage(
self.path, theme, self.getThemeData(theme)) self.path, theme, self.getThemeData(theme))
@ -819,7 +818,7 @@ class ThemeManager(QtGui.QWidget):
""" """
theme = Theme(xml_data) theme = Theme(xml_data)
new_theme = ThemeXML() new_theme = ThemeXML()
new_theme.theme_name = self.bad_v1_name_chars.sub(u'', theme.Name) new_theme.theme_name = self.badV1NameChars.sub(u'', theme.Name)
if theme.BackgroundType == 0: if theme.BackgroundType == 0:
new_theme.background_type = \ new_theme.background_type = \
BackgroundType.to_string(BackgroundType.Solid) BackgroundType.to_string(BackgroundType.Solid)

View File

@ -552,11 +552,9 @@ class Ui_ThemeWizard(object):
themeWizard.setButtonText(QtGui.QWizard.CustomButton1, themeWizard.setButtonText(QtGui.QWizard.CustomButton1,
translate('OpenLP.ThemeWizard', 'Layout Preview')) translate('OpenLP.ThemeWizard', 'Layout Preview'))
self.previewPage.setTitle( self.previewPage.setTitle(
translate('OpenLP.ThemeWizard', 'Save and Preview')) translate('OpenLP.ThemeWizard', 'Preview and Save'))
self.previewPage.setSubTitle( self.previewPage.setSubTitle(
translate('OpenLP.ThemeWizard', 'View the theme and save it ' translate('OpenLP.ThemeWizard', 'Preview the theme and save it.'))
'replacing the current one or change the name to create a '
'new theme'))
self.themeNameLabel.setText( self.themeNameLabel.setText(
translate('OpenLP.ThemeWizard', 'Theme name:')) translate('OpenLP.ThemeWizard', 'Theme name:'))
# Align all QFormLayouts towards each other. # Align all QFormLayouts towards each other.

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
import logging import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate, \ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver Receiver, ImageSource
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.plugins.images.lib import ImageMediaItem, ImageTab from openlp.plugins.images.lib import ImageMediaItem, ImageTab
@ -98,4 +98,5 @@ class ImagePlugin(Plugin):
""" """
background = QtGui.QColor(Settings().value(self.settingsSection background = QtGui.QColor(Settings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000'))) + u'/background color', QtCore.QVariant(u'#000000')))
self.liveController.imageManager.updateImages(u'image', background) self.liveController.imageManager.updateImagesBorder(
ImageSource.ImagePlugin, background)

View File

@ -229,8 +229,7 @@ class ImageMediaItem(MediaManagerItem):
bitem = self.listView.item(item.row()) bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename): if os.path.exists(filename):
name = os.path.split(filename)[1] if self.plugin.liveController.display.directImage(
if self.plugin.liveController.display.directImage(name,
filename, background): filename, background):
self.resetAction.setVisible(True) self.resetAction.setVisible(True)
else: else:

View File

@ -278,8 +278,7 @@ class MessageListener(object):
item = message[0] item = message[0]
log.debug(u'Startup called with message %s' % message) log.debug(u'Startup called with message %s' % message)
hide_mode = message[2] hide_mode = message[2]
file = os.path.join(item.get_frame_path(), file = os.path.join(item.get_frame_path(), item.get_frame_title())
item.get_frame_title())
self.handler = item.title self.handler = item.title
if self.handler == self.mediaitem.Automatic: if self.handler == self.mediaitem.Automatic:
self.handler = self.mediaitem.findControllerByType(file) self.handler = self.mediaitem.findControllerByType(file)

View File

@ -541,12 +541,13 @@ class SongImportSourcePage(QtGui.QWizardPage):
if wizard.formatWidgets[format][u'fileListWidget'].count() > 0: if wizard.formatWidgets[format][u'fileListWidget'].count() > 0:
return True return True
else: else:
filepath = wizard.formatWidgets[format][u'filepathEdit'].text() filepath = unicode(
if not filepath.isEmpty(): wizard.formatWidgets[format][u'filepathEdit'].text())
if select_mode == SongFormatSelect.SingleFile \ if filepath:
and os.path.isfile(filepath): if select_mode == SongFormatSelect.SingleFile and \
os.path.isfile(filepath):
return True return True
elif select_mode == SongFormatSelect.SingleFolder \ elif select_mode == SongFormatSelect.SingleFolder and \
and os.path.isdir(filepath): os.path.isdir(filepath):
return True return True
return False return False

View File

@ -36,6 +36,104 @@ from ui import SongStrings
WHITESPACE = re.compile(r'[\W_]+', re.UNICODE) WHITESPACE = re.compile(r'[\W_]+', re.UNICODE)
APOSTROPHE = re.compile(u'[\'`ʻ]', re.UNICODE) APOSTROPHE = re.compile(u'[\'`ʻ]', re.UNICODE)
PATTERN = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'"
r"([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
# RTF control words which specify a "destination" to be ignored.
DESTINATIONS = frozenset((
u'aftncn', u'aftnsep', u'aftnsepc', u'annotation', u'atnauthor',
u'atndate', u'atnicn', u'atnid', u'atnparent', u'atnref', u'atntime',
u'atrfend', u'atrfstart', u'author', u'background', u'bkmkend',
u'bkmkstart', u'blipuid', u'buptim', u'category',
u'colorschememapping', u'colortbl', u'comment', u'company', u'creatim',
u'datafield', u'datastore', u'defchp', u'defpap', u'do', u'doccomm',
u'docvar', u'dptxbxtext', u'ebcend', u'ebcstart', u'factoidname',
u'falt', u'fchars', u'ffdeftext', u'ffentrymcr', u'ffexitmcr',
u'ffformat', u'ffhelptext', u'ffl', u'ffname', u'ffstattext', u'field',
u'file', u'filetbl', u'fldinst', u'fldrslt', u'fldtype', u'fname',
u'fontemb', u'fontfile', u'footer', u'footerf', u'footerl', u'footerr',
u'footnote', u'formfield', u'ftncn', u'ftnsep', u'ftnsepc', u'g',
u'generator', u'gridtbl', u'header', u'headerf', u'headerl',
u'headerr', u'hl', u'hlfr', u'hlinkbase', u'hlloc', u'hlsrc', u'hsv',
u'htmltag', u'info', u'keycode', u'keywords', u'latentstyles',
u'lchars', u'levelnumbers', u'leveltext', u'lfolevel', u'linkval',
u'list', u'listlevel', u'listname', u'listoverride',
u'listoverridetable', u'listpicture', u'liststylename', u'listtable',
u'listtext', u'lsdlockedexcept', u'macc', u'maccPr', u'mailmerge',
u'maln', u'malnScr', u'manager', u'margPr', u'mbar', u'mbarPr',
u'mbaseJc', u'mbegChr', u'mborderBox', u'mborderBoxPr', u'mbox',
u'mboxPr', u'mchr', u'mcount', u'mctrlPr', u'md', u'mdeg', u'mdegHide',
u'mden', u'mdiff', u'mdPr', u'me', u'mendChr', u'meqArr', u'meqArrPr',
u'mf', u'mfName', u'mfPr', u'mfunc', u'mfuncPr', u'mgroupChr',
u'mgroupChrPr', u'mgrow', u'mhideBot', u'mhideLeft', u'mhideRight',
u'mhideTop', u'mhtmltag', u'mlim', u'mlimloc', u'mlimlow',
u'mlimlowPr', u'mlimupp', u'mlimuppPr', u'mm', u'mmaddfieldname',
u'mmath', u'mmathPict', u'mmathPr', u'mmaxdist', u'mmc', u'mmcJc',
u'mmconnectstr', u'mmconnectstrdata', u'mmcPr', u'mmcs',
u'mmdatasource', u'mmheadersource', u'mmmailsubject', u'mmodso',
u'mmodsofilter', u'mmodsofldmpdata', u'mmodsomappedname',
u'mmodsoname', u'mmodsorecipdata', u'mmodsosort', u'mmodsosrc',
u'mmodsotable', u'mmodsoudl', u'mmodsoudldata', u'mmodsouniquetag',
u'mmPr', u'mmquery', u'mmr', u'mnary', u'mnaryPr', u'mnoBreak',
u'mnum', u'mobjDist', u'moMath', u'moMathPara', u'moMathParaPr',
u'mopEmu', u'mphant', u'mphantPr', u'mplcHide', u'mpos', u'mr',
u'mrad', u'mradPr', u'mrPr', u'msepChr', u'mshow', u'mshp', u'msPre',
u'msPrePr', u'msSub', u'msSubPr', u'msSubSup', u'msSubSupPr', u'msSup',
u'msSupPr', u'mstrikeBLTR', u'mstrikeH', u'mstrikeTLBR', u'mstrikeV',
u'msub', u'msubHide', u'msup', u'msupHide', u'mtransp', u'mtype',
u'mvertJc', u'mvfmf', u'mvfml', u'mvtof', u'mvtol', u'mzeroAsc',
u'mzeroDesc', u'mzeroWid', u'nesttableprops', u'nextfile',
u'nonesttables', u'objalias', u'objclass', u'objdata', u'object',
u'objname', u'objsect', u'objtime', u'oldcprops', u'oldpprops',
u'oldsprops', u'oldtprops', u'oleclsid', u'operator', u'panose',
u'password', u'passwordhash', u'pgp', u'pgptbl', u'picprop', u'pict',
u'pn', u'pnseclvl', u'pntext', u'pntxta', u'pntxtb', u'printim',
u'private', u'propname', u'protend', u'protstart', u'protusertbl',
u'pxe', u'result', u'revtbl', u'revtim', u'rsidtbl', u'rxe', u'shp',
u'shpgrp', u'shpinst', u'shppict', u'shprslt', u'shptxt', u'sn', u'sp',
u'staticval', u'stylesheet', u'subject', u'sv', u'svb', u'tc',
u'template', u'themedata', u'title', u'txe', u'ud', u'upr',
u'userprops', u'wgrffmtfilter', u'windowcaption', u'writereservation',
u'writereservhash', u'xe', u'xform', u'xmlattrname', u'xmlattrvalue',
u'xmlclose', u'xmlname', u'xmlnstbl', u'xmlopen'))
# Translation of some special characters.
SPECIAL_CHARS = {
u'par': u'\n',
u'sect': u'\n\n',
# Required page and column break.
# Would be good if we could split verse into subverses here.
u'page': u'\n\n',
u'column': u'\n\n',
# Soft breaks.
u'softpage': u'[---]',
u'softcol': u'[---]',
u'line': u'\n',
u'tab': u'\t',
u'emdash': u'\u2014',
u'endash': u'\u2013',
u'emspace': u'\u2003',
u'enspace': u'\u2002',
u'qmspace': u'\u2005',
u'bullet': u'\u2022',
u'lquote': u'\u2018',
u'rquote': u'\u2019',
u'ldblquote': u'\u201C',
u'rdblquote': u'\u201D',
u'ltrmark': u'\u200E',
u'rtlmark': u'\u200F',
u'zwj': u'\u200D',
u'zwnj': u'\u200C'}
CHARSET_MAPPING = {
u'fcharset0': u'cp1252',
u'fcharset161': u'cp1253',
u'fcharset162': u'cp1254',
u'fcharset163': u'cp1258',
u'fcharset177': u'cp1255',
u'fcharset178': u'cp1256',
u'fcharset186': u'cp1257',
u'fcharset204': u'cp1251',
u'fcharset222': u'cp874',
u'fcharset238': u'cp1250'}
class VerseType(object): class VerseType(object):
""" """
@ -366,6 +464,136 @@ def clean_song(manager, song):
if song.copyright: if song.copyright:
song.copyright = CONTROL_CHARS.sub(u'', song.copyright).strip() song.copyright = CONTROL_CHARS.sub(u'', song.copyright).strip()
def get_encoding(font, font_table, default_encoding, failed=False):
"""
Finds an encoding to use. Asks user, if necessary.
``font``
The number of currently active font.
``font_table``
Dictionary of fonts and respective encodings.
``default_encoding``
The defaul encoding to use when font_table is empty or no font is used.
``failed``
A boolean indicating whether the previous encoding didn't work.
"""
encoding = None
if font in font_table:
encoding = font_table[font]
if not encoding and default_encoding:
encoding = default_encoding
if not encoding or failed:
encoding = retrieve_windows_encoding()
default_encoding = encoding
font_table[font] = encoding
return encoding, default_encoding
def strip_rtf(text, default_encoding=None):
"""
This function strips RTF control structures and returns an unicode string.
Thanks to Markus Jarderot (MizardX) for this code, used by permission.
http://stackoverflow.com/questions/188545
``text``
RTF-encoded text, a string.
``default_encoding``
Default encoding to use when no encoding is specified.
"""
# Current font is the font tag we last met.
font = u''
# Character encoding is defined inside fonttable.
# font_table could contain eg u'0': u'cp1252'
font_table = {u'': u''}
# Stack of things to keep track of when entering/leaving groups.
stack = []
# Whether this group (and all inside it) are "ignorable".
ignorable = False
# Number of ASCII characters to skip after an unicode character.
ucskip = 1
# Number of ASCII characters left to skip.
curskip = 0
# Output buffer.
out = []
for match in PATTERN.finditer(text):
word, arg, hex, char, brace, tchar = match.groups()
if brace:
curskip = 0
if brace == u'{':
# Push state
stack.append((ucskip, ignorable, font))
elif brace == u'}':
# Pop state
ucskip, ignorable, font = stack.pop()
# \x (not a letter)
elif char:
curskip = 0
if char == u'~' and not ignorable:
out.append(u'\xA0')
elif char in u'{}\\' and not ignorable:
out.append(char)
elif char == u'-' and not ignorable:
out.append(u'\u00AD')
elif char == u'_' and not ignorable:
out.append(u'\u2011')
elif char == u'*':
ignorable = True
# \command
elif word:
curskip = 0
if word in DESTINATIONS:
ignorable = True
elif word in SPECIAL_CHARS:
out.append(SPECIAL_CHARS[word])
elif word == u'uc':
ucskip = int(arg)
elif word == u' ':
c = int(arg)
if c < 0:
c += 0x10000
out.append(unichr(c))
curskip = ucskip
elif word == u'fonttbl':
ignorable = True
elif word == u'f':
font = arg
elif word == u'ansicpg':
font_table[font] = 'cp' + arg
elif word == u'fcharset' and font not in font_table and \
word + arg in CHARSET_MAPPING:
# \ansicpg overrides \fcharset, if present.
font_table[font] = CHARSET_MAPPING[word + arg]
# \'xx
elif hex:
if curskip > 0:
curskip -= 1
elif not ignorable:
charcode = int(hex, 16)
failed = False
while True:
try:
encoding, default_encoding = get_encoding(font,
font_table, default_encoding, failed=failed)
out.append(chr(charcode).decode(encoding))
except UnicodeDecodeError:
failed = True
else:
break
elif tchar:
if curskip > 0:
curskip -= 1
elif not ignorable:
out.append(tchar)
text = u''.join(out)
return text, default_encoding
from xml import OpenLyrics, SongXML from xml import OpenLyrics, SongXML
from songstab import SongsTab from songstab import SongsTab
from mediaitem import SongMediaItem from mediaitem import SongMediaItem

View File

@ -36,7 +36,7 @@ import re
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import retrieve_windows_encoding from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
from songimport import SongImport from songimport import SongImport
RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}') RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
@ -45,101 +45,6 @@ SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
NUMBER_REGEX = re.compile(r'[0-9]+') NUMBER_REGEX = re.compile(r'[0-9]+')
NOTE_REGEX = re.compile(r'\(.*?\)') NOTE_REGEX = re.compile(r'\(.*?\)')
def strip_rtf(blob, encoding):
depth = 0
control = False
clear_text = []
control_word = []
# workaround for \tx bug: remove one pair of curly braces
# if \tx is encountered
match = RTF_STRIPPING_REGEX.search(blob)
if match:
# start and end indices of match are curly braces - filter them out
blob = ''.join([blob[i] for i in xrange(len(blob))
if i != match.start() and i !=match.end()])
for c in blob:
if control:
# for delimiters, set control to False
if c == '{':
if control_word:
depth += 1
control = False
elif c == '}':
if control_word:
depth -= 1
control = False
elif c == '\\':
new_control = bool(control_word)
control = False
elif c.isspace():
control = False
else:
control_word.append(c)
if len(control_word) == 3 and control_word[0] == '\'':
control = False
if not control:
if not control_word:
if c == '{' or c == '}' or c == '\\':
clear_text.append(c)
else:
control_str = ''.join(control_word)
if control_str == 'par' or control_str == 'line':
clear_text.append(u'\n')
elif control_str == 'tab':
clear_text.append(u'\t')
# Prefer the encoding specified by the RTF data to that
# specified by the Paradox table header
# West European encoding
elif control_str == 'fcharset0':
encoding = u'cp1252'
# Greek encoding
elif control_str == 'fcharset161':
encoding = u'cp1253'
# Turkish encoding
elif control_str == 'fcharset162':
encoding = u'cp1254'
# Vietnamese encoding
elif control_str == 'fcharset163':
encoding = u'cp1258'
# Hebrew encoding
elif control_str == 'fcharset177':
encoding = u'cp1255'
# Arabic encoding
elif control_str == 'fcharset178':
encoding = u'cp1256'
# Baltic encoding
elif control_str == 'fcharset186':
encoding = u'cp1257'
# Cyrillic encoding
elif control_str == 'fcharset204':
encoding = u'cp1251'
# Thai encoding
elif control_str == 'fcharset222':
encoding = u'cp874'
# Central+East European encoding
elif control_str == 'fcharset238':
encoding = u'cp1250'
elif control_str[0] == '\'':
s = chr(int(control_str[1:3], 16))
clear_text.append(s.decode(encoding))
del control_word[:]
if c == '\\' and new_control:
control = True
elif c == '{':
depth += 1
elif c == '}':
depth -= 1
elif depth > 2:
continue
elif c == '\n' or c == '\r':
continue
elif c == '\\':
control = True
else:
clear_text.append(c)
return u''.join(clear_text)
class FieldDescEntry: class FieldDescEntry:
def __init__(self, name, type, size): def __init__(self, name, type, size):
@ -274,7 +179,7 @@ class EasyWorshipSongImport(SongImport):
self.addAuthor(author_name.strip()) self.addAuthor(author_name.strip())
if words: if words:
# Format the lyrics # Format the lyrics
words = strip_rtf(words, self.encoding) words, self.encoding = strip_rtf(words, self.encoding)
verse_type = VerseType.Tags[VerseType.Verse] verse_type = VerseType.Tags[VerseType.Verse]
for verse in SLIDE_BREAK_REGEX.split(words): for verse in SLIDE_BREAK_REGEX.split(words):
verse = verse.strip() verse = verse.strip()

View File

@ -44,6 +44,7 @@ from powersongimport import PowerSongImport
from ewimport import EasyWorshipSongImport from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport from songshowplusimport import SongShowPlusImport
from sundayplusimport import SundayPlusImport
from foilpresenterimport import FoilPresenterImport from foilpresenterimport import FoilPresenterImport
from zionworximport import ZionWorxImport from zionworximport import ZionWorxImport
# Imports that might fail # Imports that might fail
@ -145,9 +146,10 @@ class SongFormat(object):
SongBeamer = 11 SongBeamer = 11
SongShowPlus = 12 SongShowPlus = 12
SongsOfFellowship = 13 SongsOfFellowship = 13
WordsOfWorship = 14 SundayPlus = 14
ZionWorx = 15 WordsOfWorship = 15
#CSV = 16 ZionWorx = 16
#CSV = 17
# Set optional attribute defaults # Set optional attribute defaults
__defaults__ = { __defaults__ = {
@ -275,6 +277,13 @@ class SongFormat(object):
'The Songs of Fellowship importer has been disabled because ' 'The Songs of Fellowship importer has been disabled because '
'OpenLP cannot access OpenOffice or LibreOffice.') 'OpenLP cannot access OpenOffice or LibreOffice.')
}, },
SundayPlus: {
u'class': SundayPlusImport,
u'name': u'SundayPlus',
u'prefix': u'sundayPlus',
u'filter': u'%s (*.ptf)' % translate(
'SongsPlugin.ImportWizardForm', 'SundayPlus Song Files')
},
WordsOfWorship: { WordsOfWorship: {
u'class': WowImport, u'class': WowImport,
u'name': u'Words of Worship', u'name': u'Words of Worship',
@ -322,6 +331,7 @@ class SongFormat(object):
SongFormat.SongBeamer, SongFormat.SongBeamer,
SongFormat.SongShowPlus, SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship, SongFormat.SongsOfFellowship,
SongFormat.SundayPlus,
SongFormat.WordsOfWorship, SongFormat.WordsOfWorship,
SongFormat.ZionWorx SongFormat.ZionWorx
] ]

View File

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2012 Raoul Snyman #
# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Edwin Lunando, Joshua Miller, Stevan Pettit, #
# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, #
# Simon Scudder, Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon #
# Tibble, Dave Warnock, Frode Woldsund #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import os
import re
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib import strip_rtf
from openlp.plugins.songs.lib.songimport import SongImport
HOTKEY_TO_VERSE_TYPE = {
u'1': u'v1',
u'2': u'v2',
u'3': u'v3',
u'4': u'v4',
u'5': u'v5',
u'6': u'v6',
u'7': u'v7',
u'8': u'v8',
u'9': u'v9',
u'C': u'c',
u'+': u'b',
u'Z': u'o'}
class SundayPlusImport(SongImport):
"""
Import Sunday Plus songs
The format examples can be found attached to bug report at
<http://support.openlp.org/issues/395>
"""
def __init__(self, manager, **kwargs):
"""
Initialise the class.
"""
SongImport.__init__(self, manager, **kwargs)
self.encoding = u'us-ascii'
def doImport(self):
self.importWizard.progressBar.setMaximum(len(self.importSource))
for filename in self.importSource:
if self.stopImportFlag:
return
song_file = open(filename)
self.doImportFile(song_file)
song_file.close()
def doImportFile(self, file):
"""
Process the Sunday Plus file object.
"""
self.setDefaults()
if not self.parse(file.read()):
self.logError(file.name)
return
if not self.title:
self.title = self.titleFromFilename(file.name)
if not self.finish():
self.logError(file.name)
def parse(self, data, cell=False):
if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
self.logError(u'File is malformed')
return False
i = 1
verse_type = VerseType.Tags[VerseType.Verse]
while i < len(data):
# Data is held as #name: value pairs inside groups marked as [].
# Now we are looking for the name.
if data[i:i + 1] == '#':
name_end = data.find(':', i + 1)
name = data[i + 1:name_end]
i = name_end + 1
while data[i:i + 1] == ' ':
i += 1
if data[i:i + 1] == '"':
end = data.find('"', i + 1)
value = data[i + 1:end]
elif data[i:i + 1] == '[':
j = i
inside_quotes = False
while j < len(data):
char = data[j:j + 1]
if char == '"':
inside_quotes = not inside_quotes
elif not inside_quotes and char == ']':
end = j + 1
break
j += 1
value = data[i:end]
else:
end = data.find(',', i + 1)
if data.find('(', i, end) != -1:
end = data.find(')', i) + 1
value = data[i:end]
# If we are in the main group.
if cell == False:
if name == 'title':
self.title = self.decode(self.unescape(value))
elif name == 'Author':
author = self.decode(self.unescape(value))
if len(author):
self.addAuthor(author)
elif name == 'Copyright':
self.copyright = self.decode(self.unescape(value))
elif name[0:4] == 'CELL':
self.parse(value, cell = name[4:])
# We are in a verse group.
else:
if name == 'MARKER_NAME':
value = value.strip()
if len(value):
verse_type = VerseType.Tags[
VerseType.from_loose_input(value[0])]
if len(value) >= 2 and value[-1] in ['0', '1', '2',
'3', '4', '5', '6', '7', '8', '9']:
verse_type = "%s%s" % (verse_type, value[-1])
elif name == 'Hotkey':
# Hotkey always appears after MARKER_NAME, so it
# effectively overrides MARKER_NAME, if present.
if len(value) and \
value in HOTKEY_TO_VERSE_TYPE.keys():
verse_type = HOTKEY_TO_VERSE_TYPE[value]
if name == 'rtf':
value = self.unescape(value)
verse, self.encoding = strip_rtf(value, self.encoding)
lines = verse.strip().split('\n')
# If any line inside any verse contains CCLI or
# only Public Domain, we treat this as special data:
# we remove that line and add data to specific field.
for i in xrange(len(lines)):
lines[i] = lines[i].strip()
line = lines[i]
if line[:4].lower() == u'ccli':
m = re.search(r'[0-9]+', line)
if m:
self.ccliNumber = int(m.group(0))
lines.pop(i)
elif line.lower() == u'public domain':
self.copyright = u'Public Domain'
lines.pop(i)
self.addVerse('\n'.join(lines).strip(), verse_type)
if end == -1:
break
i = end + 1
i += 1
return True
def titleFromFilename(self, filename):
title = os.path.split(filename)[1]
if title.endswith(u'.ptf'):
title = title[:-4]
# For some strange reason all example files names ended with 1-7.
if title.endswith(u'1-7'):
title = title[:-3]
return title.replace(u'_', u' ')
def decode(self, blob):
while True:
try:
return unicode(blob, self.encoding)
except:
self.encoding = retrieve_windows_encoding()
def unescape(self, text):
text = text.replace('^^', '"')
text = text.replace('^', '\'')
return text.strip()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -199,7 +199,11 @@ def download_translations():
request = urllib2.Request(url + '?details') request = urllib2.Request(url + '?details')
request.add_header('Authorization', auth_header) request.add_header('Authorization', auth_header)
print_verbose(u'Downloading list of languages from: %s' % url) print_verbose(u'Downloading list of languages from: %s' % url)
json_response = urllib2.urlopen(request) try:
json_response = urllib2.urlopen(request)
except urllib2.HTTPError:
print_quiet(u'Username or password incorrect.')
return False
json_dict = json.loads(json_response.read()) json_dict = json.loads(json_response.read())
languages = [lang[u'code'] for lang in json_dict[u'available_languages']] languages = [lang[u'code'] for lang in json_dict[u'available_languages']]
for language in languages: for language in languages:
@ -214,6 +218,7 @@ def download_translations():
fd.write(response.read()) fd.write(response.read())
fd.close() fd.close()
print_quiet(u' Done.') print_quiet(u' Done.')
return True
def prepare_project(): def prepare_project():
""" """
@ -310,7 +315,8 @@ def process_stack(command_stack):
for command in command_stack: for command in command_stack:
print_quiet(u'%d.' % (command_stack.current_index), False) print_quiet(u'%d.' % (command_stack.current_index), False)
if command == Command.Download: if command == Command.Download:
download_translations() if not download_translations():
return
elif command == Command.Prepare: elif command == Command.Prepare:
prepare_project() prepare_project()
elif command == Command.Update: elif command == Command.Update: