diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 4f0f78cd9..93d2dd953 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -384,6 +384,7 @@ def create_separated_list(stringlist): from eventreceiver import Receiver +from filedialog import FileDialog from listwidgetwithdnd import ListWidgetWithDnD from formattingtags import FormattingTags from spelltextedit import SpellTextEdit diff --git a/openlp/core/lib/filedialog.py b/openlp/core/lib/filedialog.py new file mode 100644 index 000000000..d1a5bd69a --- /dev/null +++ b/openlp/core/lib/filedialog.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +""" +Provide a work around for a bug in QFileDialog + +""" +import os +import urllib + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib.ui import UiStrings + +class FileDialog(QtGui.QFileDialog): + """ + Subclass QFileDialog to work round a bug + """ + @staticmethod + def getOpenFileNames(parent, title, path, filters): + """ + Reimplement getOpenFileNames to fix the way it returns some file + names that url encoded when selecting multiple files/ + """ + files = QtGui.QFileDialog.getOpenFileNames(parent, title, path, filters) + file_list = QtCore.QStringList() + for file in files: + file = unicode(file) + if not os.path.exists(file): + file = urllib.unquote(file) + if not os.path.exists(file): + QtGui.QMessageBox.information(parent, + UiStrings().FileNotFound, + UiStrings().FileNotFoundMessage % file) + continue + file_list.append(QtCore.QString(file)) + return file_list \ No newline at end of file diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py index ff091f2f4..146490395 100644 --- a/openlp/core/lib/formattingtags.py +++ b/openlp/core/lib/formattingtags.py @@ -30,6 +30,7 @@ Provide HTML Tag management and Formatting Tag access class """ import cPickle +import json from PyQt4 import QtCore @@ -71,8 +72,8 @@ class FormattingTags(object): if isinstance(tag[element], unicode): tag[element] = tag[element].encode('utf8') # Formatting Tags were also known as display tags. - Settings().setValue(u'displayTags/html_tags', - QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) + Settings().setValue(u'formattingTags/html_tags', + QtCore.QVariant(json.dumps(tags) if tags else u'')) @staticmethod def load_tags(): @@ -167,12 +168,24 @@ class FormattingTags(object): FormattingTags.add_html_tags(temporary_tags) # Formatting Tags were also known as display tags. - user_expands = Settings().value(u'displayTags/html_tags', + user_expands = Settings().value(u'formattingTags/html_tags', QtCore.QVariant(u'')).toString() + json_loaded = True + if not user_expands: + user_expands = Settings().value(u'displayTags/html_tags', + QtCore.QVariant(u'')).toString() + json_loaded = False # cPickle only accepts str not unicode strings user_expands_string = str(user_expands) if user_expands_string: - user_tags = cPickle.loads(user_expands_string) + if json_loaded: + user_tags = json.loads(user_expands_string) + else: + user_tags = cPickle.loads(user_expands_string) + # move the formatting tags to json and remove old settings + Settings().setValue(u'formattingTags/html_tags', + QtCore.QVariant(json.dumps(user_tags))) + Settings().remove(u'displayTags/html_tags') for tag in user_tags: for element in tag: if isinstance(tag[element], str): diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 541206828..9fd589a12 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -35,7 +35,7 @@ import re from PyQt4 import QtCore, QtGui -from openlp.core.lib import SettingsManager, OpenLPToolbar, ServiceItem, \ +from openlp.core.lib import FileDialog, SettingsManager, OpenLPToolbar, ServiceItem, \ StringContent, build_icon, translate, Receiver, ListWidgetWithDnD from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, create_widget_action, \ @@ -337,7 +337,7 @@ class MediaManagerItem(QtGui.QWidget): """ Add a file to the list widget to make it available for showing """ - files = QtGui.QFileDialog.getOpenFileNames( + files = FileDialog.getOpenFileNames( self, self.onNewPrompt, SettingsManager.get_last_dir(self.settingsSection), self.onNewFileMasks) diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index 5353d12f6..03403816a 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -77,6 +77,10 @@ class UiStrings(object): self.Error = translate('OpenLP.Ui', 'Error') self.Export = translate('OpenLP.Ui', 'Export') self.File = translate('OpenLP.Ui', 'File') + self.FileNotFound = unicode(translate('OpenLP.Ui', + 'File Not Found')) + self.FileNotFoundMessage = unicode(translate('OpenLP.Ui', + 'File %s not found.\nPlease try selecting it individually.')) self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit') self.Help = translate('OpenLP.Ui', 'Help') diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 0742a92b6..a24876e88 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -26,7 +26,9 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - +""" +The First Time Wizard +""" import io import logging import os @@ -48,6 +50,7 @@ from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage log = logging.getLogger(__name__) + class ThemeScreenshotThread(QtCore.QThread): """ This thread downloads the theme screenshots. @@ -56,6 +59,9 @@ class ThemeScreenshotThread(QtCore.QThread): QtCore.QThread.__init__(self, parent) def run(self): + """ + Run the thread. + """ themes = self.parent().config.get(u'themes', u'files') themes = themes.split(u',') themes_dir = self.parent().config.get(u'themes', u'directory') @@ -180,15 +186,28 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): Determine the next page in the Wizard to go to. """ Receiver.send_message(u'openlp_process_events') + # If we are currently on the plugins page if self.currentId() == FirstTimePage.Plugins: + # But we don't have Internet access if not self.webAccess: return FirstTimePage.NoInternet - else: + # The songs plugin is enabled + elif self.songsCheckBox.isChecked(): return FirstTimePage.Songs + # The Bibles plugin is enabled + elif self.bibleCheckBox.isChecked(): + return FirstTimePage.Bibles + else: + return FirstTimePage.Themes elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: return FirstTimePage.Progress + elif self.currentId() == FirstTimePage.Songs: + if self.bibleCheckBox.isChecked(): + return FirstTimePage.Bibles + else: + return FirstTimePage.Themes elif self.currentId() == FirstTimePage.Themes: Receiver.send_message(u'cursor_busy') Receiver.send_message(u'openlp_process_events') @@ -395,7 +414,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): self.max_progress += size if self.max_progress: # Add on 2 for plugins status setting plus a "finished" point. - self.max_progress = self.max_progress + 2 + self.max_progress += 2 self.progressBar.setValue(0) self.progressBar.setMinimum(0) self.progressBar.setMaximum(self.max_progress) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 2ba0bda3c..d792c4f99 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -34,7 +34,11 @@ import sys from openlp.core.lib import translate from openlp.core.lib.ui import add_welcome_page + class FirstTimePage(object): + """ + An enumeration object to make it easy for a developer to determine which page is which by index + """ Welcome = 0 Plugins = 1 NoInternet = 2 diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py index e580285b9..f2a529527 100644 --- a/openlp/core/ui/formattingtagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -109,6 +109,7 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): has been changed. """ self.savePushButton.setEnabled(True) + self.savePushButton.setDefault(True) def onNewClicked(self): """ @@ -123,11 +124,11 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): return # Add new tag to list tag = { - u'desc': translate('OpenLP.FormattingTagForm', 'New Tag'), + u'desc': unicode(translate('OpenLP.FormattingTagForm', 'New Tag')), u'start tag': u'{n}', - u'start html': translate('OpenLP.FormattingTagForm', ''), + u'start html': unicode(translate('OpenLP.FormattingTagForm', '')), u'end tag': u'{/n}', - u'end html': translate('OpenLP.FormattingTagForm', ''), + u'end html': unicode(translate('OpenLP.FormattingTagForm', '')), u'protected': False, u'temporary': False } diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index 1a1224360..264ac2ee2 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -92,14 +92,14 @@ class GeneralTab(SettingsTab): self.monitorLayout.addWidget(self.customWidthLabel, 3, 3) self.customWidthValueEdit = QtGui.QSpinBox(self.monitorGroupBox) self.customWidthValueEdit.setObjectName(u'customWidthValueEdit') - self.customWidthValueEdit.setMaximum(9999) + self.customWidthValueEdit.setRange(1, 9999) self.monitorLayout.addWidget(self.customWidthValueEdit, 4, 3) self.customHeightLabel = QtGui.QLabel(self.monitorGroupBox) self.customHeightLabel.setObjectName(u'customHeightLabel') self.monitorLayout.addWidget(self.customHeightLabel, 3, 4) self.customHeightValueEdit = QtGui.QSpinBox(self.monitorGroupBox) self.customHeightValueEdit.setObjectName(u'customHeightValueEdit') - self.customHeightValueEdit.setMaximum(9999) + self.customHeightValueEdit.setRange(1, 9999) self.monitorLayout.addWidget(self.customHeightValueEdit, 4, 4) self.displayOnMonitorCheck = QtGui.QCheckBox(self.monitorGroupBox) self.displayOnMonitorCheck.setObjectName(u'monitorComboBox') diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 11f451b4a..845382a28 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -89,7 +89,8 @@ VIDEO_EXT = [ u'*.xa', u'*.iso', u'*.vob', - u'*.webm' + u'*.webm', + u'*.divx' ] diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index 71a19b768..666e6abd8 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -229,32 +229,32 @@ FLASH_HTML = u""" """ VIDEO_EXT = [ - u'*.3gp' - , u'*.3gpp' - , u'*.3g2' - , u'*.3gpp2' - , u'*.aac' - , u'*.flv' - , u'*.f4a' - , u'*.f4b' - , u'*.f4p' - , u'*.f4v' - , u'*.mov' - , u'*.m4a' - , u'*.m4b' - , u'*.m4p' - , u'*.m4v' - , u'*.mkv' - , u'*.mp4' - , u'*.ogv' - , u'*.webm' - , u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi' - , u'*.swf' + u'*.3gp', + u'*.3gpp', + u'*.3g2', + u'*.3gpp2', + u'*.aac', + u'*.flv', + u'*.f4a', + u'*.f4b', + u'*.f4p', + u'*.f4v', + u'*.mov', + u'*.m4a', + u'*.m4b', + u'*.m4p', + u'*.m4v', + u'*.mkv', + u'*.mp4', + u'*.ogv', + u'*.webm', + u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi', + u'*.swf' ] AUDIO_EXT = [ - u'*.mp3' - , u'*.ogg' + u'*.mp3', + u'*.ogg' ] diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6593e1091..e6f00878f 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -32,6 +32,7 @@ import logging import os import shutil import zipfile +import json from tempfile import mkstemp from datetime import datetime, timedelta @@ -477,7 +478,7 @@ class ServiceManager(QtGui.QWidget): path_file_name = unicode(self.fileName()) path, file_name = os.path.split(path_file_name) basename = os.path.splitext(file_name)[0] - service_file_name = '%s.osd' % basename + service_file_name = '%s.osj' % basename log.debug(u'ServiceManager.saveFile - %s', path_file_name) SettingsManager.set_last_dir( self.mainwindow.serviceManagerSettingsSection, @@ -542,7 +543,7 @@ class ServiceManager(QtGui.QWidget): total_size += file_size log.debug(u'ServiceManager.saveFile - ZIP contents size is %i bytes' % total_size) - service_content = cPickle.dumps(service) + service_content = json.dumps(service) # Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be # extracted using unzip in UNIX. allow_zip_64 = (total_size > 2147483648 + len(service_content)) @@ -672,12 +673,15 @@ class ServiceManager(QtGui.QWidget): log.debug(u'Extract file: %s', osfile) zipinfo.filename = osfile zip.extract(zipinfo, self.servicePath) - if osfile.endswith(u'osd'): + if osfile.endswith(u'osd') or osfile.endswith(u'osj'): p_file = os.path.join(self.servicePath, osfile) if 'p_file' in locals(): Receiver.send_message(u'cursor_busy') fileTo = open(p_file, u'r') - items = cPickle.load(fileTo) + if p_file.endswith(u'osj'): + items = json.load(fileTo) + else: + items = cPickle.load(fileTo) fileTo.close() self.newFile() self.mainwindow.displayProgressBar(len(items)) @@ -792,7 +796,7 @@ class ServiceManager(QtGui.QWidget): self.serviceItems[item][u'service_item'].notes) if self.serviceNoteForm.exec_(): self.serviceItems[item][u'service_item'].notes = \ - self.serviceNoteForm.textEdit.toPlainText() + unicode(self.serviceNoteForm.textEdit.toPlainText()) self.repaintServiceList(item, -1) self.setModified() diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index 46e2de9e6..1ad562a4a 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -32,6 +32,7 @@ from PyQt4 import QtGui from openlp.core.lib import translate, SpellTextEdit from openlp.core.lib.ui import create_button_box + class ServiceNoteForm(QtGui.QDialog): """ This is the form that is used to edit the verses of the song. diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 2cb7bf55e..7381e32e7 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -36,7 +36,7 @@ from openlp.core.lib import Receiver, translate from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui import ThemeLayoutForm -from openlp.core.utils import get_images_filter +from openlp.core.utils import get_images_filter, is_not_image_file from themewizard import Ui_ThemeWizard log = logging.getLogger(__name__) @@ -78,6 +78,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): QtCore.SIGNAL(u'clicked()'), self.onGradientEndButtonClicked) QtCore.QObject.connect(self.imageBrowseButton, QtCore.SIGNAL(u'clicked()'), self.onImageBrowseButtonClicked) + QtCore.QObject.connect(self.imageFileEdit, + QtCore.SIGNAL(u'editingFinished()'), self.onImageFileEditEditingFinished) QtCore.QObject.connect(self.mainColorButton, QtCore.SIGNAL(u'clicked()'), self.onMainColorButtonClicked) QtCore.QObject.connect(self.outlineColorButton, @@ -233,7 +235,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): background_image = BackgroundType.to_string(BackgroundType.Image) if self.page(self.currentId()) == self.backgroundPage and \ self.theme.background_type == background_image and \ - self.imageFileEdit.text().isEmpty(): + is_not_image_file(self.theme.background_filename): QtGui.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'), translate('OpenLP.ThemeWizard', 'You have not selected a ' @@ -545,6 +547,12 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard): self.theme.background_filename = unicode(filename) self.setBackgroundPageValues() + def onImageFileEditEditingFinished(self): + """ + Background image path edited + """ + self.theme.background_filename = unicode(self.imageFileEdit.text()) + def onMainColorButtonClicked(self): self.theme.font_main_color = \ self._colorButton(self.theme.font_main_color) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 9804b3eca..9d53230a1 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -36,7 +36,7 @@ import re from xml.etree.ElementTree import ElementTree, XML from PyQt4 import QtCore, QtGui -from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ +from openlp.core.lib import FileDialog, OpenLPToolbar, get_text_file_string, build_icon, \ Receiver, SettingsManager, translate, check_item_selected, \ check_directory_exists, create_thumb, validate_thumb, ImageSource from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ @@ -420,7 +420,7 @@ class ThemeManager(QtGui.QWidget): attempting to extract OpenLP themes from those files. This process will load both OpenLP version 1 and version 2 themes. """ - files = QtGui.QFileDialog.getOpenFileNames(self, + files = FileDialog.getOpenFileNames(self, translate('OpenLP.ThemeManager', 'Select Theme Import File'), SettingsManager.get_last_dir(self.settingsSection), unicode(translate('OpenLP.ThemeManager', diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 664f98d8c..a33cd2604 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -364,6 +364,21 @@ def get_images_filter(): visible_formats, actual_formats) return IMAGES_FILTER +def is_not_image_file(file_name): + """ + Validate that the file is not an image file. + + ``file_name`` + File name to be checked. + """ + if not file_name: + return True + formats = [unicode(fmt).lower() + for fmt in QtGui.QImageReader.supportedImageFormats()] + file_part, file_extension = os.path.splitext(unicode(file_name)) + if file_extension[1:].lower() in formats and os.path.exists(file_name): + return False + return True def join_url(base, *args): """ diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 0cc3a1c7a..3def6bc3e 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -862,6 +862,26 @@ class BiblesResourcesDB(QtCore.QObject, Manager): return book[0] return None + @staticmethod + def get_language_by_id(language_id): + """ + Return a dict containing the language id, name and code by id. + + ``id`` + The id of the language in the database. + """ + log.debug(u'BiblesResourcesDB.get_language_by_id(%d)', language_id) + language = BiblesResourcesDB.run_sql(u'SELECT id, name, code FROM ' + u'language WHERE id = ?', (language_id,)) + if language: + return { + u'id': language[0][0], + u'name': unicode(language[0][1]), + u'code': unicode(language[0][2]) + } + else: + return None + @staticmethod def get_language(name): """ diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 214c427af..75a214c1e 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -245,6 +245,8 @@ class BGExtract(object): return None Receiver.send_message(u'openlp_process_events') div = soup.find('div', 'result-text-style-normal') + if not div: + return None self._clean_soup(div) span_list = div.findAll('span', 'text') log.debug('Span list: %s', span_list) @@ -375,6 +377,7 @@ class BSExtract(object): content = content.findAll(u'li') return [ book.contents[0].contents[0] for book in content + if len(book.contents[0].contents) ] diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index 1a0028f37..3c23e1c4a 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -34,7 +34,7 @@ import codecs import re from openlp.core.lib import Receiver, translate -from openlp.core.utils import AppLocation +from openlp.core.utils import AppLocation, LanguageManager from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB log = logging.getLogger(__name__) @@ -148,10 +148,22 @@ class OSISBible(BibleDB): self.filename) return False book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) - if not db_book or db_book.name != book_details[u'name']: - log.debug(u'New book: "%s"' % book_details[u'name']) + bible_language = BiblesResourcesDB.get_language_by_id(language_id) + if bible_language is not None: + # The language of this bible was found, so we can + # translate the name of this book + custom_translator = LanguageManager.get_translator( + bible_language['code'])[0] + book_name_localized = unicode(custom_translator.translate( + 'BiblesPlugin', book_details[u'name'])) + else: + # The language of this bible was not found, so we just + # use the English name for this book + book_name_localized = book_details[u'name'] + if not db_book or db_book.name != book_name_localized: + log.debug(u'New book: "%s"' % book_name_localized) db_book = self.create_book( - book_details[u'name'], + book_name_localized, book_ref_id, book_details[u'testament_id']) if last_chapter == 0: @@ -162,7 +174,7 @@ class OSISBible(BibleDB): self.wizard.incrementProgressBar(unicode(translate( 'BiblesPlugin.OsisImport', 'Importing %s %s...', 'Importing ...')) % - (book_details[u'name'], chapter)) + (book_name_localized, chapter)) last_chapter = chapter # All of this rigmarol below is because the mod2osis # tool from the Sword library embeds XML in the OSIS diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index aef32dabc..37890eb37 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -44,6 +44,7 @@ import time if os.name == u'nt': from win32com.client import Dispatch import pywintypes + # Declare an empty exception to match the exception imported from UNO class ErrorCodeIOException(Exception): pass @@ -63,6 +64,7 @@ from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) + class ImpressController(PresentationController): """ Class to control interactions with Impress presentations. @@ -79,7 +81,7 @@ class ImpressController(PresentationController): PresentationController.__init__(self, plugin, u'Impress', ImpressDocument) self.supports = [u'odp'] - self.alsosupports = [u'ppt', u'pps', u'pptx', u'ppsx'] + self.alsosupports = [u'ppt', u'pps', u'pptx', u'ppsx', u'pptm'] self.process = None self.desktop = None self.manager = None diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index b307eebb3..2f944810f 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -45,6 +45,7 @@ log = logging.getLogger(__name__) ERROR = QtGui.QImage(u':/general/general_delete.png') + class PresentationMediaItem(MediaManagerItem): """ This is the Presentation media manager item for Presentation Items. @@ -88,10 +89,10 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[controller].enabled(): types = self.controllers[controller].supports + \ self.controllers[controller].alsosupports - for type in types: - if fileType.find(type) == -1: - fileType += u'*.%s ' % type - self.plugin.serviceManager.supportedSuffixes(type) + for type_ in types: + if fileType.find(type_) == -1: + fileType += u'*.%s ' % type_ + self.plugin.serviceManager.supportedSuffixes(type_) self.onNewFileMasks = unicode(translate('PresentationPlugin.MediaItem', 'Presentations (%s)')) % fileType diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index fac11a2b6..4f078f928 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -43,6 +43,7 @@ log = logging.getLogger(__name__) # PPT API documentation: # http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx + class PowerpointController(PresentationController): """ Class to control interactions with PowerPoint Presentations @@ -58,7 +59,7 @@ class PowerpointController(PresentationController): log.debug(u'Initialising') PresentationController.__init__(self, plugin, u'Powerpoint', PowerpointDocument) - self.supports = [u'ppt', u'pps', u'pptx', u'ppsx'] + self.supports = [u'ppt', u'pps', u'pptx', u'ppsx', u'pptm'] self.process = None def check_available(self): diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index a568985de..0242f0597 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -38,6 +38,7 @@ from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) + class PptviewController(PresentationController): """ Class to control interactions with PowerPOint Viewer Presentations @@ -54,7 +55,7 @@ class PptviewController(PresentationController): self.process = None PresentationController.__init__(self, plugin, u'Powerpoint Viewer', PptviewDocument) - self.supports = [u'ppt', u'pps', u'pptx', u'ppsx'] + self.supports = [u'ppt', u'pps', u'pptx', u'ppsx', u'pptm'] def check_available(self): """ diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 7593e4a95..3c5ddb010 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -34,7 +34,7 @@ import shutil from PyQt4 import QtCore, QtGui -from openlp.core.lib import PluginStatus, Receiver, MediaType, translate, \ +from openlp.core.lib import FileDialog, PluginStatus, Receiver, MediaType, translate, \ create_separated_list, check_directory_exists from openlp.core.lib.ui import UiStrings, set_case_insensitive_completer, \ critical_error_message_box, find_and_set_in_combo_box @@ -47,6 +47,7 @@ from editsongdialog import Ui_EditSongDialog log = logging.getLogger(__name__) + class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Class to manage the editing of a song @@ -229,6 +230,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.loadTopics() self.loadBooks() self.loadMediaFiles() + self.themeComboBox.setEditText(u'') self.themeComboBox.setCurrentIndex(0) # it's a new song to preview is not possible self.previewButton.setVisible(False) @@ -261,6 +263,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): if self.song.theme_name: find_and_set_in_combo_box( self.themeComboBox, unicode(self.song.theme_name)) + else: + self.themeComboBox.setEditText(u'') + self.themeComboBox.setCurrentIndex(0) self.copyrightEdit.setText( self.song.copyright if self.song.copyright else u'') self.commentsEdit.setPlainText( @@ -757,7 +762,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Loads file(s) from the filesystem. """ filters = u'%s (*)' % UiStrings().AllFiles - filenames = QtGui.QFileDialog.getOpenFileNames(self, + filenames = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), QtCore.QString(), filters) for filename in filenames: diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index e58137903..2db16abc5 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -35,7 +35,7 @@ import os from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver, SettingsManager, translate +from openlp.core.lib import FileDialog, Receiver, SettingsManager, translate from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.settings import Settings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings @@ -281,7 +281,7 @@ class SongImportForm(OpenLPWizard): if filters: filters += u';;' filters += u'%s (*)' % UiStrings().AllFiles - filenames = QtGui.QFileDialog.getOpenFileNames(self, title, + filenames = FileDialog.getOpenFileNames(self, title, SettingsManager.get_last_dir(self.plugin.settingsSection, 1), filters) if filenames: diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index fc9afd602..1ac37e29d 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -96,6 +96,7 @@ import os from lxml import etree, objectify +from openlp.core.lib import translate from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.songimport import SongImport @@ -115,7 +116,7 @@ class FoilPresenterImport(SongImport): """ log.debug(u'initialise FoilPresenterImport') SongImport.__init__(self, manager, **kwargs) - self.FoilPresenter = FoilPresenter(self.manager) + self.FoilPresenter = FoilPresenter(self.manager, self) def doImport(self): """ @@ -202,8 +203,9 @@ class FoilPresenter(object): tag. """ - def __init__(self, manager): + def __init__(self, manager, importer): self.manager = manager + self.importer = importer def xml_to_song(self, xml): """ @@ -222,6 +224,7 @@ class FoilPresenter(object): song.search_lyrics = u'' song.verse_order = u'' song.search_title = u'' + self.save_song = True # Because "text" seems to be an reserverd word, we have to recompile it. xml = re.compile(u'').sub(u'', xml) xml = re.compile(u'').sub(u'', xml) @@ -236,8 +239,9 @@ class FoilPresenter(object): self._process_authors(foilpresenterfolie, song) self._process_songbooks(foilpresenterfolie, song) self._process_topics(foilpresenterfolie, song) - clean_song(self.manager, song) - self.manager.save_object(song) + if self.save_song: + clean_song(self.manager, song) + self.manager.save_object(song) def _child(self, element): """ @@ -424,6 +428,12 @@ class FoilPresenter(object): VerseType.Tags[VerseType.Intro]: 1, VerseType.Tags[VerseType.PreChorus]: 1 } + if not hasattr(foilpresenterfolie.strophen, u'strophe'): + self.importer.logError(self._child(foilpresenterfolie.titel), + unicode(translate('SongsPlugin.FoilPresenterSongImport', + 'Invalid Foilpresenter song file. No verses found.'))) + self.save_song = False + return for strophe in foilpresenterfolie.strophen.strophe: text = self._child(strophe.text_) if hasattr(strophe, u'text_') \ else u'' diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index f39ad0495..00d6c89b0 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -49,6 +49,9 @@ class OpenSongImport(SongImport): the OpenSong web site. However, it doesn't describe the section, so here's an attempt: + If the first charachter of a line is a space, then the rest of that line + is lyrics. If it is not a space the following applies. + Verses can be expressed in one of 2 ways, either in complete verses, or by line grouping, i.e. grouping all line 1's of a verse together, all line 2's of a verse together, and so on. @@ -171,13 +174,11 @@ class OpenSongImport(SongImport): else: lyrics = u'' for this_line in lyrics.split(u'\n'): - # remove comments - semicolon = this_line.find(u';') - if semicolon >= 0: - this_line = this_line[:semicolon] - this_line = this_line.strip() if not this_line: continue + # skip this line if it is a comment + if this_line.startswith(u';'): + continue # skip guitar chords and page and column breaks if this_line.startswith(u'.') or this_line.startswith(u'---') \ or this_line.startswith(u'-!!'): @@ -212,7 +213,6 @@ class OpenSongImport(SongImport): if this_line[0].isdigit(): verse_num = this_line[0] this_line = this_line[1:].strip() - our_verse_order.append([verse_tag, verse_num, inst]) verses.setdefault(verse_tag, {}) verses[verse_tag].setdefault(verse_num, {}) if inst not in verses[verse_tag][verse_num]: @@ -222,6 +222,7 @@ class OpenSongImport(SongImport): this_line = self.tidyText(this_line) this_line = this_line.replace(u'_', u'') this_line = this_line.replace(u'|', u'\n') + this_line = this_line.strip() verses[verse_tag][verse_num][inst].append(this_line) # done parsing # add verses in original order diff --git a/openlp/plugins/songs/lib/songshowplusimport.py b/openlp/plugins/songs/lib/songshowplusimport.py index 04fe10370..45746bdd1 100644 --- a/openlp/plugins/songs/lib/songshowplusimport.py +++ b/openlp/plugins/songs/lib/songshowplusimport.py @@ -30,13 +30,14 @@ The :mod:`songshowplusimport` module provides the functionality for importing SongShow Plus songs into the OpenLP database. """ +import chardet import os import logging import re import struct from openlp.core.ui.wizard import WizardStrings -from openlp.plugins.songs.lib import VerseType +from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding from openlp.plugins.songs.lib.songimport import SongImport TITLE = 1 @@ -142,44 +143,44 @@ class SongShowPlusImport(SongImport): log.debug(length_descriptor_size) data = song_data.read(length_descriptor) if block_key == TITLE: - self.title = unicode(data, u'cp1252') + self.title = self.decode(data) elif block_key == AUTHOR: authors = data.split(" / ") for author in authors: if author.find(",") !=-1: authorParts = author.split(", ") author = authorParts[1] + " " + authorParts[0] - self.parseAuthor(unicode(author, u'cp1252')) + self.parseAuthor(self.decode(author)) elif block_key == COPYRIGHT: - self.addCopyright(unicode(data, u'cp1252')) + self.addCopyright(self.decode(data)) elif block_key == CCLI_NO: self.ccliNumber = int(data) elif block_key == VERSE: - self.addVerse(unicode(data, u'cp1252'), + self.addVerse(self.decode(data), "%s%s" % (VerseType.Tags[VerseType.Verse], verse_no)) elif block_key == CHORUS: - self.addVerse(unicode(data, u'cp1252'), + self.addVerse(self.decode(data), "%s%s" % (VerseType.Tags[VerseType.Chorus], verse_no)) elif block_key == BRIDGE: - self.addVerse(unicode(data, u'cp1252'), + self.addVerse(self.decode(data), "%s%s" % (VerseType.Tags[VerseType.Bridge], verse_no)) elif block_key == TOPIC: - self.topics.append(unicode(data, u'cp1252')) + self.topics.append(self.decode(data)) elif block_key == COMMENTS: - self.comments = unicode(data, u'cp1252') + self.comments = self.decode(data) elif block_key == VERSE_ORDER: verse_tag = self.toOpenLPVerseTag(data, True) if verse_tag: if not isinstance(verse_tag, unicode): - verse_tag = unicode(verse_tag, u'cp1252') + verse_tag = self.decode(verse_tag) self.sspVerseOrderList.append(verse_tag) elif block_key == SONG_BOOK: - self.songBookName = unicode(data, u'cp1252') + self.songBookName = self.decode(data) elif block_key == SONG_NUMBER: self.songNumber = ord(data) elif block_key == CUSTOM_VERSE: verse_tag = self.toOpenLPVerseTag(verse_name) - self.addVerse(unicode(data, u'cp1252'), verse_tag) + self.addVerse(self.decode(data), verse_tag) else: log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data)) @@ -221,3 +222,9 @@ class SongShowPlusImport(SongImport): verse_tag = VerseType.Tags[VerseType.Other] verse_number = self.otherList[verse_name] return verse_tag + verse_number + + def decode(self, data): + try: + return unicode(data, chardet.detect(data)['encoding']) + except: + return unicode(data, retrieve_windows_encoding()) diff --git a/openlp/plugins/songs/lib/sundayplusimport.py b/openlp/plugins/songs/lib/sundayplusimport.py index 9af6099e0..410c35e88 100644 --- a/openlp/plugins/songs/lib/sundayplusimport.py +++ b/openlp/plugins/songs/lib/sundayplusimport.py @@ -68,7 +68,7 @@ class SundayPlusImport(SongImport): for filename in self.importSource: if self.stopImportFlag: return - song_file = open(filename) + song_file = open(filename, 'rb') self.doImportFile(song_file) song_file.close() @@ -96,7 +96,7 @@ class SundayPlusImport(SongImport): # Now we are looking for the name. if data[i:i + 1] == '#': name_end = data.find(':', i + 1) - name = data[i + 1:name_end] + name = data[i + 1:name_end].upper() i = name_end + 1 while data[i:i + 1] == ' ': i += 1 @@ -122,13 +122,13 @@ class SundayPlusImport(SongImport): value = data[i:end] # If we are in the main group. if cell == False: - if name == 'title': + if name == 'TITLE': self.title = self.decode(self.unescape(value)) - elif name == 'Author': + elif name == 'AUTHOR': author = self.decode(self.unescape(value)) if len(author): self.addAuthor(author) - elif name == 'Copyright': + elif name == 'COPYRIGHT': self.copyright = self.decode(self.unescape(value)) elif name[0:4] == 'CELL': self.parse(value, cell = name[4:]) @@ -142,13 +142,13 @@ class SundayPlusImport(SongImport): if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: verse_type = "%s%s" % (verse_type, value[-1]) - elif name == 'Hotkey': + elif name == 'HOTKEY': # Hotkey always appears after MARKER_NAME, so it # effectively overrides MARKER_NAME, if present. if len(value) and \ value in HOTKEY_TO_VERSE_TYPE.keys(): verse_type = HOTKEY_TO_VERSE_TYPE[value] - if name == 'rtf': + if name == 'RTF': value = self.unescape(value) result = strip_rtf(value, self.encoding) if result is None: diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 1cd349f13..db0defa8e 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -79,6 +79,33 @@ NAMESPACE = u'http://openlyrics.info/namespace/2009/song' NSMAP = '{' + NAMESPACE + '}' + '%s' +def valid_XML_char_ordinal(char): + """ + Undertake the filter test. + + ``char`` + The individual character to be checked. + """ + return ( + 0x20 <= char <= 0xD7FF + or char in (0x9, 0xA, 0xD) + or 0xE000 <= char <= 0xFFFD + or 0x10000 <= char <= 0x10FFFF + ) + + +def clean_xml_string(xml): + """ + Filter out invalid characters in xml + Source + + ``xml`` + The actual text to be checked. + + """ + return ''.join(char for char in xml if valid_XML_char_ordinal(ord(char))) + + class SongXML(object): """ This class builds and parses the XML used to describe songs. @@ -112,6 +139,7 @@ class SongXML(object): The verse's language code (ISO-639). This is not required, but should be added if available. """ + content = clean_xml_string(content) verse = etree.Element(u'verse', type=unicode(type), label=unicode(number)) if lang: