This commit is contained in:
Erik Lundin 2012-10-15 21:09:18 +02:00
commit afd646d10a
42 changed files with 474 additions and 206 deletions

View File

@ -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',
'<strong>WARNING:</strong> 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

View File

@ -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):
"""

View File

@ -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

View File

@ -48,7 +48,7 @@ import sys
from inspect import getargspec
__version__ = "N/A"
build_date = "Fri Sep 28 22:48:50 2012"
build_date = "Fri Oct 5 21:35:59 2012"
if sys.version_info[0] > 2:
str = str
@ -680,6 +680,19 @@ class LogCb(ctypes.c_void_p):
\note Log message handlers <b>must</b> be thread-safe.
"""
pass
class VideoLockCb(ctypes.c_void_p):
"""Callback prototype to allocate and lock a picture buffer.
Whenever a new video frame needs to be decoded, the lock callback is
invoked. Depending on the video chroma, one or three pixel planes of
adequate dimensions must be returned via the second parameter. Those
planes must be aligned on 32-bytes boundaries.
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param planes start address of the pixel planes (LibVLC allocates the array
of void pointers, this callback must initialize the array) [OUT]
\return a private pointer for the display and unlock callbacks to identify
the picture buffers
"""
pass
class VideoUnlockCb(ctypes.c_void_p):
"""Callback prototype to unlock a picture buffer.
When the video frame decoding is complete, the unlock callback is invoked.
@ -687,7 +700,7 @@ This callback might not be needed at all. It is only an indication that the
application can now read the pixel values if it needs to.
\warning A picture buffer is unlocked after the picture is decoded,
but before the picture is displayed.
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param picture private pointer returned from the @ref libvlc_video_lock_cb
callback [IN]
\param planes pixel planes as defined by the @ref libvlc_video_lock_cb
@ -698,7 +711,7 @@ class VideoDisplayCb(ctypes.c_void_p):
"""Callback prototype to display a picture.
When the video frame needs to be shown, as determined by the media playback
clock, the display callback is invoked.
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param picture private pointer returned from the @ref libvlc_video_lock_cb
callback [IN]
"""
@ -710,7 +723,7 @@ and the chain of video filters (if any). It can opt to change any parameter
as it needs. In that case, LibVLC will attempt to convert the video format
(rescaling and chroma conversion) but these operations can be CPU intensive.
\param opaque pointer to the private pointer passed to
libvlc_video_set_callbacks() [IN/OUT]
L{libvlc_video_set_callbacks}() [IN/OUT]
\param chroma pointer to the 4 bytes video format identifier [IN/OUT]
\param width pointer to the pixel width [IN/OUT]
\param height pointer to the pixel height [IN/OUT]
@ -730,7 +743,7 @@ in the video decoders, video filters and/or video converters.
pass
class VideoCleanupCb(ctypes.c_void_p):
"""Callback prototype to configure picture buffers format.
\param opaque private pointer as passed to libvlc_video_set_callbacks()
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}()
(and possibly modified by @ref libvlc_video_format_cb) [IN]
"""
pass
@ -806,6 +819,18 @@ class CallbackDecorators(object):
\param args variable argument list for the format
\note Log message handlers <b>must</b> be thread-safe.
'''
VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer.
Whenever a new video frame needs to be decoded, the lock callback is
invoked. Depending on the video chroma, one or three pixel planes of
adequate dimensions must be returned via the second parameter. Those
planes must be aligned on 32-bytes boundaries.
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param planes start address of the pixel planes (LibVLC allocates the array
of void pointers, this callback must initialize the array) [OUT]
\return a private pointer for the display and unlock callbacks to identify
the picture buffers
'''
VideoUnlockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
VideoUnlockCb.__doc__ = '''Callback prototype to unlock a picture buffer.
When the video frame decoding is complete, the unlock callback is invoked.
@ -813,7 +838,7 @@ This callback might not be needed at all. It is only an indication that the
application can now read the pixel values if it needs to.
\warning A picture buffer is unlocked after the picture is decoded,
but before the picture is displayed.
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param picture private pointer returned from the @ref libvlc_video_lock_cb
callback [IN]
\param planes pixel planes as defined by the @ref libvlc_video_lock_cb
@ -823,7 +848,7 @@ but before the picture is displayed.
VideoDisplayCb.__doc__ = '''Callback prototype to display a picture.
When the video frame needs to be shown, as determined by the media playback
clock, the display callback is invoked.
\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN]
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN]
\param picture private pointer returned from the @ref libvlc_video_lock_cb
callback [IN]
'''
@ -834,7 +859,7 @@ and the chain of video filters (if any). It can opt to change any parameter
as it needs. In that case, LibVLC will attempt to convert the video format
(rescaling and chroma conversion) but these operations can be CPU intensive.
\param opaque pointer to the private pointer passed to
libvlc_video_set_callbacks() [IN/OUT]
L{libvlc_video_set_callbacks}() [IN/OUT]
\param chroma pointer to the 4 bytes video format identifier [IN/OUT]
\param width pointer to the pixel width [IN/OUT]
\param height pointer to the pixel height [IN/OUT]
@ -853,7 +878,7 @@ in the video decoders, video filters and/or video converters.
'''
VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
VideoCleanupCb.__doc__ = '''Callback prototype to configure picture buffers format.
\param opaque private pointer as passed to libvlc_video_set_callbacks()
\param opaque private pointer as passed to L{libvlc_video_set_callbacks}()
(and possibly modified by @ref libvlc_video_format_cb) [IN]
'''
AudioPlayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int64)
@ -2483,9 +2508,22 @@ class MediaPlayer(_Ctype):
'''
return libvlc_media_player_stop(self)
def video_set_callbacks(self, lock, unlock, display, opaque):
'''Set callbacks and private data to render decoded video to a custom area
in memory.
Use L{video_set_format}() or L{video_set_format_callbacks}()
to configure the decoded format.
@param lock: callback to lock video memory (must not be NULL).
@param unlock: callback to unlock video memory (or NULL if not needed).
@param display: callback to display video (or NULL if not needed).
@param opaque: private pointer for the three callbacks (as first parameter).
@version: LibVLC 1.1.1 or later.
'''
return libvlc_video_set_callbacks(self, lock, unlock, display, opaque)
def video_set_format(self, chroma, width, height, pitch):
'''Set decoded video chroma and dimensions.
This only works in combination with libvlc_video_set_callbacks(),
This only works in combination with L{video_set_callbacks}(),
and is mutually exclusive with L{video_set_format_callbacks}().
@param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV").
@param width: pixel width.
@ -2498,7 +2536,7 @@ class MediaPlayer(_Ctype):
def video_set_format_callbacks(self, setup, cleanup):
'''Set decoded video chroma and dimensions. This only works in combination with
libvlc_video_set_callbacks().
L{video_set_callbacks}().
@param setup: callback to select the video format (cannot be NULL).
@param cleanup: callback to release any allocated resources (or NULL).
@version: LibVLC 2.0.0 or later.
@ -4382,9 +4420,26 @@ def libvlc_media_player_stop(p_mi):
None, MediaPlayer)
return f(p_mi)
def libvlc_video_set_callbacks(mp, lock, unlock, display, opaque):
'''Set callbacks and private data to render decoded video to a custom area
in memory.
Use L{libvlc_video_set_format}() or L{libvlc_video_set_format_callbacks}()
to configure the decoded format.
@param mp: the media player.
@param lock: callback to lock video memory (must not be NULL).
@param unlock: callback to unlock video memory (or NULL if not needed).
@param display: callback to display video (or NULL if not needed).
@param opaque: private pointer for the three callbacks (as first parameter).
@version: LibVLC 1.1.1 or later.
'''
f = _Cfunctions.get('libvlc_video_set_callbacks', None) or \
_Cfunction('libvlc_video_set_callbacks', ((1,), (1,), (1,), (1,), (1,),), None,
None, MediaPlayer, VideoLockCb, VideoUnlockCb, VideoDisplayCb, ctypes.c_void_p)
return f(mp, lock, unlock, display, opaque)
def libvlc_video_set_format(mp, chroma, width, height, pitch):
'''Set decoded video chroma and dimensions.
This only works in combination with libvlc_video_set_callbacks(),
This only works in combination with L{libvlc_video_set_callbacks}(),
and is mutually exclusive with L{libvlc_video_set_format_callbacks}().
@param mp: the media player.
@param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV").
@ -4401,7 +4456,7 @@ def libvlc_video_set_format(mp, chroma, width, height, pitch):
def libvlc_video_set_format_callbacks(mp, setup, cleanup):
'''Set decoded video chroma and dimensions. This only works in combination with
libvlc_video_set_callbacks().
L{libvlc_video_set_callbacks}().
@param mp: the media player.
@param setup: callback to select the video format (cannot be NULL).
@param cleanup: callback to release any allocated resources (or NULL).
@ -5878,10 +5933,9 @@ def libvlc_vlm_get_event_manager(p_instance):
return f(p_instance)
# 3 function(s) blacklisted:
# 2 function(s) blacklisted:
# libvlc_printerr
# libvlc_set_exit_handler
# libvlc_video_set_callbacks
# 17 function(s) not wrapped as methods:
# libvlc_audio_output_list_release

View File

@ -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:

View File

@ -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__)
@ -457,9 +457,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

View File

@ -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']

View File

@ -105,6 +105,7 @@ CSS = """
font-size: %spt;
color: %s;
background-color: %s;
word-wrap: break-word;
}
"""

View File

@ -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()

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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):

View File

@ -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')

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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()

View File

@ -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):
"""

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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):
"""

View File

@ -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()

View File

@ -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):

View File

@ -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):
"""

