This commit is contained in:
Jonathan Corwin 2012-07-05 19:41:59 +01:00
commit 577be9a9c1
24 changed files with 683 additions and 283 deletions

View File

@ -27,6 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import sys
from openlp.core import main
@ -34,4 +35,10 @@ if __name__ == u'__main__':
"""
Instantiate and run the application.
"""
# Mac OS X passes arguments like '-psn_XXXX' to gui application.
# This argument is process serial number. However, this causes
# conflict with other OpenLP arguments. Since we do not use this
# argument we can delete it to avoid any potential conflicts.
if sys.platform.startswith('darwin'):
sys.argv = filter(lambda x: not x.startswith('-psn'), sys.argv)
main()

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.
@ -265,14 +282,15 @@ def resize_image(image_path, width, height, background=u'#000000'):
if image_ratio == resize_ratio:
# We neither need to centre the image nor add "bars" to the image.
return preview
realw = preview.width()
realh = preview.height()
real_width = preview.width()
real_height = preview.height()
# and move it to the centre of the preview space
new_image = QtGui.QImage(width, height,
QtGui.QImage.Format_ARGB32_Premultiplied)
painter = QtGui.QPainter(new_image)
painter.fillRect(new_image.rect(), QtGui.QColor(background))
painter.drawImage((width - realw) / 2, (height - realh) / 2, preview)
painter.drawImage(
(width - real_width) / 2, (height - real_height) / 2, preview)
return new_image

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

@ -41,7 +41,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, PluginStatus, Receiver, build_icon, \
check_directory_exists
from openlp.core.lib.settings import Settings
from openlp.core.utils import get_web_page, AppLocation
from openlp.core.utils import get_web_page, AppLocation, get_filesystem_encoding
from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage
log = logging.getLogger(__name__)
@ -65,7 +65,8 @@ class ThemeScreenshotThread(QtCore.QThread):
filename = config.get(u'theme_%s' % theme, u'filename')
screenshot = config.get(u'theme_%s' % theme, u'screenshot')
urllib.urlretrieve(u'%s%s' % (self.parent().web, screenshot),
os.path.join(gettempdir(), u'openlp', screenshot))
os.path.join(unicode(gettempdir(), get_filesystem_encoding()),
u'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.parent().themesListWidget)
item.setData(QtCore.Qt.UserRole, QtCore.QVariant(filename))
item.setCheckState(QtCore.Qt.Unchecked)
@ -115,7 +116,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
Set up display at start of theme edit.
"""
self.restart()
check_directory_exists(os.path.join(gettempdir(), u'openlp'))
check_directory_exists(os.path.join(
unicode(gettempdir(), get_filesystem_encoding()), u'openlp'))
self.noInternetFinishButton.setVisible(False)
# Check if this is a re-run of the wizard.
self.hasRunWizard = Settings().value(
@ -304,8 +306,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
item = self.themesListWidget.item(index)
if item.data(QtCore.Qt.UserRole) == QtCore.QVariant(filename):
break
item.setIcon(build_icon(
os.path.join(gettempdir(), u'openlp', screenshot)))
item.setIcon(build_icon(os.path.join(unicode(gettempdir(),
get_filesystem_encoding()), u'openlp', screenshot)))
def _getFileSize(self, url):
site = urllib.urlopen(url)
@ -426,7 +428,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._setPluginStatus(self.alertCheckBox, u'alerts/status')
if self.webAccess:
# Build directories for downloads
songs_destination = os.path.join(unicode(gettempdir()), u'openlp')
songs_destination = os.path.join(
unicode(gettempdir(), get_filesystem_encoding()), u'openlp')
bibles_destination = AppLocation.get_section_data_path(u'bibles')
themes_destination = AppLocation.get_section_data_path(u'themes')
# Download songs

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
@ -1035,8 +1035,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# Make sure it's a .conf file.
if not export_file_name.endswith(u'conf'):
export_file_name = export_file_name + u'.conf'
temp_file = os.path.join(unicode(gettempdir()),
u'openlp', u'exportConf.tmp')
temp_file = os.path.join(unicode(gettempdir(),
get_filesystem_encoding()), u'openlp', u'exportConf.tmp')
self.saveSettings()
setting_sections = []
# Add main sections.

View File

@ -330,7 +330,24 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
Copies the display text to the clipboard as plain text
"""
self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toPlainText())
cursor = QtGui.QTextCursor(self.document)
cursor.select(QtGui.QTextCursor.Document)
clipboard_text = cursor.selectedText()
# We now have the unprocessed unicode service text in the cursor
# So we replace u2028 with \n and u2029 with \n\n and a few others
clipboard_text = clipboard_text.replace(u'\u2028', u'\n')
clipboard_text = clipboard_text.replace(u'\u2029', u'\n\n')
clipboard_text = clipboard_text.replace(u'\u2018', u'\'')
clipboard_text = clipboard_text.replace(u'\u2019', u'\'')
clipboard_text = clipboard_text.replace(u'\u201c', u'"')
clipboard_text = clipboard_text.replace(u'\u201d', u'"')
clipboard_text = clipboard_text.replace(u'\u2026', u'...')
clipboard_text = clipboard_text.replace(u'\u2013', u'-')
clipboard_text = clipboard_text.replace(u'\u2014', u'-')
# remove the icon from the text
clipboard_text = clipboard_text.replace(u'\ufffc\xa0', u'')
# and put it all on the clipboard
self.mainWindow.clipboard.setText(clipboard_text)
def copyHtmlText(self):
"""

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

@ -40,7 +40,7 @@ from openlp.core.lib import Receiver, SettingsManager, translate, \
from openlp.core.lib.ui import UiStrings, critical_error_message_box
from openlp.core.lib.settings import Settings
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.core.utils import AppLocation, delete_file
from openlp.core.utils import AppLocation, delete_file, get_filesystem_encoding
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, \
BiblesResourcesDB
from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract
@ -73,7 +73,8 @@ class BibleUpgradeForm(OpenLPWizard):
self.suffix = u'.sqlite'
self.settingsSection = u'bibles'
self.path = AppLocation.get_section_data_path(self.settingsSection)
self.temp_dir = os.path.join(gettempdir(), u'openlp')
self.temp_dir = os.path.join(
unicode(gettempdir(), get_filesystem_encoding()), u'openlp')
self.files = self.manager.old_bible_databases
self.success = {}
self.newbibles = {}

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

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

@ -45,6 +45,7 @@ from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport
from songproimport import SongProImport
from sundayplusimport import SundayPlusImport
from foilpresenterimport import FoilPresenterImport
from zionworximport import ZionWorxImport
# Imports that might fail
@ -75,6 +76,7 @@ if os.name == u'nt':
except ImportError:
log.exception('Error importing %s', 'MediaShoutImport')
class SongFormatSelect(object):
"""
This is a special enumeration class listing available file selection modes.
@ -154,8 +156,9 @@ class SongFormat(object):
SongPro = 13
SongShowPlus = 14
SongsOfFellowship = 15
WordsOfWorship = 16
ZionWorx = 17
SundayPlus = 16
WordsOfWorship = 17
ZionWorx = 18
# Set optional attribute defaults
__defaults__ = {
@ -303,6 +306,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',
@ -346,6 +356,7 @@ class SongFormat(object):
SongFormat.SongPro,
SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship,
SongFormat.SundayPlus,
SongFormat.WordsOfWorship,
SongFormat.ZionWorx
]

View File

@ -338,8 +338,9 @@ class SongMediaItem(MediaManagerItem):
def onImportClick(self):
if not hasattr(self, u'importWizard'):
self.importWizard = SongImportForm(self, self.plugin)
if self.importWizard.exec_() == QtGui.QDialog.Accepted:
Receiver.send_message(u'songs_load_list')
self.importWizard.exec_()
# Run song load as list may have been cancelled but some songs loaded
Receiver.send_message(u'songs_load_list')
def onExportClick(self):
if not hasattr(self, u'exportWizard'):

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

View File

@ -36,6 +36,7 @@ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.utils import get_filesystem_encoding
from openlp.core.utils.actions import ActionList
from openlp.plugins.songs.lib import clean_song, upgrade, SongMediaItem, \
SongsTab
@ -234,7 +235,8 @@ class SongsPlugin(Plugin):
new songs into the database.
"""
self.onToolsReindexItemTriggered()
db_dir = unicode(os.path.join(gettempdir(), u'openlp'))
db_dir = unicode(os.path.join(
unicode(gettempdir(), get_filesystem_encoding()), u'openlp'))
if not os.path.exists(db_dir):
return
song_dbs = []