This commit is contained in:
Andreas Preikschat 2012-05-26 19:04:13 +02:00
commit d2a38f676c
26 changed files with 517 additions and 211 deletions

View File

@ -26,12 +26,6 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
# Import uuid now, to avoid the rare bug described in the support system:
# http://support.openlp.org/issues/102
# If https://bugs.gentoo.org/show_bug.cgi?id=317557 is fixed, the import can be
# removed.
import uuid
from openlp.core import main from openlp.core import main

View File

@ -109,7 +109,7 @@ class OpenLP(QtGui.QApplication):
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor) QtCore.SIGNAL(u'cursor_normal'), self.setNormalCursor)
# Decide how many screens we have and their size # Decide how many screens we have and their size
screens = ScreenList(self.desktop()) screens = ScreenList.create(self.desktop())
# First time checks in settings # First time checks in settings
has_run_wizard = QtCore.QSettings().value( has_run_wizard = QtCore.QSettings().value(
u'general/has run wizard', QtCore.QVariant(False)).toBool() u'general/has run wizard', QtCore.QVariant(False)).toBool()

View File

@ -52,9 +52,8 @@ class OpenLPDockWidget(QtGui.QDockWidget):
if icon: if icon:
self.setWindowIcon(build_icon(icon)) self.setWindowIcon(build_icon(icon))
# Sort out the minimum width. # Sort out the minimum width.
screens = ScreenList.get_instance() screens = ScreenList()
screen_width = screens.current[u'size'].width() mainwindow_docbars = screens.current[u'size'].width() / 5
mainwindow_docbars = screen_width / 5
if mainwindow_docbars > 300: if mainwindow_docbars > 300:
self.setMinimumWidth(300) self.setMinimumWidth(300)
else: else:

View File

@ -46,13 +46,36 @@ class FormattingTags(object):
""" """
Provide access to the html_expands list. Provide access to the html_expands list.
""" """
# Load user defined tags otherwise user defined tags are not present.
return FormattingTags.html_expands return FormattingTags.html_expands
@staticmethod @staticmethod
def reset_html_tags(): def save_html_tags():
""" """
Resets the html_expands list. Saves all formatting tags except protected ones.
"""
tags = []
for tag in FormattingTags.html_expands:
if not tag[u'protected'] and not tag.get(u'temporary'):
# Using dict ensures that copy is made and encoding of values
# a little later does not affect tags in the original list
tags.append(dict(tag))
tag = tags[-1]
# Remove key 'temporary' from tags.
# It is not needed to be saved.
if u'temporary' in tag:
del tag[u'temporary']
for element in tag:
if isinstance(tag[element], unicode):
tag[element] = tag[element].encode('utf8')
# Formatting Tags were also known as display tags.
QtCore.QSettings().setValue(u'displayTags/html_tags',
QtCore.QVariant(cPickle.dumps(tags) if tags else u''))
@staticmethod
def load_tags():
"""
Load the Tags from store so can be used in the system or used to
update the display.
""" """
temporary_tags = [tag for tag in FormattingTags.html_expands temporary_tags = [tag for tag in FormattingTags.html_expands
if tag.get(u'temporary')] if tag.get(u'temporary')]
@ -140,38 +163,6 @@ class FormattingTags(object):
FormattingTags.add_html_tags(base_tags) FormattingTags.add_html_tags(base_tags)
FormattingTags.add_html_tags(temporary_tags) FormattingTags.add_html_tags(temporary_tags)
@staticmethod
def save_html_tags():
"""
Saves all formatting tags except protected ones.
"""
tags = []
for tag in FormattingTags.html_expands:
if not tag[u'protected'] and not tag.get(u'temporary'):
# Using dict ensures that copy is made and encoding of values
# a little later does not affect tags in the original list
tags.append(dict(tag))
tag = tags[-1]
# Remove key 'temporary' from tags.
# It is not needed to be saved.
if u'temporary' in tag:
del tag[u'temporary']
for element in tag:
if isinstance(tag[element], unicode):
tag[element] = tag[element].encode('utf8')
# Formatting Tags were also known as display tags.
QtCore.QSettings().setValue(u'displayTags/html_tags',
QtCore.QVariant(cPickle.dumps(tags) if tags else u''))
@staticmethod
def load_tags():
"""
Load the Tags from store so can be used in the system or used to
update the display. If Cancel was selected this is needed to reset the
dsiplay to the correct version.
"""
# Initial Load of the Tags
FormattingTags.reset_html_tags()
# Formatting Tags were also known as display tags. # Formatting Tags were also known as display tags.
user_expands = QtCore.QSettings().value(u'displayTags/html_tags', user_expands = QtCore.QSettings().value(u'displayTags/html_tags',
QtCore.QVariant(u'')).toString() QtCore.QVariant(u'')).toString()
@ -187,17 +178,13 @@ class FormattingTags(object):
FormattingTags.add_html_tags(user_tags) FormattingTags.add_html_tags(user_tags)
@staticmethod @staticmethod
def add_html_tags(tags, save=False): def add_html_tags(tags):
""" """
Add a list of tags to the list. Add a list of tags to the list.
``tags`` ``tags``
The list with tags to add. The list with tags to add.
``save``
Defaults to ``False``. If set to ``True`` the given ``tags`` are
saved to the config.
Each **tag** has to be a ``dict`` and should have the following keys: Each **tag** has to be a ``dict`` and should have the following keys:
* desc * desc
@ -225,8 +212,6 @@ class FormattingTags(object):
displaying text containing the tag. It has to be a ``boolean``. displaying text containing the tag. It has to be a ``boolean``.
""" """
FormattingTags.html_expands.extend(tags) FormattingTags.html_expands.extend(tags)
if save:
FormattingTags.save_html_tags()
@staticmethod @staticmethod
def remove_html_tag(tag_id): def remove_html_tag(tag_id):

View File

@ -163,7 +163,7 @@ class ImageManager(QtCore.QObject):
def __init__(self): def __init__(self):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
current_screen = ScreenList.get_instance().current current_screen = ScreenList().current
self.width = current_screen[u'size'].width() self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height() self.height = current_screen[u'size'].height()
self._cache = {} self._cache = {}
@ -177,7 +177,7 @@ class ImageManager(QtCore.QObject):
Screen has changed size so rebuild the cache to new size. Screen has changed size so rebuild the cache to new size.
""" """
log.debug(u'update_display') log.debug(u'update_display')
current_screen = ScreenList.get_instance().current current_screen = ScreenList().current
self.width = current_screen[u'size'].width() self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height() self.height = current_screen[u'size'].height()
# Mark the images as dirty for a rebuild by setting the image and byte # Mark the images as dirty for a rebuild by setting the image and byte

View File

@ -69,7 +69,7 @@ class Renderer(object):
log.debug(u'Initialisation started') log.debug(u'Initialisation started')
self.themeManager = themeManager self.themeManager = themeManager
self.imageManager = imageManager self.imageManager = imageManager
self.screens = ScreenList.get_instance() self.screens = ScreenList()
self.service_theme = u'' self.service_theme = u''
self.theme_level = u'' self.theme_level = u''
self.override_background = None self.override_background = None
@ -125,7 +125,7 @@ class Renderer(object):
Set the appropriate theme depending on the theme level. Set the appropriate theme depending on the theme level.
Called by the service item when building a display frame Called by the service item when building a display frame
``theme`` ``override_theme``
The name of the song-level theme. None means the service The name of the song-level theme. None means the service
item wants to use the given value. item wants to use the given value.
@ -235,8 +235,8 @@ class Renderer(object):
# the first two slides (and neglect the last for now). # the first two slides (and neglect the last for now).
if len(slides) == 3: if len(slides) == 3:
html_text = expand_tags(u'\n'.join(slides[:2])) html_text = expand_tags(u'\n'.join(slides[:2]))
# We check both slides to determine if the optional break is # We check both slides to determine if the optional split is
# needed (there is only one optional break). # needed (there is only one optional split).
else: else:
html_text = expand_tags(u'\n'.join(slides)) html_text = expand_tags(u'\n'.join(slides))
html_text = html_text.replace(u'\n', u'<br>') html_text = html_text.replace(u'\n', u'<br>')
@ -247,14 +247,18 @@ class Renderer(object):
else: else:
# The first optional slide fits, which means we have to # The first optional slide fits, which means we have to
# render the first optional slide. # render the first optional slide.
text_contains_break = u'[---]' in text text_contains_split = u'[---]' in text
if text_contains_break: if text_contains_split:
try: try:
text_to_render, text = \ text_to_render, text = \
text.split(u'\n[---]\n', 1) text.split(u'\n[---]\n', 1)
except: except:
text_to_render = text.split(u'\n[---]\n')[0] text_to_render = text.split(u'\n[---]\n')[0]
text = u'' text = u''
text_to_render, raw_tags, html_tags = \
self._get_start_tags(text_to_render)
if text:
text = raw_tags + text
else: else:
text_to_render = text text_to_render = text
text = u'' text = u''
@ -263,7 +267,7 @@ class Renderer(object):
if len(slides) > 1 and text: if len(slides) > 1 and text:
# Add all slides apart from the last one the list. # Add all slides apart from the last one the list.
pages.extend(slides[:-1]) pages.extend(slides[:-1])
if text_contains_break: if text_contains_split:
text = slides[-1] + u'\n[---]\n' + text text = slides[-1] + u'\n[---]\n' + text
else: else:
text = slides[-1] + u'\n'+ text text = slides[-1] + u'\n'+ text
@ -492,7 +496,7 @@ class Renderer(object):
(raw_text.find(tag[u'start tag']), tag[u'start tag'], (raw_text.find(tag[u'start tag']), tag[u'start tag'],
tag[u'end tag'])) tag[u'end tag']))
html_tags.append( html_tags.append(
(raw_text.find(tag[u'start tag']), tag[u'start html'])) (raw_text.find(tag[u'start tag']), tag[u'start html']))
# Sort the lists, so that the tags which were opened first on the first # Sort the lists, so that the tags which were opened first on the first
# slide (the text we are checking) will be opened first on the next # slide (the text we are checking) will be opened first on the next
# slide as well. # slide as well.

View File

@ -313,17 +313,12 @@ class ServiceItem(object):
self.from_plugin = header[u'from_plugin'] self.from_plugin = header[u'from_plugin']
self.capabilities = header[u'capabilities'] self.capabilities = header[u'capabilities']
# Added later so may not be present in older services. # Added later so may not be present in older services.
if u'search' in header: self.search_string = header.get(u'search', u'')
self.search_string = header[u'search'] self.data_string = header.get(u'data', u'')
self.data_string = header[u'data'] self.xml_version = header.get(u'xml_version')
if u'xml_version' in header: self.start_time = header.get(u'start_time', 0)
self.xml_version = header[u'xml_version'] self.end_time = header.get(u'end_time', 0)
if u'start_time' in header: self.media_length = header.get(u'media_length', 0)
self.start_time = header[u'start_time']
if u'end_time' in header:
self.end_time = header[u'end_time']
if u'media_length' in header:
self.media_length = header[u'media_length']
if u'background_audio' in header: if u'background_audio' in header:
self.background_audio = [] self.background_audio = []
for filename in header[u'background_audio']: for filename in header[u'background_audio']:

View File

@ -94,6 +94,7 @@ class UiStrings(object):
self.NewService = translate('OpenLP.Ui', 'New Service') self.NewService = translate('OpenLP.Ui', 'New Service')
self.NewTheme = translate('OpenLP.Ui', 'New Theme') self.NewTheme = translate('OpenLP.Ui', 'New Theme')
self.NextTrack = translate('OpenLP.Ui', 'Next Track') self.NextTrack = translate('OpenLP.Ui', 'Next Track')
self.NFdSs = translate('OpenLP.Ui', 'No Folder Selected', 'Singular')
self.NFSs = translate('OpenLP.Ui', 'No File Selected', 'Singular') self.NFSs = translate('OpenLP.Ui', 'No File Selected', 'Singular')
self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural') self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural')
self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')

View File

@ -57,6 +57,14 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
QtCore.SIGNAL(u'clicked()'), self.onDeleteClicked) QtCore.SIGNAL(u'clicked()'), self.onDeleteClicked)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'), QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(u'rejected()'),
self.close) self.close)
QtCore.QObject.connect(self.descriptionLineEdit,
QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited)
QtCore.QObject.connect(self.tagLineEdit,
QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited)
QtCore.QObject.connect(self.startTagLineEdit,
QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited)
QtCore.QObject.connect(self.endTagLineEdit,
QtCore.SIGNAL(u'textEdited(QString)'), self.onTextEdited)
# Forces reloading of tags from openlp configuration. # Forces reloading of tags from openlp configuration.
FormattingTags.load_tags() FormattingTags.load_tags()
@ -65,7 +73,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
Load Display and set field state. Load Display and set field state.
""" """
# Create initial copy from master # Create initial copy from master
self._resetTable() self._reloadTable()
self.selected = -1 self.selected = -1
return QtGui.QDialog.exec_(self) return QtGui.QDialog.exec_(self)
@ -73,9 +81,9 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
""" """
Table Row selected so display items and set field state. Table Row selected so display items and set field state.
""" """
row = self.tagTableWidget.currentRow() self.savePushButton.setEnabled(False)
html = FormattingTags.html_expands[row] self.selected = self.tagTableWidget.currentRow()
self.selected = row html = FormattingTags.get_html_tags()[self.selected]
self.descriptionLineEdit.setText(html[u'desc']) self.descriptionLineEdit.setText(html[u'desc'])
self.tagLineEdit.setText(self._strip(html[u'start tag'])) self.tagLineEdit.setText(self._strip(html[u'start tag']))
self.startTagLineEdit.setText(html[u'start html']) self.startTagLineEdit.setText(html[u'start html'])
@ -85,21 +93,26 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
self.tagLineEdit.setEnabled(False) self.tagLineEdit.setEnabled(False)
self.startTagLineEdit.setEnabled(False) self.startTagLineEdit.setEnabled(False)
self.endTagLineEdit.setEnabled(False) self.endTagLineEdit.setEnabled(False)
self.savePushButton.setEnabled(False)
self.deletePushButton.setEnabled(False) self.deletePushButton.setEnabled(False)
else: else:
self.descriptionLineEdit.setEnabled(True) self.descriptionLineEdit.setEnabled(True)
self.tagLineEdit.setEnabled(True) self.tagLineEdit.setEnabled(True)
self.startTagLineEdit.setEnabled(True) self.startTagLineEdit.setEnabled(True)
self.endTagLineEdit.setEnabled(True) self.endTagLineEdit.setEnabled(True)
self.savePushButton.setEnabled(True)
self.deletePushButton.setEnabled(True) self.deletePushButton.setEnabled(True)
def onTextEdited(self, text):
"""
Enable the ``savePushButton`` when any of the selected tag's properties
has been changed.
"""
self.savePushButton.setEnabled(True)
def onNewClicked(self): def onNewClicked(self):
""" """
Add a new tag to list only if it is not a duplicate. Add a new tag to list only if it is not a duplicate.
""" """
for html in FormattingTags.html_expands: for html in FormattingTags.get_html_tags():
if self._strip(html[u'start tag']) == u'n': if self._strip(html[u'start tag']) == u'n':
critical_error_message_box( critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'), translate('OpenLP.FormattingTagForm', 'Update Error'),
@ -117,11 +130,13 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
u'temporary': False u'temporary': False
} }
FormattingTags.add_html_tags([tag]) FormattingTags.add_html_tags([tag])
self._resetTable() FormattingTags.save_html_tags()
self._reloadTable()
# Highlight new row # Highlight new row
self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1) self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1)
self.onRowSelected() self.onRowSelected()
self.tagTableWidget.scrollToBottom() self.tagTableWidget.scrollToBottom()
#self.savePushButton.setEnabled(False)
def onDeleteClicked(self): def onDeleteClicked(self):
""" """
@ -130,14 +145,14 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
if self.selected != -1: if self.selected != -1:
FormattingTags.remove_html_tag(self.selected) FormattingTags.remove_html_tag(self.selected)
self.selected = -1 self.selected = -1
self._resetTable() FormattingTags.save_html_tags()
FormattingTags.save_html_tags() self._reloadTable()
def onSavedClicked(self): def onSavedClicked(self):
""" """
Update Custom Tag details if not duplicate and save the data. Update Custom Tag details if not duplicate and save the data.
""" """
html_expands = FormattingTags.html_expands html_expands = FormattingTags.get_html_tags()
if self.selected != -1: if self.selected != -1:
html = html_expands[self.selected] html = html_expands[self.selected]
tag = unicode(self.tagLineEdit.text()) tag = unicode(self.tagLineEdit.text())
@ -157,14 +172,13 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
# Keep temporary tags when the user changes one. # Keep temporary tags when the user changes one.
html[u'temporary'] = False html[u'temporary'] = False
self.selected = -1 self.selected = -1
self._resetTable()
FormattingTags.save_html_tags() FormattingTags.save_html_tags()
self._reloadTable()
def _resetTable(self): def _reloadTable(self):
""" """
Reset List for loading. Reset List for loading.
""" """
FormattingTags.load_tags()
self.tagTableWidget.clearContents() self.tagTableWidget.clearContents()
self.tagTableWidget.setRowCount(0) self.tagTableWidget.setRowCount(0)
self.newPushButton.setEnabled(True) self.newPushButton.setEnabled(True)

View File

@ -42,7 +42,7 @@ class GeneralTab(SettingsTab):
""" """
Initialise the general settings tab Initialise the general settings tab
""" """
self.screens = ScreenList.get_instance() self.screens = ScreenList()
self.iconPath = u':/icon/openlp-logo-16x16.png' self.iconPath = u':/icon/openlp-logo-16x16.png'
generalTranslated = translate('OpenLP.GeneralTab', 'General') generalTranslated = translate('OpenLP.GeneralTab', 'General')
SettingsTab.__init__(self, parent, u'General', generalTranslated) SettingsTab.__init__(self, parent, u'General', generalTranslated)

View File

@ -119,7 +119,7 @@ class MainDisplay(Display):
def __init__(self, parent, imageManager, live, controller): def __init__(self, parent, imageManager, live, controller):
Display.__init__(self, parent, live, controller) Display.__init__(self, parent, live, controller)
self.imageManager = imageManager self.imageManager = imageManager
self.screens = ScreenList.get_instance() self.screens = ScreenList()
self.plugins = PluginManager.get_instance().plugins self.plugins = PluginManager.get_instance().plugins
self.rebuildCSS = False self.rebuildCSS = False
self.hideMode = None self.hideMode = None

View File

@ -795,7 +795,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if answer == QtGui.QMessageBox.No: if answer == QtGui.QMessageBox.No:
return return
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
screens = ScreenList.get_instance() screens = ScreenList()
FirstTimeForm(screens, self).exec_() FirstTimeForm(screens, self).exec_()
self.firstTime() self.firstTime()
for plugin in self.pluginManager.plugins: for plugin in self.pluginManager.plugins:

View File

@ -41,36 +41,40 @@ class ScreenList(object):
""" """
Wrapper to handle the parameters of the display screen. Wrapper to handle the parameters of the display screen.
To get access to the screen list call ``ScreenList.get_instance()``. To get access to the screen list call ``ScreenList()``.
""" """
log.info(u'Screen loaded') log.info(u'Screen loaded')
instance = None __instance__ = None
@staticmethod def __new__(cls):
def get_instance(): if not cls.__instance__:
return ScreenList.instance cls.__instance__ = object.__new__(cls)
return cls.__instance__
def __init__(self, desktop): @classmethod
def create(cls, desktop):
""" """
Initialise the screen list. Initialise the screen list.
``desktop`` ``desktop``
A ``QDesktopWidget`` object. A ``QDesktopWidget`` object.
""" """
ScreenList.instance = self screen_list = cls()
self.desktop = desktop screen_list.desktop = desktop
self.preview = None screen_list.preview = None
self.current = None screen_list.current = None
self.override = None screen_list.override = None
self.screen_list = [] screen_list.screen_list = []
self.display_count = 0 screen_list.display_count = 0
self.screen_count_changed() screen_list.screen_count_changed()
self._load_screen_settings() screen_list._load_screen_settings()
QtCore.QObject.connect(desktop, QtCore.QObject.connect(desktop,
QtCore.SIGNAL(u'resized(int)'), self.screen_resolution_changed) QtCore.SIGNAL(u'resized(int)'),
screen_list.screen_resolution_changed)
QtCore.QObject.connect(desktop, QtCore.QObject.connect(desktop,
QtCore.SIGNAL(u'screenCountChanged(int)'), QtCore.SIGNAL(u'screenCountChanged(int)'),
self.screen_count_changed) screen_list.screen_count_changed)
return screen_list
def screen_resolution_changed(self, number): def screen_resolution_changed(self, number):
""" """
@ -233,8 +237,8 @@ class ScreenList(object):
y = window.y() + (window.height() / 2) y = window.y() + (window.height() / 2)
for screen in self.screen_list: for screen in self.screen_list:
size = screen[u'size'] size = screen[u'size']
if x >= size.x() and x <= (size.x() + size.width()) \ if x >= size.x() and x <= (size.x() + size.width()) and \
and y >= size.y() and y <= (size.y() + size.height()): y >= size.y() and y <= (size.y() + size.height()):
return screen[u'number'] return screen[u'number']
def _load_screen_settings(self): def _load_screen_settings(self):

View File

@ -51,7 +51,7 @@ class ServiceManagerList(QtGui.QTreeWidget):
""" """
Set up key bindings and mouse behaviour for the service list Set up key bindings and mouse behaviour for the service list
""" """
def __init__(self, serviceManager, parent=None, name=None): def __init__(self, serviceManager, parent=None):
QtGui.QTreeWidget.__init__(self, parent) QtGui.QTreeWidget.__init__(self, parent)
self.serviceManager = serviceManager self.serviceManager = serviceManager
@ -64,6 +64,9 @@ class ServiceManagerList(QtGui.QTreeWidget):
elif event.key() == QtCore.Qt.Key_Down: elif event.key() == QtCore.Qt.Key_Down:
self.serviceManager.onMoveSelectionDown() self.serviceManager.onMoveSelectionDown()
event.accept() event.accept()
elif event.key() == QtCore.Qt.Key_Delete:
self.serviceManager.onDeleteFromService()
event.accept()
event.ignore() event.ignore()
else: else:
event.ignore() event.ignore()
@ -101,7 +104,6 @@ class ServiceManager(QtGui.QWidget):
QtGui.QWidget.__init__(self, parent) QtGui.QWidget.__init__(self, parent)
self.mainwindow = mainwindow self.mainwindow = mainwindow
self.serviceItems = [] self.serviceItems = []
self.serviceName = u''
self.suffixes = [] self.suffixes = []
self.dropPosition = 0 self.dropPosition = 0
self.expandTabs = False self.expandTabs = False
@ -219,6 +221,7 @@ class ServiceManager(QtGui.QWidget):
icon=u':/general/general_delete.png', icon=u':/general/general_delete.png',
tooltip=translate('OpenLP.ServiceManager', tooltip=translate('OpenLP.ServiceManager',
'Delete the selected item from the service.'), 'Delete the selected item from the service.'),
shortcuts=[QtCore.Qt.Key_Delete],
triggers=self.onDeleteFromService) triggers=self.onDeleteFromService)
self.orderToolbar.addSeparator() self.orderToolbar.addSeparator()
self.serviceManagerList.expand = self.orderToolbar.addToolbarAction( self.serviceManagerList.expand = self.orderToolbar.addToolbarAction(
@ -299,17 +302,14 @@ class ServiceManager(QtGui.QWidget):
self.timeAction = create_widget_action(self.menu, self.timeAction = create_widget_action(self.menu,
text=translate('OpenLP.ServiceManager', '&Start Time'), text=translate('OpenLP.ServiceManager', '&Start Time'),
icon=u':/media/media_time.png', triggers=self.onStartTimeForm) icon=u':/media/media_time.png', triggers=self.onStartTimeForm)
self.deleteAction = create_widget_action(self.menu, # Add already existing delete action to the menu.
text=translate('OpenLP.ServiceManager', '&Delete From Service'), self.menu.addAction(self.serviceManagerList.delete)
icon=u':/general/general_delete.png',
triggers=self.onDeleteFromService)
self.menu.addSeparator() self.menu.addSeparator()
self.previewAction = create_widget_action(self.menu, self.previewAction = create_widget_action(self.menu,
text=translate('OpenLP.ServiceManager', 'Show &Preview'), text=translate('OpenLP.ServiceManager', 'Show &Preview'),
icon=u':/general/general_preview.png', triggers=self.makePreview) icon=u':/general/general_preview.png', triggers=self.makePreview)
self.liveAction = create_widget_action(self.menu, # Add already existing make live action to the menu.
text=translate('OpenLP.ServiceManager', 'Show &Live'), self.menu.addAction(self.serviceManagerList.makeLive)
icon=u':/general/general_live.png', triggers=self.makeLive)
self.menu.addSeparator() self.menu.addSeparator()
self.themeMenu = QtGui.QMenu( self.themeMenu = QtGui.QMenu(
translate('OpenLP.ServiceManager', '&Change Item Theme')) translate('OpenLP.ServiceManager', '&Change Item Theme'))
@ -561,14 +561,12 @@ class ServiceManager(QtGui.QWidget):
zip.write(audio_from, audio_to.encode(u'utf-8')) zip.write(audio_from, audio_to.encode(u'utf-8'))
except IOError: except IOError:
log.exception(u'Failed to save service to disk: %s', temp_file_name) log.exception(u'Failed to save service to disk: %s', temp_file_name)
# Add this line in after the release to notify the user that saving Receiver.send_message(u'openlp_error_message', {
# their file failed. Commented out due to string freeze. u'title': translate(u'OpenLP.ServiceManager',
#Receiver.send_message(u'openlp_error_message', { u'Error Saving File'),
# u'title': translate(u'OpenLP.ServiceManager', u'message': translate(u'OpenLP.ServiceManager',
# u'Error Saving File'), u'There was an error saving your file.')
# u'message': translate(u'OpenLP.ServiceManager', })
# u'There was an error saving your file.')
#})
success = False success = False
finally: finally:
if zip: if zip:
@ -1318,15 +1316,15 @@ class ServiceManager(QtGui.QWidget):
def findServiceItem(self): def findServiceItem(self):
""" """
Finds the selected ServiceItem in the list and returns the position of Finds the first selected ServiceItem in the list and returns the
the serviceitem and its selected child item. For example, if the third position of the serviceitem and its selected child item. For example,
child item (in the Slidecontroller known as slide) in the second service if the third child item (in the Slidecontroller known as slide) in the
item is selected this will return:: second service item is selected this will return::
(1, 2) (1, 2)
""" """
items = self.serviceManagerList.selectedItems() items = self.serviceManagerList.selectedItems()
serviceItem = 0 serviceItem = -1
serviceItemChild = -1 serviceItemChild = -1
for item in items: for item in items:
parentitem = item.parent() parentitem = item.parent()
@ -1335,8 +1333,10 @@ class ServiceManager(QtGui.QWidget):
else: else:
serviceItem = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0] serviceItem = parentitem.data(0, QtCore.Qt.UserRole).toInt()[0]
serviceItemChild = item.data(0, QtCore.Qt.UserRole).toInt()[0] serviceItemChild = item.data(0, QtCore.Qt.UserRole).toInt()[0]
# Adjust for zero based arrays. # Adjust for zero based arrays.
serviceItem -= 1 serviceItem -= 1
# Only process the first item on the list for this method.
break
return serviceItem, serviceItemChild return serviceItem, serviceItemChild
def dragEnterEvent(self, event): def dragEnterEvent(self, event):

View File

@ -83,7 +83,7 @@ class SlideController(Controller):
Set up the Slide Controller. Set up the Slide Controller.
""" """
Controller.__init__(self, parent, isLive) Controller.__init__(self, parent, isLive)
self.screens = ScreenList.get_instance() self.screens = ScreenList()
try: try:
self.ratio = float(self.screens.current[u'size'].width()) / \ self.ratio = float(self.screens.current[u'size'].width()) / \
float(self.screens.current[u'size'].height()) float(self.screens.current[u'size'].height())

View File

@ -136,16 +136,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
""" """
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'background_type', self.backgroundComboBox) u'background_type', self.backgroundComboBox)
self.backgroundPage.registerField( self.backgroundPage.registerField(u'color', self.colorButton)
u'color', self.colorButton)
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'grandient_start', self.gradientStartButton) u'grandient_start', self.gradientStartButton)
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'grandient_end', self.gradientEndButton) u'grandient_end', self.gradientEndButton)
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'background_image', self.imageFileEdit) u'background_image', self.imageFileEdit)
self.backgroundPage.registerField( self.backgroundPage.registerField(u'gradient', self.gradientComboBox)
u'gradient', self.gradientComboBox)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
u'mainColorButton', self.mainColorButton) u'mainColorButton', self.mainColorButton)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
@ -158,8 +156,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
u'outlineColorButton', self.outlineColorButton) u'outlineColorButton', self.outlineColorButton)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
u'outlineSizeSpinBox', self.outlineSizeSpinBox) u'outlineSizeSpinBox', self.outlineSizeSpinBox)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(u'shadowCheckBox', self.shadowCheckBox)
u'shadowCheckBox', self.shadowCheckBox)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
u'mainBoldCheckBox', self.mainBoldCheckBox) u'mainBoldCheckBox', self.mainBoldCheckBox)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
@ -170,10 +167,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
u'shadowSizeSpinBox', self.shadowSizeSpinBox) u'shadowSizeSpinBox', self.shadowSizeSpinBox)
self.mainAreaPage.registerField( self.mainAreaPage.registerField(
u'footerSizeSpinBox', self.footerSizeSpinBox) u'footerSizeSpinBox', self.footerSizeSpinBox)
self.areaPositionPage.registerField( self.areaPositionPage.registerField(u'mainPositionX', self.mainXSpinBox)
u'mainPositionX', self.mainXSpinBox) self.areaPositionPage.registerField(u'mainPositionY', self.mainYSpinBox)
self.areaPositionPage.registerField(
u'mainPositionY', self.mainYSpinBox)
self.areaPositionPage.registerField( self.areaPositionPage.registerField(
u'mainPositionWidth', self.mainWidthSpinBox) u'mainPositionWidth', self.mainWidthSpinBox)
self.areaPositionPage.registerField( self.areaPositionPage.registerField(
@ -188,12 +183,10 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
u'footerPositionHeight', self.footerHeightSpinBox) u'footerPositionHeight', self.footerHeightSpinBox)
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'horizontal', self.horizontalComboBox) u'horizontal', self.horizontalComboBox)
self.backgroundPage.registerField( self.backgroundPage.registerField(u'vertical', self.verticalComboBox)
u'vertical', self.verticalComboBox)
self.backgroundPage.registerField( self.backgroundPage.registerField(
u'slideTransition', self.transitionsCheckBox) u'slideTransition', self.transitionsCheckBox)
self.backgroundPage.registerField( self.backgroundPage.registerField(u'name', self.themeNameEdit)
u'name', self.themeNameEdit)
def calculateLines(self): def calculateLines(self):
""" """
@ -269,10 +262,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
Change state as Outline check box changed Change state as Outline check box changed
""" """
if self.updateThemeAllowed: if self.updateThemeAllowed:
if state == QtCore.Qt.Checked: self.theme.font_main_outline = state == QtCore.Qt.Checked
self.theme.font_main_outline = True
else:
self.theme.font_main_outline = False
self.outlineColorButton.setEnabled(self.theme.font_main_outline) self.outlineColorButton.setEnabled(self.theme.font_main_outline)
self.outlineSizeSpinBox.setEnabled(self.theme.font_main_outline) self.outlineSizeSpinBox.setEnabled(self.theme.font_main_outline)
self.calculateLines() self.calculateLines()
@ -350,19 +340,19 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
if self.theme.background_type == \ if self.theme.background_type == \
BackgroundType.to_string(BackgroundType.Solid): BackgroundType.to_string(BackgroundType.Solid):
self.colorButton.setStyleSheet(u'background-color: %s' % self.colorButton.setStyleSheet(u'background-color: %s' %
self.theme.background_color) self.theme.background_color)
self.setField(u'background_type', QtCore.QVariant(0)) self.setField(u'background_type', QtCore.QVariant(0))
elif self.theme.background_type == \ elif self.theme.background_type == \
BackgroundType.to_string(BackgroundType.Gradient): BackgroundType.to_string(BackgroundType.Gradient):
self.gradientStartButton.setStyleSheet(u'background-color: %s' % self.gradientStartButton.setStyleSheet(u'background-color: %s' %
self.theme.background_start_color) self.theme.background_start_color)
self.gradientEndButton.setStyleSheet(u'background-color: %s' % self.gradientEndButton.setStyleSheet(u'background-color: %s' %
self.theme.background_end_color) self.theme.background_end_color)
self.setField(u'background_type', QtCore.QVariant(1)) self.setField(u'background_type', QtCore.QVariant(1))
elif self.theme.background_type == \ elif self.theme.background_type == \
BackgroundType.to_string(BackgroundType.Image): BackgroundType.to_string(BackgroundType.Image):
self.imageColorButton.setStyleSheet(u'background-color: %s' % self.imageColorButton.setStyleSheet(u'background-color: %s' %
self.theme.background_border_color) self.theme.background_border_color)
self.imageFileEdit.setText(self.theme.background_filename) self.imageFileEdit.setText(self.theme.background_filename)
self.setField(u'background_type', QtCore.QVariant(2)) self.setField(u'background_type', QtCore.QVariant(2))
elif self.theme.background_type == \ elif self.theme.background_type == \
@ -642,8 +632,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
""" """
Handle Color buttons Handle Color buttons
""" """
new_color = QtGui.QColorDialog.getColor( new_color = QtGui.QColorDialog.getColor(QtGui.QColor(field), self)
QtGui.QColor(field), self)
if new_color.isValid(): if new_color.isValid():
field = new_color.name() field = new_color.name()
return field return field

View File

@ -53,6 +53,7 @@ class WizardStrings(object):
OL = u'OpenLyrics' OL = u'OpenLyrics'
OS = u'OpenSong' OS = u'OpenSong'
OSIS = u'OSIS' OSIS = u'OSIS'
PS = u'PowerSong 1.0'
SB = u'SongBeamer' SB = u'SongBeamer'
SoF = u'Songs of Fellowship' SoF = u'Songs of Fellowship'
SSP = u'SongShow Plus' SSP = u'SongShow Plus'
@ -71,11 +72,14 @@ class WizardStrings(object):
'importer, you will need to install the "python-sqlite" ' 'importer, you will need to install the "python-sqlite" '
'module.') 'module.')
OpenTypeFile = unicode(translate('OpenLP.Ui', 'Open %s File')) OpenTypeFile = unicode(translate('OpenLP.Ui', 'Open %s File'))
OpenTypeFolder = unicode(translate('OpenLP.Ui', 'Open %s Folder'))
PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%')) PercentSymbolFormat = unicode(translate('OpenLP.Ui', '%p%'))
Ready = translate('OpenLP.Ui', 'Ready.') Ready = translate('OpenLP.Ui', 'Ready.')
StartingImport = translate('OpenLP.Ui', 'Starting import...') StartingImport = translate('OpenLP.Ui', 'Starting import...')
YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at ' YouSpecifyFile = unicode(translate('OpenLP.Ui', 'You need to specify at '
'least one %s file to import from.', 'A file type e.g. OpenSong')) 'least one %s file to import from.', 'A file type e.g. OpenSong'))
YouSpecifyFolder = unicode(translate('OpenLP.Ui', 'You need to specify a '
'%s folder to import from.', 'A file type e.g. OpenSong'))
class OpenLPWizard(QtGui.QWizard): class OpenLPWizard(QtGui.QWizard):
@ -253,7 +257,7 @@ class OpenLPWizard(QtGui.QWizard):
The title of the dialog (unicode). The title of the dialog (unicode).
``editbox`` ``editbox``
A editbox (QLineEdit). An editbox (QLineEdit).
``filters`` ``filters``
The file extension filters. It should contain the file description The file extension filters. It should contain the file description
@ -264,11 +268,28 @@ class OpenLPWizard(QtGui.QWizard):
if filters: if filters:
filters += u';;' filters += u';;'
filters += u'%s (*)' % UiStrings().AllFiles filters += u'%s (*)' % UiStrings().AllFiles
filename = QtGui.QFileDialog.getOpenFileName(self, title, filename = unicode(QtGui.QFileDialog.getOpenFileName(self, title,
os.path.dirname(SettingsManager.get_last_dir( os.path.dirname(SettingsManager.get_last_dir(
self.plugin.settingsSection, 1)), filters) self.plugin.settingsSection, 1)), filters))
if filename: if filename:
editbox.setText(filename) editbox.setText(filename)
SettingsManager.set_last_dir(self.plugin.settingsSection, SettingsManager.set_last_dir(self.plugin.settingsSection,
filename, 1) filename, 1)
def getFolder(self, title, editbox):
"""
Opens a QFileDialog and saves the selected folder to the given editbox.
``title``
The title of the dialog (unicode).
``editbox``
An editbox (QLineEdit).
"""
folder = unicode(QtGui.QFileDialog.getExistingDirectory(self, title,
os.path.dirname(SettingsManager.get_last_dir(
self.plugin.settingsSection, 1)), QtGui.QFileDialog.ShowDirsOnly))
if folder:
editbox.setText(folder)
SettingsManager.set_last_dir(self.plugin.settingsSection,
folder, 1)

View File

@ -330,13 +330,7 @@ class BibleManager(object):
'Import Wizard to install one or more Bibles.') 'Import Wizard to install one or more Bibles.')
}) })
return None return None
language_selection = self.get_meta_data(bible, u'book_name_language') language_selection = self.get_language_selection(bible)
if language_selection:
language_selection = int(language_selection.value)
if language_selection is None or language_selection == -1:
language_selection = QtCore.QSettings().value(
self.settingsSection + u'/bookname language',
QtCore.QVariant(0)).toInt()[0]
reflist = parse_reference(versetext, self.db_cache[bible], reflist = parse_reference(versetext, self.db_cache[bible],
language_selection, book_ref_id) language_selection, book_ref_id)
if reflist: if reflist:
@ -378,12 +372,16 @@ class BibleManager(object):
""" """
log.debug(u'BibleManager.get_language_selection("%s")', bible) log.debug(u'BibleManager.get_language_selection("%s")', bible)
language_selection = self.get_meta_data(bible, u'book_name_language') language_selection = self.get_meta_data(bible, u'book_name_language')
if language_selection and language_selection.value != u'None': if language_selection:
return int(language_selection.value) try:
if language_selection is None or language_selection.value == u'None': language_selection = int(language_selection.value)
return QtCore.QSettings().value( except (ValueError, TypeError):
language_selection = LanguageSelection.Application
if language_selection is None or language_selection == -1:
language_selection = QtCore.QSettings().value(
self.settingsSection + u'/bookname language', self.settingsSection + u'/bookname language',
QtCore.QVariant(0)).toInt()[0] QtCore.QVariant(0)).toInt()[0]
return language_selection
def verse_search(self, bible, second_bible, text): def verse_search(self, bible, second_bible, text):
""" """

View File

@ -843,10 +843,11 @@ class BibleMediaItem(MediaManagerItem):
items = [] items = []
language_selection = self.plugin.manager.get_language_selection(bible) language_selection = self.plugin.manager.get_language_selection(bible)
for count, verse in enumerate(search_results): for count, verse in enumerate(search_results):
book = None
if language_selection == LanguageSelection.Bible: if language_selection == LanguageSelection.Bible:
book = verse.book.name book = verse.book.name
elif language_selection == LanguageSelection.Application: elif language_selection == LanguageSelection.Application:
book_names = BibleStrings().Booknames book_names = BibleStrings().BookNames
data = BiblesResourcesDB.get_book_by_id( data = BiblesResourcesDB.get_book_by_id(
verse.book.book_reference_id) verse.book.book_reference_id)
book = unicode(book_names[data[u'abbreviation']]) book = unicode(book_names[data[u'abbreviation']])

View File

@ -363,11 +363,5 @@ class SongExportForm(OpenLPWizard):
Called when the *directoryButton* was clicked. Opens a dialog and writes Called when the *directoryButton* was clicked. Opens a dialog and writes
the path to *directoryLineEdit*. the path to *directoryLineEdit*.
""" """
path = unicode(QtGui.QFileDialog.getExistingDirectory(self, self.getFolder(translate('SongsPlugin.ExportWizardForm',
translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), self.directoryLineEdit)
'Select Destination Folder'),
SettingsManager.get_last_dir(self.plugin.settingsSection, 1),
options=QtGui.QFileDialog.ShowDirsOnly))
SettingsManager.set_last_dir(self.plugin.settingsSection, path, 1)
self.directoryLineEdit.setText(path)

View File

@ -105,6 +105,9 @@ class SongImportForm(OpenLPWizard):
QtCore.QObject.connect(self.openLP1BrowseButton, QtCore.QObject.connect(self.openLP1BrowseButton,
QtCore.SIGNAL(u'clicked()'), QtCore.SIGNAL(u'clicked()'),
self.onOpenLP1BrowseButtonClicked) self.onOpenLP1BrowseButtonClicked)
QtCore.QObject.connect(self.powerSongBrowseButton,
QtCore.SIGNAL(u'clicked()'),
self.onPowerSongBrowseButtonClicked)
QtCore.QObject.connect(self.openLyricsAddButton, QtCore.QObject.connect(self.openLyricsAddButton,
QtCore.SIGNAL(u'clicked()'), QtCore.SIGNAL(u'clicked()'),
self.onOpenLyricsAddButtonClicked) self.onOpenLyricsAddButtonClicked)
@ -217,6 +220,8 @@ class SongImportForm(OpenLPWizard):
self.addFileSelectItem(u'foilPresenter') self.addFileSelectItem(u'foilPresenter')
# Open Song # Open Song
self.addFileSelectItem(u'openSong', u'OpenSong') self.addFileSelectItem(u'openSong', u'OpenSong')
# PowerSong
self.addFileSelectItem(u'powerSong', single_select=True)
# SongBeamer # SongBeamer
self.addFileSelectItem(u'songBeamer') self.addFileSelectItem(u'songBeamer')
# Song Show Plus # Song Show Plus
@ -264,6 +269,8 @@ class SongImportForm(OpenLPWizard):
self.formatComboBox.setItemText( self.formatComboBox.setItemText(
SongFormat.FoilPresenter, WizardStrings.FP) SongFormat.FoilPresenter, WizardStrings.FP)
self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS) self.formatComboBox.setItemText(SongFormat.OpenSong, WizardStrings.OS)
self.formatComboBox.setItemText(
SongFormat.PowerSong, WizardStrings.PS)
self.formatComboBox.setItemText( self.formatComboBox.setItemText(
SongFormat.SongBeamer, WizardStrings.SB) SongFormat.SongBeamer, WizardStrings.SB)
self.formatComboBox.setItemText( self.formatComboBox.setItemText(
@ -280,6 +287,9 @@ class SongImportForm(OpenLPWizard):
translate('SongsPlugin.ImportWizardForm', 'Filename:')) translate('SongsPlugin.ImportWizardForm', 'Filename:'))
self.openLP1BrowseButton.setText(UiStrings().Browse) self.openLP1BrowseButton.setText(UiStrings().Browse)
self.openLP1DisabledLabel.setText(WizardStrings.NoSqlite) self.openLP1DisabledLabel.setText(WizardStrings.NoSqlite)
self.powerSongFilenameLabel.setText(
translate('SongsPlugin.ImportWizardForm', 'Folder:'))
self.powerSongBrowseButton.setText(UiStrings().Browse)
self.openLyricsAddButton.setText( self.openLyricsAddButton.setText(
translate('SongsPlugin.ImportWizardForm', 'Add Files...')) translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
self.openLyricsRemoveButton.setText( self.openLyricsRemoveButton.setText(
@ -375,6 +385,7 @@ class SongImportForm(OpenLPWizard):
source_format = self.formatComboBox.currentIndex() source_format = self.formatComboBox.currentIndex()
QtCore.QSettings().setValue(u'songs/last import type', QtCore.QSettings().setValue(u'songs/last import type',
source_format) source_format)
import_class = SongFormat.get_class(source_format)
if source_format == SongFormat.OpenLP2: if source_format == SongFormat.OpenLP2:
if self.openLP2FilenameEdit.text().isEmpty(): if self.openLP2FilenameEdit.text().isEmpty():
critical_error_message_box(UiStrings().NFSs, critical_error_message_box(UiStrings().NFSs,
@ -387,6 +398,14 @@ class SongImportForm(OpenLPWizard):
WizardStrings.YouSpecifyFile % UiStrings().OLPV1) WizardStrings.YouSpecifyFile % UiStrings().OLPV1)
self.openLP1BrowseButton.setFocus() self.openLP1BrowseButton.setFocus()
return False return False
elif source_format == SongFormat.PowerSong:
if self.powerSongFilenameEdit.text().isEmpty() or \
not import_class.isValidSource(
folder=self.powerSongFilenameEdit.text()):
critical_error_message_box(UiStrings().NFdSs,
WizardStrings.YouSpecifyFolder % WizardStrings.PS)
self.powerSongBrowseButton.setFocus()
return False
elif source_format == SongFormat.OpenLyrics: elif source_format == SongFormat.OpenLyrics:
if self.openLyricsFileListWidget.count() == 0: if self.openLyricsFileListWidget.count() == 0:
critical_error_message_box(UiStrings().NFSp, critical_error_message_box(UiStrings().NFSp,
@ -526,6 +545,13 @@ class SongImportForm(OpenLPWizard):
'openlp.org v1.x Databases') 'openlp.org v1.x Databases')
) )
def onPowerSongBrowseButtonClicked(self):
"""
Get PowerSong song database folder
"""
self.getFolder(WizardStrings.OpenTypeFolder % WizardStrings.PS,
self.powerSongFilenameEdit)
def onOpenLyricsAddButtonClicked(self): def onOpenLyricsAddButtonClicked(self):
""" """
Get OpenLyrics song database files Get OpenLyrics song database files
@ -712,6 +738,7 @@ class SongImportForm(OpenLPWizard):
self.formatComboBox.setCurrentIndex(last_import_type) self.formatComboBox.setCurrentIndex(last_import_type)
self.openLP2FilenameEdit.setText(u'') self.openLP2FilenameEdit.setText(u'')
self.openLP1FilenameEdit.setText(u'') self.openLP1FilenameEdit.setText(u'')
self.powerSongFilenameEdit.setText(u'')
self.openLyricsFileListWidget.clear() self.openLyricsFileListWidget.clear()
self.openSongFileListWidget.clear() self.openSongFileListWidget.clear()
self.wordsOfWorshipFileListWidget.clear() self.wordsOfWorshipFileListWidget.clear()
@ -757,6 +784,11 @@ class SongImportForm(OpenLPWizard):
filename=unicode(self.openLP1FilenameEdit.text()), filename=unicode(self.openLP1FilenameEdit.text()),
plugin=self.plugin plugin=self.plugin
) )
elif source_format == SongFormat.PowerSong:
# Import PowerSong folder
importer = self.plugin.importSongs(SongFormat.PowerSong,
folder=unicode(self.powerSongFilenameEdit.text())
)
elif source_format == SongFormat.OpenLyrics: elif source_format == SongFormat.OpenLyrics:
# Import OpenLyrics songs # Import OpenLyrics songs
importer = self.plugin.importSongs(SongFormat.OpenLyrics, importer = self.plugin.importSongs(SongFormat.OpenLyrics,
@ -821,11 +853,7 @@ class SongImportForm(OpenLPWizard):
filenames=self.getListOfFiles(self.foilPresenterFileListWidget) filenames=self.getListOfFiles(self.foilPresenterFileListWidget)
) )
importer.doImport() importer.doImport()
if importer.errorLog: self.progressLabel.setText(WizardStrings.FinishedImport)
self.progressLabel.setText(translate(
'SongsPlugin.SongImportForm', 'Your song import failed.'))
else:
self.progressLabel.setText(WizardStrings.FinishedImport)
def onErrorCopyToButtonClicked(self): def onErrorCopyToButtonClicked(self):
""" """

View File

@ -36,6 +36,7 @@ from openlyricsimport import OpenLyricsImport
from wowimport import WowImport from wowimport import WowImport
from cclifileimport import CCLIFileImport from cclifileimport import CCLIFileImport
from dreambeamimport import DreamBeamImport from dreambeamimport import DreamBeamImport
from powersongimport import PowerSongImport
from ewimport import EasyWorshipSongImport from ewimport import EasyWorshipSongImport
from songbeamerimport import SongBeamerImport from songbeamerimport import SongBeamerImport
from songshowplusimport import SongShowPlusImport from songshowplusimport import SongShowPlusImport
@ -79,16 +80,17 @@ class SongFormat(object):
EasyWorship = 7 EasyWorship = 7
FoilPresenter = 8 FoilPresenter = 8
OpenSong = 9 OpenSong = 9
SongBeamer = 10 PowerSong = 10
SongShowPlus = 11 SongBeamer = 11
SongsOfFellowship = 12 SongShowPlus = 12
WordsOfWorship = 13 SongsOfFellowship = 13
#CSV = 14 WordsOfWorship = 14
#CSV = 15
@staticmethod @staticmethod
def get_class(format): def get_class(format):
""" """
Return the appropriate imeplementation class. Return the appropriate implementation class.
``format`` ``format``
The song format. The song format.
@ -111,6 +113,8 @@ class SongFormat(object):
return CCLIFileImport return CCLIFileImport
elif format == SongFormat.DreamBeam: elif format == SongFormat.DreamBeam:
return DreamBeamImport return DreamBeamImport
elif format == SongFormat.PowerSong:
return PowerSongImport
elif format == SongFormat.EasySlides: elif format == SongFormat.EasySlides:
return EasySlidesImport return EasySlidesImport
elif format == SongFormat.EasyWorship: elif format == SongFormat.EasyWorship:
@ -139,6 +143,7 @@ class SongFormat(object):
SongFormat.EasyWorship, SongFormat.EasyWorship,
SongFormat.FoilPresenter, SongFormat.FoilPresenter,
SongFormat.OpenSong, SongFormat.OpenSong,
SongFormat.PowerSong,
SongFormat.SongBeamer, SongFormat.SongBeamer,
SongFormat.SongShowPlus, SongFormat.SongShowPlus,
SongFormat.SongsOfFellowship, SongFormat.SongsOfFellowship,

View File

@ -0,0 +1,225 @@
# -*- 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, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`powersongimport` module provides the functionality for importing
PowerSong songs into the OpenLP database.
"""
import logging
import fnmatch
import os
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class PowerSongImport(SongImport):
"""
The :class:`PowerSongImport` class provides the ability to import song files
from PowerSong.
**PowerSong 1.0 Song File Format:**
The file has a number of label-field (think key-value) pairs.
Label and Field strings:
* Every label and field is a variable length string preceded by an
integer specifying it's byte length.
* Integer is 32-bit but is encoded in 7-bit format to save space. Thus
if length will fit in 7 bits (ie <= 127) it takes up only one byte.
Metadata fields:
* Every PowerSong file has a TITLE field.
* There is zero or more AUTHOR fields.
* There is always a COPYRIGHTLINE label, but its field may be empty.
This field may also contain a CCLI number: e.g. "CCLI 176263".
Lyrics fields:
* Each verse is contained in a PART field.
* Lines have Windows line endings ``CRLF`` (0x0d, 0x0a).
* There is no concept of verse types.
Valid extensions for a PowerSong song file are:
* .song
"""
@staticmethod
def isValidSource(**kwargs):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
* contains at least one *.song file
"""
if u'folder' in kwargs:
dir = kwargs[u'folder']
if os.path.isdir(dir):
for file in os.listdir(dir):
if fnmatch.fnmatch(file, u'*.song'):
return True
return False
def doImport(self):
"""
Receive either a list of files or a folder (unicode) to import.
"""
if isinstance(self.importSource, unicode):
if os.path.isdir(self.importSource):
dir = self.importSource
self.importSource = []
for file in os.listdir(dir):
if fnmatch.fnmatch(file, u'*.song'):
self.importSource.append(os.path.join(dir, file))
else:
self.importSource = u''
if not self.importSource or not isinstance(self.importSource, list):
self.logError(unicode(translate('SongsPlugin.PowerSongImport',
'No songs to import.')),
unicode(translate('SongsPlugin.PowerSongImport',
'No %s files found.' % WizardStrings.PS)))
return
self.importWizard.progressBar.setMaximum(len(self.importSource))
for file in self.importSource:
if self.stopImportFlag:
return
self.setDefaults()
parse_error = False
with open(file, 'rb') as song_data:
while True:
try:
label = self._readString(song_data)
if not label:
break
field = self._readString(song_data)
except ValueError:
parse_error = True
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Unexpected byte value.'
% WizardStrings.PS)))
break
else:
if label == u'TITLE':
self.title = field.replace(u'\n', u' ')
elif label == u'AUTHOR':
self.parseAuthor(field)
elif label == u'COPYRIGHTLINE':
found_copyright = True
self._parseCopyrightCCLI(field)
elif label == u'PART':
self.addVerse(field)
if parse_error:
continue
# Check that file had TITLE field
if not self.title:
self.logError(os.path.basename(file), unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "TITLE" header.'
% WizardStrings.PS)))
continue
# Check that file had COPYRIGHTLINE label
if not found_copyright:
self.logError(self.title, unicode(
translate('SongsPlugin.PowerSongImport',
'Invalid %s file. Missing "COPYRIGHTLINE" '
'header.' % WizardStrings.PS)))
continue
# Check that file had at least one verse
if not self.verses:
self.logError(self.title, unicode(
translate('SongsPlugin.PowerSongImport',
'Verses not found. Missing "PART" header.')))
continue
if not self.finish():
self.logError(self.title)
def _readString(self, file_object):
"""
Reads in next variable-length string.
"""
string_len = self._read7BitEncodedInteger(file_object)
return unicode(file_object.read(string_len), u'utf-8', u'ignore')
def _read7BitEncodedInteger(self, file_object):
"""
Reads in a 32-bit integer in compressed 7-bit format.
Accomplished by reading the integer 7 bits at a time. The high bit
of the byte when set means to continue reading more bytes.
If the integer will fit in 7 bits (ie <= 127), it only takes up one
byte. Otherwise, it may take up to 5 bytes.
Reference: .NET method System.IO.BinaryReader.Read7BitEncodedInt
"""
val = 0
shift = 0
i = 0
while True:
# Check for corrupted stream (since max 5 bytes per 32-bit integer)
if i == 5:
raise ValueError
byte = self._readByte(file_object)
# Strip high bit and shift left
val += (byte & 0x7f) << shift
shift += 7
high_bit_set = byte & 0x80
if not high_bit_set:
break
i += 1
return val
def _readByte(self, file_object):
"""
Reads in next byte as an unsigned integer
Note: returns 0 at end of file.
"""
byte_str = file_object.read(1)
# If read result is empty, then reached end of file
if not byte_str:
return 0
else:
return ord(byte_str)
def _parseCopyrightCCLI(self, field):
"""
Look for CCLI song number, and get copyright
"""
copyright, sep, ccli_no = field.rpartition(u'CCLI')
if not sep:
copyright = ccli_no
ccli_no = u''
if copyright:
self.addCopyright(copyright.rstrip(u'\n').replace(u'\n', u' '))
if ccli_no:
ccli_no = ccli_no.strip(u' :')
if ccli_no.isdigit():
self.ccliNumber = ccli_no

View File

@ -50,6 +50,13 @@ class SongImport(QtCore.QObject):
whether the authors etc already exist and add them or refer to them whether the authors etc already exist and add them or refer to them
as necessary as necessary
""" """
@staticmethod
def isValidSource(**kwargs):
"""
Override this method to validate the source prior to import.
"""
pass
def __init__(self, manager, **kwargs): def __init__(self, manager, **kwargs):
""" """
Initialise and create defaults for properties Initialise and create defaults for properties
@ -65,14 +72,16 @@ class SongImport(QtCore.QObject):
self.importSource = kwargs[u'filename'] self.importSource = kwargs[u'filename']
elif u'filenames' in kwargs: elif u'filenames' in kwargs:
self.importSource = kwargs[u'filenames'] self.importSource = kwargs[u'filenames']
elif u'folder' in kwargs:
self.importSource = kwargs[u'folder']
else: else:
raise KeyError(u'Keyword arguments "filename[s]" not supplied.') raise KeyError(
u'Keyword arguments "filename[s]" or "folder" not supplied.')
log.debug(self.importSource) log.debug(self.importSource)
self.importWizard = None self.importWizard = None
self.song = None self.song = None
self.stopImportFlag = False self.stopImportFlag = False
self.setDefaults() self.setDefaults()
self.errorLog = []
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_stop_wizard'), self.stopImport) QtCore.SIGNAL(u'openlp_stop_wizard'), self.stopImport)
@ -107,11 +116,11 @@ class SongImport(QtCore.QObject):
``filepath`` ``filepath``
This should be the file path if ``self.importSource`` is a list This should be the file path if ``self.importSource`` is a list
with different files. If it is not a list, but a single file (for with different files. If it is not a list, but a single file (for
instance a database), then this should be the song's title. instance a database), then this should be the song's title.
``reason`` ``reason``
The reason, why the import failed. The string should be as The reason why the import failed. The string should be as
informative as possible. informative as possible.
""" """
self.setDefaults() self.setDefaults()

View File

@ -71,7 +71,7 @@ class WowImport(SongImport):
* ``SOH`` (0x01) - Chorus * ``SOH`` (0x01) - Chorus
* ``STX`` (0x02) - Bridge * ``STX`` (0x02) - Bridge
Blocks are seperated by two bytes. The first byte is 0x01, and the Blocks are separated by two bytes. The first byte is 0x01, and the
second byte is 0x80. second byte is 0x80.
Lines: Lines:
@ -126,7 +126,7 @@ class WowImport(SongImport):
('Invalid Words of Worship song file. Missing ' ('Invalid Words of Worship song file. Missing '
'"CSongDoc::CBlock" string.')))) '"CSongDoc::CBlock" string.'))))
continue continue
# Seek to the beging of the first block # Seek to the beginning of the first block
song_data.seek(82) song_data.seek(82)
for block in range(no_of_blocks): for block in range(no_of_blocks):
self.linesToRead = ord(song_data.read(4)[:1]) self.linesToRead = ord(song_data.read(4)[:1])
@ -140,7 +140,7 @@ class WowImport(SongImport):
block_text += self.lineText block_text += self.lineText
self.linesToRead -= 1 self.linesToRead -= 1
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])] block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
# Blocks are seperated by 2 bytes, skip them, but not if # Blocks are separated by 2 bytes, skip them, but not if
# this is the last block! # this is the last block!
if block + 1 < no_of_blocks: if block + 1 < no_of_blocks:
song_data.seek(2, os.SEEK_CUR) song_data.seek(2, os.SEEK_CUR)

View File

@ -33,7 +33,7 @@ The basic XML for storing the lyrics in the song database looks like this::
<song version="1.0"> <song version="1.0">
<lyrics> <lyrics>
<verse type="c" label="1" lang="en"> <verse type="c" label="1" lang="en">
<![CDATA[Chorus virtual slide 1[---]Chorus virtual slide 2]]> <![CDATA[Chorus optional split 1[---]Chorus optional split 2]]>
</verse> </verse>
</lyrics> </lyrics>
</song> </song>
@ -135,7 +135,7 @@ class SongXML(object):
The returned list has the following format:: The returned list has the following format::
[[{'type': 'v', 'label': '1'}, [[{'type': 'v', 'label': '1'},
u"virtual slide 1[---]virtual slide 2"], u"optional slide split 1[---]optional slide split 2"],
[{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]] [{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]]
""" """
self.song_xml = None self.song_xml = None
@ -317,9 +317,7 @@ class OpenLyrics(object):
tags_element = None tags_element = None
match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE) match = re.search(u'\{/?\w+\}', song.lyrics, re.UNICODE)
if match: if match:
# Reset available tags. # Named 'format_' - 'format' is built-in fuction in Python.
FormattingTags.reset_html_tags()
# Named 'formatting' - 'format' is built-in fuction in Python.
format_ = etree.SubElement(song_xml, u'format') format_ = etree.SubElement(song_xml, u'format')
tags_element = etree.SubElement(format_, u'tags') tags_element = etree.SubElement(format_, u'tags')
tags_element.set(u'application', u'OpenLP') tags_element.set(u'application', u'OpenLP')
@ -334,18 +332,59 @@ class OpenLyrics(object):
self._add_text_to_element(u'verse', lyrics, None, verse_def) self._add_text_to_element(u'verse', lyrics, None, verse_def)
if u'lang' in verse[0]: if u'lang' in verse[0]:
verse_element.set(u'lang', verse[0][u'lang']) verse_element.set(u'lang', verse[0][u'lang'])
# Create a list with all "virtual" verses. # Create a list with all "optional" verses.
virtual_verses = cgi.escape(verse[1]) optional_verses = cgi.escape(verse[1])
virtual_verses = virtual_verses.split(u'[---]') optional_verses = optional_verses.split(u'\n[---]\n')
for index, virtual_verse in enumerate(virtual_verses): start_tags = u''
end_tags = u''
for index, optional_verse in enumerate(optional_verses):
# Fix up missing end and start tags such as {r} or {/r}.
optional_verse = start_tags + optional_verse
start_tags, end_tags = self._get_missing_tags(optional_verse)
optional_verse += end_tags
# Add formatting tags to text # Add formatting tags to text
lines_element = self._add_text_with_tags_to_lines(verse_element, lines_element = self._add_text_with_tags_to_lines(verse_element,
virtual_verse, tags_element) optional_verse, tags_element)
# Do not add the break attribute to the last lines element. # Do not add the break attribute to the last lines element.
if index < len(virtual_verses) - 1: if index < len(optional_verses) - 1:
lines_element.set(u'break', u'optional') lines_element.set(u'break', u'optional')
return self._extract_xml(song_xml) return self._extract_xml(song_xml)
def _get_missing_tags(self, text):
"""
Tests the given text for not closed formatting tags and returns a tuple
consisting of two unicode strings::
(u'{st}{r}', u'{/r}{/st}')
The first unicode string are the start tags (for the next slide). The
second unicode string are the end tags.
``text``
The text to test. The text must **not** contain html tags, only
OpenLP formatting tags are allowed::
{st}{r}Text text text
"""
tags = []
for tag in FormattingTags.get_html_tags():
if tag[u'start tag'] == u'{br}':
continue
if text.count(tag[u'start tag']) != text.count(tag[u'end tag']):
tags.append((text.find(tag[u'start tag']),
tag[u'start tag'], tag[u'end tag']))
# Sort the lists, so that the tags which were opened first on the first
# slide (the text we are checking) will be opened first on the next
# slide as well.
tags.sort(key=lambda tag: tag[0])
end_tags = []
start_tags = []
for tag in tags:
start_tags.append(tag[1])
end_tags.append(tag[2])
end_tags.reverse()
return u''.join(start_tags), u''.join(end_tags)
def xml_to_song(self, xml, parse_and_temporary_save=False): def xml_to_song(self, xml, parse_and_temporary_save=False):
""" """
Create and save a song from OpenLyrics format xml to the database. Since Create and save a song from OpenLyrics format xml to the database. Since
@ -572,7 +611,8 @@ class OpenLyrics(object):
for tag in FormattingTags.get_html_tags()] for tag in FormattingTags.get_html_tags()]
new_tags = [tag for tag in found_tags new_tags = [tag for tag in found_tags
if tag[u'start tag'] not in existing_tag_ids] if tag[u'start tag'] not in existing_tag_ids]
FormattingTags.add_html_tags(new_tags, True) FormattingTags.add_html_tags(new_tags)
FormattingTags.save_html_tags()
def _process_lines_mixed_content(self, element, newlines=True): def _process_lines_mixed_content(self, element, newlines=True):
""" """