diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 8899f5846..b90375506 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -85,6 +85,14 @@ class PluginManager(object): log.debug(u'finding plugins in %s at depth %d', unicode(plugin_dir), startdepth) for root, dirs, files in os.walk(plugin_dir): + # TODO Presentation plugin is not yet working on Mac OS X. + # For now just ignore it. The following code will hide it + # in settings dialog. + if sys.platform == 'darwin': + present_plugin_dir = os.path.join(plugin_dir, 'presentations') + # Ignore files from the presentation plugin directory. + if root.startswith(present_plugin_dir): + continue for name in files: if name.endswith(u'.py') and not name.startswith(u'__'): path = os.path.abspath(os.path.join(root, name)) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index ab8228658..67b5c1b04 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -29,12 +29,12 @@ The :mod:`advancedtab` provides an advanced settings facility. """ from datetime import datetime, timedelta - -from PyQt4 import QtCore, QtGui - import logging import os import sys + +from PyQt4 import QtCore, QtGui + from openlp.core.lib import SettingsTab, translate, build_icon, Receiver from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings @@ -432,8 +432,7 @@ class AdvancedTab(SettingsTab): translate('OpenLP.AdvancedTab', 'WARNING: New data directory location contains ' 'OpenLP data files. These files WILL be replaced during a copy.')) - self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab', - 'X11')) + self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab', 'X11')) self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab', 'Bypass X11 Window Manager')) # Slide Limits @@ -493,8 +492,14 @@ class AdvancedTab(SettingsTab): QtCore.QVariant(True)).toBool() self.serviceNameCheckBox.setChecked(default_service_enabled) self.serviceNameCheckBoxToggled(default_service_enabled) - self.x11BypassCheckBox.setChecked( - settings.value(u'x11 bypass wm', QtCore.QVariant(True)).toBool()) + # Fix for bug #1014422. + x11_bypass_default = True + if sys.platform.startswith(u'linux'): + # Default to False on Gnome. + x11_bypass_default = bool(not + os.environ.get(u'GNOME_DESKTOP_SESSION_ID')) + self.x11BypassCheckBox.setChecked(settings.value( + u'x11 bypass wm', QtCore.QVariant(x11_bypass_default)).toBool()) self.defaultColor = settings.value(u'default color', QtCore.QVariant(u'#ffffff')).toString() self.defaultFileEdit.setText(settings.value(u'default image', @@ -766,7 +771,7 @@ class AdvancedTab(SettingsTab): self.dataExists = False self.dataDirectoryCopyCheckBox.setChecked(True) self.newDataDirectoryHasFilesLabel.hide() - + def onDataDirectoryCancelButtonClicked(self): """ Cancel the data directory location change diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 1bd746759..6a18b2011 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -177,8 +177,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): return FirstTimePage.Progress elif self.currentId() == FirstTimePage.Themes: Receiver.send_message(u'cursor_busy') + Receiver.send_message(u'openlp_process_events') while not self.themeScreenshotThread.isFinished(): time.sleep(0.1) + Receiver.send_message(u'openlp_process_events') # Build the screenshot icons, as this can not be done in the thread. self._buildThemeScreenshots() Receiver.send_message(u'cursor_normal') @@ -188,10 +190,11 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): def onCurrentIdChanged(self, pageId): """ - Detects Page changes and updates as approprate. + Detects Page changes and updates as appropriate. """ # Keep track of the page we are at. Triggering "Cancel" causes pageId # to be a -1. + Receiver.send_message(u'openlp_process_events') if pageId != -1: self.lastId = pageId if pageId == FirstTimePage.Plugins: @@ -227,10 +230,12 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): self.cancelButton.setVisible(False) elif pageId == FirstTimePage.Progress: Receiver.send_message(u'cursor_busy') + self.repaint() + Receiver.send_message(u'openlp_process_events') + # Try to give the wizard a chance to redraw itself + time.sleep(0.2) self._preWizard() - Receiver.send_message(u'openlp_process_events') self._performWizard() - Receiver.send_message(u'openlp_process_events') self._postWizard() Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') @@ -263,8 +268,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): """ Receiver.send_message(u'cursor_busy') self._performWizard() - Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'cursor_normal') + Receiver.send_message(u'openlp_process_events') Settings().setValue(u'general/has run wizard', QtCore.QVariant(True)) self.close() @@ -344,6 +349,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): Receiver.send_message(u'openlp_process_events') # Loop through the songs list and increase for each selected item for i in xrange(self.songsListWidget.count()): + Receiver.send_message(u'openlp_process_events') item = self.songsListWidget.item(i) if item.checkState() == QtCore.Qt.Checked: filename = item.data(QtCore.Qt.UserRole).toString() @@ -352,6 +358,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): # Loop through the Bibles list and increase for each selected item iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) while iterator.value(): + Receiver.send_message(u'openlp_process_events') item = iterator.value() if item.parent() and item.checkState(0) == QtCore.Qt.Checked: filename = item.data(0, QtCore.Qt.UserRole).toString() @@ -360,6 +367,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): iterator += 1 # Loop through the themes list and increase for each selected item for i in xrange(self.themesListWidget.count()): + Receiver.send_message(u'openlp_process_events') item = self.themesListWidget.item(i) if item.checkState() == QtCore.Qt.Checked: filename = item.data(QtCore.Qt.UserRole).toString() @@ -381,6 +389,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Setting Up')) self.progressPage.setSubTitle(u'Setup complete.') + self.repaint() + Receiver.send_message(u'openlp_process_events') + # Try to give the wizard a chance to repaint itself + time.sleep(0.1) def _postWizard(self): """ @@ -418,8 +430,11 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): 'Enabling selected plugins...')) self._setPluginStatus(self.songsCheckBox, u'songs/status') self._setPluginStatus(self.bibleCheckBox, u'bibles/status') - self._setPluginStatus(self.presentationCheckBox, - u'presentations/status') + # TODO Presentation plugin is not yet working on Mac OS X. + # For now just ignore it. + if sys.platform != 'darwin': + self._setPluginStatus(self.presentationCheckBox, + u'presentations/status') self._setPluginStatus(self.imageCheckBox, u'images/status') self._setPluginStatus(self.mediaCheckBox, u'media/status') self._setPluginStatus(self.remoteCheckBox, u'remotes/status') diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 1ce523cbc..4477dd4cb 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -82,13 +82,13 @@ class Ui_FirstTimeWizard(object): self.imageCheckBox.setChecked(True) self.imageCheckBox.setObjectName(u'imageCheckBox') self.pluginLayout.addWidget(self.imageCheckBox) - self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage) - if sys.platform == "darwin": - self.presentationCheckBox.setChecked(False) - else: + # TODO Presentation plugin is not yet working on Mac OS X. + # For now just ignore it. + if sys.platform != 'darwin': + self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage) self.presentationCheckBox.setChecked(True) - self.presentationCheckBox.setObjectName(u'presentationCheckBox') - self.pluginLayout.addWidget(self.presentationCheckBox) + self.presentationCheckBox.setObjectName(u'presentationCheckBox') + self.pluginLayout.addWidget(self.presentationCheckBox) self.mediaCheckBox = QtGui.QCheckBox(self.pluginPage) self.mediaCheckBox.setChecked(True) self.mediaCheckBox.setObjectName(u'mediaCheckBox') @@ -214,10 +214,11 @@ class Ui_FirstTimeWizard(object): self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible')) self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Images')) - self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard', - 'Presentations')) - if sys.platform == "darwin": - self.presentationCheckBox.setEnabled(False) + # TODO Presentation plugin is not yet working on Mac OS X. + # For now just ignore it. + if sys.platform != 'darwin': + self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard', + 'Presentations')) self.mediaCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)')) self.remoteCheckBox.setText(translate('OpenLP.FirstTimeWizard', diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 306bb7511..a1cc0215d 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -31,6 +31,7 @@ and play multimedia within OpenLP. """ import cgi import logging +import os import sys from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL @@ -135,8 +136,14 @@ class MainDisplay(Display): self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') windowFlags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | \ QtCore.Qt.WindowStaysOnTopHint + # Fix for bug #1014422. + x11_bypass_default = True + if sys.platform.startswith(u'linux'): + # Default to False on Gnome. + x11_bypass_default = bool(not + os.environ.get(u'GNOME_DESKTOP_SESSION_ID')) if Settings().value(u'advanced/x11 bypass wm', - QtCore.QVariant(True)).toBool(): + QtCore.QVariant(x11_bypass_default)).toBool(): windowFlags |= QtCore.Qt.X11BypassWindowManagerHint # TODO: The following combination of windowFlags works correctly # on Mac OS X. For next OpenLP version we should test it on other diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 2d108e72c..0f0f15a3d 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -855,7 +855,11 @@ class SlideController(Controller): else: label = QtGui.QLabel() label.setMargin(4) - label.setScaledContents(True) + if serviceItem.is_media(): + label.setAlignment(QtCore.Qt.AlignHCenter | + QtCore.Qt.AlignVCenter) + else: + label.setScaledContents(True) if self.serviceItem.is_command(): label.setPixmap(QtGui.QPixmap(frame[u'image'])) else: diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 844ffec8c..70bf78b29 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -30,7 +30,6 @@ import os import zipfile import shutil import logging -import locale import re from xml.etree.ElementTree import ElementTree, XML @@ -46,7 +45,8 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_widget_action from openlp.core.theme import Theme from openlp.core.ui import FileRenameForm, ThemeForm -from openlp.core.utils import AppLocation, delete_file, get_filesystem_encoding +from openlp.core.utils import AppLocation, delete_file, locale_compare, \ + get_filesystem_encoding log = logging.getLogger(__name__) @@ -284,6 +284,7 @@ class ThemeManager(QtGui.QWidget): plugin.renameTheme(old_theme_name, new_theme_name) self.mainwindow.renderer.update_theme( new_theme_name, old_theme_name) + self.loadThemes() def onCopyTheme(self): """ @@ -457,9 +458,8 @@ class ThemeManager(QtGui.QWidget): self.configUpdated() files = SettingsManager.get_files(self.settingsSection, u'.png') # Sort the themes by its name considering language specific characters. - # lower() is needed for windows! - files.sort(key=lambda file_name: unicode(file_name).lower(), - cmp=locale.strcoll) + files.sort(key=lambda file_name: unicode(file_name), + cmp=locale_compare) # now process the file list of png files for name in files: # check to see file is in theme root directory diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 41a097a15..742b1e4bc 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -488,10 +488,30 @@ def format_time(text, local_time): return re.sub('\%[a-zA-Z]', match_formatting, text) +def locale_compare(string1, string2): + """ + Compares two strings according to the current locale settings. + + As any other compare function, returns a negative, or a positive value, + or 0, depending on whether string1 collates before or after string2 or + is equal to it. Comparison is case insensitive. + """ + # Function locale.strcol() from standard Python library does not work + # properly on Windows and probably somewhere else. + return QtCore.QString.localeAwareCompare(string1.lower(), string2.lower()) + + +# For performance reasons provide direct reference to compare function +# without wrapping it in another function making te string lowercase. +# This is needed for sorting songs. +locale_direct_compare = QtCore.QString.localeAwareCompare + + from languagemanager import LanguageManager 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'get_uno_command', u'get_uno_instance', - u'delete_file', u'clean_filename', u'format_time'] + u'delete_file', u'clean_filename', u'format_time', u'locale_compare', + u'locale_direct_compare'] diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 2d7fd8296..1720fa382 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -105,6 +105,7 @@ CSS = """ font-size: %spt; color: %s; background-color: %s; + word-wrap: break-word; } """ diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index df4209e9d..09c098cf4 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -35,7 +35,7 @@ class Ui_AlertDialog(object): def setupUi(self, alertDialog): alertDialog.setObjectName(u'alertDialog') alertDialog.resize(400, 300) - alertDialog.setWindowIcon(build_icon(u':/icon/openlp.org-icon-32.bmp')) + alertDialog.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png')) self.alertDialogLayout = QtGui.QGridLayout(alertDialog) self.alertDialogLayout.setObjectName(u'alertDialogLayout') self.alertTextLayout = QtGui.QFormLayout() diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index ab4b6583e..7832f0c7d 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -80,6 +80,10 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): item_name = QtGui.QListWidgetItem(alert.text) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(alert.id)) self.alertListWidget.addItem(item_name) + if alert.text == unicode(self.alertTextEdit.text()): + self.item_id = alert.id + self.alertListWidget.setCurrentRow( + self.alertListWidget.row(item_name)) def onDisplayClicked(self): self.triggerAlert(unicode(self.alertTextEdit.text())) @@ -112,7 +116,6 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): alert = AlertItem() alert.text = unicode(self.alertTextEdit.text()) self.manager.save_object(alert) - self.alertTextEdit.setText(u'') self.loadList() def onSaveClick(self): @@ -125,6 +128,7 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): self.manager.save_object(alert) self.item_id = None self.loadList() + self.saveButton.setEnabled(False) def onTextChanged(self): """ diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 018b38af2..7b43c202a 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -57,8 +57,6 @@ class AlertsManager(QtCore.QObject): """ if message: self.displayAlert(message[0]) - else: - self.displayAlert(u'') def displayAlert(self, text=u''): """ @@ -68,14 +66,15 @@ class AlertsManager(QtCore.QObject): display text """ log.debug(u'display alert called %s' % text) - self.alertList.append(text) - if self.timer_id != 0: - Receiver.send_message(u'mainwindow_status_text', - translate('AlertsPlugin.AlertsManager', - 'Alert message created and displayed.')) - return - Receiver.send_message(u'mainwindow_status_text', u'') - self.generateAlert() + if text: + self.alertList.append(text) + if self.timer_id != 0: + Receiver.send_message(u'mainwindow_status_text', + translate('AlertsPlugin.AlertsManager', + 'Alert message created and displayed.')) + return + Receiver.send_message(u'mainwindow_status_text', u'') + self.generateAlert() def generateAlert(self): """ diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 0a1d4cc23..d805ba34f 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -30,7 +30,6 @@ The bible import functions for OpenLP """ import logging import os -import locale from PyQt4 import QtCore, QtGui @@ -39,7 +38,7 @@ from openlp.core.lib.db import delete_database 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 -from openlp.core.utils import AppLocation +from openlp.core.utils import AppLocation, locale_compare from openlp.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename @@ -523,7 +522,7 @@ class BibleImportForm(OpenLPWizard): """ self.webTranslationComboBox.clear() bibles = self.web_bible_list[index].keys() - bibles.sort(cmp=locale.strcoll) + bibles.sort(cmp=locale_compare) self.webTranslationComboBox.addItems(bibles) def onOsisBrowseButtonClicked(self): diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 73e5ebe8b..97e4ce72e 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -355,37 +355,8 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False): log.debug(u'Matched reference %s' % reference) book = match.group(u'book') if not book_ref_id: - book_names = BibleStrings().BookNames - # escape reserved characters - book_escaped = book - for character in u'\\.^$*+?{}[]()': - book_escaped = book_escaped.replace( - character, u'\\' + character) - regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join( - book_escaped.split()), re.UNICODE | re.IGNORECASE) - if language_selection == LanguageSelection.Bible: - db_book = bible.get_book(book) - if db_book: - book_ref_id = db_book.book_reference_id - elif language_selection == LanguageSelection.Application: - books = filter(lambda key: - regex_book.match(unicode(book_names[key])), book_names.keys()) - books = filter(None, map(BiblesResourcesDB.get_book, books)) - for value in books: - if bible.get_book_by_book_ref_id(value[u'id']): - book_ref_id = value[u'id'] - break - elif language_selection == LanguageSelection.English: - books = BiblesResourcesDB.get_books_like(book) - if books: - book_list = filter( - lambda value: regex_book.match(value[u'name']), books) - if not book_list: - book_list = books - for value in book_list: - if bible.get_book_by_book_ref_id(value[u'id']): - book_ref_id = value[u'id'] - break + book_ref_id = bible.get_book_ref_id_by_localised_name( + book, language_selection) elif not bible.get_book_by_book_ref_id(book_ref_id): book_ref_id = False ranges = match.group(u'ranges') diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index acf7cf461..d4fd930ed 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -29,6 +29,7 @@ import chardet import logging import os +import re import sqlite3 from PyQt4 import QtCore @@ -44,6 +45,8 @@ import upgrade log = logging.getLogger(__name__) +RESERVED_CHARACTERS = u'\\.^$*+?{}[]()' + class BibleMeta(BaseModel): """ Bible Meta Data @@ -352,6 +355,53 @@ class BibleDB(QtCore.QObject, Manager): book, book_id, language_id) return book_id + def get_book_ref_id_by_localised_name(self, book, + language_selection): + """ + Return the id of a named book. + + ``book`` + The name of the book, according to the selected language. + + ``language_selection`` + The language selection the user has chosen in the settings + section of the Bible. + """ + log.debug(u'get_book_ref_id_by_localised_name("%s", "%s")', + book, language_selection) + from openlp.plugins.bibles.lib import LanguageSelection, \ + BibleStrings + book_names = BibleStrings().BookNames + # escape reserved characters + book_escaped = book + for character in RESERVED_CHARACTERS: + book_escaped = book_escaped.replace( + character, u'\\' + character) + regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join( + book_escaped.split()), re.UNICODE | re.IGNORECASE) + if language_selection == LanguageSelection.Bible: + db_book = self.get_book(book) + if db_book: + return db_book.book_reference_id + elif language_selection == LanguageSelection.Application: + books = filter(lambda key: + regex_book.match(unicode(book_names[key])), book_names.keys()) + books = filter(None, map(BiblesResourcesDB.get_book, books)) + for value in books: + if self.get_book_by_book_ref_id(value[u'id']): + return value[u'id'] + elif language_selection == LanguageSelection.English: + books = BiblesResourcesDB.get_books_like(book) + if books: + book_list = filter( + lambda value: regex_book.match(value[u'name']), books) + if not book_list: + book_list = books + for value in book_list: + if self.get_book_by_book_ref_id(value[u'id']): + return value[u'id'] + return False + def get_verses(self, reference_list, show_error=True): """ This is probably the most used function. It retrieves the list of diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index a56aac80b..467f7c90a 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -277,8 +277,9 @@ class BibleManager(object): """ log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter) - db_book = self.db_cache[bible].get_book(book) - book_ref_id = db_book.book_reference_id + language_selection = self.get_language_selection(bible) + book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name( + book, language_selection) return self.db_cache[bible].get_verse_count(book_ref_id, chapter) def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter): diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 5176e7d47..0647076c8 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -27,7 +27,6 @@ ############################################################################### import logging -import locale from PyQt4 import QtCore, QtGui @@ -38,6 +37,7 @@ from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings, set_case_insensitive_completer, \ create_horizontal_adjusting_combo_box, critical_error_message_box, \ find_and_set_in_combo_box, build_icon +from openlp.core.utils import locale_compare from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \ VerseReferenceList, get_reference_separator, LanguageSelection, \ @@ -381,7 +381,7 @@ class BibleMediaItem(MediaManagerItem): # Get all bibles and sort the list. bibles = self.plugin.manager.get_bibles().keys() bibles = filter(None, bibles) - bibles.sort(cmp=locale.strcoll) + bibles.sort(cmp=locale_compare) # Load the bibles into the combo boxes. self.quickVersionComboBox.addItems(bibles) self.quickSecondComboBox.addItems(bibles) @@ -538,7 +538,7 @@ class BibleMediaItem(MediaManagerItem): data = BiblesResourcesDB.get_book_by_id( book.book_reference_id) books.append(data[u'name'] + u' ') - books.sort(cmp=locale.strcoll) + books.sort(cmp=locale_compare) set_case_insensitive_completer(books, self.quickSearchEdit) def onImportClick(self): diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index e29f4431a..fe20108e1 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.py @@ -36,7 +36,7 @@ class Ui_CustomEditDialog(object): customEditDialog.setObjectName(u'customEditDialog') customEditDialog.resize(450, 350) customEditDialog.setWindowIcon( - build_icon(u':/icon/openlp.org-icon-32.bmp')) + build_icon(u':/icon/openlp-logo-16x16.png')) self.dialogLayout = QtGui.QVBoxLayout(customEditDialog) self.dialogLayout.setObjectName(u'dialogLayout') self.titleLayout = QtGui.QHBoxLayout() diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py index 6f3155f48..c885562f2 100644 --- a/openlp/plugins/custom/lib/db.py +++ b/openlp/plugins/custom/lib/db.py @@ -34,12 +34,21 @@ from sqlalchemy import Column, Table, types from sqlalchemy.orm import mapper from openlp.core.lib.db import BaseModel, init_db +from openlp.core.utils import locale_compare class CustomSlide(BaseModel): """ CustomSlide model """ - pass + # By default sort the customs by its title considering language specific + # characters. + def __lt__(self, other): + r = locale_compare(self.title, other.title) + return True if r < 0 else False + + def __eq__(self, other): + return 0 == locale_compare(self.title, other.title) + def init_schema(url): """ diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 663c5489d..10a3ec056 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -27,7 +27,6 @@ ############################################################################### import logging -import locale from PyQt4 import QtCore, QtGui from sqlalchemy.sql import or_, func @@ -109,10 +108,7 @@ class CustomMediaItem(MediaManagerItem): # Sort out what custom we want to select after loading the list. self.saveAutoSelectId() self.listView.clear() - # Sort the customs by its title considering language specific - # characters. lower() is needed for windows! - custom_slides.sort( - cmp=locale.strcoll, key=lambda custom: custom.title.lower()) + custom_slides.sort() for custom_slide in custom_slides: custom_name = QtGui.QListWidgetItem(custom_slide.title) custom_name.setData( diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 0fdb1537c..9239f8d72 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -28,7 +28,6 @@ import logging import os -import locale from PyQt4 import QtCore, QtGui @@ -37,7 +36,8 @@ from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ Receiver, create_thumb, validate_thumb from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.settings import Settings -from openlp.core.utils import AppLocation, delete_file, get_images_filter +from openlp.core.utils import AppLocation, delete_file, locale_compare, \ + get_images_filter log = logging.getLogger(__name__) @@ -126,10 +126,10 @@ class ImageMediaItem(MediaManagerItem): if not initialLoad: Receiver.send_message(u'cursor_busy') self.plugin.formParent.displayProgressBar(len(images)) - # Sort the themes by its filename considering language specific - # characters. lower() is needed for windows! - images.sort(cmp=locale.strcoll, - key=lambda filename: os.path.split(unicode(filename))[1].lower()) + # Sort the images by its filename considering language specific + # characters. + images.sort(cmp=locale_compare, + key=lambda filename: os.path.split(unicode(filename))[1]) for imageFile in images: filename = os.path.split(unicode(imageFile))[1] thumb = os.path.join(self.servicePath, filename) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index fe66b90d8..9f661149b 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -28,7 +28,6 @@ import logging import os -import locale from PyQt4 import QtCore, QtGui @@ -39,11 +38,13 @@ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_horizontal_adjusting_combo_box from openlp.core.ui import Controller, Display from openlp.core.ui.media import get_media_players, set_media_players +from openlp.core.utils import locale_compare log = logging.getLogger(__name__) -CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png') -#TODO: Add an appropriate Icon for DVDs, CDs, ... +CLAPPERBOARD = u':/media/slidecontroller_multimedia.png' +VIDEO = QtGui.QImage(u':/media/media_video.png') +AUDIO = QtGui.QImage(u':/media/media_audio.png') DVD_ICON = QtGui.QImage(u':/media/media_video.png') class MediaMediaItem(MediaManagerItem): @@ -218,7 +219,7 @@ class MediaMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.RequiresMedia) # force a non-existent theme service_item.theme = -1 - frame = u':/media/image_clapperboard.png' + frame = CLAPPERBOARD (path, name) = os.path.split(filename) service_item.add_from_command(path, name, frame) return True @@ -284,16 +285,16 @@ class MediaMediaItem(MediaManagerItem): u'media', self.getFileList()) def loadList(self, media): - # Sort the themes by its filename considering language specific - # characters. lower() is needed for windows! - media.sort(cmp=locale.strcoll, - key=lambda filename: os.path.split(unicode(filename))[1].lower()) + # Sort the media by its filename considering language specific + # characters. + media.sort(cmp=locale_compare, + key=lambda filename: os.path.split(unicode(filename))[1]) for track in media: track_info = QtCore.QFileInfo(track) - if not track_info.isFile(): + if track_info.isFile(): filename = os.path.split(unicode(track))[1] item_name = QtGui.QListWidgetItem(filename) - item_name.setIcon(build_icon(CLAPPERBOARD)) + item_name.setIcon(build_icon(VIDEO)) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track)) else: filename = os.path.split(unicode(track))[1] @@ -306,8 +307,8 @@ class MediaMediaItem(MediaManagerItem): def getList(self, type=MediaType.Audio): media = SettingsManager.load_list(self.settingsSection, u'media') - media.sort(cmp=locale.strcoll, - key=lambda filename: os.path.split(unicode(filename))[1].lower()) + media.sort(cmp=locale_compare, + key=lambda filename: os.path.split(unicode(filename))[1]) ext = [] if type == MediaType.Audio: ext = self.plugin.audio_extensions_list diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index a85f43592..3d78f7bdb 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -153,7 +153,7 @@ class ImpressController(PresentationController): desktop = None try: desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') - except AttributeError: + except (AttributeError, pywintypes.com_error): log.warn(u'Failure to find desktop - Impress may have closed') return desktop if desktop else None @@ -284,6 +284,8 @@ class ImpressDocument(PresentationDocument): props = tuple(props) doc = self.document pages = doc.getDrawPages() + if not pages: + return if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) for idx in range(pages.getCount()): @@ -359,7 +361,7 @@ class ImpressDocument(PresentationDocument): log.debug(u'is active OpenOffice') if not self.is_loaded(): return False - return self.control is not None + return self.control.isRunning() if self.control else False def unblank_screen(self): """ @@ -380,7 +382,7 @@ class ImpressDocument(PresentationDocument): Returns true if screen is blank """ log.debug(u'is blank OpenOffice') - if self.control: + if self.control and self.control.isRunning(): return self.control.isPaused() else: return False @@ -436,7 +438,11 @@ class ImpressDocument(PresentationDocument): """ Triggers the next effect of slide on the running presentation """ + is_paused = self.control.isPaused() self.control.gotoNextEffect() + time.sleep(0.1) + if not is_paused and self.control.isPaused(): + self.control.gotoPreviousEffect() def previous_step(self): """ diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 290882e26..9cfed177c 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -28,7 +28,6 @@ import logging import os -import locale from PyQt4 import QtCore, QtGui @@ -38,6 +37,7 @@ from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \ from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ create_horizontal_adjusting_combo_box from openlp.core.lib.settings import Settings +from openlp.core.utils import locale_compare from openlp.plugins.presentations.lib import MessageListener log = logging.getLogger(__name__) @@ -169,10 +169,10 @@ class PresentationMediaItem(MediaManagerItem): if not initialLoad: Receiver.send_message(u'cursor_busy') self.plugin.formParent.displayProgressBar(len(files)) - # Sort the themes by its filename considering language specific - # characters. lower() is needed for windows! - files.sort(cmp=locale.strcoll, - key=lambda filename: os.path.split(unicode(filename))[1].lower()) + # Sort the presentations by its filename considering language specific + # characters. + files.sort(cmp=locale_compare, + key=lambda filename: os.path.split(unicode(filename))[1]) for file in files: if not initialLoad: self.plugin.formParent.incrementProgressBar() diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index cb8f7b7b8..7cc12c7f9 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -49,6 +49,7 @@ class Controller(object): """ self.is_live = live self.doc = None + self.hide_mode = None log.info(u'%s controller loaded' % live) def add_handler(self, controller, file, hide_mode, slide_no): @@ -67,6 +68,7 @@ class Controller(object): # Inform slidecontroller that the action failed? return self.doc.slidenumber = slide_no + self.hide_mode = hide_mode if self.is_live: if hide_mode == HideMode.Screen: Receiver.send_message(u'live_display_hide', HideMode.Screen) @@ -78,7 +80,7 @@ class Controller(object): else: self.doc.start_presentation() Receiver.send_message(u'live_display_hide', HideMode.Screen) - self.doc.slidenumber = 0 + self.doc.slidenumber = 1 if slide_no > 1: self.slide(slide_no) @@ -88,100 +90,134 @@ class Controller(object): Use the last slide number. """ log.debug(u'Live = %s, activate' % self.is_live) + if not self.doc: + return False if self.doc.is_active(): - return + return True if not self.doc.is_loaded(): if not self.doc.load_presentation(): - return + log.warn(u'Failed to activate %s' % self.doc.filepath) + return False if self.is_live: self.doc.start_presentation() if self.doc.slidenumber > 1: if self.doc.slidenumber > self.doc.get_slide_count(): self.doc.slidenumber = self.doc.get_slide_count() self.doc.goto_slide(self.doc.slidenumber) + if self.doc.is_active(): + return True + else: + log.warn(u'Failed to activate %s' % self.doc.filepath) + return False def slide(self, slide): """ Go to a specific slide """ log.debug(u'Live = %s, slide' % self.is_live) + if not self.doc: + return if not self.is_live: return - if self.doc.is_blank(): + if self.hide_mode: self.doc.slidenumber = int(slide) + 1 + self.poll() + return + if not self.activate(): return - self.activate() self.doc.goto_slide(int(slide) + 1) - self.doc.poll_slidenumber(self.is_live) + self.poll() def first(self): """ Based on the handler passed at startup triggers the first slide """ log.debug(u'Live = %s, first' % self.is_live) + if not self.doc: + return if not self.is_live: return - if self.doc.is_blank(): + if self.hide_mode: self.doc.slidenumber = 1 + self.poll() + return + if not self.activate(): return - self.activate() self.doc.start_presentation() - self.doc.poll_slidenumber(self.is_live) + self.poll() def last(self): """ Based on the handler passed at startup triggers the last slide """ log.debug(u'Live = %s, last' % self.is_live) + if not self.doc: + return if not self.is_live: return - if self.doc.is_blank(): + if self.hide_mode: self.doc.slidenumber = self.doc.get_slide_count() + self.poll() + return + if not self.activate(): return - self.activate() self.doc.goto_slide(self.doc.get_slide_count()) - self.doc.poll_slidenumber(self.is_live) + self.poll() def next(self): """ Based on the handler passed at startup triggers the next slide event """ log.debug(u'Live = %s, next' % self.is_live) + if not self.doc: + return if not self.is_live: return - if self.doc.is_blank(): + if self.hide_mode: + if not self.doc.is_active(): + return if self.doc.slidenumber < self.doc.get_slide_count(): self.doc.slidenumber = self.doc.slidenumber + 1 + self.poll() + return + if not self.activate(): return # The "End of slideshow" screen is after the last slide # Note, we can't just stop on the last slide, since it may # contain animations that need to be stepped through. if self.doc.slidenumber > self.doc.get_slide_count(): return - self.activate() self.doc.next_step() - self.doc.poll_slidenumber(self.is_live) + self.poll() def previous(self): """ Based on the handler passed at startup triggers the previous slide event """ log.debug(u'Live = %s, previous' % self.is_live) + if not self.doc: + return if not self.is_live: return - if self.doc.is_blank(): + if self.hide_mode: + if not self.doc.is_active(): + return if self.doc.slidenumber > 1: self.doc.slidenumber = self.doc.slidenumber - 1 + self.poll() + return + if not self.activate(): return - self.activate() self.doc.previous_step() - self.doc.poll_slidenumber(self.is_live) + self.poll() def shutdown(self): """ Based on the handler passed at startup triggers slide show to shut down """ log.debug(u'Live = %s, shutdown' % self.is_live) + if not self.doc: + return self.doc.close_presentation() self.doc = None @@ -190,21 +226,30 @@ class Controller(object): Instruct the controller to blank the presentation """ log.debug(u'Live = %s, blank' % self.is_live) + self.hide_mode = hide_mode + if not self.doc: + return if not self.is_live: return - if not self.doc.is_loaded(): - return - if not self.doc.is_active(): - return if hide_mode == HideMode.Theme: + if not self.doc.is_loaded(): + return + if not self.doc.is_active(): + return Receiver.send_message(u'live_display_hide', HideMode.Theme) - self.doc.blank_screen() + elif hide_mode == HideMode.Blank: + if not self.activate(): + return + self.doc.blank_screen() def stop(self): """ Instruct the controller to stop and hide the presentation """ log.debug(u'Live = %s, stop' % self.is_live) + self.hide_mode = HideMode.Screen + if not self.doc: + return if not self.is_live: return if not self.doc.is_loaded(): @@ -218,9 +263,13 @@ class Controller(object): Instruct the controller to unblank the presentation """ log.debug(u'Live = %s, unblank' % self.is_live) + self.hide_mode = None + if not self.doc: + return if not self.is_live: return - self.activate() + if not self.activate(): + return if self.doc.slidenumber and \ self.doc.slidenumber != self.doc.get_slide_number(): self.doc.goto_slide(self.doc.slidenumber) @@ -228,7 +277,9 @@ class Controller(object): Receiver.send_message(u'live_display_hide', HideMode.Screen) def poll(self): - self.doc.poll_slidenumber(self.is_live) + if not self.doc: + return + self.doc.poll_slidenumber(self.is_live, self.hide_mode) class MessageListener(object): diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index 54b9c2144..7da9a95d7 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -94,9 +94,9 @@ class PowerpointController(PresentationController): self.docs[0].close_presentation() if self.process is None: return - if self.process.Presentations.Count > 0: - return try: + if self.process.Presentations.Count > 0: + return self.process.Quit() except pywintypes.com_error: pass @@ -210,6 +210,13 @@ class PowerpointDocument(PresentationDocument): self.presentation.SlideShowSettings.Run() self.presentation.SlideShowWindow.View.State = 1 self.presentation.SlideShowWindow.Activate() + if self.presentation.Application.Version == u'14.0': + # Unblanking is broken in PowerPoint 2010, need to redisplay + slide = self.presentation.SlideShowWindow.View.CurrentShowPosition + click = self.presentation.SlideShowWindow.View.GetClickIndex() + self.presentation.SlideShowWindow.View.GotoSlide(slide) + if click: + self.presentation.SlideShowWindow.View.GotoClick(click) def blank_screen(self): """ @@ -253,6 +260,8 @@ class PowerpointDocument(PresentationDocument): renderer = self.controller.plugin.renderer rect = renderer.screens.current[u'size'] ppt_window = self.presentation.SlideShowSettings.Run() + if not ppt_window: + return ppt_window.Top = rect.y() * 72 / dpi ppt_window.Height = rect.height() * 72 / dpi ppt_window.Left = rect.x() * 72 / dpi @@ -286,6 +295,8 @@ class PowerpointDocument(PresentationDocument): """ log.debug(u'next_step') self.presentation.SlideShowWindow.View.Next() + if self.get_slide_number() > self.get_slide_count(): + self.previous_step() def previous_step(self): """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index b72e1e9d4..4006cbc3e 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -260,16 +260,17 @@ class PresentationDocument(object): else: return None - def poll_slidenumber(self, is_live): + def poll_slidenumber(self, is_live, hide_mode): """ Check the current slide number """ if not self.is_active(): return - current = self.get_slide_number() - if current == self.slidenumber: - return - self.slidenumber = current + if not hide_mode: + current = self.get_slide_number() + if current == self.slidenumber: + return + self.slidenumber = current if is_live: prefix = u'live' else: diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html index dad2f8351..148d444bf 100644 --- a/openlp/plugins/remotes/html/index.html +++ b/openlp/plugins/remotes/html/index.html @@ -66,10 +66,10 @@ ${refresh}
@@ -97,10 +97,10 @@ ${refresh}
@@ -127,10 +127,10 @@

${alerts}

@@ -148,10 +148,10 @@

${search}

diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 26c13d2f1..87c82c5b4 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -55,7 +55,9 @@ window.OpenLP = { ); }, loadService: function (event) { - event.preventDefault(); + if (event) { + event.preventDefault(); + } $.getJSON( "/api/service/list", function (data, status) { @@ -150,6 +152,10 @@ window.OpenLP = { OpenLP.currentSlide = data.results.slide; OpenLP.currentItem = data.results.item; if ($("#service-manager").is(":visible")) { + if (OpenLP.currentService != data.results.service) { + OpenLP.currentService = data.results.service; + OpenLP.loadService(); + } $("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c"); $("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () { var item = $(this); @@ -307,7 +313,7 @@ window.OpenLP = { } ); }, - escapeString: function (string) { + escapeString: function (string) { return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") } } diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index 38bd0fe01..6c5f35e1e 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -139,8 +139,10 @@ window.OpenLP = { "/api/poll", function (data, status) { OpenLP.updateClock(data); - if (OpenLP.currentItem != data.results.item) { + if (OpenLP.currentItem != data.results.item || + OpenLP.currentService != data.results.service) { OpenLP.currentItem = data.results.item; + OpenLP.currentService = data.results.service; OpenLP.loadSlides(); } else if (OpenLP.currentSlide != data.results.slide) { diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index cd2802542..8a66b919a 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -308,7 +308,9 @@ class HttpConnection(object): 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), 'no_results': translate('RemotePlugin.Mobile', 'No Results'), - 'options': translate('RemotePlugin.Mobile', 'Options') + 'options': translate('RemotePlugin.Mobile', 'Options'), + 'service': translate('RemotePlugin.Mobile', 'Service'), + 'slides': translate('RemotePlugin.Mobile', 'Slides') } def ready_read(self): diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index c483c91b6..1f2988568 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -29,7 +29,6 @@ The :mod:`songexportform` module provides the wizard for exporting songs to the OpenLyrics format. """ -import locale import logging from PyQt4 import QtCore, QtGui @@ -38,6 +37,7 @@ from openlp.core.lib import build_icon, Receiver, SettingsManager, translate, \ create_separated_list from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui.wizard import OpenLPWizard, WizardStrings +from openlp.core.utils import locale_direct_compare from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport @@ -252,7 +252,8 @@ class SongExportForm(OpenLPWizard): # Load the list of songs. Receiver.send_message(u'cursor_busy') songs = self.plugin.manager.get_all_objects(Song) - songs.sort(cmp=locale.strcoll, key=lambda song: song.title.lower()) + songs.sort( + cmp=locale_direct_compare, key=lambda song: song.sort_string) for song in songs: # No need to export temporary songs. if song.temporary: diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index d79d177fd..122d29859 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -31,8 +31,9 @@ the Songs plugin """ from sqlalchemy import Column, ForeignKey, Table, types -from sqlalchemy.orm import mapper, relation +from sqlalchemy.orm import mapper, relation, reconstructor from sqlalchemy.sql.expression import func +from PyQt4 import QtCore from openlp.core.lib.db import BaseModel, init_db @@ -63,7 +64,22 @@ class Song(BaseModel): """ Song model """ - pass + def __init__(self): + self.sort_string = '' + + # This decorator tells sqlalchemy to call this method everytime + # any data on this object are updated. + @reconstructor + def init_on_load(self): + """ + Precompute string to be used for sorting. + + Song sorting is performance sensitive operation. + To get maximum speed lets precompute the string + used for comparison. + """ + # Avoid the overhead of converting string to lowercase and to QString + self.sort_string = QtCore.QString(self.title.lower()) class Topic(BaseModel): diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 88999dce4..05da69f61 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -27,7 +27,6 @@ ############################################################################### import logging -import locale import re import os import shutil @@ -40,7 +39,7 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ check_directory_exists from openlp.core.lib.ui import UiStrings, create_widget_action from openlp.core.lib.settings import Settings -from openlp.core.utils import AppLocation +from openlp.core.utils import AppLocation, locale_direct_compare from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ SongImportForm, SongExportForm from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \ @@ -260,10 +259,8 @@ class SongMediaItem(MediaManagerItem): log.debug(u'display results Song') self.saveAutoSelectId() self.listView.clear() - # Sort the songs by its title considering language specific characters. - # lower() is needed for windows! searchresults.sort( - cmp=locale.strcoll, key=lambda song: song.title.lower()) + cmp=locale_direct_compare, key=lambda song: song.sort_string) for song in searchresults: # Do not display temporary songs if song.temporary: diff --git a/openlp/plugins/songs/lib/olpimport.py b/openlp/plugins/songs/lib/olpimport.py index 7a9d98c98..4b97fdafe 100644 --- a/openlp/plugins/songs/lib/olpimport.py +++ b/openlp/plugins/songs/lib/olpimport.py @@ -63,10 +63,14 @@ class OpenLPSongImport(SongImport): SongImport.__init__(self, manager, **kwargs) self.sourceSession = None - def doImport(self): + def doImport(self, progressDialog=None): """ Run the import for an OpenLP version 2 song database. + + ``progressDialog`` + The QProgressDialog used when importing songs from the FRW. """ + class OldAuthor(BaseModel): """ Author model @@ -101,13 +105,14 @@ class OpenLPSongImport(SongImport): """ pass - + # Check the file type if not self.importSource.endswith(u'.sqlite'): self.logError(self.importSource, translate('SongsPlugin.OpenLPSongImport', 'Not a valid OpenLP 2.0 song database.')) return self.importSource = u'sqlite:///%s' % self.importSource + # Load the db file engine = create_engine(self.importSource) source_meta = MetaData() source_meta.reflect(engine) @@ -224,7 +229,11 @@ class OpenLPSongImport(SongImport): file_name=media_file.file_name)) clean_song(self.manager, new_song) self.manager.save_object(new_song) - if self.importWizard: + if progressDialog: + progressDialog.setValue(progressDialog.value() + 1) + progressDialog.setLabelText( + WizardStrings.ImportingType % new_song.title) + else: self.importWizard.incrementProgressBar( WizardStrings.ImportingType % new_song.title) if self.stopImportFlag: diff --git a/openlp/plugins/songs/lib/oooimport.py b/openlp/plugins/songs/lib/oooimport.py index 49e6c2a70..da96dc936 100644 --- a/openlp/plugins/songs/lib/oooimport.py +++ b/openlp/plugins/songs/lib/oooimport.py @@ -27,6 +27,7 @@ ############################################################################### import logging import os +import time from PyQt4 import QtCore @@ -123,6 +124,7 @@ class OooImport(SongImport): try: uno_instance = get_uno_instance(resolver) except NoConnectException: + time.sleep(0.1) log.exception("Failed to resolve uno connection") self.startOooProcess() loop += 1 diff --git a/openlp/plugins/songs/lib/sundayplusimport.py b/openlp/plugins/songs/lib/sundayplusimport.py index fcf324d41..11b54bdc8 100644 --- a/openlp/plugins/songs/lib/sundayplusimport.py +++ b/openlp/plugins/songs/lib/sundayplusimport.py @@ -154,18 +154,20 @@ class SundayPlusImport(SongImport): # If any line inside any verse contains CCLI or # only Public Domain, we treat this as special data: # we remove that line and add data to specific field. + processed_lines = [] for i in xrange(len(lines)): - lines[i] = lines[i].strip() - line = lines[i] - if line[:4].lower() == u'ccli': + line = lines[i].strip() + if line[:3].lower() == u'ccl': m = re.search(r'[0-9]+', line) if m: self.ccliNumber = int(m.group(0)) - lines.pop(i) + continue elif line.lower() == u'public domain': self.copyright = u'Public Domain' - lines.pop(i) - self.addVerse('\n'.join(lines).strip(), verse_type) + continue + processed_lines.append(line) + self.addVerse('\n'.join(processed_lines).strip(), + verse_type) if end == -1: break i = end + 1 diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 72f0ccd7a..407971ae0 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -260,7 +260,7 @@ class OpenLyrics(object): IMPLEMENTED_VERSION = u'0.8' START_TAGS_REGEX = re.compile(r'\{(\w+)\}') END_TAGS_REGEX = re.compile(r'\{\/(\w+)\}') - VERSE_NUMBER_REGEX = re.compile(u'[a-zA-Z]*') + VERSE_TAG_SPLITTER = re.compile(u'([a-zA-Z]+)([0-9]*)([a-zA-Z]?)') def __init__(self, manager): self.manager = manager @@ -325,10 +325,22 @@ class OpenLyrics(object): # Process the song's lyrics. lyrics = etree.SubElement(song_xml, u'lyrics') verse_list = sxml.get_verses(song.lyrics) + # Add a suffix letter to each verse + verse_tags = [] for verse in verse_list: verse_tag = verse[0][u'type'][0].lower() verse_number = verse[0][u'label'] verse_def = verse_tag + verse_number + verse_tags.append(verse_def) + # Create the letter from the number of duplicates + verse[0][u'suffix'] = chr(96 + verse_tags.count(verse_def)) + # If the verse tag is a duplicate use the suffix letter + for verse in verse_list: + verse_tag = verse[0][u'type'][0].lower() + verse_number = verse[0][u'label'] + verse_def = verse_tag + verse_number + if verse_tags.count(verse_def) > 1: + verse_def += verse[0][u'suffix'] verse_element = \ self._add_text_to_element(u'verse', lyrics, None, verse_def) if u'lang' in verse[0]: @@ -742,28 +754,28 @@ class OpenLyrics(object): if lines.get(u'break') is not None: text += u'\n[---]' verse_def = verse.get(u'name', u' ').lower() - if verse_def[0] in VerseType.Tags: - verse_tag = verse_def[0] - else: + verse_tag, verse_number, verse_part = \ + OpenLyrics.VERSE_TAG_SPLITTER.search(verse_def).groups() + if verse_tag not in VerseType.Tags: verse_tag = VerseType.Tags[VerseType.Other] - verse_number = OpenLyrics.VERSE_NUMBER_REGEX.sub(u'', verse_def) # OpenLyrics allows e. g. "c", but we need "c1". However, this does # not correct the verse order. if not verse_number: verse_number = u'1' lang = verse.get(u'lang') + translit = verse.get(u'translit') # In OpenLP 1.9.6 we used v1a, v1b ... to represent visual slide # breaks. In OpenLyrics 0.7 an attribute has been added. if song_xml.get(u'modifiedIn') in (u'1.9.6', u'OpenLP 1.9.6') and \ song_xml.get(u'version') == u'0.7' and \ - (verse_tag, verse_number, lang) in verses: - verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text + (verse_tag, verse_number, lang, translit) in verses: + verses[(verse_tag, verse_number, lang, translit, None)] += u'\n[---]\n' + text # Merge v1a, v1b, .... to v1. - elif (verse_tag, verse_number, lang) in verses: - verses[(verse_tag, verse_number, lang)] += u'\n' + text + elif (verse_tag, verse_number, lang, translit, verse_part) in verses: + verses[(verse_tag, verse_number, lang, translit, verse_part)] += u'\n' + text else: - verses[(verse_tag, verse_number, lang)] = text - verse_def_list.append((verse_tag, verse_number, lang)) + verses[(verse_tag, verse_number, lang, translit, verse_part)] = text + verse_def_list.append((verse_tag, verse_number, lang, translit, verse_part)) # We have to use a list to keep the order, as dicts are not sorted. for verse in verse_def_list: sxml.add_verse_to_lyrics( diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 8b7bd36e8..46506a478 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -29,6 +29,7 @@ import logging import os from tempfile import gettempdir +import sqlite3 from PyQt4 import QtCore, QtGui @@ -82,7 +83,7 @@ class SongsPlugin(Plugin): unicode(UiStrings().Tools)) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'servicemanager_new_service'), - self.clearTemporarySongs) + self.clearTemporarySongs) def addImportMenuItem(self, import_menu): @@ -235,31 +236,37 @@ class SongsPlugin(Plugin): If the first time wizard has run, this function is run to import all the new songs into the database. """ + Receiver.send_message(u'openlp_process_events') self.onToolsReindexItemTriggered() + Receiver.send_message(u'openlp_process_events') db_dir = unicode(os.path.join( unicode(gettempdir(), get_filesystem_encoding()), u'openlp')) if not os.path.exists(db_dir): return song_dbs = [] + song_count = 0 for sfile in os.listdir(db_dir): if sfile.startswith(u'songs_') and sfile.endswith(u'.sqlite'): + Receiver.send_message(u'openlp_process_events') song_dbs.append(os.path.join(db_dir, sfile)) + song_count += self._countSongs(os.path.join(db_dir, sfile)) if not song_dbs: return + Receiver.send_message(u'openlp_process_events') progress = QtGui.QProgressDialog(self.formParent) progress.setWindowModality(QtCore.Qt.WindowModal) progress.setWindowTitle(translate('OpenLP.Ui', 'Importing Songs')) progress.setLabelText(translate('OpenLP.Ui', 'Starting import...')) progress.setCancelButton(None) - progress.setRange(0, len(song_dbs)) + progress.setRange(0, song_count) progress.setMinimumDuration(0) progress.forceShow() - for idx, db in enumerate(song_dbs): - progress.setValue(idx) - Receiver.send_message(u'openlp_process_events') + Receiver.send_message(u'openlp_process_events') + for db in song_dbs: importer = OpenLPSongImport(self.manager, filename=db) - importer.doImport() - progress.setValue(len(song_dbs)) + importer.doImport(progress) + Receiver.send_message(u'openlp_process_events') + progress.setValue(song_count) self.mediaItem.onSearchTextButtonClicked() def finalise(self): @@ -287,3 +294,15 @@ class SongsPlugin(Plugin): songs = self.manager.get_all_objects(Song, Song.temporary == True) for song in songs: self.manager.delete_object(Song, song.id) + + def _countSongs(self, db_file): + connection = sqlite3.connect(db_file) + cursor = connection.cursor() + cursor.execute(u'SELECT COUNT(id) AS song_count FROM songs') + song_count = cursor.fetchone()[0] + connection.close() + try: + song_count = int(song_count) + except (TypeError, ValueError): + song_count = 0 + return song_count diff --git a/resources/images/media_audio.png b/resources/images/media_audio.png new file mode 100644 index 000000000..f05a5bf55 Binary files /dev/null and b/resources/images/media_audio.png differ diff --git a/resources/images/media_video.png b/resources/images/media_video.png new file mode 100644 index 000000000..6e7340a13 Binary files /dev/null and b/resources/images/media_video.png differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 128042bf0..78651f9ce 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -130,7 +130,9 @@ media_time.png media_stop.png - image_clapperboard.png + media_audio.png + media_video.png + slidecontroller_multimedia.png messagebox_critical.png diff --git a/resources/images/openlp.org-icon-32.bmp b/resources/images/openlp.org-icon-32.bmp deleted file mode 100644 index be77f45b7..000000000 Binary files a/resources/images/openlp.org-icon-32.bmp and /dev/null differ diff --git a/resources/images/slidecontroller_multimedia.png b/resources/images/slidecontroller_multimedia.png new file mode 100644 index 000000000..55f63d880 Binary files /dev/null and b/resources/images/slidecontroller_multimedia.png differ