diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index e627b1ad8..a687ded64 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -80,6 +80,9 @@ def get_text_file_string(text_file): content_string = None try: file_handle = open(text_file, u'r') + if not file_handle.read(3) == '\xEF\xBB\xBF': + # no BOM was found + file_handle.seek(0) content = file_handle.read() content_string = content.decode(u'utf-8') except (IOError, UnicodeError): diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py index ea5547f27..bc0055325 100644 --- a/openlp/core/lib/formattingtags.py +++ b/openlp/core/lib/formattingtags.py @@ -149,11 +149,17 @@ class FormattingTags(object): tags = [] for tag in FormattingTags.html_expands: if not tag[u'protected'] and not tag.get(u'temporary'): - tags.append(tag) - # Remove key 'temporary' from tags. It is not needed to be saved. - for tag in tags: - if u'temporary' in tag: - del tag[u'temporary'] + # Using dict ensures that copy is made and encoding of values + # a little later does not affect tags in the original list + tags.append(dict(tag)) + tag = tags[-1] + # Remove key 'temporary' from tags. + # It is not needed to be saved. + if u'temporary' in tag: + del tag[u'temporary'] + for element in tag: + if isinstance(tag[element], unicode): + tag[element] = tag[element].encode('utf8') # Formatting Tags were also known as display tags. QtCore.QSettings().setValue(u'displayTags/html_tags', QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) @@ -171,9 +177,13 @@ class FormattingTags(object): user_expands = QtCore.QSettings().value(u'displayTags/html_tags', QtCore.QVariant(u'')).toString() # cPickle only accepts str not unicode strings - user_expands_string = str(unicode(user_expands).encode(u'utf8')) + user_expands_string = str(user_expands) if user_expands_string: user_tags = cPickle.loads(user_expands_string) + for tag in user_tags: + for element in tag: + if isinstance(tag[element], str): + tag[element] = tag[element].decode('utf8') # If we have some user ones added them as well FormattingTags.add_html_tags(user_tags) diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 8216d3e7a..92849e0b9 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -126,7 +126,8 @@ class Ui_AboutDialog(object): u'Tim "TRB143" Bentley (Fedora and Android)', u'Matthias "matthub" Hub (Mac OS X)', u'Stevan "ElderP" Pettit (Windows)', - u'Raoul "superfly" Snyman (Ubuntu)'] + u'Raoul "superfly" Snyman (Ubuntu)', + u'Garrett "floft" Wilson (Arch Linux)'] translators = { u'af': [u'Johan "nuvolari" Mynhardt'], u'de': [u'Patrick "madmuffin" Br\xfcckner', diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py index 6611a9df7..7d233a4fa 100644 --- a/openlp/core/ui/filerenamedialog.py +++ b/openlp/core/ui/filerenamedialog.py @@ -41,7 +41,7 @@ class Ui_FileRenameDialog(object): self.dialogLayout.addWidget(self.fileNameLabel, 0, 0) self.fileNameEdit = QtGui.QLineEdit(fileRenameDialog) self.fileNameEdit.setValidator(QtGui.QRegExpValidator( - QtCore.QRegExp(r'[^/\\?*|<>\[\]":<>+%]+'), self)) + QtCore.QRegExp(r'[^/\\?*|<>\[\]":+%]+'), self)) self.fileNameEdit.setObjectName(u'fileNameEdit') self.dialogLayout.addWidget(self.fileNameEdit, 0, 1) self.buttonBox = create_accept_reject_button_box(fileRenameDialog, True) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index b58afeb1e..8697cc7a0 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -219,6 +219,7 @@ class MediaController(object): # Build the seekSlider. controller.seekSlider = QtGui.QSlider(QtCore.Qt.Horizontal) controller.seekSlider.setMaximum(1000) + controller.seekSlider.setTracking(False) controller.seekSlider.setToolTip(translate( 'OpenLP.SlideController', 'Video position.')) controller.seekSlider.setGeometry(QtCore.QRect(90, 260, 221, 24)) @@ -231,6 +232,7 @@ class MediaController(object): controller.volumeSlider.setTickPosition(QtGui.QSlider.TicksAbove) controller.volumeSlider.setMinimum(0) controller.volumeSlider.setMaximum(100) + controller.volumeSlider.setTracking(True) controller.volumeSlider.setToolTip(translate( 'OpenLP.SlideController', 'Audio Volume.')) controller.volumeSlider.setValue(controller.media_info.volume) @@ -242,9 +244,9 @@ class MediaController(object): controller.mediabar.setVisible(False) # Signals QtCore.QObject.connect(controller.seekSlider, - QtCore.SIGNAL(u'sliderMoved(int)'), controller.sendToPlugins) + QtCore.SIGNAL(u'valueChanged(int)'), controller.sendToPlugins) QtCore.QObject.connect(controller.volumeSlider, - QtCore.SIGNAL(u'sliderMoved(int)'), controller.sendToPlugins) + QtCore.SIGNAL(u'valueChanged(int)'), controller.sendToPlugins) def setup_special_controls(self, controller, control_panel): """ diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 0eb1e77c5..67c12ed4a 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -43,8 +43,7 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ context_menu_action, context_menu_separator, find_and_set_in_combo_box from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm from openlp.core.ui.printserviceform import PrintServiceForm -from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \ - split_filename +from openlp.core.utils import AppLocation, delete_file, split_filename from openlp.core.utils.actions import ActionList, CategoryOrder class ServiceManagerList(QtGui.QTreeWidget): @@ -636,8 +635,11 @@ class ServiceManager(QtGui.QWidget): try: zip = zipfile.ZipFile(fileName) for zipinfo in zip.infolist(): - ucsfile = file_is_unicode(zipinfo.filename) - if not ucsfile: + try: + ucsfile = zipinfo.filename.decode(u'utf-8') + except UnicodeDecodeError: + log.exception(u'Filename "%s" is not valid UTF-8' % + zipinfo.filename.decode(u'utf-8', u'replace')) critical_error_message_box( message=translate('OpenLP.ServiceManager', 'File is not a valid service.\n' diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 1eccddc95..711b46c43 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -344,8 +344,11 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): if category.name is None: continue for action in category.actions: - if self.changedActions .has_key(action): + if action in self.changedActions: + old_shortcuts = map(unicode, + map(QtGui.QKeySequence.toString, action.shortcuts())) action.setShortcuts(self.changedActions[action]) + self.action_list.update_shortcut_map(action, old_shortcuts) settings.setValue( action.objectName(), QtCore.QVariant(action.shortcuts())) settings.endGroup() @@ -452,7 +455,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): those shortcuts which are not saved yet but already assigned (as changes are applied when closing the dialog). """ - if self.changedActions.has_key(action): + if action in self.changedActions: return self.changedActions[action] return action.shortcuts() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index cd4663297..b6e18c32a 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -95,7 +95,7 @@ class SlideController(Controller): u'Edit Song', ] self.nextPreviousList = [ - u'Previous Slide', + u'Previous Slide', u'Next Slide' ] self.timer_id = 0 @@ -114,8 +114,8 @@ class SlideController(Controller): self.typeLabel.setText(UiStrings().Live) self.split = 1 self.typePrefix = u'live' - self.keypress_queue = deque() - self.keypress_loop = False + self.keypress_queue = deque() + self.keypress_loop = False else: self.typeLabel.setText(UiStrings().Preview) self.split = 0 @@ -187,7 +187,7 @@ class SlideController(Controller): translate('OpenLP.SlideController', 'Hide'), self.toolbar)) self.blankScreen = shortcut_action(self.hideMenu, u'blankScreen', [QtCore.Qt.Key_Period], self.onBlankDisplay, - u':/slides/slide_blank.png', False, + u':/slides/slide_blank.png', False, unicode(UiStrings().LiveToolbar)) self.blankScreen.setText( translate('OpenLP.SlideController', 'Blank Screen')) @@ -412,6 +412,9 @@ class SlideController(Controller): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_live_spin_delay'), self.receiveSpinDelay) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'slidecontroller_toggle_display'), + self.toggleDisplay) self.toolbar.makeWidgetsInvisible(self.loopList) else: QtCore.QObject.connect(self.previewListWidget, @@ -570,6 +573,21 @@ class SlideController(Controller): self.display.setVisible(False) self.mediaController.video_stop([self]) + def toggleDisplay(self, action): + """ + Toggle the display settings triggered from remote messages. + """ + if action == u'blank' or action == u'hide': + self.onBlankDisplay(True) + elif action == u'theme': + self.onThemeDisplay(True) + elif action == u'desktop': + self.onHideDisplay(True) + elif action == u'show': + self.onBlankDisplay(False) + self.onThemeDisplay(False) + self.onHideDisplay(False) + def servicePrevious(self): """ Live event to select the previous service item from the service manager. @@ -618,8 +636,8 @@ class SlideController(Controller): self.previewSizeChanged() self.previewDisplay.setup() serviceItem = ServiceItem() - self.previewDisplay.webView.setHtml(build_html(serviceItem, - self.previewDisplay.screen, None, self.isLive, None, + self.previewDisplay.webView.setHtml(build_html(serviceItem, + self.previewDisplay.screen, None, self.isLive, None, plugins=PluginManager.get_instance().plugins)) self.mediaController.setup_display(self.previewDisplay) if self.serviceItem: diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index b8767d736..5dfb6a0f0 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -30,6 +30,7 @@ import zipfile import shutil import logging import locale +import re from xml.etree.ElementTree import ElementTree, XML from PyQt4 import QtCore, QtGui @@ -43,8 +44,7 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ context_menu_action, context_menu_separator from openlp.core.theme import Theme from openlp.core.ui import FileRenameForm, ThemeForm -from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \ - get_filesystem_encoding +from openlp.core.utils import AppLocation, delete_file, get_filesystem_encoding log = logging.getLogger(__name__) @@ -147,6 +147,7 @@ class ThemeManager(QtGui.QWidget): check_directory_exists(self.thumbPath) self.themeForm.path = self.path self.oldBackgroundImage = None + self.bad_v1_name_chars = re.compile(r'[%+\[\]]') # Last little bits of setting up self.configUpdated() @@ -524,44 +525,50 @@ class ThemeManager(QtGui.QWidget): filexml = None try: zip = zipfile.ZipFile(filename) - themename = None - for file in zip.namelist(): - # Handle UTF-8 files - ucsfile = file_is_unicode(file) - if not ucsfile: - # Handle native Unicode files from Windows - ucsfile = file - osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) - theme_dir = None - if osfile.endswith(os.path.sep): - theme_dir = os.path.join(dir, osfile) - check_directory_exists(theme_dir) - else: - fullpath = os.path.join(dir, osfile) - names = osfile.split(os.path.sep) - if len(names) > 1: - # not preview file - if themename is None: - themename = names[0] - if theme_dir is None: - theme_dir = os.path.join(dir, names[0]) - check_directory_exists(theme_dir) - if os.path.splitext(ucsfile)[1].lower() in [u'.xml']: - xml_data = zip.read(file) - xml_data = file_is_unicode(xml_data) - if not xml_data: - break - filexml = self._checkVersionAndConvert(xml_data) - outfile = open(fullpath, u'w') - outfile.write(filexml.encode(u'utf-8')) - else: - outfile = open(fullpath, u'wb') - outfile.write(zip.read(file)) - except (IOError, NameError, zipfile.BadZipfile): - critical_error_message_box( - translate('OpenLP.ThemeManager', 'Validation Error'), - translate('OpenLP.ThemeManager', 'File is not a valid theme.')) + xmlfile = filter(lambda name: + os.path.splitext(name)[1].lower() == u'.xml', zip.namelist()) + if len(xmlfile) != 1: + log.exception(u'Theme contains "%s" XML files' % len(xmlfile)) + raise Exception(u'validation') + xml_tree = ElementTree(element=XML(zip.read(xmlfile[0]))).getroot() + v1_background = xml_tree.find(u'BackgroundType') + if v1_background is not None: + (themename, filexml, outfile) = self.unzipVersion122(dir, zip, + xmlfile[0], xml_tree, v1_background, outfile) + else: + themename = xml_tree.find(u'name').text.strip() + for name in zip.namelist(): + try: + uname = unicode(name, u'utf-8') + except UnicodeDecodeError: + log.exception(u'Theme file contains non utf-8 filename' + u' "%s"' % name.decode(u'utf-8', u'replace')) + raise Exception(u'validation') + uname = unicode(QtCore.QDir.toNativeSeparators(uname)) + splitname = uname.split(os.path.sep) + if splitname[-1] == u'' or len(splitname) == 1: + # is directory or preview file + continue + fullname = os.path.join(dir, uname) + check_directory_exists(os.path.dirname(fullname)) + if os.path.splitext(uname)[1].lower() == u'.xml': + filexml = unicode(zip.read(name), u'utf-8') + outfile = open(fullname, u'w') + outfile.write(filexml.encode(u'utf-8')) + else: + outfile = open(fullname, u'wb') + outfile.write(zip.read(name)) + outfile.close() + except (IOError, zipfile.BadZipfile): log.exception(u'Importing theme from zip failed %s' % filename) + raise Exception(u'validation') + except Exception as info: + if unicode(info) == u'validation': + critical_error_message_box(translate('OpenLP.ThemeManager', + 'Validation Error'), translate('OpenLP.ThemeManager', + 'File is not a valid theme.')) + else: + raise finally: # Close the files, to be able to continue creating the theme. if zip: @@ -582,6 +589,36 @@ class ThemeManager(QtGui.QWidget): log.exception(u'Theme file does not contain XML data %s' % filename) + def unzipVersion122(self, dir, zip, xmlfile, xml_tree, background, outfile): + """ + Unzip openlp.org 1.2x theme file and upgrade the theme xml. When calling + this method, please keep in mind, that some parameters are redundant. + """ + themename = xml_tree.find(u'Name').text.strip() + themename = self.bad_v1_name_chars.sub(u'', themename) + themedir = os.path.join(dir, themename) + check_directory_exists(themedir) + filexml = unicode(zip.read(xmlfile), u'utf-8') + filexml = self._migrateVersion122(filexml) + outfile = open(os.path.join(themedir, themename + u'.xml'), u'w') + outfile.write(filexml.encode(u'utf-8')) + outfile.close() + if background.text.strip() == u'2': + imagename = xml_tree.find(u'BackgroundParameter1').text.strip() + # image file has same extension and is in subfolder + imagefile = filter(lambda name: os.path.splitext(name)[1].lower() + == os.path.splitext(imagename)[1].lower() and name.find(r'/'), + zip.namelist()) + if len(imagefile) >= 1: + outfile = open(os.path.join(themedir, imagename), u'wb') + outfile.write(zip.read(imagefile[0])) + outfile.close() + else: + log.exception(u'Theme file does not contain image file "%s"' % + imagename.decode(u'utf-8', u'replace')) + raise Exception(u'validation') + return (themename, filexml, outfile) + def checkIfThemeExists(self, themeName): """ Check if theme already exists and displays error message @@ -692,22 +729,6 @@ class ThemeManager(QtGui.QWidget): image = os.path.join(self.path, theme + u'.png') return image - def _checkVersionAndConvert(self, xml_data): - """ - Check if a theme is from OpenLP version 1 - - ``xml_data`` - Theme XML to check the version of - """ - log.debug(u'checkVersion1 ') - theme = xml_data.encode(u'ascii', u'xmlcharrefreplace') - tree = ElementTree(element=XML(theme)).getroot() - # look for old version 1 tags - if tree.find(u'BackgroundType') is None: - return xml_data - else: - return self._migrateVersion122(xml_data) - def _createThemeFromXml(self, themeXml, path): """ Return a theme object using information parsed from XML @@ -772,7 +793,7 @@ class ThemeManager(QtGui.QWidget): """ theme = Theme(xml_data) newtheme = ThemeXML() - newtheme.theme_name = theme.Name + newtheme.theme_name = self.bad_v1_name_chars.sub(u'', theme.Name) if theme.BackgroundType == 0: newtheme.background_type = \ BackgroundType.to_string(BackgroundType.Solid) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index fbf185474..2e73fbb80 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -455,26 +455,6 @@ def get_web_page(url, header=None, update_openlp=False): log.debug(page) return page -def file_is_unicode(filename): - """ - Checks if a file is valid unicode and returns the unicode decoded file or - None. - - ``filename`` - File to check is valid unicode. - """ - if not filename: - return None - ucsfile = None - try: - ucsfile = filename.decode(u'utf-8') - except UnicodeDecodeError: - log.exception(u'Filename "%s" is not valid UTF-8' % - filename.decode(u'utf-8', u'replace')) - if not ucsfile: - return None - return ucsfile - def get_uno_command(): """ Returns the UNO command to launch an openoffice.org instance. @@ -507,5 +487,5 @@ from actions import ActionList __all__ = [u'AppLocation', u'get_application_version', u'check_latest_version', u'add_actions', u'get_filesystem_encoding', u'LanguageManager', - u'ActionList', u'get_web_page', u'file_is_unicode', u'get_uno_command', - u'get_uno_instance', u'delete_file', u'clean_filename'] + u'ActionList', u'get_web_page', u'get_uno_command', u'get_uno_instance', + u'delete_file', u'clean_filename'] diff --git a/openlp/core/utils/actions.py b/openlp/core/utils/actions.py index 8b807bf9e..857e8ceed 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.py @@ -188,6 +188,7 @@ class ActionList(object): actions or categories. """ instance = None + shortcut_map = {} def __init__(self): self.categories = CategoryList() @@ -224,17 +225,45 @@ class ActionList(object): self.categories[category].actions.append(action) else: self.categories[category].actions.add(action, weight) - if not category: - # Stop here, as this action is not configurable. - return # Load the shortcut from the config. settings = QtCore.QSettings() settings.beginGroup(u'shortcuts') shortcuts = settings.value(action.objectName(), QtCore.QVariant(action.shortcuts())).toStringList() + settings.endGroup() + if not shortcuts: + action.setShortcuts([]) + return + # We have to do this to ensure that the loaded shortcut list e. g. + # STRG+O (German) is converted to CTRL+O, which is only done when we + # convert the strings in this way (QKeySequence -> QString -> unicode). + shortcuts = map(QtGui.QKeySequence, shortcuts) + shortcuts = map(unicode, map(QtGui.QKeySequence.toString, shortcuts)) + # Check the alternate shortcut first, to avoid problems when the + # alternate shortcut becomes the primary shortcut after removing the + # (initial) primary shortcut due to conflicts. + if len(shortcuts) == 2: + existing_actions = ActionList.shortcut_map.get(shortcuts[1], []) + # Check for conflicts with other actions considering the shortcut + # context. + if self._is_shortcut_available(existing_actions, action): + actions = ActionList.shortcut_map.get(shortcuts[1], []) + actions.append(action) + ActionList.shortcut_map[shortcuts[1]] = actions + else: + shortcuts.remove(shortcuts[1]) + # Check the primary shortcut. + existing_actions = ActionList.shortcut_map.get(shortcuts[0], []) + # Check for conflicts with other actions considering the shortcut + # context. + if self._is_shortcut_available(existing_actions, action): + actions = ActionList.shortcut_map.get(shortcuts[0], []) + actions.append(action) + ActionList.shortcut_map[shortcuts[0]] = actions + else: + shortcuts.remove(shortcuts[0]) action.setShortcuts( [QtGui.QKeySequence(shortcut) for shortcut in shortcuts]) - settings.endGroup() def remove_action(self, action, category=None): """ @@ -242,7 +271,7 @@ class ActionList(object): automatically removed. ``action`` - The QAction object to be removed. + The ``QAction`` object to be removed. ``category`` The name (unicode string) of the category, which contains the @@ -254,6 +283,15 @@ class ActionList(object): # Remove empty categories. if len(self.categories[category].actions) == 0: self.categories.remove(category) + shortcuts = map(unicode, + map(QtGui.QKeySequence.toString, action.shortcuts())) + for shortcut in shortcuts: + # Remove action from the list of actions which are using this + # shortcut. + ActionList.shortcut_map[shortcut].remove(action) + # Remove empty entries. + if not ActionList.shortcut_map[shortcut]: + del ActionList.shortcut_map[shortcut] def add_category(self, name, weight): """ @@ -275,6 +313,60 @@ class ActionList(object): return self.categories.add(name, weight) + def update_shortcut_map(self, action, old_shortcuts): + """ + Remove the action for the given ``old_shortcuts`` from the + ``shortcut_map`` to ensure its up-to-dateness. + + **Note**: The new action's shortcuts **must** be assigned to the given + ``action`` **before** calling this method. + + ``action`` + The action whose shortcuts are supposed to be updated in the + ``shortcut_map``. + + ``old_shortcuts`` + A list of unicode keysequences. + """ + for old_shortcut in old_shortcuts: + # Remove action from the list of actions which are using this + # shortcut. + ActionList.shortcut_map[old_shortcut].remove(action) + # Remove empty entries. + if not ActionList.shortcut_map[old_shortcut]: + del ActionList.shortcut_map[old_shortcut] + new_shortcuts = map(unicode, + map(QtGui.QKeySequence.toString, action.shortcuts())) + # Add the new shortcuts to the map. + for new_shortcut in new_shortcuts: + existing_actions = ActionList.shortcut_map.get(new_shortcut, []) + existing_actions.append(action) + ActionList.shortcut_map[new_shortcut] = existing_actions + + def _is_shortcut_available(self, existing_actions, action): + """ + Checks if the given ``action`` may use its assigned shortcut(s) or not. + Returns ``True`` or ``False. + + ``existing_actions`` + A list of actions which already use a particular shortcut. + + ``action`` + The action which wants to use a particular shortcut. + """ + for existing_action in existing_actions: + if action is existing_action: + continue + if existing_action.parent() is action.parent(): + return False + if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, + QtCore.Qt.ApplicationShortcut]: + return False + if action.shortcutContext() in [QtCore.Qt.WindowShortcut, + QtCore.Qt.ApplicationShortcut]: + return False + return True + class CategoryOrder(object): """ diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index 6735a7344..55feae7d4 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -28,17 +28,7 @@ The :mod:`cvsbible` modules provides a facility to import bibles from a set of CSV files. -The module expects two mandatory files containing the books and the verses and -will accept an optional third file containing the testaments. - -The format of the testament file is: - - , - - For example: - - 1,Old Testament - 2,New Testament +The module expects two mandatory files containing the books and the verses. The format of the books file is: @@ -110,6 +100,9 @@ class CSVBible(BibleDB): try: details = get_file_encoding(self.booksfile) books_file = open(self.booksfile, 'r') + if not books_file.read(3) == '\xEF\xBB\xBF': + # no BOM was found + books_file.seek(0) books_reader = csv.reader(books_file, delimiter=',', quotechar='"') for line in books_reader: if self.stop_import_flag: @@ -144,6 +137,9 @@ class CSVBible(BibleDB): book_ptr = None details = get_file_encoding(self.versesfile) verse_file = open(self.versesfile, 'rb') + if not verse_file.read(3) == '\xEF\xBB\xBF': + # no BOM was found + verse_file.seek(0) verse_reader = csv.reader(verse_file, delimiter=',', quotechar='"') for line in verse_reader: if self.stop_import_flag: diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 228d4758c..e2cbcdef0 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -92,6 +92,15 @@ class BGExtract(object): if headings: for heading in headings: heading.extract() + chapter_notes = soup.findAll('div', 'footnotes') + if chapter_notes: + log.debug('Found chapter notes') + for note in chapter_notes: + note.extract() + note_comments = soup.findAll(text=u'end of footnotes') + if note_comments: + for comment in note_comments: + comment.extract() cleanup = [(re.compile('\s+'), lambda match: ' ')] verses = BeautifulSoup(str(soup), markupMassage=cleanup) verse_list = {} diff --git a/openlp/plugins/bibles/lib/openlp1.py b/openlp/plugins/bibles/lib/openlp1.py index b822b9b9d..7701f1828 100644 --- a/openlp/plugins/bibles/lib/openlp1.py +++ b/openlp/plugins/bibles/lib/openlp1.py @@ -27,6 +27,7 @@ import logging import sqlite +import sys from openlp.core.lib import Receiver from openlp.core.ui.wizard import WizardStrings @@ -53,9 +54,14 @@ class OpenLP1Bible(BibleDB): connection = None cursor = None try: - connection = sqlite.connect(self.filename) + connection = sqlite.connect( + self.filename.encode(sys.getfilesystemencoding())) cursor = connection.cursor() - except: + except sqlite.DatabaseError: + log.exception(u'File "%s" is encrypted or not a sqlite database, ' + 'therefore not an openlp.org 1.x database either' % self.filename) + # Please add an user error here! + # This file is not an openlp.org 1.x bible database. return False #Create the bible language language_id = self.get_language(bible_name) @@ -63,7 +69,14 @@ class OpenLP1Bible(BibleDB): log.exception(u'Importing books from "%s" failed' % self.filename) return False # Create all books. - cursor.execute(u'SELECT id, testament_id, name, abbreviation FROM book') + try: + cursor.execute( + u'SELECT id, testament_id, name, abbreviation FROM book') + except sqlite.DatabaseError as error: + log.exception(u'DatabaseError: %s' % error) + # Please add an user error here! + # This file is not an openlp.org 1.x bible database. + return False books = cursor.fetchall() self.wizard.progressBar.setMaximum(len(books) + 1) for book in books: diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index b802cda85..d4d797513 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -78,8 +78,7 @@ class OSISBible(BibleDB): fbibles = open(filepath, u'r') for line in fbibles: book = line.split(u',') - self.books[book[0]] = (book[1].lstrip().rstrip(), - book[2].lstrip().rstrip()) + self.books[book[0]] = (book[1].strip(), book[2].strip()) except IOError: log.exception(u'OSIS bible import failed') finally: diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 0d5d8eeff..8a7eb86eb 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -54,7 +54,7 @@ class MediaMediaItem(MediaManagerItem): self.iconPath = u'images/image' self.background = False self.previewFunction = CLAPPERBOARD - self.Automatic = u'' + self.automatic = u'' MediaManagerItem.__init__(self, parent, plugin, icon) self.singleServiceItem = False self.hasSearch = True @@ -101,7 +101,7 @@ class MediaMediaItem(MediaManagerItem): self.replaceAction.setToolTip(UiStrings().ReplaceLiveBG) self.resetAction.setText(UiStrings().ResetBG) self.resetAction.setToolTip(UiStrings().ResetLiveBG) - self.Automatic = translate('MediaPlugin.MediaItem', + self.automatic = translate('MediaPlugin.MediaItem', 'Automatic') self.displayTypeLabel.setText( translate('MediaPlugin.MediaItem', 'Use Player:')) @@ -253,7 +253,7 @@ class MediaMediaItem(MediaManagerItem): # load the drop down selection self.displayTypeComboBox.addItem(title) if self.displayTypeComboBox.count() > 1: - self.displayTypeComboBox.insertItem(0, self.Automatic) + self.displayTypeComboBox.insertItem(0, self.automatic) self.displayTypeComboBox.setCurrentIndex(0) if QtCore.QSettings().value(self.settingsSection + u'/override player', QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: diff --git a/openlp/plugins/media/lib/mediatab.py b/openlp/plugins/media/lib/mediatab.py index 4e39bc419..1b75016cc 100644 --- a/openlp/plugins/media/lib/mediatab.py +++ b/openlp/plugins/media/lib/mediatab.py @@ -35,7 +35,7 @@ class MediaTab(SettingsTab): MediaTab is the Media settings tab in the settings dialog. """ def __init__(self, parent, title, visible_title, media_players, icon_path): - self.media_players = media_players + self.mediaPlayers = media_players SettingsTab.__init__(self, parent, title, visible_title, icon_path) def setupUi(self): @@ -45,13 +45,13 @@ class MediaTab(SettingsTab): self.mediaPlayerGroupBox.setObjectName(u'mediaPlayerGroupBox') self.mediaPlayerLayout = QtGui.QVBoxLayout(self.mediaPlayerGroupBox) self.mediaPlayerLayout.setObjectName(u'mediaPlayerLayout') - self.PlayerCheckBoxes = {} - for key in self.media_players: - player = self.media_players[key] + self.playerCheckBoxes = {} + for key, player in self.mediaPlayers.iteritems(): + player = self.mediaPlayers[key] checkbox = QtGui.QCheckBox(self.mediaPlayerGroupBox) checkbox.setEnabled(player.available) checkbox.setObjectName(player.name + u'CheckBox') - self.PlayerCheckBoxes[player.name] = checkbox + self.playerCheckBoxes[player.name] = checkbox self.mediaPlayerLayout.addWidget(checkbox) self.leftLayout.addWidget(self.mediaPlayerGroupBox) self.playerOrderGroupBox = QtGui.QGroupBox(self.leftColumn) @@ -88,19 +88,19 @@ class MediaTab(SettingsTab): self.orderingButtonLayout.addWidget(self.orderingUpButton) self.playerOrderLayout.addWidget(self.orderingButtonsWidget) self.leftLayout.addWidget(self.playerOrderGroupBox) - self.AdvancedGroupBox = QtGui.QGroupBox(self.leftColumn) - self.AdvancedGroupBox.setObjectName(u'AdvancedGroupBox') - self.AdvancedLayout = QtGui.QVBoxLayout(self.AdvancedGroupBox) - self.AdvancedLayout.setObjectName(u'AdvancedLayout') - self.OverridePlayerCheckBox = QtGui.QCheckBox(self.AdvancedGroupBox) - self.OverridePlayerCheckBox.setObjectName(u'OverridePlayerCheckBox') - self.AdvancedLayout.addWidget(self.OverridePlayerCheckBox) - self.leftLayout.addWidget(self.AdvancedGroupBox) + self.advancedGroupBox = QtGui.QGroupBox(self.leftColumn) + self.advancedGroupBox.setObjectName(u'advancedGroupBox') + self.advancedLayout = QtGui.QVBoxLayout(self.advancedGroupBox) + self.advancedLayout.setObjectName(u'advancedLayout') + self.overridePlayerCheckBox = QtGui.QCheckBox(self.advancedGroupBox) + self.overridePlayerCheckBox.setObjectName(u'overridePlayerCheckBox') + self.advancedLayout.addWidget(self.overridePlayerCheckBox) + self.leftLayout.addWidget(self.advancedGroupBox) self.leftLayout.addStretch() self.rightLayout.addStretch() - for key in self.media_players: - player = self.media_players[key] - checkbox = self.PlayerCheckBoxes[player.name] + for key in self.mediaPlayers: + player = self.mediaPlayers[key] + checkbox = self.playerCheckBoxes[player.name] QtCore.QObject.connect(checkbox, QtCore.SIGNAL(u'stateChanged(int)'), self.onPlayerCheckBoxChanged) @@ -112,9 +112,9 @@ class MediaTab(SettingsTab): def retranslateUi(self): self.mediaPlayerGroupBox.setTitle( translate('MediaPlugin.MediaTab', 'Available Media Players')) - for key in self.media_players: - player = self.media_players[key] - checkbox = self.PlayerCheckBoxes[player.name] + for key in self.mediaPlayers: + player = self.mediaPlayers[key] + checkbox = self.playerCheckBoxes[player.name] if player.available: checkbox.setText(player.name) else: @@ -127,8 +127,8 @@ class MediaTab(SettingsTab): translate('MediaPlugin.MediaTab', 'Down')) self.orderingUpButton.setText( translate('MediaPlugin.MediaTab', 'Up')) - self.AdvancedGroupBox.setTitle(UiStrings().Advanced) - self.OverridePlayerCheckBox.setText( + self.advancedGroupBox.setTitle(UiStrings().Advanced) + self.overridePlayerCheckBox.setText( translate('MediaPlugin.MediaTab', 'Allow media player to be overriden')) @@ -144,12 +144,12 @@ class MediaTab(SettingsTab): def updatePlayerList(self): self.playerOrderlistWidget.clear() for player in self.usedPlayers: - if player in self.PlayerCheckBoxes.keys(): + if player in self.playerCheckBoxes.keys(): if len(self.usedPlayers) == 1: # at least one media player have to stay active - self.PlayerCheckBoxes[u'%s' % player].setEnabled(False) + self.playerCheckBoxes[u'%s' % player].setEnabled(False) else: - self.PlayerCheckBoxes[u'%s' % player].setEnabled(True) + self.playerCheckBoxes[u'%s' % player].setEnabled(True) self.playerOrderlistWidget.addItem(player) def onOrderingUpButtonPressed(self): @@ -172,34 +172,34 @@ class MediaTab(SettingsTab): self.usedPlayers = QtCore.QSettings().value( self.settingsSection + u'/players', QtCore.QVariant(u'webkit')).toString().split(u',') - for key in self.media_players: - player = self.media_players[key] - checkbox = self.PlayerCheckBoxes[player.name] + for key in self.mediaPlayers: + player = self.mediaPlayers[key] + checkbox = self.playerCheckBoxes[player.name] if player.available and player.name in self.usedPlayers: checkbox.setChecked(True) self.updatePlayerList() - self.OverridePlayerCheckBox.setChecked(QtCore.QSettings().value( + self.overridePlayerCheckBox.setChecked(QtCore.QSettings().value( self.settingsSection + u'/override player', QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0]) def save(self): override_changed = False player_string_changed = False - oldPlayerString = QtCore.QSettings().value( + old_players = QtCore.QSettings().value( self.settingsSection + u'/players', QtCore.QVariant(u'webkit')).toString() - newPlayerString = self.usedPlayers.join(u',') - if oldPlayerString != newPlayerString: + new_players = self.usedPlayers.join(u',') + if old_players != new_players: # clean old Media stuff QtCore.QSettings().setValue(self.settingsSection + u'/players', - QtCore.QVariant(newPlayerString)) + QtCore.QVariant(new_players)) player_string_changed = True override_changed = True setting_key = self.settingsSection + u'/override player' if QtCore.QSettings().value(setting_key) != \ - self.OverridePlayerCheckBox.checkState(): + self.overridePlayerCheckBox.checkState(): QtCore.QSettings().setValue(setting_key, - QtCore.QVariant(self.OverridePlayerCheckBox.checkState())) + QtCore.QVariant(self.overridePlayerCheckBox.checkState())) override_changed = True if override_changed: Receiver.send_message(u'mediaitem_media_rebuild') diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 2825d899e..3b790dd93 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -27,6 +27,8 @@ import logging +from PyQt4 import QtCore + from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.plugins.media.lib import MediaMediaItem, MediaTab @@ -117,3 +119,29 @@ class MediaPlugin(Plugin): Add html code to htmlbuilder """ return self.mediaController.get_media_display_html() + + def appStartup(self): + """ + Do a couple of things when the app starts up. In this particular case + we want to check if we have the old "Use Phonon" setting, and convert + it to "enable Phonon" and "make it the first one in the list". + """ + settings = QtCore.QSettings() + settings.beginGroup(self.settingsSection) + if settings.contains(u'use phonon'): + log.info(u'Found old Phonon setting') + players = self.mediaController.mediaPlayers.keys() + has_phonon = u'phonon' in players + if settings.value(u'use phonon').toBool() and has_phonon: + log.debug(u'Converting old setting to new setting') + new_players = [] + if players: + new_players = [player for player in players \ + if player != u'phonon'] + new_players.insert(0, u'phonon') + self.mediaController.mediaPlayers[u'phonon'].isActive = True + settings.setValue(u'players', \ + QtCore.QVariant(u','.join(new_players))) + self.settings_tab.load() + settings.remove(u'use phonon') + settings.endGroup() diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html index 1339e9dda..fb88ee0dc 100644 --- a/openlp/plugins/remotes/html/index.html +++ b/openlp/plugins/remotes/html/index.html @@ -58,7 +58,7 @@
- ${back} + ${back}

${service_manager}

${refresh}
@@ -89,7 +89,7 @@
- ${back} + ${back}

${slide_controller}

${refresh}
@@ -120,7 +120,7 @@
- ${back} + ${back}

${alerts}

@@ -133,7 +133,7 @@