diff --git a/documentation/manual/source/dualmonitors.rst b/documentation/manual/source/dualmonitors.rst index 3e1a8d210..7e39edb45 100644 --- a/documentation/manual/source/dualmonitors.rst +++ b/documentation/manual/source/dualmonitors.rst @@ -1,3 +1,5 @@ +.. _dualmonitors: + ================== Dual Monitor Setup ================== @@ -32,6 +34,14 @@ projector hooked up to your computer as the second monitor. With the option of extending your desktop across the second monitor, or your operating system's equivalent. +**Special Note For Projectors Using USB Connections** + +Users have reported experiencing difficulties when using a projector with a USB +connection, as third party software is often required to properly configure +dual monitors. If possible, it is best to use a direct output (VGA, DVI, HDMI, +S-Video) from your machine's video card. If a USB connection is your only option +please consult the manufacturer's manual for instructions on a proper setup. + Microsoft Windows ----------------- diff --git a/documentation/manual/source/pics/configureadvanced.png b/documentation/manual/source/pics/configureadvanced.png index f31812f93..44908e828 100644 Binary files a/documentation/manual/source/pics/configureadvanced.png and b/documentation/manual/source/pics/configureadvanced.png differ diff --git a/documentation/manual/source/pics/configurealerts.png b/documentation/manual/source/pics/configurealerts.png index ac46801dc..a15de9e5d 100644 Binary files a/documentation/manual/source/pics/configurealerts.png and b/documentation/manual/source/pics/configurealerts.png differ diff --git a/documentation/manual/source/pics/configurebibles.png b/documentation/manual/source/pics/configurebibles.png index cd44ee4af..ab8bc1930 100644 Binary files a/documentation/manual/source/pics/configurebibles.png and b/documentation/manual/source/pics/configurebibles.png differ diff --git a/documentation/manual/source/pics/configurecustom.png b/documentation/manual/source/pics/configurecustom.png index 3db5c4989..33514de18 100644 Binary files a/documentation/manual/source/pics/configurecustom.png and b/documentation/manual/source/pics/configurecustom.png differ diff --git a/documentation/manual/source/pics/configuregeneral.png b/documentation/manual/source/pics/configuregeneral.png index b1bae76e0..18a89474e 100644 Binary files a/documentation/manual/source/pics/configuregeneral.png and b/documentation/manual/source/pics/configuregeneral.png differ diff --git a/documentation/manual/source/pics/configuremedia.png b/documentation/manual/source/pics/configuremedia.png index cc9e11a38..9179873ea 100644 Binary files a/documentation/manual/source/pics/configuremedia.png and b/documentation/manual/source/pics/configuremedia.png differ diff --git a/documentation/manual/source/pics/configurepresentations.png b/documentation/manual/source/pics/configurepresentations.png index 4960aa8e5..4f8a98bc7 100644 Binary files a/documentation/manual/source/pics/configurepresentations.png and b/documentation/manual/source/pics/configurepresentations.png differ diff --git a/documentation/manual/source/pics/configureremotes.png b/documentation/manual/source/pics/configureremotes.png index 9eb22131c..d8801aa78 100644 Binary files a/documentation/manual/source/pics/configureremotes.png and b/documentation/manual/source/pics/configureremotes.png differ diff --git a/documentation/manual/source/pics/configuresongs.png b/documentation/manual/source/pics/configuresongs.png index 19796f3b5..9da70deb2 100644 Binary files a/documentation/manual/source/pics/configuresongs.png and b/documentation/manual/source/pics/configuresongs.png differ diff --git a/documentation/manual/source/pics/configurethemes.png b/documentation/manual/source/pics/configurethemes.png deleted file mode 100644 index c8241ddaf..000000000 Binary files a/documentation/manual/source/pics/configurethemes.png and /dev/null differ diff --git a/documentation/manual/source/troubleshooting.rst b/documentation/manual/source/troubleshooting.rst index 1d200ed8e..6f5f21a74 100644 --- a/documentation/manual/source/troubleshooting.rst +++ b/documentation/manual/source/troubleshooting.rst @@ -175,3 +175,23 @@ only download the section you search for. If you do not have an internet connection where you intend to use OpenLP you will need another scripture source. For more information about acquiring Bibles please see :ref:`bibleimporter`. +OpenLP is using a large amount of RAM when showing a presentation +----------------------------------------------------------------- + +OpenLP uses a large amount of RAM when showing a presentation due to the way it +handles presentations. OpenLP itself is unable to show those presentations or +load the presentation files, so it interacts with the presentation through +either Microsoft PowerPoint or LibreOffice Impress. In order to show the slides +in the slide controller, OpenLP requests that the presentation application +export the slides to images, and then uses those images as slides. This results +in a large amount of RAM being used, especially in presentations with more than +about 20 slides. + +OpenLP is not displaying correctly, or is not on the correct screen +------------------------------------------------------------------- + +Please read the documentation on :ref:`dualmonitors`. It is very important to +have dual monitors setup properly for OpenLP to function as expected. + + + diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 698f0d644..7f2204c67 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -637,4 +637,4 @@ class ThemeXML(object): self.font_footer_shadow_size) self.add_display(self.display_horizontal_align, self.display_vertical_align, - self.display_slide_transition) \ No newline at end of file + self.display_slide_transition) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 94bcb0801..b6dd1cb27 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -41,11 +41,11 @@ class AdvancedTab(SettingsTab): """ Initialise the settings tab """ - generalTranslated = translate('AdvancedTab', 'Advanced') - SettingsTab.__init__(self, parent ,u'Advanced', generalTranslated) + advancedTranslated = translate('OpenLP.AdvancedTab', 'Advanced') self.default_image = u':/graphics/openlp-splash-screen.png' self.default_color = u'#ffffff' self.icon_path = u':/system/system_settings.png' + SettingsTab.__init__(self, parent, u'Advanced', advancedTranslated) def setupUi(self): """ @@ -82,14 +82,6 @@ class AdvancedTab(SettingsTab): u'enableAutoCloseCheckBox') self.uiLayout.addRow(self.enableAutoCloseCheckBox) self.leftLayout.addWidget(self.uiGroupBox) - self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn) - self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox') - self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox) - self.hideMouseLayout.setObjectName(u'hideMouseLayout') - self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox) - self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox') - self.hideMouseLayout.addWidget(self.hideMouseCheckBox) - self.leftLayout.addWidget(self.hideMouseGroupBox) self.leftLayout.addStretch() self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn) self.defaultImageGroupBox.setObjectName(u'defaultImageGroupBox') @@ -109,26 +101,42 @@ class AdvancedTab(SettingsTab): self.defaultBrowseButton.setObjectName(u'defaultBrowseButton') self.defaultBrowseButton.setIcon( build_icon(u':/general/general_open.png')) + self.defaultRevertButton = QtGui.QToolButton(self.defaultImageGroupBox) + self.defaultRevertButton.setObjectName(u'defaultRevertButton') + self.defaultRevertButton.setIcon( + build_icon(u':/general/general_revert.png')) self.defaultFileLayout = QtGui.QHBoxLayout() self.defaultFileLayout.setObjectName(u'defaultFileLayout') self.defaultFileLayout.addWidget(self.defaultFileEdit) self.defaultFileLayout.addWidget(self.defaultBrowseButton) + self.defaultFileLayout.addWidget(self.defaultRevertButton) self.defaultImageLayout.addRow(self.defaultFileLabel, self.defaultFileLayout) self.rightLayout.addWidget(self.defaultImageGroupBox) + self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn) + self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox') + self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox) + self.hideMouseLayout.setObjectName(u'hideMouseLayout') + self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox) + self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox') + self.hideMouseLayout.addWidget(self.hideMouseCheckBox) + self.rightLayout.addWidget(self.hideMouseGroupBox) self.rightLayout.addStretch() QtCore.QObject.connect(self.defaultColorButton, QtCore.SIGNAL(u'pressed()'), self.onDefaultColorButtonPressed) QtCore.QObject.connect(self.defaultBrowseButton, QtCore.SIGNAL(u'pressed()'), self.onDefaultBrowseButtonPressed) + QtCore.QObject.connect(self.defaultRevertButton, + QtCore.SIGNAL(u'pressed()'), self.onDefaultRevertButtonPressed) def retranslateUi(self): """ Setup the interface translation strings. """ self.tabTitleVisible = UiStrings().Advanced - self.uiGroupBox.setTitle(translate('OpenLP.AdvancedTab', 'UI Settings')) + self.uiGroupBox.setTitle( + translate('OpenLP.AdvancedTab', 'UI Settings')) self.recentLabel.setText( translate('OpenLP.AdvancedTab', 'Number of recent files to display:')) @@ -150,8 +158,14 @@ class AdvancedTab(SettingsTab): 'Default Image')) self.defaultColorLabel.setText(translate('OpenLP.AdvancedTab', 'Background color:')) + self.defaultColorButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Click to select a color.')) self.defaultFileLabel.setText(translate('OpenLP.AdvancedTab', 'Image file:')) + self.defaultBrowseButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Browse for an image file to display.')) + self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab', + 'Revert to the default OpenLP logo.')) def load(self): """ @@ -232,4 +246,8 @@ class AdvancedTab(SettingsTab): file_filters) if filename: self.defaultFileEdit.setText(filename) - self.defaultFileEdit.setFocus() \ No newline at end of file + self.defaultFileEdit.setFocus() + + def onDefaultRevertButtonPressed(self): + self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png') + self.defaultFileEdit.setFocus() diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index c413ec8c9..7843284b3 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -142,7 +142,8 @@ class MainDisplay(DisplayWidget): image_file = QtCore.QSettings().value(u'advanced/default image', QtCore.QVariant(u':/graphics/openlp-splash-screen.png'))\ .toString() - background_color = QtGui.QColor(QtCore.QSettings().value( + background_color = QtGui.QColor() + background_color.setNamedColor(QtCore.QSettings().value( u'advanced/default color', QtCore.QVariant(u'#ffffff')).toString()) if not background_color.isValid(): diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 42b773198..bb87cf32f 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -46,41 +46,58 @@ http://doc.trolltech.com/4.7/richtext-html-subset.html#css-properties color:black; } +.item { + color:black; +} + .itemTitle { font-weight:600; font-size:large; - color:black; } -.itemText { - color:black; -} +.itemText {} .itemFooter { font-size:8px; - color:black; } +.itemNotes {} + .itemNotesTitle { font-weight:bold; font-size:12px; - color:black; } .itemNotesText { font-size:11px; - color:black; +} + +.media {} + +.mediaTitle { + font-weight:bold; + font-size:11px; +} + +.mediaText {} + +.imageList {} + +.customNotes { + margin-top: 10px; } .customNotesTitle { font-weight:bold; font-size:11px; - color:black; } .customNotesText { font-size:11px; - color:black; +} + +.newPage { + page-break-before:always; } """ @@ -153,86 +170,90 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): """ Creates the html text and updates the html of *self.document*. """ - html_data = html.fromstring( - u'%s' % unicode(self.titleLineEdit.text())) + html_data = self._addElement(u'html') + self._addElement(u'head', parent=html_data) + self._addElement(u'title', unicode(self.titleLineEdit.text()), + html_data.head) css_path = os.path.join( - AppLocation.get_data_path(), u'servicePrint.css') - if not os.path.isfile(css_path): - # Create default css file. - css_file = open(css_path, u'w') - css_file.write(DEFAULT_CSS) - css_file.close() + AppLocation.get_data_path(), u'service_print.css') custom_css = get_text_file_string(css_path) - self._addChildToParent( - u'style', custom_css, html_data.head, u'type', u'text/css') - self._addChildToParent(u'body', parent=html_data) - self._addChildToParent(u'span', unicode(self.titleLineEdit.text()), - html_data.body, u'class', u'serviceTitle') + if not custom_css: + custom_css = DEFAULT_CSS + self._addElement(u'style', custom_css, html_data.head, + attribute=(u'type', u'text/css')) + self._addElement(u'body', parent=html_data) + self._addElement(u'h1', unicode(self.titleLineEdit.text()), + html_data.body, classId=u'serviceTitle') for index, item in enumerate(self.serviceManager.serviceItems): - item = item[u'service_item'] - div = self._addChildToParent(u'div', parent=html_data.body) - # Add the title of the service item. - item_title = self._addChildToParent( - u'h2', parent=div, attribute=u'class', value=u'itemTitle') - self._addChildToParent( - u'img', parent=item_title, attribute=u'src', value=item.icon) - self._fromstring( - u' %s' % item.get_display_title(), item_title) - if self.slideTextCheckBox.isChecked(): - # Add the text of the service item. - if item.is_text(): - verse_def = None - for slide in item.get_frames(): - if not verse_def or verse_def != slide[u'verseTag']: - p = self._addChildToParent(u'p', parent=div, - attribute=u'class', value=u'itemText') - else: - self._addChildToParent(u'br', parent=p) - self._fromstring(u'%s' % slide[u'html'], p) - verse_def = slide[u'verseTag'] - # Break the page before the div element. - if index != 0 and self.pageBreakAfterText.isChecked(): - div.set(u'style', u'page-break-before:always') - # Add the image names of the service item. - elif item.is_image(): - ol = self._addChildToParent(u'ol', parent=div) - for slide in range(len(item.get_frames())): - self._addChildToParent(u'li', item.get_frame_title(slide), ol) - # add footer - if item.foot_text: - self._fromstring( - item.foot_text, div, u'class', u'itemFooter') - # Add service items' notes. - if self.notesCheckBox.isChecked(): - if item.notes: - p = self._addChildToParent(u'p', parent=div) - self._addChildToParent(u'span', unicode( - translate('OpenLP.ServiceManager', 'Notes:')), p, - u'class', u'itemNotesTitle') - self._fromstring(u' %s' % item.notes.replace( - u'\n', u'
'), p, u'class', u'itemNotesText') - # Add play length of media files. - if item.is_media() and self.metaDataCheckBox.isChecked(): - tme = item.media_length - if item.end_time > 0: - tme = item.end_time - item.start_time - title = self._fromstring(u'

%s

' % - translate('OpenLP.ServiceManager', 'Playing time:'), div) - self._fromstring(u'%s' % - unicode(datetime.timedelta(seconds=tme)), title) + self._addPreviewItem(html_data.body, item[u'service_item'], index) # Add the custom service notes: if self.footerTextEdit.toPlainText(): - div = self._addChildToParent(u'div', parent=html_data.body) - self._addChildToParent(u'span', translate('OpenLP.ServiceManager', - u'Custom Service Notes:'), div, u'class', u'customNotesTitle') - self._addChildToParent( - u'span', u' %s' % self.footerTextEdit.toPlainText(), div, - u'class', u'customNotesText') + div = self._addElement(u'div', parent=html_data.body, + classId=u'customNotes') + self._addElement(u'span', translate('OpenLP.ServiceManager', + 'Custom Service Notes: '), div, classId=u'customNotesTitle') + self._addElement(u'span', self.footerTextEdit.toPlainText(), div, + classId=u'customNotesText') self.document.setHtml(html.tostring(html_data)) self.previewWidget.updatePreview() - def _addChildToParent(self, tag, text=None, parent=None, attribute=None, - value=None): + def _addPreviewItem(self, body, item, index): + div = self._addElement(u'div', classId=u'item', parent=body) + # Add the title of the service item. + item_title = self._addElement(u'h2', parent=div, classId=u'itemTitle') + self._addElement(u'img', parent=item_title, + attribute=(u'src', item.icon)) + self._addElement(u'span', u' ' + item.get_display_title(), + item_title) + if self.slideTextCheckBox.isChecked(): + # Add the text of the service item. + if item.is_text(): + verse_def = None + for slide in item.get_frames(): + if not verse_def or verse_def != slide[u'verseTag']: + p = self._addElement(u'div', parent=div, + classId=u'itemText') + else: + self._addElement(u'br', parent=p) + self._addElement(u'p', slide[u'html'], p) + verse_def = slide[u'verseTag'] + # Break the page before the div element. + if index != 0 and self.pageBreakAfterText.isChecked(): + div.set(u'class', u'item newPage') + # Add the image names of the service item. + elif item.is_image(): + ol = self._addElement(u'ol', parent=div, classId=u'imageList') + for slide in range(len(item.get_frames())): + self._addElement(u'li', item.get_frame_title(slide), ol) + # add footer + foot_text = item.foot_text + foot_text = foot_text.partition(u'
')[2] + if foot_text: + foot = self._addElement(u'div', foot_text, parent=div, + classId=u'itemFooter') + # Add service items' notes. + if self.notesCheckBox.isChecked(): + if item.notes: + p = self._addElement(u'div', classId=u'itemNotes', parent=div) + self._addElement(u'span', + translate('OpenLP.ServiceManager', 'Notes: '), p, + classId=u'itemNotesTitle') + notes = self._addElement(u'span', + item.notes.replace(u'\n', u'
'), p, + classId=u'itemNotesText') + # Add play length of media files. + if item.is_media() and self.metaDataCheckBox.isChecked(): + tme = item.media_length + if item.end_time > 0: + tme = item.end_time - item.start_time + title = self._addElement(u'div', classId=u'media', parent=div) + self._addElement(u'span', translate('OpenLP.ServiceManager', + 'Playing time: '), title, classId=u'mediaTitle') + self._addElement(u'span', unicode(datetime.timedelta(seconds=tme)), + title, classId=u'mediaText') + + def _addElement(self, tag, text=None, parent=None, classId=None, + attribute=None): """ Creates a html element. If ``text`` is given, the element's text will set and if a ``parent`` is given, the element is appended. @@ -246,30 +267,22 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): ``parent`` The parent element. Defaults to ``None``. - ``attribute`` - An optional attribute, for instance ``u'class``. + ``classId`` + Value for the class attribute - ``value`` - The value for the given ``attribute``. It does not have a meaning, - if the attribute is left to its default. + ``attribute`` + Tuple name/value pair to add as an optional attribute """ - element = html.Element(tag) if text is not None: - element.text = unicode(text) + element = html.fragment_fromstring(unicode(text), create_parent=tag) + else: + element = html.Element(tag) if parent is not None: parent.append(element) + if classId is not None: + element.set(u'class', classId) if attribute is not None: - element.set(attribute, value if value is not None else u'') - return element - - def _fromstring(self, string, parent, attribute=None, value=None): - """ - This is used to create a child html element from a string. - """ - element = html.fromstring(string) - if attribute is not None: - element.set(attribute, value if value is not None else u'') - parent.append(element) + element.set(attribute[0], attribute[1]) return element def paintRequested(self, printer): @@ -380,4 +393,4 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): QtCore.QVariant(self.metaDataCheckBox.isChecked())) settings.setValue(u'print notes', QtCore.QVariant(self.notesCheckBox.isChecked())) - settings.endGroup() \ No newline at end of file + settings.endGroup() diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 40bd5e9d4..a2260cce9 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -686,6 +686,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems[item][u'service_item'].notes = \ self.serviceNoteForm.textEdit.toPlainText() self.repaintServiceList(item, -1) + self.setModified() def onStartTimeForm(self): item = self.findServiceItem()[0] @@ -789,7 +790,7 @@ class ServiceManager(QtGui.QWidget): # Top Item was selected so set the last one if setLastItem: lastItem.setSelected(True) - self.setModified(True) + self.setModified() def onMoveSelectionDown(self): """ @@ -811,7 +812,7 @@ class ServiceManager(QtGui.QWidget): serviceIterator += 1 if setSelected: firstItem.setSelected(True) - self.setModified(True) + self.setModified() def onCollapseAll(self): """ @@ -855,7 +856,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(0, temp) self.repaintServiceList(0, child) - self.setModified(True) + self.setModified() def onServiceUp(self): """ @@ -867,7 +868,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item - 1, temp) self.repaintServiceList(item - 1, child) - self.setModified(True) + self.setModified() def onServiceDown(self): """ @@ -879,7 +880,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item + 1, temp) self.repaintServiceList(item + 1, child) - self.setModified(True) + self.setModified() def onServiceEnd(self): """ @@ -891,7 +892,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(len(self.serviceItems), temp) self.repaintServiceList(len(self.serviceItems) - 1, child) - self.setModified(True) + self.setModified() def onDeleteFromService(self): """ @@ -901,7 +902,7 @@ class ServiceManager(QtGui.QWidget): if item != -1: self.serviceItems.remove(self.serviceItems[item]) self.repaintServiceList(item - 1, -1) - self.setModified(True) + self.setModified() def repaintServiceList(self, serviceItem, serviceItemChild): """ @@ -1026,7 +1027,7 @@ class ServiceManager(QtGui.QWidget): item[u'service_item'], False, expand=item[u'expanded']) # Set to False as items may have changed rendering # does not impact the saved song so True may also be valid - self.setModified(True) + self.setModified() Receiver.send_message(u'cursor_normal') def serviceItemUpdate(self, message): @@ -1037,7 +1038,7 @@ class ServiceManager(QtGui.QWidget): for item in self.serviceItems: if item[u'service_item']._uuid == uuid: item[u'service_item'].edit_id = int(editId) - self.setModified(True) + self.setModified() def replaceServiceItem(self, newItem): """ @@ -1053,7 +1054,7 @@ class ServiceManager(QtGui.QWidget): self.repaintServiceList(itemcount + 1, 0) self.mainwindow.liveController.replaceServiceManagerItem( newItem) - self.setModified(True) + self.setModified() def addServiceItem(self, item, rebuild=False, expand=None, replace=False): """ @@ -1098,7 +1099,7 @@ class ServiceManager(QtGui.QWidget): if rebuild: self.mainwindow.liveController.replaceServiceManagerItem(item) self.dropPosition = 0 - self.setModified(True) + self.setModified() def makePreview(self): """ @@ -1235,7 +1236,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(serviceItem) self.serviceItems.insert(endpos, serviceItem) self.repaintServiceList(endpos, child) - self.setModified(True) + self.setModified() else: # we are not over anything so drop replace = False diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 5f2c63c23..dfa313cc1 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -380,18 +380,21 @@ class SlideController(QtGui.QWidget): action_list.add_action(self.previousItem) action_list.add_action(self.nextItem) self.previousService = shortcut_action(parent, u'previousService', - [QtCore.Qt.Key_Left], self.servicePrevious, UiStrings().LiveToolbar) - self.previousService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + [QtCore.Qt.Key_Left], self.servicePrevious, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) self.previousService.setText( translate('OpenLP.SlideController', 'Previous Service')) self.nextService = shortcut_action(parent, 'nextService', - [QtCore.Qt.Key_Right], self.serviceNext, UiStrings().LiveToolbar) - self.nextService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + [QtCore.Qt.Key_Right], self.serviceNext, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) self.nextService.setText( translate('OpenLP.SlideController', 'Next Service')) self.escapeItem = shortcut_action(parent, 'escapeItem', - [QtCore.Qt.Key_Escape], self.liveEscape, UiStrings().LiveToolbar) - self.escapeItem.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + [QtCore.Qt.Key_Escape], self.liveEscape, + category=UiStrings().LiveToolbar, + context=QtCore.Qt.WidgetWithChildrenShortcut) self.escapeItem.setText( translate('OpenLP.SlideController', 'Escape Item')) @@ -1150,4 +1153,5 @@ class SlideController(QtGui.QWidget): elif self.desktopScreen.isChecked(): return HideMode.Screen else: - return None \ No newline at end of file + return None + diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index ebba45e7c..019ab5bfe 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -56,6 +56,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.setupUi(self) self.registerFields() self.updateThemeAllowed = True + self.temp_background_filename = u'' QtCore.QObject.connect(self.backgroundComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onBackgroundComboBoxCurrentIndexChanged) @@ -279,6 +280,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): Run the wizard. """ log.debug(u'Editing theme %s' % self.theme.theme_name) + self.temp_background_filename = u'' self.updateThemeAllowed = False self.setDefaults() self.updateThemeAllowed = True @@ -432,6 +434,16 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): # do not allow updates when screen is building for the first time. if self.updateThemeAllowed: self.theme.background_type = BackgroundType.to_string(index) + if self.theme.background_type != \ + BackgroundType.to_string(BackgroundType.Image) and \ + self.temp_background_filename == u'': + self.temp_background_filename = self.theme.background_filename + self.theme.background_filename = u'' + if self.theme.background_type == \ + BackgroundType.to_string(BackgroundType.Image) and \ + self.temp_background_filename != u'': + self.theme.background_filename = self.temp_background_filename + self.temp_background_filename = u'' self.setBackgroundPageValues() def onGradientComboBoxCurrentIndexChanged(self, index): @@ -589,4 +601,4 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtGui.QColor(field), self) if new_color.isValid(): field = new_color.name() - return field \ No newline at end of file + return field diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 74ca0715a..9d1147638 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -95,6 +95,10 @@ class OpenLPWizard(QtGui.QWizard): self.customSignals() QtCore.QObject.connect(self, QtCore.SIGNAL(u'currentIdChanged(int)'), self.onCurrentIdChanged) + QtCore.QObject.connect(self.errorCopyToButton, + QtCore.SIGNAL(u'clicked()'), self.onErrorCopyToButtonClicked) + QtCore.QObject.connect(self.errorSaveToButton, + QtCore.SIGNAL(u'clicked()'), self.onErrorSaveToButtonClicked) def setupUi(self, image): """ @@ -129,10 +133,36 @@ class OpenLPWizard(QtGui.QWizard): self.progressLayout.setObjectName(u'progressLayout') self.progressLabel = QtGui.QLabel(self.progressPage) self.progressLabel.setObjectName(u'progressLabel') + self.progressLabel.setWordWrap(True) self.progressLayout.addWidget(self.progressLabel) self.progressBar = QtGui.QProgressBar(self.progressPage) self.progressBar.setObjectName(u'progressBar') self.progressLayout.addWidget(self.progressBar) + # Add a QTextEdit and a copy to file and copy to clipboard button to be + # able to provide feedback to the user. Hidden by default. + self.errorReportTextEdit = QtGui.QTextEdit(self.progressPage) + self.errorReportTextEdit.setObjectName(u'progresserrorReportTextEdit') + self.errorReportTextEdit.setHidden(True) + self.errorReportTextEdit.setReadOnly(True) + self.progressLayout.addWidget(self.errorReportTextEdit) + self.errorButtonLayout = QtGui.QHBoxLayout() + self.errorButtonLayout.setObjectName(u'errorButtonLayout') + spacer = QtGui.QSpacerItem(40, 20, + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.errorButtonLayout.addItem(spacer) + self.errorCopyToButton = QtGui.QPushButton(self.progressPage) + self.errorCopyToButton.setObjectName(u'errorCopyToButton') + self.errorCopyToButton.setHidden(True) + self.errorCopyToButton.setIcon( + build_icon(u':/system/system_edit_copy.png')) + self.errorButtonLayout.addWidget(self.errorCopyToButton) + self.errorSaveToButton = QtGui.QPushButton(self.progressPage) + self.errorSaveToButton.setObjectName(u'errorSaveToButton') + self.errorSaveToButton.setHidden(True) + self.errorSaveToButton.setIcon( + build_icon(u':/general/general_save.png')) + self.errorButtonLayout.addWidget(self.errorSaveToButton) + self.progressLayout.addLayout(self.errorButtonLayout) self.addPage(self.progressPage) def exec_(self): @@ -160,6 +190,18 @@ class OpenLPWizard(QtGui.QWizard): self.performWizard() self.postWizard() + def onErrorCopyToButtonClicked(self): + """ + Called when the ``onErrorCopyToButtonClicked`` has been clicked. + """ + pass + + def onErrorSaveToButtonClicked(self): + """ + Called when the ``onErrorSaveToButtonClicked`` has been clicked. + """ + pass + def incrementProgressBar(self, status_text, increment=1): """ Update the wizard progress page. @@ -219,4 +261,5 @@ class OpenLPWizard(QtGui.QWizard): if filename: editbox.setText(filename) SettingsManager.set_last_dir(self.plugin.settingsSection, - filename, 1) \ No newline at end of file + filename, 1) + diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index e2eb25b4f..a080524eb 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -37,6 +37,9 @@ from openlp.plugins.bibles.lib.db import BibleDB log = logging.getLogger(__name__) +def replacement(match): + return match.group(2).upper() + class OSISBible(BibleDB): """ `OSIS `_ Bible format importer class. @@ -60,6 +63,7 @@ class OSISBible(BibleDB): self.lg_regex = re.compile(r'') self.l_regex = re.compile(r'') self.w_regex = re.compile(r'') + self.q_regex = re.compile(r'') self.q1_regex = re.compile(r'') self.q2_regex = re.compile(r'') self.trans_regex = re.compile(r'(.*?)') @@ -106,6 +110,7 @@ class OSISBible(BibleDB): detect_file.close() try: osis = codecs.open(self.filename, u'r', details['encoding']) + repl = replacement for file_record in osis: if self.stop_import_flag: break @@ -148,12 +153,13 @@ class OSISBible(BibleDB): verse_text = self.rf_regex.sub(u'', verse_text) verse_text = self.lb_regex.sub(u' ', verse_text) verse_text = self.lg_regex.sub(u'', verse_text) - verse_text = self.l_regex.sub(u'', verse_text) + verse_text = self.l_regex.sub(u' ', verse_text) verse_text = self.w_regex.sub(u'', verse_text) verse_text = self.q1_regex.sub(u'"', verse_text) verse_text = self.q2_regex.sub(u'\'', verse_text) + verse_text = self.q_regex.sub(u'', verse_text) + verse_text = self.divine_name_regex.sub(repl, verse_text) verse_text = self.trans_regex.sub(u'', verse_text) - verse_text = self.divine_name_regex.sub(u'', verse_text) verse_text = verse_text.replace(u'', u'')\ .replace(u'', u'').replace(u'', u'')\ .replace(u'', u'').replace(u'', u'')\ diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index dde8826e0..8c5dbeb8a 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -26,6 +26,7 @@ """ The song import functions for OpenLP. """ +import codecs import logging import os @@ -55,6 +56,7 @@ class SongImportForm(OpenLPWizard): ``plugin`` The songs plugin. """ + self.clipboard = plugin.formparent.clipboard OpenLPWizard.__init__(self, parent, plugin, u'songImportWizard', u':/wizards/wizard_importsong.bmp') @@ -330,6 +332,10 @@ class SongImportForm(OpenLPWizard): 'Please wait while your songs are imported.')) self.progressLabel.setText(WizardStrings.Ready) self.progressBar.setFormat(WizardStrings.PercentSymbolFormat) + self.errorCopyToButton.setText(translate('SongsPlugin.ImportWizardForm', + 'Copy')) + self.errorSaveToButton.setText(translate('SongsPlugin.ImportWizardForm', + 'Save to File')) # Align all QFormLayouts towards each other. width = max(self.formatLabel.minimumSizeHint().width(), self.openLP2FilenameLabel.minimumSizeHint().width()) @@ -459,10 +465,7 @@ class SongImportForm(OpenLPWizard): """ Return a list of file from the listbox """ - files = [] - for row in range(0, listbox.count()): - files.append(unicode(listbox.item(row).text())) - return files + return [unicode(listbox.item(i).text()) for i in range(listbox.count())] def removeSelectedItems(self, listbox): """ @@ -659,6 +662,10 @@ class SongImportForm(OpenLPWizard): self.songShowPlusFileListWidget.clear() self.foilPresenterFileListWidget.clear() #self.csvFilenameEdit.setText(u'') + self.errorReportTextEdit.clear() + self.errorReportTextEdit.setHidden(True) + self.errorCopyToButton.setHidden(True) + self.errorSaveToButton.setHidden(True) def preWizard(self): """ @@ -743,12 +750,30 @@ class SongImportForm(OpenLPWizard): importer = self.plugin.importSongs(SongFormat.FoilPresenter, filenames=self.getListOfFiles(self.foilPresenterFileListWidget) ) - if importer.do_import(): - self.progressLabel.setText(WizardStrings.FinishedImport) + importer.do_import() + if importer.error_log: + self.progressLabel.setText(translate( + 'SongsPlugin.SongImportForm', 'Your song import failed.')) else: - self.progressLabel.setText( - translate('SongsPlugin.SongImportForm', - 'Your song import failed.')) + self.progressLabel.setText(WizardStrings.FinishedImport) + + def onErrorCopyToButtonClicked(self): + """ + Copy the error report to the clipboard. + """ + self.clipboard.setText(self.errorReportTextEdit.toPlainText()) + + def onErrorSaveToButtonClicked(self): + """ + Save the error report to a file. + """ + filename = QtGui.QFileDialog.getSaveFileName(self, + SettingsManager.get_last_dir(self.plugin.settingsSection, 1)) + if not filename: + return + file = codecs.open(filename, u'w', u'utf-8') + file.write(self.errorReportTextEdit.toPlainText()) + file.close() def addFileSelectItem(self, prefix, obj_prefix=None, can_disable=False, single_select=False): @@ -836,4 +861,4 @@ class SongImportForm(OpenLPWizard): setattr(self, prefix + u'DisabledLayout', disabledLayout) setattr(self, prefix + u'DisabledLabel', disabledLabel) setattr(self, prefix + u'ImportWidget', importWidget) - return importWidget \ No newline at end of file + return importWidget diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 48e614eeb..c1437ce0e 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -386,7 +386,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): existing_author = self.manager.get_object_filtered(Author, and_(Author.first_name == old_author.first_name, Author.last_name == old_author.last_name, - Author.display_name == old_author.display_name)) + Author.display_name == old_author.display_name, + Author.id != old_author.id)) # Find the songs, which have the old_author as author. songs = self.manager.get_all_objects(Song, Song.authors.contains(old_author)) @@ -408,7 +409,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): """ # Find the duplicate. existing_topic = self.manager.get_object_filtered(Topic, - Topic.name == old_topic.name) + and_(Topic.name == old_topic.name, Topic.id != old_topic.id)) # Find the songs, which have the old_topic as topic. songs = self.manager.get_all_objects(Song, Song.topics.contains(old_topic)) @@ -431,7 +432,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): # Find the duplicate. existing_book = self.manager.get_object_filtered(Book, and_(Book.name == old_book.name, - Book.publisher == old_book.publisher)) + Book.publisher == old_book.publisher, + Book.id != old_book.id)) # Find the songs, which have the old_book as book. songs = self.manager.get_all_objects(Song, Song.song_book_id == old_book.id) @@ -503,4 +505,5 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): editButton.setEnabled(False) else: deleteButton.setEnabled(True) - editButton.setEnabled(True) \ No newline at end of file + editButton.setEnabled(True) + diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 4f95791b3..249a76a38 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -278,24 +278,29 @@ def clean_song(manager, song): # List for later comparison. compare_order = [] for verse in verses: - type = VerseType.Tags[VerseType.from_loose_input(verse[0][u'type'])] + verse_type = VerseType.Tags[VerseType.from_loose_input( + verse[0][u'type'])] sxml.add_verse_to_lyrics( - type, + verse_type, verse[0][u'label'], verse[1], verse[0][u'lang'] if verse[0].has_key(u'lang') else None ) - compare_order.append((u'%s%s' % (type, verse[0][u'label'])).upper()) + compare_order.append((u'%s%s' % (verse_type, verse[0][u'label']) + ).upper()) + if verse[0][u'label'] == u'1': + compare_order.append(verse_type.upper()) song.lyrics = unicode(sxml.extract_xml(), u'utf-8') # Rebuild the verse order, to convert translated verse tags, which might # have been added prior to 1.9.5. order = song.verse_order.strip().split() new_order = [] for verse_def in order: - new_order.append((u'%s%s' % ( - VerseType.Tags[VerseType.from_loose_input(verse_def[0])], - verse_def[1:])).upper() - ) + verse_type = VerseType.Tags[VerseType.from_loose_input(verse_def[0])] + if len(verse_def) > 1: + new_order.append((u'%s%s' % (verse_type, verse_def[1:])).upper()) + else: + new_order.append(verse_type.upper()) song.verse_order = u' '.join(new_order) # Check if the verse order contains tags for verses which do not exist. for order in new_order: diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py index 03a86c455..d304b0241 100644 --- a/openlp/plugins/songs/lib/cclifileimport.py +++ b/openlp/plugins/songs/lib/cclifileimport.py @@ -59,16 +59,10 @@ class CCLIFileImport(SongImport): Import either a ``.usr`` or a ``.txt`` SongSelect file. """ log.debug(u'Starting CCLI File Import') - song_total = len(self.import_source) - self.import_wizard.progressBar.setMaximum(song_total) - song_count = 1 + self.import_wizard.progressBar.setMaximum(len(self.import_source)) for filename in self.import_source: - self.import_wizard.incrementProgressBar(unicode(translate( - 'SongsPlugin.CCLIFileImport', 'Importing song %d of %d')) % - (song_count, song_total)) filename = unicode(filename) log.debug(u'Importing CCLI File: %s', filename) - self.set_defaults() lines = [] if os.path.isfile(filename): detect_file = open(filename, u'r') @@ -81,19 +75,23 @@ class CCLIFileImport(SongImport): detect_file.close() infile = codecs.open(filename, u'r', details['encoding']) lines = infile.readlines() + infile.close() ext = os.path.splitext(filename)[1] if ext.lower() == u'.usr': log.info(u'SongSelect .usr format file found: %s', filename) - self.do_import_usr_file(lines) + if not self.do_import_usr_file(lines): + self.log_error(filename) elif ext.lower() == u'.txt': log.info(u'SongSelect .txt format file found: %s', filename) - self.do_import_txt_file(lines) + if not self.do_import_txt_file(lines): + self.log_error(filename) else: + self.log_error(filename, + translate('SongsPlugin.CCLIFileImport', + 'The file does not have a valid extension.')) log.info(u'Extension %s is not valid', filename) - song_count += 1 if self.stop_import_flag: - return False - return True + return def do_import_usr_file(self, textList): """ @@ -218,7 +216,7 @@ class CCLIFileImport(SongImport): else: self.add_author(author) self.topics = [topic.strip() for topic in song_topics.split(u'/t')] - self.finish() + return self.finish() def do_import_txt_file(self, textList): """ @@ -334,6 +332,5 @@ class CCLIFileImport(SongImport): if len(author_list) < 2: author_list = song_author.split(u'|') # Clean spaces before and after author names. - for author_name in author_list: - self.add_author(author_name.strip()) - self.finish() + [self.add_author(author_name.strip()) for author_name in author_list] + return self.finish() diff --git a/openlp/plugins/songs/lib/easislidesimport.py b/openlp/plugins/songs/lib/easislidesimport.py index d326e83ca..0c710377a 100644 --- a/openlp/plugins/songs/lib/easislidesimport.py +++ b/openlp/plugins/songs/lib/easislidesimport.py @@ -26,11 +26,13 @@ import logging import os -from lxml import etree, objectify import re +from lxml import etree, objectify + from openlp.core.lib import translate from openlp.core.ui.wizard import WizardStrings +from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) @@ -56,26 +58,16 @@ class EasiSlidesImport(SongImport): multiple opensong files. If `self.commit` is set False, the import will not be committed to the database (useful for test scripts). """ - self.import_wizard.progressBar.setMaximum(1) log.info(u'Importing EasiSlides XML file %s', self.import_source) parser = etree.XMLParser(remove_blank_text=True) file = etree.parse(self.import_source, parser) xml = unicode(etree.tostring(file)) song_xml = objectify.fromstring(xml) - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % os.path.split(self.import_source)[-1]) self.import_wizard.progressBar.setMaximum(len(song_xml.Item)) for song in song_xml.Item: - self.import_wizard.incrementProgressBar( - unicode(translate('SongsPlugin.ImportWizardForm', - u'Importing %s, song %s...')) % - (os.path.split(self.import_source)[-1], song.Title1)) - success = self._parse_song(song) - if not success or self.stop_import_flag: - return False - elif self.commit: - self.finish() - return True + if self.stop_import_flag: + return + self._parse_song(song) def _parse_song(self, song): self._success = True @@ -90,7 +82,11 @@ class EasiSlidesImport(SongImport): self._add_copyright(song.LicenceAdmin2) self._add_unicode_attribute(u'song_book_name', song.BookReference) self._parse_and_add_lyrics(song) - return self._success + if self._success: + if not self.finish(): + self.log_error(song.Title1 if song.Title1 else u'') + else: + self.set_defaults() def _add_unicode_attribute(self, self_attribute, import_attribute, mandatory=False): @@ -122,10 +118,8 @@ class EasiSlidesImport(SongImport): def _add_authors(self, song): try: authors = unicode(song.Writer).split(u',') - for author in authors: - author = author.strip() - if len(author): - self.authors.append(author) + self.authors = \ + [author.strip() for author in authors if author.strip()] except UnicodeDecodeError: log.exception(u'Unicode decode error while decoding Writer') self._success = False @@ -188,12 +182,13 @@ class EasiSlidesImport(SongImport): # if the regions are inside verses regionsInVerses = (regions and regionlines[regionlines.keys()[0]] > 1) MarkTypes = { - u'CHORUS': u'C', - u'VERSE': u'V', - u'INTRO': u'I', - u'ENDING': u'E', - u'BRIDGE': u'B', - u'PRECHORUS': u'P'} + u'CHORUS': VerseType.Tags[VerseType.Chorus], + u'VERSE': VerseType.Tags[VerseType.Verse], + u'INTRO': VerseType.Tags[VerseType.Intro], + u'ENDING': VerseType.Tags[VerseType.Ending], + u'BRIDGE': VerseType.Tags[VerseType.Bridge], + u'PRECHORUS': VerseType.Tags[VerseType.PreChorus] + } verses = {} # list as [region, versetype, versenum, instance] our_verse_order = [] diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index e68b19494..784558c5b 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -33,6 +33,7 @@ import struct from openlp.core.lib import translate from openlp.core.ui.wizard import WizardStrings +from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import retrieve_windows_encoding from songimport import SongImport @@ -142,12 +143,12 @@ class EasyWorshipSongImport(SongImport): # Open the DB and MB files if they exist import_source_mb = self.import_source.replace('.DB', '.MB') if not os.path.isfile(self.import_source): - return False + return if not os.path.isfile(import_source_mb): - return False + return db_size = os.path.getsize(self.import_source) if db_size < 0x800: - return False + return db_file = open(self.import_source, 'rb') self.memo_file = open(import_source_mb, 'rb') # Don't accept files that are clearly not paradox files @@ -156,7 +157,7 @@ class EasyWorshipSongImport(SongImport): if header_size != 0x800 or block_size < 1 or block_size > 4: db_file.close() self.memo_file.close() - return False + return # Take a stab at how text is encoded self.encoding = u'cp1252' db_file.seek(106) @@ -183,7 +184,7 @@ class EasyWorshipSongImport(SongImport): self.encoding = u'cp874' self.encoding = retrieve_windows_encoding(self.encoding) if not self.encoding: - return False + return # There does not appear to be a _reliable_ way of getting the number # of songs/records, so let's use file blocks for measuring progress. total_blocks = (db_size - header_size) / (block_size * 1024) @@ -203,8 +204,8 @@ class EasyWorshipSongImport(SongImport): field_size)) self.set_record_struct(field_descs) # Pick out the field description indexes we will need - success = True try: + success = True fi_title = self.find_field(u'Title') fi_author = self.find_field(u'Author') fi_copy = self.find_field(u'Copyright') @@ -223,31 +224,25 @@ class EasyWorshipSongImport(SongImport): # Loop through each record within the current block for i in range(rec_count): if self.stop_import_flag: - success = False break raw_record = db_file.read(record_size) self.fields = self.record_struct.unpack(raw_record) self.set_defaults() - # Get title and update progress bar message - title = self.get_field(fi_title) - if title: - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % title, 0) - self.title = title - # Get remaining fields + self.title = self.get_field(fi_title) + # Get remaining fields. copy = self.get_field(fi_copy) admin = self.get_field(fi_admin) ccli = self.get_field(fi_ccli) authors = self.get_field(fi_author) words = self.get_field(fi_words) - # Set the SongImport object members + # Set the SongImport object members. if copy: self.copyright = copy if admin: if copy: self.copyright += u', ' self.copyright += \ - unicode(translate('SongsPlugin.ImportWizardForm', + unicode(translate('SongsPlugin.EasyWorshipSongImport', 'Administered by %s')) % admin if ccli: self.ccli_number = ccli @@ -264,19 +259,17 @@ class EasyWorshipSongImport(SongImport): # Format the lyrics words = strip_rtf(words, self.encoding) for verse in words.split(u'\n\n'): - self.add_verse(verse.strip(), u'V') + self.add_verse( + verse.strip(), VerseType.Tags[VerseType.Verse]) if self.stop_import_flag: - success = False break - self.finish() - if not self.stop_import_flag: - self.import_wizard.incrementProgressBar(u'') + if not self.finish(): + self.log_error(self.import_source) db_file.close() self.memo_file.close() - return success def find_field(self, field_name): - return [i for i, x in enumerate(self.field_descs) \ + return [i for i, x in enumerate(self.field_descs) if x.name == field_name][0] def set_record_struct(self, field_descs): diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index 44ea56147..0c7152bfd 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -97,6 +97,7 @@ from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.songimport import SongImport from openlp.plugins.songs.lib.db import Author, Book, Song, Topic +from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.xml import SongXML log = logging.getLogger(__name__) @@ -121,17 +122,16 @@ class FoilPresenterImport(SongImport): parser = etree.XMLParser(remove_blank_text=True) for file_path in self.import_source: if self.stop_import_flag: - return False + return self.import_wizard.incrementProgressBar( WizardStrings.ImportingType % os.path.basename(file_path)) try: parsed_file = etree.parse(file_path, parser) xml = unicode(etree.tostring(parsed_file)) - if self.FoilPresenter.xml_to_song(xml) is None: - log.debug(u'File could not be imported: %s' % file_path) + self.FoilPresenter.xml_to_song(xml) except etree.XMLSyntaxError: + self.log_error(file_path, SongStrings.XMLSyntaxError) log.exception(u'XML syntax error in file %s' % file_path) - return True class FoilPresenter(object): @@ -211,7 +211,7 @@ class FoilPresenter(object): """ # No xml get out of here. if not xml: - return None + return if xml[:5] == u'', copyright, re.U) copyright = re.compile(u'(?<=) *:').sub(u'', copyright) - i = 0 x = 0 - while i != 1: + while True: if copyright.find(u'') != -1: temp = copyright.partition(u'') if temp[0].strip() and x > 0: @@ -316,9 +314,9 @@ class FoilPresenter(object): x += 1 elif x > 0: strings.append(copyright) - i = 1 + break else: - i = 1 + break author_temp = [] for author in strings: temp = re.split(u',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', @@ -349,8 +347,8 @@ class FoilPresenter(object): if author is None: # We need to create a new author, as the author does not exist. author = Author.populate(display_name=display_name, - last_name = display_name.split(u' ')[-1], - first_name = u' '.join(display_name.split(u' ')[:-1])) + last_name=display_name.split(u' ')[-1], + first_name=u' '.join(display_name.split(u' ')[:-1])) self.manager.save_object(author) song.authors.append(author) @@ -414,8 +412,15 @@ class FoilPresenter(object): temp_verse_order_backup = [] temp_sortnr_backup = 1 temp_sortnr_liste = [] - versenumber = {u'V': 1, u'C': 1, u'B': 1, u'E': 1, u'O': 1, u'I': 1, - u'P': 1} + versenumber = { + VerseType.Tags[VerseType.Verse]: 1, + VerseType.Tags[VerseType.Chorus]: 1, + VerseType.Tags[VerseType.Bridge]: 1, + VerseType.Tags[VerseType.Ending]: 1, + VerseType.Tags[VerseType.Other]: 1, + VerseType.Tags[VerseType.Intro]: 1, + VerseType.Tags[VerseType.PreChorus]: 1 + } for strophe in foilpresenterfolie.strophen.strophe: text = self._child(strophe.text_) verse_name = self._child(strophe.key) @@ -434,25 +439,25 @@ class FoilPresenter(object): temp_verse_name = re.compile(u'[0-9].*').sub(u'', verse_name) temp_verse_name = temp_verse_name[:3].lower() if temp_verse_name == u'ref': - verse_type = u'C' + verse_type = VerseType.Tags[VerseType.Chorus] elif temp_verse_name == u'r': - verse_type = u'C' + verse_type = VerseType.Tags[VerseType.Chorus] elif temp_verse_name == u'': - verse_type = u'V' + verse_type = VerseType.Tags[VerseType.Verse] elif temp_verse_name == u'v': - verse_type = u'V' + verse_type = VerseType.Tags[VerseType.Verse] elif temp_verse_name == u'bri': - verse_type = u'B' + verse_type = VerseType.Tags[VerseType.Bridge] elif temp_verse_name == u'cod': - verse_type = u'E' + verse_type = VerseType.Tags[VerseType.Ending] elif temp_verse_name == u'sch': - verse_type = u'E' + verse_type = VerseType.Tags[VerseType.Ending] elif temp_verse_name == u'pre': - verse_type = u'P' + verse_type = VerseType.Tags[VerseType.PreChorus] elif temp_verse_name == u'int': - verse_type = u'I' + verse_type = VerseType.Tags[VerseType.Intro] else: - verse_type = u'O' + verse_type = VerseType.Tags[VerseType.Other] verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name) # Foilpresenter allows e. g. "C", but we need "C1". if not verse_number: @@ -466,8 +471,8 @@ class FoilPresenter(object): verse_number = unicode(int(verse_number) + 1) verse_type_index = VerseType.from_tag(verse_type[0]) verse_type = VerseType.Names[verse_type_index] - temp_verse_order[verse_sortnr] = (u''.join((verse_type[0], - verse_number))) + temp_verse_order[verse_sortnr] = u''.join((verse_type[0], + verse_number)) temp_verse_order_backup.append(u''.join((verse_type[0], verse_number))) sxml.add_verse_to_lyrics(verse_type, verse_number, text) diff --git a/openlp/plugins/songs/lib/olp1import.py b/openlp/plugins/songs/lib/olp1import.py index 232b17db0..7ba933a8f 100644 --- a/openlp/plugins/songs/lib/olp1import.py +++ b/openlp/plugins/songs/lib/olp1import.py @@ -32,7 +32,7 @@ import logging from chardet.universaldetector import UniversalDetector import sqlite -from openlp.core.ui.wizard import WizardStrings +from openlp.core.lib import translate from openlp.plugins.songs.lib import retrieve_windows_encoding from songimport import SongImport @@ -61,10 +61,15 @@ class OpenLP1SongImport(SongImport): """ Run the import for an openlp.org 1.x song database. """ - # Connect to the database + if not self.import_source.endswith(u'.olp'): + self.log_error(self.import_source, + translate('SongsPlugin.OpenLP1SongImport', + 'Not a valid openlp.org 1.x song database.')) + return encoding = self.get_encoding() if not encoding: - return False + return + # Connect to the database connection = sqlite.connect(self.import_source, mode=0444, encoding=(encoding, 'replace')) cursor = connection.cursor() @@ -72,12 +77,6 @@ class OpenLP1SongImport(SongImport): cursor.execute(u'SELECT name FROM sqlite_master ' u'WHERE type = \'table\' AND name = \'tracks\'') new_db = len(cursor.fetchall()) > 0 - # Count the number of records we need to import, for the progress bar - cursor.execute(u'-- types int') - cursor.execute(u'SELECT COUNT(songid) FROM songs') - count = cursor.fetchone()[0] - success = True - self.import_wizard.progressBar.setMaximum(count) # "cache" our list of authors cursor.execute(u'-- types int, unicode') cursor.execute(u'SELECT authorid, authorname FROM authors') @@ -92,37 +91,29 @@ class OpenLP1SongImport(SongImport): cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, ' u'copyrightinfo FROM songs') songs = cursor.fetchall() + self.import_wizard.progressBar.setMaximum(len(songs)) for song in songs: self.set_defaults() if self.stop_import_flag: - success = False break song_id = song[0] - title = song[1] + self.title = song[1] lyrics = song[2].replace(u'\r\n', u'\n') - copyright = song[3] - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % title) - self.title = title + self.add_copyright(song[3]) verses = lyrics.split(u'\n\n') - for verse in verses: - if verse.strip() != u'': - self.add_verse(verse.strip()) - self.add_copyright(copyright) + [self.add_verse(verse.strip()) for verse in verses if verse.strip()] cursor.execute(u'-- types int') cursor.execute(u'SELECT authorid FROM songauthors ' u'WHERE songid = %s' % song_id) author_ids = cursor.fetchall() for author_id in author_ids: if self.stop_import_flag: - success = False break for author in authors: if author[0] == author_id[0]: self.parse_author(author[1]) break if self.stop_import_flag: - success = False break if new_db: cursor.execute(u'-- types int') @@ -131,17 +122,15 @@ class OpenLP1SongImport(SongImport): track_ids = cursor.fetchall() for track_id in track_ids: if self.stop_import_flag: - success = False break for track in tracks: if track[0] == track_id[0]: self.add_media_file(track[1]) break if self.stop_import_flag: - success = False break - self.finish() - return success + if not self.finish(): + self.log_error(self.import_source) def get_encoding(self): """ diff --git a/openlp/plugins/songs/lib/olpimport.py b/openlp/plugins/songs/lib/olpimport.py index e8b9f0dd7..a201c1783 100644 --- a/openlp/plugins/songs/lib/olpimport.py +++ b/openlp/plugins/songs/lib/olpimport.py @@ -36,6 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.lib import translate from openlp.core.lib.db import BaseModel +from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile from songimport import SongImport @@ -93,13 +94,18 @@ class OpenLPSongImport(SongImport): The database providing the data to import. """ SongImport.__init__(self, manager, **kwargs) - self.import_source = u'sqlite:///%s' % self.import_source self.source_session = None def do_import(self): """ Run the import for an OpenLP version 2 song database. """ + if not self.import_source.endswith(u'.sqlite'): + self.log_error(self.import_source, + translate('SongsPlugin.OpenLPSongImport', + 'Not a valid OpenLP 2.0 song database.')) + return + self.import_source = u'sqlite:///%s' % self.import_source engine = create_engine(self.import_source) source_meta = MetaData() source_meta.reflect(engine) @@ -124,10 +130,10 @@ class OpenLPSongImport(SongImport): mapper(OldMediaFile, source_media_files_table) song_props = { 'authors': relation(OldAuthor, backref='songs', - secondary=source_authors_songs_table), + secondary=source_authors_songs_table), 'book': relation(OldBook, backref='songs'), 'topics': relation(OldTopic, backref='songs', - secondary=source_songs_topics_table) + secondary=source_songs_topics_table) } if has_media_files: song_props['media_files'] = relation(OldMediaFile, backref='songs', @@ -150,15 +156,9 @@ class OpenLPSongImport(SongImport): mapper(OldTopic, source_topics_table) source_songs = self.source_session.query(OldSong).all() - song_total = len(source_songs) if self.import_wizard: - self.import_wizard.progressBar.setMaximum(song_total) - song_count = 1 + self.import_wizard.progressBar.setMaximum(len(source_songs)) for song in source_songs: - if self.import_wizard: - self.import_wizard.incrementProgressBar( - unicode(translate('SongsPlugin.OpenLPSongImport', - 'Importing song %d of %d.')) % (song_count, song_total)) new_song = Song() new_song.title = song.title if has_media_files and hasattr(song, 'alternate_title'): @@ -213,8 +213,9 @@ class OpenLPSongImport(SongImport): # file_name=media_file.file_name)) clean_song(self.manager, new_song) self.manager.save_object(new_song) - song_count += 1 + if self.import_wizard: + self.import_wizard.incrementProgressBar( + WizardStrings.ImportingType % new_song.title) if self.stop_import_flag: - return False + break engine.dispose() - return True diff --git a/openlp/plugins/songs/lib/oooimport.py b/openlp/plugins/songs/lib/oooimport.py index 2ab66820c..d43541bc7 100644 --- a/openlp/plugins/songs/lib/oooimport.py +++ b/openlp/plugins/songs/lib/oooimport.py @@ -56,13 +56,11 @@ class OooImport(SongImport): self.process_started = False def do_import(self): - self.stop_import_flag = False - self.import_wizard.progressBar.setMaximum(0) self.start_ooo() + self.import_wizard.progressBar.setMaximum(len(self.import_source)) for filename in self.import_source: if self.stop_import_flag: - self.import_wizard.incrementProgressBar(u'Import cancelled', 0) - return + break filename = unicode(filename) if os.path.isfile(filename): self.open_ooo_file(filename) @@ -70,9 +68,6 @@ class OooImport(SongImport): self.process_ooo_document() self.close_ooo_file() self.close_ooo() - self.import_wizard.progressBar.setMaximum(1) - self.import_wizard.incrementProgressBar(u'', 1) - return True def process_ooo_document(self): """ diff --git a/openlp/plugins/songs/lib/openlyricsimport.py b/openlp/plugins/songs/lib/openlyricsimport.py index c29abc0b5..4c0e5189a 100644 --- a/openlp/plugins/songs/lib/openlyricsimport.py +++ b/openlp/plugins/songs/lib/openlyricsimport.py @@ -35,6 +35,7 @@ from lxml import etree from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib.songimport import SongImport +from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib import OpenLyrics log = logging.getLogger(__name__) @@ -59,7 +60,7 @@ class OpenLyricsImport(SongImport): parser = etree.XMLParser(remove_blank_text=True) for file_path in self.import_source: if self.stop_import_flag: - return False + return self.import_wizard.incrementProgressBar( WizardStrings.ImportingType % os.path.basename(file_path)) try: @@ -67,8 +68,7 @@ class OpenLyricsImport(SongImport): # special characters in the path (see lp:757673 and lp:744337). parsed_file = etree.parse(open(file_path, u'r'), parser) xml = unicode(etree.tostring(parsed_file)) - if self.openLyrics.xml_to_song(xml) is None: - log.debug(u'File could not be imported: %s' % file_path) + self.openLyrics.xml_to_song(xml) except etree.XMLSyntaxError: log.exception(u'XML syntax error in file %s' % file_path) - return True + self.log_error(file_path, SongStrings.XMLSyntaxError) diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index 37fc2b5ef..365f20268 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -26,13 +26,15 @@ import logging import os +import re from zipfile import ZipFile + from lxml import objectify from lxml.etree import Error, LxmlError -import re -from openlp.core.ui.wizard import WizardStrings +from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport +from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) @@ -105,77 +107,62 @@ class OpenSongImport(SongImport): Initialise the class. """ SongImport.__init__(self, manager, **kwargs) - self.commit = True def do_import(self): """ Import either each of the files in self.import_source - each element of which can be either a single opensong file, or a zipfile containing - multiple opensong files. If `self.commit` is set False, the - import will not be committed to the database (useful for test scripts). + multiple opensong files. """ - success = True numfiles = 0 for filename in self.import_source: ext = os.path.splitext(filename)[1] if ext.lower() == u'.zip': z = ZipFile(filename, u'r') numfiles += len(z.infolist()) + z.close() else: numfiles += 1 log.debug(u'Total number of files: %d', numfiles) self.import_wizard.progressBar.setMaximum(numfiles) for filename in self.import_source: if self.stop_import_flag: - success = False - break + return ext = os.path.splitext(filename)[1] if ext.lower() == u'.zip': log.debug(u'Zipfile found %s', filename) z = ZipFile(filename, u'r') for song in z.infolist(): if self.stop_import_flag: - success = False - break + z.close() + return parts = os.path.split(song.filename) if parts[-1] == u'': - #No final part => directory + # No final part => directory continue log.info(u'Zip importing %s', parts[-1]) - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % parts[-1]) - songfile = z.open(song) - if self.do_import_file(songfile) and self.commit and \ - not self.stop_import_flag: - self.finish() - else: - success = False - break + song_file = z.open(song) + self.do_import_file(song_file) + song_file.close() + z.close() else: # not a zipfile log.info(u'Direct import %s', filename) - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % os.path.split(filename)[-1]) song_file = open(filename) - if self.do_import_file(song_file) and self.commit and \ - not self.stop_import_flag: - self.finish() - else: - success = False - break - return success + self.do_import_file(song_file) + song_file.close() def do_import_file(self, file): """ - Process the OpenSong file - pass in a file-like object, - not a filename + Process the OpenSong file - pass in a file-like object, not a file path. """ self.set_defaults() try: tree = objectify.parse(file) except (Error, LxmlError): + self.log_error(file.name, SongStrings.XMLSyntaxError) log.exception(u'Error parsing XML') - return False + return root = tree.getroot() fields = dir(root) decode = { @@ -193,9 +180,6 @@ class OpenSongImport(SongImport): setattr(self, fn_or_string, ustring) else: fn_or_string(ustring) - if not len(self.title): - # to prevent creation of empty songs from wrong files - return False if u'theme' in fields and unicode(root.theme) not in self.topics: self.topics.append(unicode(root.theme)) if u'alttheme' in fields and unicode(root.alttheme) not in self.topics: @@ -205,11 +189,14 @@ class OpenSongImport(SongImport): # keep track of verses appearance order our_verse_order = [] # default verse - verse_tag = u'v' + verse_tag = VerseType.Tags[VerseType.Verse] verse_num = u'1' # for the case where song has several sections with same marker inst = 1 - lyrics = unicode(root.lyrics) + if u'lyrics' in fields: + lyrics = unicode(root.lyrics) + else: + lyrics = u'' for this_line in lyrics.split(u'\n'): # remove comments semicolon = this_line.find(u';') @@ -230,7 +217,7 @@ class OpenSongImport(SongImport): # have we got any digits? # If so, verse number is everything from the digits # to the end (even if there are some alpha chars on the end) - match = re.match(u'(.*)(\d+.*)', content) + match = re.match(u'(\D*)(\d+.*)', content) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) @@ -239,12 +226,13 @@ class OpenSongImport(SongImport): # the verse tag verse_tag = content verse_num = u'1' + verse_index = VerseType.from_loose_input(verse_tag) + verse_tag = VerseType.Tags[verse_index] inst = 1 if [verse_tag, verse_num, inst] in our_verse_order \ and verses.has_key(verse_tag) \ and verses[verse_tag].has_key(verse_num): - inst = len(verses[verse_tag][verse_num])+1 - our_verse_order.append([verse_tag, verse_num, inst]) + inst = len(verses[verse_tag][verse_num]) + 1 continue # number at start of line.. it's verse number if this_line[0].isdigit(): @@ -257,6 +245,7 @@ class OpenSongImport(SongImport): verses[verse_tag][verse_num] = {} if not verses[verse_tag][verse_num].has_key(inst): verses[verse_tag][verse_num][inst] = [] + our_verse_order.append([verse_tag, verse_num, inst]) # Tidy text and remove the ____s from extended words this_line = self.tidy_text(this_line) this_line = this_line.replace(u'_', u'') @@ -268,28 +257,31 @@ class OpenSongImport(SongImport): verse_def = u'%s%s' % (verse_tag, verse_num) lines = u'\n'.join(verses[verse_tag][verse_num][inst]) self.add_verse(lines, verse_def) + if not self.verses: + self.add_verse('') # figure out the presentation order, if present - if u'presentation' in fields and root.presentation != u'': + if u'presentation' in fields and root.presentation: order = unicode(root.presentation) # We make all the tags in the lyrics lower case, so match that here # and then split into a list on the whitespace order = order.lower().split() for verse_def in order: - match = re.match(u'(.*)(\d+.*)', verse_def) + match = re.match(u'(\D*)(\d+.*)', verse_def) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) if not len(verse_tag): - verse_tag = u'v' + verse_tag = VerseType.Tags[VerseType.Verse] else: # Assume it's no.1 if there are no digits verse_tag = verse_def verse_num = u'1' verse_def = u'%s%s' % (verse_tag, verse_num) - if verses.has_key(verse_tag) \ - and verses[verse_tag].has_key(verse_num): + if verses.has_key(verse_tag) and \ + verses[verse_tag].has_key(verse_num): self.verse_order_list.append(verse_def) else: log.info(u'Got order %s but not in verse tags, dropping' u'this item from presentation order', verse_def) - return True + if not self.finish(): + self.log_error(file.name) diff --git a/openlp/plugins/songs/lib/sofimport.py b/openlp/plugins/songs/lib/sofimport.py index 0b7e0c139..7f9fa16bc 100644 --- a/openlp/plugins/songs/lib/sofimport.py +++ b/openlp/plugins/songs/lib/sofimport.py @@ -88,7 +88,6 @@ class SofImport(OooImport): paragraphs = self.document.getText().createEnumeration() while paragraphs.hasMoreElements(): if self.stop_import_flag: - self.import_wizard.incrementProgressBar(u'Import cancelled', 0) return paragraph = paragraphs.nextElement() if paragraph.supportsService("com.sun.star.text.Paragraph"): diff --git a/openlp/plugins/songs/lib/songbeamerimport.py b/openlp/plugins/songs/lib/songbeamerimport.py index 5cd2c4329..5a8ee9ca8 100644 --- a/openlp/plugins/songs/lib/songbeamerimport.py +++ b/openlp/plugins/songs/lib/songbeamerimport.py @@ -33,9 +33,9 @@ import logging import os import re -from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport +from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) @@ -78,58 +78,55 @@ class SongBeamerImport(SongImport): """ Receive a single file or a list of files to import. """ - if isinstance(self.import_source, list): - self.import_wizard.progressBar.setMaximum( - len(self.import_source)) - for file in self.import_source: - # TODO: check that it is a valid SongBeamer file - self.set_defaults() - self.current_verse = u'' - self.current_verse_type = VerseType.Tags[VerseType.Verse] - read_verses = False - file_name = os.path.split(file)[1] - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name, 0) - if os.path.isfile(file): - detect_file = open(file, u'r') - details = chardet.detect(detect_file.read(2048)) - detect_file.close() - infile = codecs.open(file, u'r', details['encoding']) - songData = infile.readlines() - infile.close() - else: - return False - self.title = file_name.split('.sng')[0] - read_verses = False - for line in songData: - # Just make sure that the line is of the type 'Unicode'. - line = unicode(line).strip() - if line.startswith(u'#') and not read_verses: - self.parse_tags(line) - elif line.startswith(u'---'): - if self.current_verse: - self.replace_html_tags() - self.add_verse(self.current_verse, - self.current_verse_type) - self.current_verse = u'' - self.current_verse_type = VerseType.Tags[VerseType.Verse] - read_verses = True - verse_start = True - elif read_verses: - if verse_start: - verse_start = False - if not self.check_verse_marks(line): - self.current_verse = line + u'\n' - else: - self.current_verse += line + u'\n' - if self.current_verse: - self.replace_html_tags() - self.add_verse(self.current_verse, self.current_verse_type) - if self.check_complete(): - self.finish() - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name) - return True + self.import_wizard.progressBar.setMaximum(len(self.import_source)) + if not isinstance(self.import_source, list): + return + for file in self.import_source: + # TODO: check that it is a valid SongBeamer file + if self.stop_import_flag: + return + self.set_defaults() + self.current_verse = u'' + self.current_verse_type = VerseType.Tags[VerseType.Verse] + read_verses = False + file_name = os.path.split(file)[1] + if os.path.isfile(file): + detect_file = open(file, u'r') + details = chardet.detect(detect_file.read(2048)) + detect_file.close() + infile = codecs.open(file, u'r', details['encoding']) + songData = infile.readlines() + infile.close() + else: + continue + self.title = file_name.split('.sng')[0] + read_verses = False + for line in songData: + # Just make sure that the line is of the type 'Unicode'. + line = unicode(line).strip() + if line.startswith(u'#') and not read_verses: + self.parse_tags(line) + elif line.startswith(u'---'): + if self.current_verse: + self.replace_html_tags() + self.add_verse(self.current_verse, + self.current_verse_type) + self.current_verse = u'' + self.current_verse_type = VerseType.Tags[VerseType.Verse] + read_verses = True + verse_start = True + elif read_verses: + if verse_start: + verse_start = False + if not self.check_verse_marks(line): + self.current_verse = line + u'\n' + else: + self.current_verse += line + u'\n' + if self.current_verse: + self.replace_html_tags() + self.add_verse(self.current_verse, self.current_verse_type) + if not self.finish(): + self.log_error(file) def replace_html_tags(self): """ @@ -189,7 +186,7 @@ class SongBeamerImport(SongImport): elif tag_val[0] == u'#Bible': pass elif tag_val[0] == u'#Categories': - self.topics = line.split(',') + self.topics = tag_val[1].split(',') elif tag_val[0] == u'#CCLI': self.ccli_number = tag_val[1] elif tag_val[0] == u'#Chords': @@ -236,11 +233,12 @@ class SongBeamerImport(SongImport): pass elif tag_val[0] == u'#Rights': song_book_pub = tag_val[1] - elif tag_val[0] == u'#Songbook': - book_num = tag_val[1].split(' / ') - self.song_book_name = book_num[0] - if len(book_num) == book_num[1]: - self.song_number = u'' + elif tag_val[0] == u'#Songbook' or tag_val[0] == u'#SongBook': + book_data = tag_val[1].split(u'/') + self.song_book_name = book_data[0].strip() + if len(book_data) == 2: + number = book_data[1].strip() + self.song_number = number if number.isdigit() else u'' elif tag_val[0] == u'#Speed': pass elif tag_val[0] == u'Tempo': @@ -287,5 +285,4 @@ class SongBeamerImport(SongImport): if marks[1].isdigit(): self.current_verse_type += marks[1] return True - else: - return False + return False diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 5bc1f15d0..78275d210 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -23,12 +23,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - import logging import re + from PyQt4 import QtCore -from openlp.core.lib import Receiver, translate +from openlp.core.lib import Receiver, translate, check_directory_exists +from openlp.core.ui.wizard import WizardStrings +from openlp.core.utils import AppLocation from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.ui import SongStrings @@ -66,6 +68,7 @@ class SongImport(QtCore.QObject): self.song = None self.stop_import_flag = False self.set_defaults() + self.error_log = [] QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import) @@ -94,6 +97,32 @@ class SongImport(QtCore.QObject): self.copyright_string = unicode(translate( 'SongsPlugin.SongImport', 'copyright')) + def log_error(self, filepath, reason=SongStrings.SongIncomplete): + """ + This should be called, when a song could not be imported. + + ``filepath`` + This should be the file path if ``self.import_source`` is a list + 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. + + ``reason`` + The reason, why the import failed. The string should be as + informative as possible. + """ + self.set_defaults() + if self.import_wizard is None: + return + if self.import_wizard.errorReportTextEdit.isHidden(): + self.import_wizard.errorReportTextEdit.setText( + translate('SongsPlugin.SongImport', + 'The following songs could not be imported:')) + self.import_wizard.errorReportTextEdit.setVisible(True) + self.import_wizard.errorCopyToButton.setVisible(True) + self.import_wizard.errorSaveToButton.setVisible(True) + self.import_wizard.errorReportTextEdit.append( + u'- %s (%s)' % (filepath, reason)) + def stop_import(self): """ Sets the flag for importers to stop their import @@ -240,7 +269,7 @@ class SongImport(QtCore.QObject): Author not checked here, if no author then "Author unknown" is automatically added """ - if self.title == u'' or len(self.verses) == 0: + if not self.title or not len(self.verses): return False else: return True @@ -249,9 +278,15 @@ class SongImport(QtCore.QObject): """ All fields have been set to this song. Write the song to disk. """ + if not self.check_complete(): + self.set_defaults() + return False log.info(u'committing song %s to database', self.title) song = Song() song.title = self.title + if self.import_wizard is not None: + self.import_wizard.incrementProgressBar( + WizardStrings.ImportingType % song.title) song.alternate_title = self.alternate_title # Values will be set when cleaning the song. song.search_title = u'' @@ -308,7 +343,7 @@ class SongImport(QtCore.QObject): publisher=self.song_book_pub) song.book = song_book for topictext in self.topics: - if len(topictext) == 0: + if not topictext: continue topic = self.manager.get_object_filtered(Topic, Topic.name == topictext) @@ -318,6 +353,7 @@ class SongImport(QtCore.QObject): clean_song(self.manager, song) self.manager.save_object(song) self.set_defaults() + return True def print_song(self): """ diff --git a/openlp/plugins/songs/lib/songshowplusimport.py b/openlp/plugins/songs/lib/songshowplusimport.py index d5ab85f89..9fdc5804a 100644 --- a/openlp/plugins/songs/lib/songshowplusimport.py +++ b/openlp/plugins/songs/lib/songshowplusimport.py @@ -32,6 +32,7 @@ import logging import struct from openlp.core.ui.wizard import WizardStrings +from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport TITLE = 1 @@ -97,83 +98,81 @@ class SongShowPlusImport(SongImport): Receive a single file or a list of files to import. """ if isinstance(self.import_source, list): - self.import_wizard.progressBar.setMaximum(len(self.import_source)) - for file in self.import_source: - author = u'' - self.sspVerseOrderList = [] - otherCount = 0 - otherList = {} - file_name = os.path.split(file)[1] - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name, 0) - songData = open(file, 'rb') - while (1): - blockKey, = struct.unpack("I", songData.read(4)) - # The file ends with 4 NUL's - if blockKey == 0: - break - nextBlockStarts, = struct.unpack("I", songData.read(4)) - if blockKey == VERSE or blockKey == CHORUS: - null, verseNo, = struct.unpack("BB", songData.read(2)) - elif blockKey == CUSTOM_VERSE: - null, verseNameLength, = struct.unpack("BB", - songData.read(2)) - verseName = songData.read(verseNameLength) - lengthDescriptorSize, = struct.unpack("B", songData.read(1)) - # Detect if/how long the length descriptor is - if lengthDescriptorSize == 12: - lengthDescriptor, = struct.unpack("I", songData.read(4)) - elif lengthDescriptorSize == 2: - lengthDescriptor = 1 - elif lengthDescriptorSize == 9: - lengthDescriptor = 0 - else: - lengthDescriptor, = struct.unpack("B", songData.read(1)) - data = songData.read(lengthDescriptor) - if blockKey == TITLE: - self.title = unicode(data, u'cp1252') - elif blockKey == AUTHOR: - authors = data.split(" / ") - for author in authors: - if author.find(",") !=-1: - authorParts = author.split(", ") - author = authorParts[1] + " " + authorParts[0] - self.parse_author(unicode(author, u'cp1252')) - elif blockKey == COPYRIGHT: - self.add_copyright(unicode(data, u'cp1252')) - elif blockKey == CCLI_NO: - self.ccli_number = int(data) - elif blockKey == VERSE: - self.add_verse(unicode(data, u'cp1252'), - "V%s" % verseNo) - elif blockKey == CHORUS: - self.add_verse(unicode(data, u'cp1252'), - "C%s" % verseNo) - elif blockKey == TOPIC: - self.topics.append(unicode(data, u'cp1252')) - elif blockKey == COMMENTS: - self.comments = unicode(data, u'cp1252') - elif blockKey == VERSE_ORDER: - verseTag = self.toOpenLPVerseTag(data, True) - if verseTag: - self.sspVerseOrderList.append(unicode(verseTag, - u'cp1252')) - elif blockKey == SONG_BOOK: - self.song_book_name = unicode(data, u'cp1252') - elif blockKey == SONG_NUMBER: - self.song_number = ord(data) - elif blockKey == CUSTOM_VERSE: - verseTag = self.toOpenLPVerseTag(verseName) - self.add_verse(unicode(data, u'cp1252'), verseTag) - else: - log.debug("Unrecognised blockKey: %s, data: %s" - %(blockKey, data)) - self.verse_order_list = self.sspVerseOrderList - songData.close() - self.finish() - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name) - return True + return + self.import_wizard.progressBar.setMaximum(len(self.import_source)) + for file in self.import_source: + self.sspVerseOrderList = [] + otherCount = 0 + otherList = {} + file_name = os.path.split(file)[1] + self.import_wizard.incrementProgressBar( + WizardStrings.ImportingType % file_name, 0) + songData = open(file, 'rb') + while True: + blockKey, = struct.unpack("I", songData.read(4)) + # The file ends with 4 NUL's + if blockKey == 0: + break + nextBlockStarts, = struct.unpack("I", songData.read(4)) + if blockKey == VERSE or blockKey == CHORUS: + null, verseNo, = struct.unpack("BB", songData.read(2)) + elif blockKey == CUSTOM_VERSE: + null, verseNameLength, = struct.unpack("BB", + songData.read(2)) + verseName = songData.read(verseNameLength) + lengthDescriptorSize, = struct.unpack("B", songData.read(1)) + # Detect if/how long the length descriptor is + if lengthDescriptorSize == 12: + lengthDescriptor, = struct.unpack("I", songData.read(4)) + elif lengthDescriptorSize == 2: + lengthDescriptor = 1 + elif lengthDescriptorSize == 9: + lengthDescriptor = 0 + else: + lengthDescriptor, = struct.unpack("B", songData.read(1)) + data = songData.read(lengthDescriptor) + if blockKey == TITLE: + self.title = unicode(data, u'cp1252') + elif blockKey == AUTHOR: + authors = data.split(" / ") + for author in authors: + if author.find(",") !=-1: + authorParts = author.split(", ") + author = authorParts[1] + " " + authorParts[0] + self.parse_author(unicode(author, u'cp1252')) + elif blockKey == COPYRIGHT: + self.add_copyright(unicode(data, u'cp1252')) + elif blockKey == CCLI_NO: + self.ccli_number = int(data) + elif blockKey == VERSE: + self.add_verse(unicode(data, u'cp1252'), + "V%s" % verseNo) + elif blockKey == CHORUS: + self.add_verse(unicode(data, u'cp1252'), + "C%s" % verseNo) + elif blockKey == TOPIC: + self.topics.append(unicode(data, u'cp1252')) + elif blockKey == COMMENTS: + self.comments = unicode(data, u'cp1252') + elif blockKey == VERSE_ORDER: + verseTag = self.toOpenLPVerseTag(data, True) + if verseTag: + self.sspVerseOrderList.append(unicode(verseTag, + u'cp1252')) + elif blockKey == SONG_BOOK: + self.song_book_name = unicode(data, u'cp1252') + elif blockKey == SONG_NUMBER: + self.song_number = ord(data) + elif blockKey == CUSTOM_VERSE: + verseTag = self.toOpenLPVerseTag(verseName) + self.add_verse(unicode(data, u'cp1252'), verseTag) + else: + log.debug("Unrecognised blockKey: %s, data: %s" + % (blockKey, data)) + self.verse_order_list = self.sspVerseOrderList + songData.close() + if not self.finish(): + self.log_error(file) def toOpenLPVerseTag(self, verseName, ignoreUnique=False): if verseName.find(" ") != -1: @@ -185,22 +184,19 @@ class SongShowPlusImport(SongImport): verseNumber = "1" verseType = verseType.lower() if verseType == "verse": - verseTag = "V" + verseTag = VerseType.Tags[VerseType.Verse] elif verseType == "chorus": - verseTag = "C" + verseTag = VerseType.Tags[VerseType.Chorus] elif verseType == "bridge": - verseTag = "B" + verseTag = VerseType.Tags[VerseType.Bridge] elif verseType == "pre-chorus": - verseTag = "P" - elif verseType == "bridge": - verseTag = "B" + verseTag = VerseType.Tags[VerseType.PreChorus] else: if not self.otherList.has_key(verseName): if ignoreUnique: return None self.otherCount = self.otherCount + 1 self.otherList[verseName] = str(self.otherCount) - verseTag = "O" + verseTag = VerseType.Tags[VerseType.Other] verseNumber = self.otherList[verseName] - verseTag = verseTag + verseNumber - return verseTag + return verseTag + verseNumber diff --git a/openlp/plugins/songs/lib/ui.py b/openlp/plugins/songs/lib/ui.py index 0a389087e..4ed81e5d8 100644 --- a/openlp/plugins/songs/lib/ui.py +++ b/openlp/plugins/songs/lib/ui.py @@ -40,6 +40,8 @@ class SongStrings(object): CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.') SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular') SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural') + SongIncomplete = translate('OpenLP.Ui','Title and/or verses not found') SongMaintenance = translate('OpenLP.Ui', 'Song Maintenance') Topic = translate('OpenLP.Ui', 'Topic', 'Singular') Topics = translate('OpenLP.Ui', 'Topics', 'Plural') + XMLSyntaxError = translate('OpenLP.Ui', 'XML syntax error') diff --git a/openlp/plugins/songs/lib/wowimport.py b/openlp/plugins/songs/lib/wowimport.py index cbdd40758..f5293a53c 100644 --- a/openlp/plugins/songs/lib/wowimport.py +++ b/openlp/plugins/songs/lib/wowimport.py @@ -105,11 +105,7 @@ class WowImport(SongImport): if isinstance(self.import_source, list): self.import_wizard.progressBar.setMaximum(len(self.import_source)) for file in self.import_source: - author = u'' - copyright = u'' file_name = os.path.split(file)[1] - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name, 0) # Get the song title self.title = file_name.rpartition(u'.')[0] songData = open(file, 'rb') @@ -129,7 +125,7 @@ class WowImport(SongImport): self.line_text = unicode( songData.read(ord(songData.read(1))), u'cp1252') songData.seek(1, os.SEEK_CUR) - if block_text != u'': + if block_text: block_text += u'\n' block_text += self.line_text self.lines_to_read -= 1 @@ -138,22 +134,19 @@ class WowImport(SongImport): songData.seek(3, os.SEEK_CUR) # Blocks are seperated by 2 bytes, skip them, but not if # this is the last block! - if (block + 1) < no_of_blocks: + if block + 1 < no_of_blocks: songData.seek(2, os.SEEK_CUR) self.add_verse(block_text, block_type) # Now to extract the author author_length = ord(songData.read(1)) - if author_length != 0: - author = unicode(songData.read(author_length), u'cp1252') + if author_length: + self.parse_author( + unicode(songData.read(author_length), u'cp1252')) # Finally the copyright copyright_length = ord(songData.read(1)) - if copyright_length != 0: - copyright = unicode( - songData.read(copyright_length), u'cp1252') - self.parse_author(author) - self.add_copyright(copyright) + if copyright_length: + self.add_copyright(unicode( + songData.read(copyright_length), u'cp1252')) songData.close() - self.finish() - self.import_wizard.incrementProgressBar( - WizardStrings.ImportingType % file_name) - return True + if not self.finish(): + self.log_error(file) diff --git a/resources/images/general_revert.png b/resources/images/general_revert.png new file mode 100644 index 000000000..47d7415e2 Binary files /dev/null and b/resources/images/general_revert.png differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 0d3191eff..364a75810 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -52,6 +52,7 @@ general_open.png general_save.png general_email.png + general_revert.png slide_close.png