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__)
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):
"""
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.
"""
import logging
import os
import time
import Queue
@ -97,19 +98,34 @@ class Priority(object):
class Image(object):
"""
This class represents an image. To mark an image as *dirty* set the instance
variables ``image`` and ``image_bytes`` to ``None`` and add the image object
to the queue of images to process.
This class represents an image. To mark an image as *dirty* call the
:class:`ImageManager`'s ``_resetImage`` method with the Image instance as
argument.
"""
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.image = None
self.image_bytes = None
self.priority = Priority.Normal
self.source = source
self.background = background
self.timestamp = os.stat(path).st_mtime
self.secondary_priority = Image.secondary_priority
Image.secondary_priority += 1
@ -118,7 +134,7 @@ class PriorityQueue(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
the :class:`Image`'s ``secondary_priority`` attribute. The last value the
:class:`Image` instance itself::
@ -187,7 +203,7 @@ class ImageManager(QtCore.QObject):
for image in self._cache.values():
self._resetImage(image)
def updateImages(self, imageType, background):
def updateImagesBorder(self, source, background):
"""
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
# stream to None.
for image in self._cache.values():
if image.source == imageType:
if image.source == source:
image.background = background
self._resetImage(image)
def updateImage(self, name, imageType, background):
def updateImageBorder(self, path, source, background):
"""
Border has changed so update the image affected.
"""
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.
for image in self._cache.values():
if image.source == imageType and image.name == name:
image.background = background
self._resetImage(image)
image = self._cache[(path, source)]
if image.source == source:
image.background = background
self._resetImage(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_bytes = None
self._conversionQueue.modify_priority(image, Priority.Normal)
@ -224,13 +244,13 @@ class ImageManager(QtCore.QObject):
if not self.imageThread.isRunning():
self.imageThread.start()
def getImage(self, name):
def getImage(self, path, source):
"""
Return the ``QImage`` from the cache. If not present wait for the
background thread to process it.
"""
log.debug(u'getImage %s' % name)
image = self._cache[name]
log.debug(u'getImage %s' % path)
image = self._cache[(path, source)]
if image.image is None:
self._conversionQueue.modify_priority(image, Priority.High)
# 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)
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
background thread to process it.
"""
log.debug(u'getImageBytes %s' % name)
image = self._cache[name]
log.debug(u'getImageBytes %s' % path)
image = self._cache[(path, source)]
if image.image_bytes is None:
self._conversionQueue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@ -262,27 +282,22 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1)
return image.image_bytes
def deleteImage(self, name):
"""
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):
def addImage(self, path, source, background):
"""
Add image to cache if it is not already there.
"""
log.debug(u'addImage %s:%s' % (name, path))
if not name in self._cache:
image = Image(name, path, source, background)
self._cache[name] = image
log.debug(u'addImage %s' % path)
if not (path, source) in self._cache:
image = Image(path, source, background)
self._cache[(path, source)] = image
self._conversionQueue.put(
(image.priority, image.secondary_priority, image))
else:
log.debug(u'Image in cache %s:%s' % (name, path))
# Check if the there are any images with the same path and check if the
# 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.
if not self.imageThread.isRunning():
self.imageThread.start()

View File

@ -352,24 +352,23 @@ class MediaManagerItem(QtGui.QWidget):
``files``
The list of files to be loaded
"""
#FIXME: change local variables to words_separated_by_underscores.
newFiles = []
errorShown = False
new_files = []
error_shown = False
for file in files:
type = file.split(u'.')[-1]
if type.lower() not in self.onNewFileMasks:
if not errorShown:
if not error_shown:
critical_error_message_box(
translate('OpenLP.MediaManagerItem',
'Invalid File Type'),
unicode(translate('OpenLP.MediaManagerItem',
'Invalid File %s.\nSuffix not supported'))
% file)
errorShown = True
error_shown = True
else:
newFiles.append(file)
if files:
self.validateAndLoad(newFiles)
new_files.append(file)
if new_files:
self.validateAndLoad(new_files)
def validateAndLoad(self, files):
"""
@ -379,30 +378,29 @@ class MediaManagerItem(QtGui.QWidget):
``files``
The files to be loaded.
"""
#FIXME: change local variables to words_separated_by_underscores.
names = []
fullList = []
full_list = []
for count in range(self.listView.count()):
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()))
duplicatesFound = False
filesAdded = False
duplicates_found = False
files_added = False
for file in files:
filename = os.path.split(unicode(file))[1]
if filename in names:
duplicatesFound = True
duplicates_found = True
else:
filesAdded = True
fullList.append(file)
if fullList and filesAdded:
files_added = True
full_list.append(file)
if full_list and files_added:
self.listView.clear()
self.loadList(fullList)
lastDir = os.path.split(unicode(files[0]))[0]
SettingsManager.set_last_dir(self.settingsSection, lastDir)
self.loadList(full_list)
last_dir = os.path.split(unicode(files[0]))[0]
SettingsManager.set_last_dir(self.settingsSection, last_dir)
SettingsManager.set_list(self.settingsSection,
self.settingsSection, self.getFileList())
if duplicatesFound:
if duplicates_found:
critical_error_message_box(
UiStrings().Duplicate,
unicode(translate('OpenLP.MediaManagerItem',
@ -422,13 +420,13 @@ class MediaManagerItem(QtGui.QWidget):
Return the current list of files
"""
count = 0
filelist = []
file_list = []
while count < self.listView.count():
bitem = self.listView.item(count)
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
filelist.append(filename)
file_list.append(filename)
count += 1
return filelist
return file_list
def loadList(self, list):
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
"""
if Settings().value(u'advanced/single click preview',
QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \
and self.listView.selectedIndexes() \
and self.autoSelectId == -1:
QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed and \
self.listView.selectedIndexes() and self.autoSelectId == -1:
self.onPreviewClick(True)
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, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
ItemCapabilities, FormattingTags
ItemCapabilities, FormattingTags, ImageSource
from openlp.core.lib.theme import ThemeLevel
from openlp.core.ui import MainDisplay, ScreenList
@ -82,6 +82,9 @@ class Renderer(object):
self._calculate_default()
QtCore.QObject.connect(Receiver.get_receiver(),
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):
"""
@ -137,8 +140,8 @@ class Renderer(object):
self._theme_dimensions[theme_name]
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
self.image_manager.addImage(theme_data.background_filename,
ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color))
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
serviceItem.add_from_text(VERSE_FOR_LINE_COUNT)
else:
self.image_manager.deleteImage(theme_data.theme_name)
serviceItem.add_from_text(VERSE)
serviceItem.renderer = self
serviceItem.raw_footer = FOOTER
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
self.image_manager.addImage(theme_data.background_filename,
ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color))
theme_data, main, footer = self.pre_render(theme_data)
serviceItem.themedata = theme_data
@ -404,10 +406,7 @@ class Renderer(object):
if theme_data.font_main_shadow:
self.page_width -= 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_frame = self.web.page().mainFrame()
# Adjust width and height to account for shadow. outline done in css.
html = u"""<!DOCTYPE html><html><head><script>
function show_text(newtext) {

View File

@ -36,7 +36,8 @@ import logging
import os
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__)
@ -178,7 +179,7 @@ class ServiceItem(object):
self.renderer.set_item_theme(self.theme)
self.themedata, self.main, self.footer = self.renderer.pre_render()
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:
pages = self.renderer.format_slide(slide[u'raw_slide'], self)
for page in pages:
@ -217,8 +218,8 @@ class ServiceItem(object):
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path})
self.renderer.image_manager.addImage(title, path, u'image',
self.image_border)
self.renderer.image_manager.addImage(
path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
def add_from_text(self, raw_slide, verse_tag=None):
@ -432,13 +433,12 @@ class ServiceItem(object):
def get_rendered_frame(self, row):
"""
Returns the correct frame for a given list and
renders it if required.
Returns the correct frame for a given list and renders it if required.
"""
if self.service_item_type == ServiceItemType.Text:
return self._display_frames[row][u'html'].split(u'\n')[0]
elif self.service_item_type == ServiceItemType.Image:
return self._raw_frames[row][u'title']
return self._raw_frames[row][u'path']
else:
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 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.settings import Settings
@ -274,31 +274,33 @@ class MainDisplay(Display):
self.setVisible(False)
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.
"""
self.imageManager.addImage(name, path, u'image', background)
if hasattr(self, u'serviceItem'):
self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name
self.image(name)
# Update the preview frame.
if self.isLive:
self.parent().updatePreview()
return True
return False
self.imageManager.addImage(path, ImageSource.ImagePlugin, background)
if not hasattr(self, u'serviceItem'):
return False
self.override[u'image'] = path
self.override[u'theme'] = self.serviceItem.themedata.background_filename
self.image(path)
# Update the preview frame.
if self.isLive:
self.parent().updatePreview()
return True
def image(self, name):
def image(self, path):
"""
Add an image as the background. The image has already been added to the
cache.
``name``
The name of the image to be displayed.
``path``
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')
image = self.imageManager.getImageBytes(name)
image = self.imageManager.getImageBytes(path, ImageSource.ImagePlugin)
self.controller.mediaController.video_reset(self.controller)
self.displayImage(image)
@ -360,7 +362,7 @@ class MainDisplay(Display):
self.setVisible(True)
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
HTML to the display
@ -377,20 +379,23 @@ class MainDisplay(Display):
Receiver.send_message(u'video_background_replaced')
self.override = {}
# 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')
self.override = {}
else:
# replace the background
background = self.imageManager. \
getImageBytes(self.override[u'image'])
background = self.imageManager.getImageBytes(
self.override[u'image'], ImageSource.ImagePlugin)
self.setTransparency(self.serviceItem.themedata.background_type ==
BackgroundType.to_string(BackgroundType.Transparent))
if self.serviceItem.themedata.background_filename:
self.serviceItem.bg_image_bytes = self.imageManager. \
getImageBytes(self.serviceItem.themedata.theme_name)
if image:
image_bytes = self.imageManager.getImageBytes(image)
self.serviceItem.bg_image_bytes = self.imageManager.getImageBytes(
self.serviceItem.themedata.background_filename,
ImageSource.Theme)
if image_path:
image_bytes = self.imageManager.getImageBytes(
image_path, ImageSource.ImagePlugin)
else:
image_bytes = None
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
from openlp.core.ui.media import MediaController
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.ui.firsttimeform import FirstTimeForm
from openlp.core.ui import ScreenList

View File

@ -34,7 +34,7 @@ from collections import deque
from PyQt4 import QtCore, QtGui
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.settings import Settings
from openlp.core.lib import SlideLimits, ServiceItemAction
@ -861,8 +861,10 @@ class SlideController(Controller):
# If current slide set background to image
if framenumber == slideno:
self.serviceItem.bg_image_bytes = \
self.imageManager.getImageBytes(frame[u'title'])
image = self.imageManager.getImage(frame[u'title'])
self.imageManager.getImageBytes(frame[u'path'],
ImageSource.ImagePlugin)
image = self.imageManager.getImage(frame[u'path'],
ImageSource.ImagePlugin)
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * (1 / self.ratio)
@ -1092,14 +1094,14 @@ class SlideController(Controller):
u'%s_slide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive, row])
else:
toDisplay = self.serviceItem.get_rendered_frame(row)
to_display = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text():
self.display.text(toDisplay)
self.display.text(to_display)
else:
if start:
self.display.buildHtml(self.serviceItem, toDisplay)
self.display.buildHtml(self.serviceItem, to_display)
else:
self.display.image(toDisplay)
self.display.image(to_display)
# reset the store used to display first image
self.serviceItem.bg_image_bytes = None
self.updatePreview()

View File

@ -228,10 +228,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
"""
Detects Page changes and updates as approprate.
"""
if self.page(pageId) == self.areaPositionPage:
self.setOption(QtGui.QWizard.HaveCustomButton1, True)
else:
self.setOption(QtGui.QWizard.HaveCustomButton1, False)
enabled = self.page(pageId) == self.areaPositionPage
self.setOption(QtGui.QWizard.HaveCustomButton1, enabled)
if self.page(pageId) == self.previewPage:
self.updateTheme()
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, \
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, \
BackgroundGradientType
from openlp.core.lib.settings import Settings
@ -139,14 +139,14 @@ class ThemeManager(QtGui.QWidget):
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'config_updated'), self.configUpdated)
# Variables
self.theme_list = []
self.themeList = []
self.path = AppLocation.get_section_data_path(self.settingsSection)
check_directory_exists(self.path)
self.thumb_path = os.path.join(self.path, u'thumbnails')
check_directory_exists(self.thumb_path)
self.thumbPath = os.path.join(self.path, u'thumbnails')
check_directory_exists(self.thumbPath)
self.themeForm.path = self.path
self.old_background_image = None
self.bad_v1_name_chars = re.compile(r'[%+\[\]]')
self.oldBackgroundImage = None
self.badV1NameChars = re.compile(r'[%+\[\]]')
# Last little bits of setting up
self.configUpdated()
@ -194,14 +194,10 @@ class ThemeManager(QtGui.QWidget):
return
real_theme_name = unicode(item.data(QtCore.Qt.UserRole).toString())
theme_name = unicode(item.text())
self.deleteAction.setVisible(False)
self.renameAction.setVisible(False)
self.globalAction.setVisible(False)
# If default theme restrict actions
if real_theme_name == theme_name:
self.deleteAction.setVisible(True)
self.renameAction.setVisible(True)
self.globalAction.setVisible(True)
visible = real_theme_name == theme_name
self.deleteAction.setVisible(visible)
self.renameAction.setVisible(visible)
self.globalAction.setVisible(visible)
self.menu.exec_(self.themeListWidget.mapToGlobal(point))
def changeGlobalFromTab(self, theme_name):
@ -330,10 +326,10 @@ class ThemeManager(QtGui.QWidget):
theme = self.getThemeData(
unicode(item.data(QtCore.Qt.UserRole).toString()))
if theme.background_type == u'image':
self.old_background_image = theme.background_filename
self.oldBackgroundImage = theme.background_filename
self.themeForm.theme = theme
self.themeForm.exec_(True)
self.old_background_image = None
self.oldBackgroundImage = None
self.mainwindow.renderer.update_theme(theme.theme_name)
def onDeleteTheme(self):
@ -361,10 +357,10 @@ class ThemeManager(QtGui.QWidget):
``theme``
The theme to delete.
"""
self.theme_list.remove(theme)
self.themeList.remove(theme)
thumb = u'%s.png' % theme
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:
encoding = get_filesystem_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.
"""
log.debug(u'Load themes from dir')
self.theme_list = []
self.themeList = []
self.themeListWidget.clear()
files = SettingsManager.get_files(self.settingsSection, u'.png')
if firstTime:
@ -473,16 +469,17 @@ class ThemeManager(QtGui.QWidget):
'%s (default)')) % text_name
else:
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)
if validate_thumb(theme, thumb):
icon = build_icon(thumb)
else:
icon = create_thumb(theme, thumb)
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.theme_list.append(text_name)
self.themeList.append(text_name)
self._pushThemes()
def _pushThemes(self):
@ -495,7 +492,7 @@ class ThemeManager(QtGui.QWidget):
"""
Return the list of loaded themes
"""
return self.theme_list
return self.themeList
def getThemeData(self, theme_name):
"""
@ -509,7 +506,7 @@ class ThemeManager(QtGui.QWidget):
unicode(theme_name) + u'.xml')
xml = get_text_file_string(xml_file)
if not xml:
log.debug("No theme data - using default theme")
log.debug(u'No theme data - using default theme')
return ThemeXML()
else:
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()
v1_background = xml_tree.find(u'BackgroundType')
if v1_background is not None:
theme_name, file_xml, out_file, abort_import = self.unzipVersion122(dir, zip,
xml_file[0], xml_tree, v1_background, out_file)
theme_name, file_xml, out_file, abort_import = \
self.unzipVersion122(
dir, zip, xml_file[0], xml_tree, v1_background, out_file)
else:
theme_name = xml_tree.find(u'name').text.strip()
theme_folder = os.path.join(dir, theme_name)
@ -601,8 +599,8 @@ class ThemeManager(QtGui.QWidget):
if file_xml:
theme = self._createThemeFromXml(file_xml, self.path)
self.generateAndSaveImage(dir, theme_name, theme)
# Only show the error message, when IOError was not raised (in this
# case the error message has already been shown).
# Only show the error message, when IOError was not raised (in
# this case the error message has already been shown).
elif zip is not None:
critical_error_message_box(
translate('OpenLP.ThemeManager', 'Validation Error'),
@ -611,13 +609,14 @@ class ThemeManager(QtGui.QWidget):
log.exception(u'Theme file does not contain XML data %s' %
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
this method, please keep in mind, that some parameters are redundant.
"""
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_exists = os.path.exists(theme_folder)
if theme_exists and not self.overWriteMessageBox(theme_name):
@ -632,12 +631,12 @@ class ThemeManager(QtGui.QWidget):
if background.text.strip() == u'2':
image_name = xml_tree.find(u'BackgroundParameter1').text.strip()
# 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'/'),
zip.namelist())
if len(imagefile) >= 1:
if len(image_file) >= 1:
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()
else:
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)
if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.updateImage(theme.theme_name,
u'theme', QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.updateImageBorder(
theme.background_filename,
ImageSource.Theme, QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.processUpdates()
self.loadThemes()
@ -685,9 +685,8 @@ class ThemeManager(QtGui.QWidget):
theme_dir = os.path.join(self.path, name)
check_directory_exists(theme_dir)
theme_file = os.path.join(theme_dir, name + u'.xml')
if self.old_background_image and \
image_to != self.old_background_image:
delete_file(self.old_background_image)
if self.oldBackgroundImage and image_to != self.oldBackgroundImage:
delete_file(self.oldBackgroundImage)
out_file = None
try:
out_file = open(theme_file, u'w')
@ -714,7 +713,7 @@ class ThemeManager(QtGui.QWidget):
if os.path.exists(sample_path_name):
os.unlink(sample_path_name)
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)
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.
"""
self.mainwindow.displayProgressBar(len(self.theme_list))
for theme in self.theme_list:
self.mainwindow.displayProgressBar(len(self.themeList))
for theme in self.themeList:
self.mainwindow.incrementProgressBar()
self.generateAndSaveImage(
self.path, theme, self.getThemeData(theme))
@ -819,7 +818,7 @@ class ThemeManager(QtGui.QWidget):
"""
theme = Theme(xml_data)
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:
new_theme.background_type = \
BackgroundType.to_string(BackgroundType.Solid)

View File

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

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
Receiver, ImageSource
from openlp.core.lib.settings import Settings
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
@ -98,4 +98,5 @@ class ImagePlugin(Plugin):
"""
background = QtGui.QColor(Settings().value(self.settingsSection
+ 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())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
name = os.path.split(filename)[1]
if self.plugin.liveController.display.directImage(name,
if self.plugin.liveController.display.directImage(
filename, background):
self.resetAction.setVisible(True)
else:

View File

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

View File

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

View File

@ -36,6 +36,104 @@ from ui import SongStrings
WHITESPACE = re.compile(r'[\W_]+', 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):
"""
@ -366,6 +464,136 @@ def clean_song(manager, song):
if song.copyright:
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 songstab import SongsTab
from mediaitem import SongMediaItem

View File

@ -36,7 +36,7 @@ import re
from openlp.core.lib import translate
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
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]+')
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:
def __init__(self, name, type, size):
@ -274,7 +179,7 @@ class EasyWorshipSongImport(SongImport):
self.addAuthor(author_name.strip())
if words:
# Format the lyrics
words = strip_rtf(words, self.encoding)
words, self.encoding = strip_rtf(words, self.encoding)
verse_type = VerseType.Tags[VerseType.Verse]
for verse in SLIDE_BREAK_REGEX.split(words):
verse = verse.strip()

View File

@ -44,6 +44,7 @@ from powersongimport import PowerSongImport
from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport
from sundayplusimport import SundayPlusImport
from foilpresenterimport import FoilPresenterImport
from zionworximport import ZionWorxImport
# Imports that might fail
@ -145,9 +146,10 @@ class SongFormat(object):
SongBeamer = 11
SongShowPlus = 12
SongsOfFellowship = 13
WordsOfWorship = 14
ZionWorx = 15
#CSV = 16
SundayPlus = 14
WordsOfWorship = 15
ZionWorx = 16
#CSV = 17
# Set optional attribute defaults
__defaults__ = {
@ -275,6 +277,13 @@ class SongFormat(object):
'The Songs of Fellowship importer has been disabled because '
'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: {
u'class': WowImport,
u'name': u'Words of Worship',
@ -322,6 +331,7 @@ class SongFormat(object):
SongFormat.SongBeamer,
SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship,
SongFormat.SundayPlus,
SongFormat.WordsOfWorship,
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.add_header('Authorization', auth_header)
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())
languages = [lang[u'code'] for lang in json_dict[u'available_languages']]
for language in languages:
@ -214,6 +218,7 @@ def download_translations():
fd.write(response.read())
fd.close()
print_quiet(u' Done.')
return True
def prepare_project():
"""
@ -310,7 +315,8 @@ def process_stack(command_stack):
for command in command_stack:
print_quiet(u'%d.' % (command_stack.current_index), False)
if command == Command.Download:
download_translations()
if not download_translations():
return
elif command == Command.Prepare:
prepare_project()
elif command == Command.Update: