diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index dc4c5170e..d0a5e9880 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -141,10 +141,21 @@ class Settings(QtCore.QSettings): if defaultValue is None and not super(Settings, self).contains(key): return None setting = super(Settings, self).value(key, defaultValue) - # An empty list saved to the settings results in a None type being - # returned. + # On OS X (and probably on other platforms too) empty value from QSettings + # is represented as type PyQt4.QtCore.QPyNullVariant. This type has to be + # converted to proper 'None' Python type. + if isinstance(setting, QtCore.QPyNullVariant) and setting.isNull(): + setting = None + # Handle 'None' type (empty value) properly. if setting is None: - return [] + # An empty string saved to the settings results in a None type being + # returned. Convert it to empty unicode string. + if isinstance(defaultValue, unicode): + return u'' + # An empty list saved to the settings results in a None type being + # returned. + else: + return [] # Convert the setting to the correct type. if isinstance(defaultValue, bool): if isinstance(setting, bool): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e6402d1c3..7da61dc63 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -40,7 +40,7 @@ log = logging.getLogger(__name__) from PyQt4 import QtCore, QtGui from openlp.core.lib import OpenLPToolbar, ServiceItem, Receiver, build_icon, ItemCapabilities, SettingsManager, \ - translate, str_to_bool, check_directory_exists, Settings + translate, str_to_bool, check_directory_exists, Settings, PluginStatus from openlp.core.lib.theme import ThemeLevel from openlp.core.lib.ui import UiStrings, critical_error_message_box, create_widget_action, find_and_set_in_combo_box from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm @@ -252,6 +252,9 @@ class ServiceManager(QtGui.QWidget): icon=u':/media/auto-start_active.png', triggers=self.onAutoStart) # Add already existing delete action to the menu. self.menu.addAction(self.serviceManagerList.delete) + self.create_custom_action = create_widget_action(self.menu, + text=translate('OpenLP.ServiceManager', 'Create New &Custom Slide'), + icon=u':/general/general_edit.png', triggers=self.create_custom) self.menu.addSeparator() self.previewAction = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'), icon=u':/general/general_preview.png', triggers=self.makePreview) @@ -751,6 +754,7 @@ class ServiceManager(QtGui.QWidget): pos = item.data(0, QtCore.Qt.UserRole) serviceItem = self.serviceItems[pos - 1] self.editAction.setVisible(False) + self.create_custom_action.setVisible(False) self.maintainAction.setVisible(False) self.notesAction.setVisible(False) self.timeAction.setVisible(False) @@ -770,6 +774,11 @@ class ServiceManager(QtGui.QWidget): if serviceItem[u'service_item'].will_auto_start: self.autoStartAction.setText(translate('OpenLP.ServiceManager', '&Auto Start - active')) self.autoStartAction.setIcon(self.active) + if serviceItem[u'service_item'].is_text(): + for plugin in self.mainwindow.pluginManager.plugins: + if plugin.name == u'custom' and plugin.status == PluginStatus.Active: + self.create_custom_action.setVisible(True) + break self.themeMenu.menuAction().setVisible(False) # Set up the theme menu. if serviceItem[u'service_item'].is_text() and self.mainwindow.renderer.theme_level == ThemeLevel.Song: @@ -1306,6 +1315,13 @@ class ServiceManager(QtGui.QWidget): Receiver.send_message(u'%s_edit' % self.serviceItems[item][u'service_item'].name.lower(), u'L:%s' % self.serviceItems[item][u'service_item'].edit_id) + def create_custom(self): + """ + Saves the current text item as a custom slide + """ + item = self.findServiceItem()[0] + Receiver.send_message(u'custom_create_from_service', self.serviceItems[item][u'service_item']) + def findServiceItem(self): """ Finds the first selected ServiceItem in the list and returns the diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 037dd0aad..85210ba3c 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -125,8 +125,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): if not self._validate(): return False sxml = CustomXMLBuilder() - sxml.new_document() - sxml.add_lyrics_to_song() for count in range(self.slideListView.count()): sxml.add_verse_to_lyrics(u'custom', unicode(count + 1), self.slideListView.item(count).text()) diff --git a/openlp/plugins/custom/lib/customtab.py b/openlp/plugins/custom/lib/customtab.py index 69bde58e7..ee528e446 100644 --- a/openlp/plugins/custom/lib/customtab.py +++ b/openlp/plugins/custom/lib/customtab.py @@ -48,18 +48,25 @@ class CustomTab(SettingsTab): self.displayFooterCheckBox = QtGui.QCheckBox(self.customModeGroupBox) self.displayFooterCheckBox.setObjectName(u'displayFooterCheckBox') self.customModeLayout.addRow(self.displayFooterCheckBox) + self.add_from_service_checkbox = QtGui.QCheckBox(self.customModeGroupBox) + self.add_from_service_checkbox.setObjectName(u'add_from_service_checkbox') + self.customModeLayout.addRow(self.add_from_service_checkbox) self.leftLayout.addWidget(self.customModeGroupBox) self.leftLayout.addStretch() self.rightLayout.addStretch() QtCore.QObject.connect(self.displayFooterCheckBox, QtCore.SIGNAL(u'stateChanged(int)'), self.onDisplayFooterCheckBoxChanged) + QtCore.QObject.connect(self.add_from_service_checkbox, + QtCore.SIGNAL(u'stateChanged(int)'), self.on_add_from_service_check_box_changed) def retranslateUi(self): self.customModeGroupBox.setTitle(translate('CustomPlugin.CustomTab', 'Custom Display')) self.displayFooterCheckBox.setText( translate('CustomPlugin.CustomTab', 'Display footer')) + self.add_from_service_checkbox.setText(translate('CustomPlugin.CustomTab', + 'Import missing custom slides from service files')) def onDisplayFooterCheckBoxChanged(self, check_state): self.displayFooter = False @@ -67,11 +74,21 @@ class CustomTab(SettingsTab): if check_state == QtCore.Qt.Checked: self.displayFooter = True + def on_add_from_service_check_box_changed(self, check_state): + self.update_load = (check_state == QtCore.Qt.Checked) + def load(self): - self.displayFooter = Settings().value( - self.settingsSection + u'/display footer', True) + settings = Settings() + settings.beginGroup(self.settingsSection) + self.displayFooter = settings.value(u'display footer', True) + self.update_load = settings.value(u'add custom from service', True) self.displayFooterCheckBox.setChecked(self.displayFooter) + self.add_from_service_checkbox.setChecked(self.update_load) + settings.endGroup() def save(self): - Settings().setValue(self.settingsSection + u'/display footer', - self.displayFooter) + settings = Settings() + settings.beginGroup(self.settingsSection) + settings.setValue(u'display footer', self.displayFooter) + settings.setValue(u'add custom from service', self.update_load) + settings.endGroup() diff --git a/openlp/plugins/custom/lib/customxmlhandler.py b/openlp/plugins/custom/lib/customxmlhandler.py index dccfcf7ba..55a45d79b 100644 --- a/openlp/plugins/custom/lib/customxmlhandler.py +++ b/openlp/plugins/custom/lib/customxmlhandler.py @@ -62,6 +62,8 @@ class CustomXMLBuilder(object): """ # Create the minidom document self.custom_xml = Document() + self.new_document() + self.add_lyrics_to_song() def new_document(self): """ diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 816ae2158..9a134ad5d 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -30,13 +30,13 @@ import logging from PyQt4 import QtCore, QtGui -from sqlalchemy.sql import or_, func +from sqlalchemy.sql import or_, func, and_ -from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ - check_item_selected, translate, ServiceItemContext, Settings +from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, check_item_selected, translate, \ + ServiceItemContext, Settings, PluginStatus from openlp.core.lib.ui import UiStrings from openlp.plugins.custom.forms import EditCustomForm -from openlp.plugins.custom.lib import CustomXMLParser +from openlp.plugins.custom.lib import CustomXMLParser, CustomXMLBuilder from openlp.plugins.custom.lib.db import CustomSlide log = logging.getLogger(__name__) @@ -85,6 +85,12 @@ class CustomMediaItem(MediaManagerItem): QtCore.SIGNAL(u'custom_load_list'), self.loadList) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'custom_preview'), self.onPreviewClick) + QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_updated'), self.config_updated) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'custom_create_from_service'), self.create_from_service_item) + + def config_updated(self): + self.add_custom_from_service = Settings().value(self.settingsSection + u'/add custom from service', True) def retranslateUi(self): self.searchTextLabel.setText(u'%s:' % UiStrings().Search) @@ -102,6 +108,7 @@ class CustomMediaItem(MediaManagerItem): CustomSlide, order_by_ref=CustomSlide.title)) self.searchTextEdit.setCurrentSearchType(Settings().value( u'%s/last search type' % self.settingsSection, CustomSearch.Titles)) + self.config_updated() def loadList(self, custom_slides): # Sort out what custom we want to select after loading the list. @@ -198,6 +205,7 @@ class CustomMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanSoftBreak) + service_item.add_capability(ItemCapabilities.OnLoadUpdate) customSlide = self.plugin.manager.get_object(CustomSlide, item_id) title = customSlide.title credit = customSlide.credits @@ -252,6 +260,49 @@ class CustomMediaItem(MediaManagerItem): elif not text: self.onClearTextButtonClick() + def serviceLoad(self, item): + """ + Triggered by a song being loaded by the service manager. + """ + log.debug(u'serviceLoad') + if self.plugin.status != PluginStatus.Active: + return + custom = self.plugin.manager.get_object_filtered(CustomSlide, + and_(CustomSlide.title == item.title, CustomSlide.theme_name == item.theme, + CustomSlide.credits == item.raw_footer[0][len(item.title) + 1:])) + if custom: + Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item._uuid, False)) + else: + if self.add_custom_from_service: + self.create_from_service_item(item) + + def create_from_service_item(self, item): + """ + Create a custom slide from a text service item + """ + custom = CustomSlide() + custom.title = item.title + if item.theme: + custom.theme_name = item.theme + else: + custom.theme_name = u'' + footer = u' '.join(item.raw_footer) + if footer: + if footer.startswith(item.title): + custom.credits = footer[len(item.title) + 1:] + else: + custom.credits = footer + else: + custom.credits = u'' + custom_xml = CustomXMLBuilder() + for (idx, slide) in enumerate(item._raw_frames): + custom_xml.add_verse_to_lyrics(u'custom', unicode(idx + 1), slide['raw_slide']) + custom.text = unicode(custom_xml.extract_xml(), u'utf-8') + self.plugin.manager.save_object(custom) + self.onSearchTextButtonClicked() + if item.name.lower() == u'custom': + Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item._uuid, False)) + def onClearTextButtonClick(self): """ Clear the search text. diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index aa2c8928f..c317a2c52 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -93,6 +93,12 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.verseNumberBox.value()) def onVerseTypeComboBoxChanged(self): + self.updateSuggestedVerseNumber() + + def onCursorPositionChanged(self): + self.updateSuggestedVerseNumber() + + def updateSuggestedVerseNumber(self): """ Adjusts the verse number SpinBox in regard to the selected verse type and the cursor's position. @@ -116,43 +122,10 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): if match: verse_tag = match.group(1) try: - verse_num = int(match.group(2)) + verse_num = int(match.group(2)) + 1 except ValueError: verse_num = 1 - if VerseType.from_loose_input(verse_tag, False): - self.verseNumberBox.setValue(verse_num) - - def onCursorPositionChanged(self): - """ - Determines the previous verse type and number in regard to the cursor's - position and adjusts the ComboBox and SpinBox to these values. - """ - position = self.verseTextEdit.textCursor().position() - text = self.verseTextEdit.toPlainText() - if not text: - return - if text.rfind(u'[', 0, position) > text.rfind(u']', 0, position) and \ - text.find(u']', position) < text.find(u'[', position): - return - position = text.rfind(u'---[', 0, position) - if position == -1: - return - text = text[position:] - position = text.find(u']---') - if position == -1: - return - text = text[:position + 4] - match = VERSE_REGEX.match(text) - if match: - verse_type = match.group(1) - verse_type_index = VerseType.from_loose_input(verse_type, None) - try: - verse_number = int(match.group(2)) - except ValueError: - verse_number = 1 - if verse_type_index is not None: - self.verseTypeComboBox.setCurrentIndex(verse_type_index) - self.verseNumberBox.setValue(verse_number) + self.verseNumberBox.setValue(verse_num) def setVerse(self, text, single=False, tag=u'%s1' % VerseType.Tags[VerseType.Verse]):