View File

@ -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:

View File

@ -66,10 +66,10 @@
<a href="#" id="service-refresh" data-role="button" data-icon="refresh">${refresh}</a>
<div data-role="navbar">
<ul>
<li><a href="#service-manager" data-theme="e">Service</a></li>
<li><a href="#slide-controller">Slides</a></li>
<li><a href="#alerts">Alerts</a></li>
<li><a href="#search">Search</a></li>
<li><a href="#service-manager" data-theme="e">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
@ -97,10 +97,10 @@
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">${refresh}</a>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">Service</a></li>
<li><a href="#slide-controller" data-theme="e">Slides</a></li>
<li><a href="#alerts">Alerts</a></li>
<li><a href="#search">Search</a></li>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller" data-theme="e">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
@ -127,10 +127,10 @@
<h1>${alerts}</h1>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">Service</a></li>
<li><a href="#slide-controller">Slides</a></li>
<li><a href="#alerts" data-theme="e">Alerts</a></li>
<li><a href="#search">Search</a></li>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts" data-theme="e">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
@ -148,10 +148,10 @@
<h1>${search}</h1>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">Service</a></li>
<li><a href="#slide-controller">Slides</a></li>
<li><a href="#alerts">Alerts</a></li>
<li><a href="#search" data-theme="e">Search</a></li>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search" data-theme="e">${search}</a></li>
</ul>
</div>
</div>

View File

@ -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, "\\\"")
}
}

View File

@ -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) {

View File

@ -308,7 +308,9 @@ class HttpConnection(object):
'add_and_go_to_service': translate('RemotePlugin.Mobile',
'Add &amp; 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):

View File

@ -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:

View File

@ -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):

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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,11 +754,10 @@ 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:
@ -757,13 +768,13 @@ class OpenLyrics(object):
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
verses[(verse_tag, verse_number, lang, None)] += u'\n[---]\n' + text
# Merge v1a, v1b, .... to v1.
elif (verse_tag, verse_number, lang) in verses:
elif (verse_tag, verse_number, lang, verse_part) in verses:
verses[(verse_tag, verse_number, lang)] += 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, verse_part)] = text
verse_def_list.append((verse_tag, verse_number, lang, 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(

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -130,7 +130,9 @@
<qresource prefix="media">
<file>media_time.png</file>
<file>media_stop.png</file>
<file>image_clapperboard.png</file>
<file>media_audio.png</file>
<file>media_video.png</file>
<file>slidecontroller_multimedia.png</file>
</qresource>
<qresource prefix="messagebox">
<file>messagebox_critical.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB