HEAD r1401

This commit is contained in:
Armin Köhler 2011-03-18 10:15:19 +01:00
commit 9510d9713b
56 changed files with 2535 additions and 1769 deletions

View File

@ -17,3 +17,9 @@ Helper Classes & Functions
.. automodule:: openlp.plugins.remotes.lib .. automodule:: openlp.plugins.remotes.lib
:members: :members:
.. autoclass:: openlp.plugins.remotes.lib.httpserver.HttpConnection
:members:
.. autoclass:: openlp.plugins.remotes.lib.httpserver.HttpResponse
:members:

View File

@ -184,13 +184,15 @@ class OpenLP(QtGui.QApplication):
# make sure Qt really display the splash screen # make sure Qt really display the splash screen
self.processEvents() self.processEvents()
# start the main app window # start the main app window
self.mainWindow = MainWindow(screens, app_version, self.clipboard(), self.mainWindow = MainWindow(screens, app_version, self.clipboard())
not has_run_wizard)
self.mainWindow.show() self.mainWindow.show()
if show_splash: if show_splash:
# now kill the splashscreen # now kill the splashscreen
self.splash.finish(self.mainWindow) self.splash.finish(self.mainWindow)
self.mainWindow.repaint() self.mainWindow.repaint()
self.processEvents()
if not has_run_wizard:
self.mainWindow.firstTime()
update_check = QtCore.QSettings().value( update_check = QtCore.QSettings().value(
u'general/update check', QtCore.QVariant(True)).toBool() u'general/update check', QtCore.QVariant(True)).toBool()
if update_check: if update_check:

View File

@ -62,7 +62,7 @@ class ItemCapabilities(object):
AddIfNewItem = 9 AddIfNewItem = 9
ProvidesOwnDisplay = 10 ProvidesOwnDisplay = 10
AllowsDetailedTitleDisplay = 11 AllowsDetailedTitleDisplay = 11
AllowsVarableStartTime = 12 AllowsVariableStartTime = 12
class ServiceItem(object): class ServiceItem(object):

View File

@ -33,8 +33,7 @@ from ConfigParser import SafeConfigParser
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, PluginStatus, check_directory_exists, \ from openlp.core.lib import translate, PluginStatus, Receiver, build_icon
Receiver, build_icon
from openlp.core.utils import get_web_page, AppLocation from openlp.core.utils import get_web_page, AppLocation
from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage
@ -105,23 +104,26 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
u'bible_%s' % bible, u'filename')) u'bible_%s' % bible, u'filename'))
item = QtGui.QTreeWidgetItem( item = QtGui.QTreeWidgetItem(
langItem, QtCore.QStringList(title)) langItem, QtCore.QStringList(title))
item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(filename)) item.setData(0, QtCore.Qt.UserRole,
QtCore.QVariant(filename))
item.setCheckState(0, QtCore.Qt.Unchecked) item.setCheckState(0, QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.biblesTreeWidget.expandAll() self.biblesTreeWidget.expandAll()
themes = self.config.get(u'themes', u'files') themes = self.config.get(u'themes', u'files')
themes = themes.split(u',') themes = themes.split(u',')
if not os.path.exists(os.path.join(gettempdir(), u'openlp')):
os.makedirs(os.path.join(gettempdir(), u'openlp'))
for theme in themes: for theme in themes:
title = self.config.get(u'theme_%s' % theme, u'title') title = self.config.get(u'theme_%s' % theme, u'title')
filename = self.config.get(u'theme_%s' % theme, u'filename') filename = self.config.get(u'theme_%s' % theme, u'filename')
screenshot = self.config.get(u'theme_%s' % theme, u'screenshot') screenshot = self.config.get(u'theme_%s' % theme, u'screenshot')
urllib.urlretrieve(u'%s/%s' % (self.web, screenshot), urllib.urlretrieve(u'%s/%s' % (self.web, screenshot),
os.path.join(gettempdir(), screenshot)) os.path.join(gettempdir(), u'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.themesListWidget) item = QtGui.QListWidgetItem(title, self.themesListWidget)
item.setData(QtCore.Qt.UserRole, item.setData(QtCore.Qt.UserRole,
QtCore.QVariant(filename)) QtCore.QVariant(filename))
item.setIcon(build_icon( item.setIcon(build_icon(
os.path.join(gettempdir(), screenshot))) os.path.join(gettempdir(), u'openlp', screenshot)))
item.setCheckState(QtCore.Qt.Unchecked) item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
@ -158,6 +160,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._performWizard() self._performWizard()
self._postWizard() self._postWizard()
def _getFileSize(self, url):
site = urllib.urlopen(url)
meta = site.info()
return int(meta.getheaders("Content-Length")[0])
def _downloadProgress(self, count, block_size, total_size):
increment = (count * block_size) - self.previous_size
self._incrementProgressBar(None, increment)
self.previous_size = count * block_size
def _incrementProgressBar(self, status_text, increment=1): def _incrementProgressBar(self, status_text, increment=1):
""" """
Update the wizard progress page. Update the wizard progress page.
@ -182,19 +194,27 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
max_progress = 2 max_progress = 2
# Loop through the songs list and increase for each selected item # Loop through the songs list and increase for each selected item
for i in xrange(self.songsListWidget.count()): for i in xrange(self.songsListWidget.count()):
if self.songsListWidget.item(i).checkState() == QtCore.Qt.Checked: item = self.songsListWidget.item(i)
max_progress += 1 if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole).toString()
size = self._getFileSize(u'%s%s' % (self.web, filename))
max_progress += size
# Loop through the Bibles list and increase for each selected item # Loop through the Bibles list and increase for each selected item
iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
while iterator.value(): while iterator.value():
item = iterator.value() item = iterator.value()
if item.parent() and item.checkState(0) == QtCore.Qt.Checked: if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
max_progress += 1 filename = item.data(0, QtCore.Qt.UserRole).toString()
size = self._getFileSize(u'%s%s' % (self.web, filename))
max_progress += size
iterator += 1 iterator += 1
# Loop through the themes list and increase for each selected item # Loop through the themes list and increase for each selected item
for i in xrange(self.themesListWidget.count()): for i in xrange(self.themesListWidget.count()):
if self.themesListWidget.item(i).checkState() == QtCore.Qt.Checked: item = self.themesListWidget.item(i)
max_progress += 1 if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole).toString()
size = self._getFileSize(u'%s%s' % (self.web, filename))
max_progress += size
self.finishButton.setVisible(False) self.finishButton.setVisible(False)
self.progressBar.setValue(0) self.progressBar.setValue(0)
self.progressBar.setMinimum(0) self.progressBar.setMinimum(0)
@ -222,7 +242,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
'Enabling selected plugins...')) 'Enabling selected plugins...'))
self._setPluginStatus(self.songsCheckBox, u'songs/status') self._setPluginStatus(self.songsCheckBox, u'songs/status')
self._setPluginStatus(self.bibleCheckBox, u'bibles/status') self._setPluginStatus(self.bibleCheckBox, u'bibles/status')
self._setPluginStatus(self.presentationCheckBox, u'presentations/status') self._setPluginStatus(self.presentationCheckBox,
u'presentations/status')
self._setPluginStatus(self.imageCheckBox, u'images/status') self._setPluginStatus(self.imageCheckBox, u'images/status')
self._setPluginStatus(self.mediaCheckBox, u'media/status') self._setPluginStatus(self.mediaCheckBox, u'media/status')
self._setPluginStatus(self.remoteCheckBox, u'remotes/status') self._setPluginStatus(self.remoteCheckBox, u'remotes/status')
@ -230,46 +251,41 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._setPluginStatus(self.songUsageCheckBox, u'songusage/status') self._setPluginStatus(self.songUsageCheckBox, u'songusage/status')
self._setPluginStatus(self.alertCheckBox, u'alerts/status') self._setPluginStatus(self.alertCheckBox, u'alerts/status')
# Build directories for downloads # Build directories for downloads
songs_destination = AppLocation.get_section_data_path(u'songs') songs_destination = os.path.join(unicode(gettempdir()), u'openlp')
bibles_destination = AppLocation.get_section_data_path(u'bibles') bibles_destination = AppLocation.get_section_data_path(u'bibles')
themes_destination = AppLocation.get_section_data_path(u'themes') themes_destination = AppLocation.get_section_data_path(u'themes')
# Install songs # Download songs
for i in xrange(self.songsListWidget.count()): for i in xrange(self.songsListWidget.count()):
item = self.songsListWidget.item(i) item = self.songsListWidget.item(i)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole).toString() filename = item.data(QtCore.Qt.UserRole).toString()
self._incrementProgressBar(self.downloading % filename) self._incrementProgressBar(self.downloading % filename, 0)
destination = os.path.join(songs_destination, u'songs.sqlite') self.previous_size = 0
if os.path.exists(destination): destination = os.path.join(songs_destination, unicode(filename))
if QtGui.QMessageBox.question(self, urllib.urlretrieve(u'%s%s' % (self.web, filename), destination,
translate('OpenLP.FirstTimeWizard', self._downloadProgress)
'Overwrite Existing Songs?'), # Download Bibles
translate('OpenLP.FirstTimeWizard', 'Your songs '
'database already exists and your current songs will '
'be permanently lost, are you sure you want to '
'replace it ?'),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes:
continue
urllib.urlretrieve(u'%s%s' % (self.web, filename), destination)
# Install Bibles
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget) bibles_iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
while bibles_iterator.value(): while bibles_iterator.value():
item = bibles_iterator.value() item = bibles_iterator.value()
if item.parent() and item.checkState(0) == QtCore.Qt.Checked: if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
bible = unicode(item.data(0, QtCore.Qt.UserRole).toString()) bible = unicode(item.data(0, QtCore.Qt.UserRole).toString())
self._incrementProgressBar(self.downloading % bible) self._incrementProgressBar(self.downloading % bible, 0)
self.previous_size = 0
urllib.urlretrieve(u'%s%s' % (self.web, bible), urllib.urlretrieve(u'%s%s' % (self.web, bible),
os.path.join(bibles_destination, bible)) os.path.join(bibles_destination, bible),
self._downloadProgress)
bibles_iterator += 1 bibles_iterator += 1
# Install themes # Download themes
for i in xrange(self.themesListWidget.count()): for i in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(i) item = self.themesListWidget.item(i)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
theme = unicode(item.data(QtCore.Qt.UserRole).toString()) theme = unicode(item.data(QtCore.Qt.UserRole).toString())
self._incrementProgressBar(self.downloading % theme) self._incrementProgressBar(self.downloading % theme, 0)
self.previous_size = 0
urllib.urlretrieve(u'%s%s' % (self.web, theme), urllib.urlretrieve(u'%s%s' % (self.web, theme),
os.path.join(themes_destination, theme)) os.path.join(themes_destination, theme),
self._downloadProgress)
# Set Default Display # Set Default Display
if self.displayComboBox.currentIndex() != -1: if self.displayComboBox.currentIndex() != -1:
QtCore.QSettings().setValue(u'General/monitor', QtCore.QSettings().setValue(u'General/monitor',
@ -285,4 +301,3 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
status = PluginStatus.Active if field.checkState() \ status = PluginStatus.Active if field.checkState() \
== QtCore.Qt.Checked else PluginStatus.Inactive == QtCore.Qt.Checked else PluginStatus.Inactive
QtCore.QSettings().setValue(tag, QtCore.QVariant(status)) QtCore.QSettings().setValue(tag, QtCore.QVariant(status))

View File

@ -46,7 +46,8 @@ class Ui_FirstTimeLanguageDialog(object):
self.languageLabel.setObjectName(u'languageLabel') self.languageLabel.setObjectName(u'languageLabel')
self.languageLayout.addWidget(self.languageLabel) self.languageLayout.addWidget(self.languageLabel)
self.languageComboBox = QtGui.QComboBox(languageDialog) self.languageComboBox = QtGui.QComboBox(languageDialog)
self.languageComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.languageComboBox.setSizeAdjustPolicy(
QtGui.QComboBox.AdjustToContents)
self.languageComboBox.setObjectName("languageComboBox") self.languageComboBox.setObjectName("languageComboBox")
self.languageLayout.addWidget(self.languageComboBox) self.languageLayout.addWidget(self.languageComboBox)
self.dialogLayout.addLayout(self.languageLayout) self.dialogLayout.addLayout(self.languageLayout)

View File

@ -26,7 +26,6 @@
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.lib import translate
from openlp.core.utils import LanguageManager from openlp.core.utils import LanguageManager
from firsttimelanguagedialog import Ui_FirstTimeLanguageDialog from firsttimelanguagedialog import Ui_FirstTimeLanguageDialog

View File

@ -32,27 +32,6 @@ from openlp.core.lib.ui import UiStrings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ValidEdit(QtGui.QLineEdit):
"""
Only allow numeric characters to be edited
"""
def __init__(self, parent):
"""
Set up Override and Validator
"""
QtGui.QLineEdit.__init__(self, parent)
self.setValidator(QtGui.QIntValidator(0, 9999, self))
def validText(self):
"""
Only return Integers. Space is 0
"""
if self.text().isEmpty():
return QtCore.QString(u'0')
else:
return self.text()
class GeneralTab(SettingsTab): class GeneralTab(SettingsTab):
""" """
GeneralTab is the general settings tab in the settings dialog. GeneralTab is the general settings tab in the settings dialog.
@ -164,30 +143,6 @@ class GeneralTab(SettingsTab):
self.displayGroupBox.setObjectName(u'displayGroupBox') self.displayGroupBox.setObjectName(u'displayGroupBox')
self.displayLayout = QtGui.QGridLayout(self.displayGroupBox) self.displayLayout = QtGui.QGridLayout(self.displayGroupBox)
self.displayLayout.setObjectName(u'displayLayout') self.displayLayout.setObjectName(u'displayLayout')
self.currentXLabel = QtGui.QLabel(self.displayGroupBox)
self.currentXLabel.setObjectName(u'currentXLabel')
self.displayLayout.addWidget(self.currentXLabel, 0, 0)
self.currentXValueLabel = QtGui.QLabel(self.displayGroupBox)
self.currentXValueLabel.setObjectName(u'currentXValueLabel')
self.displayLayout.addWidget(self.currentXValueLabel, 1, 0)
self.currentYLabel = QtGui.QLabel(self.displayGroupBox)
self.currentYLabel.setObjectName(u'currentYLabel')
self.displayLayout.addWidget(self.currentYLabel, 0, 1)
self.currentYValueLabel = QtGui.QLabel(self.displayGroupBox)
self.currentYValueLabel.setObjectName(u'currentYValueLabel')
self.displayLayout.addWidget(self.currentYValueLabel, 1, 1)
self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox)
self.currentWidthLabel.setObjectName(u'currentWidthLabel')
self.displayLayout.addWidget(self.currentWidthLabel, 0, 2)
self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox)
self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel')
self.displayLayout.addWidget(self.currentWidthValueLabel, 1, 2)
self.currentHeightLabel = QtGui.QLabel(self.displayGroupBox)
self.currentHeightLabel.setObjectName(u'currentHeightLabel')
self.displayLayout.addWidget(self.currentHeightLabel, 0, 3)
self.currentHeightValueLabel = QtGui.QLabel(self.displayGroupBox)
self.currentHeightValueLabel.setObjectName(u'Height')
self.displayLayout.addWidget(self.currentHeightValueLabel, 1, 3)
self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox) self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox)
self.overrideCheckBox.setObjectName(u'overrideCheckBox') self.overrideCheckBox.setObjectName(u'overrideCheckBox')
self.displayLayout.addWidget(self.overrideCheckBox, 2, 0, 1, 4) self.displayLayout.addWidget(self.overrideCheckBox, 2, 0, 1, 4)
@ -196,26 +151,30 @@ class GeneralTab(SettingsTab):
self.customXLabel = QtGui.QLabel(self.displayGroupBox) self.customXLabel = QtGui.QLabel(self.displayGroupBox)
self.customXLabel.setObjectName(u'customXLabel') self.customXLabel.setObjectName(u'customXLabel')
self.displayLayout.addWidget(self.customXLabel, 3, 0) self.displayLayout.addWidget(self.customXLabel, 3, 0)
self.customXValueEdit = ValidEdit(self.displayGroupBox) self.customXValueEdit = QtGui.QSpinBox(self.displayGroupBox)
self.customXValueEdit.setObjectName(u'customXValueEdit') self.customXValueEdit.setObjectName(u'customXValueEdit')
self.customXValueEdit.setMaximum(9999)
self.displayLayout.addWidget(self.customXValueEdit, 4, 0) self.displayLayout.addWidget(self.customXValueEdit, 4, 0)
self.customYLabel = QtGui.QLabel(self.displayGroupBox) self.customYLabel = QtGui.QLabel(self.displayGroupBox)
self.customYLabel.setObjectName(u'customYLabel') self.customYLabel.setObjectName(u'customYLabel')
self.displayLayout.addWidget(self.customYLabel, 3, 1) self.displayLayout.addWidget(self.customYLabel, 3, 1)
self.customYValueEdit = ValidEdit(self.displayGroupBox) self.customYValueEdit = QtGui.QSpinBox(self.displayGroupBox)
self.customYValueEdit.setObjectName(u'customYValueEdit') self.customYValueEdit.setObjectName(u'customYValueEdit')
self.customYValueEdit.setMaximum(9999)
self.displayLayout.addWidget(self.customYValueEdit, 4, 1) self.displayLayout.addWidget(self.customYValueEdit, 4, 1)
self.customWidthLabel = QtGui.QLabel(self.displayGroupBox) self.customWidthLabel = QtGui.QLabel(self.displayGroupBox)
self.customWidthLabel.setObjectName(u'customWidthLabel') self.customWidthLabel.setObjectName(u'customWidthLabel')
self.displayLayout.addWidget(self.customWidthLabel, 3, 2) self.displayLayout.addWidget(self.customWidthLabel, 3, 2)
self.customWidthValueEdit = ValidEdit(self.displayGroupBox) self.customWidthValueEdit = QtGui.QSpinBox(self.displayGroupBox)
self.customWidthValueEdit.setObjectName(u'customWidthValueEdit') self.customWidthValueEdit.setObjectName(u'customWidthValueEdit')
self.customWidthValueEdit.setMaximum(9999)
self.displayLayout.addWidget(self.customWidthValueEdit, 4, 2) self.displayLayout.addWidget(self.customWidthValueEdit, 4, 2)
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox) self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
self.customHeightLabel.setObjectName(u'customHeightLabel') self.customHeightLabel.setObjectName(u'customHeightLabel')
self.displayLayout.addWidget(self.customHeightLabel, 3, 3) self.displayLayout.addWidget(self.customHeightLabel, 3, 3)
self.customHeightValueEdit = ValidEdit(self.displayGroupBox) self.customHeightValueEdit = QtGui.QSpinBox(self.displayGroupBox)
self.customHeightValueEdit.setObjectName(u'customHeightValueEdit') self.customHeightValueEdit.setObjectName(u'customHeightValueEdit')
self.customHeightValueEdit.setMaximum(9999)
self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3) self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3)
self.rightLayout.addWidget(self.displayGroupBox) self.rightLayout.addWidget(self.displayGroupBox)
self.rightLayout.addStretch() self.rightLayout.addStretch()
@ -223,20 +182,22 @@ class GeneralTab(SettingsTab):
QtCore.QObject.connect(self.overrideCheckBox, QtCore.QObject.connect(self.overrideCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.onOverrideCheckBoxToggled) QtCore.SIGNAL(u'toggled(bool)'), self.onOverrideCheckBoxToggled)
QtCore.QObject.connect(self.customHeightValueEdit, QtCore.QObject.connect(self.customHeightValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'), QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customWidthValueEdit, QtCore.QObject.connect(self.customWidthValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'), QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customYValueEdit, QtCore.QObject.connect(self.customYValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'), QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customXValueEdit, QtCore.QObject.connect(self.customXValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'), QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
self.onDisplayPositionChanged)
# Reload the tab, as the screen resolution/count may have changed. # Reload the tab, as the screen resolution/count may have changed.
QtCore.QObject.connect(Receiver.get_receiver(), QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'config_screen_changed'), self.load) QtCore.SIGNAL(u'config_screen_changed'), self.load)
# Remove for now
self.usernameLabel.setVisible(False)
self.usernameEdit.setVisible(False)
self.passwordLabel.setVisible(False)
self.passwordEdit.setVisible(False)
def retranslateUi(self): def retranslateUi(self):
""" """
@ -267,8 +228,7 @@ class GeneralTab(SettingsTab):
'Automatically preview next item in service')) 'Automatically preview next item in service'))
self.timeoutLabel.setText(translate('OpenLP.GeneralTab', self.timeoutLabel.setText(translate('OpenLP.GeneralTab',
'Slide loop delay:')) 'Slide loop delay:'))
self.timeoutSpinBox.setSuffix( self.timeoutSpinBox.setSuffix(translate('OpenLP.GeneralTab', ' sec'))
translate('OpenLP.GeneralTab', ' sec'))
self.ccliGroupBox.setTitle( self.ccliGroupBox.setTitle(
translate('OpenLP.GeneralTab', 'CCLI Details')) translate('OpenLP.GeneralTab', 'CCLI Details'))
self.numberLabel.setText(UiStrings.CCLINumberLabel) self.numberLabel.setText(UiStrings.CCLINumberLabel)
@ -279,22 +239,11 @@ class GeneralTab(SettingsTab):
# Moved from display tab # Moved from display tab
self.displayGroupBox.setTitle( self.displayGroupBox.setTitle(
translate('OpenLP.GeneralTab', 'Display Position')) translate('OpenLP.GeneralTab', 'Display Position'))
self.currentXLabel.setText(translate('OpenLP.GeneralTab', 'X'))
self.currentXValueLabel.setText(u'0')
self.currentYLabel.setText(translate('OpenLP.GeneralTab', 'Y'))
self.currentYValueLabel.setText(u'0')
self.currentHeightLabel.setText(
translate('OpenLP.GeneralTab', 'Height'))
self.currentHeightValueLabel.setText(u'0')
self.currentWidthLabel.setText(
translate('OpenLP.GeneralTab', 'Width'))
self.currentWidthValueLabel.setText(u'0')
self.overrideCheckBox.setText(translate('OpenLP.GeneralTab', self.overrideCheckBox.setText(translate('OpenLP.GeneralTab',
'Override display position')) 'Override display position'))
self.customXLabel.setText(translate('OpenLP.GeneralTab', 'X')) self.customXLabel.setText(translate('OpenLP.GeneralTab', 'X'))
self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y')) self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y'))
self.customHeightLabel.setText( self.customHeightLabel.setText(translate('OpenLP.GeneralTab', 'Height'))
translate('OpenLP.GeneralTab', 'Height'))
self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width')) self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width'))
def load(self): def load(self):
@ -304,8 +253,7 @@ class GeneralTab(SettingsTab):
settings = QtCore.QSettings() settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection) settings.beginGroup(self.settingsSection)
self.monitorComboBox.clear() self.monitorComboBox.clear()
for screen in self.screens.get_screen_list(): self.monitorComboBox.addItems(self.screens.get_screen_list())
self.monitorComboBox.addItem(screen)
self.numberEdit.setText(unicode(settings.value( self.numberEdit.setText(unicode(settings.value(
u'ccli number', QtCore.QVariant(u'')).toString())) u'ccli number', QtCore.QVariant(u'')).toString()))
self.usernameEdit.setText(unicode(settings.value( self.usernameEdit.setText(unicode(settings.value(
@ -328,26 +276,16 @@ class GeneralTab(SettingsTab):
QtCore.QVariant(False)).toBool()) QtCore.QVariant(False)).toBool())
self.timeoutSpinBox.setValue(settings.value(u'loop delay', self.timeoutSpinBox.setValue(settings.value(u'loop delay',
QtCore.QVariant(5)).toInt()[0]) QtCore.QVariant(5)).toInt()[0])
self.currentXValueLabel.setText(
unicode(self.screens.current[u'size'].x()))
self.currentYValueLabel.setText(
unicode(self.screens.current[u'size'].y()))
self.currentHeightValueLabel.setText(
unicode(self.screens.current[u'size'].height()))
self.currentWidthValueLabel.setText(
unicode(self.screens.current[u'size'].width()))
self.overrideCheckBox.setChecked(settings.value(u'override position', self.overrideCheckBox.setChecked(settings.value(u'override position',
QtCore.QVariant(False)).toBool()) QtCore.QVariant(False)).toBool())
self.customXValueEdit.setText(settings.value(u'x position', self.customXValueEdit.setValue(settings.value(u'x position',
QtCore.QVariant(self.screens.current[u'size'].x())).toString()) QtCore.QVariant(self.screens.current[u'size'].x())).toInt()[0])
self.customYValueEdit.setText(settings.value(u'y position', self.customYValueEdit.setValue(settings.value(u'y position',
QtCore.QVariant(self.screens.current[u'size'].y())).toString()) QtCore.QVariant(self.screens.current[u'size'].y())).toInt()[0])
self.customHeightValueEdit.setText( self.customHeightValueEdit.setValue(settings.value(u'height',
settings.value(u'height', QtCore.QVariant( QtCore.QVariant(self.screens.current[u'size'].height())).toInt()[0])
self.screens.current[u'size'].height())).toString()) self.customWidthValueEdit.setValue(settings.value(u'width',
self.customWidthValueEdit.setText( QtCore.QVariant(self.screens.current[u'size'].width())).toInt()[0])
settings.value(u'width', QtCore.QVariant(
self.screens.current[u'size'].width())).toString())
settings.endGroup() settings.endGroup()
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
@ -385,13 +323,13 @@ class GeneralTab(SettingsTab):
settings.setValue(u'songselect password', settings.setValue(u'songselect password',
QtCore.QVariant(self.passwordEdit.displayText())) QtCore.QVariant(self.passwordEdit.displayText()))
settings.setValue(u'x position', settings.setValue(u'x position',
QtCore.QVariant(self.customXValueEdit.text())) QtCore.QVariant(self.customXValueEdit.value()))
settings.setValue(u'y position', settings.setValue(u'y position',
QtCore.QVariant(self.customYValueEdit.text())) QtCore.QVariant(self.customYValueEdit.value()))
settings.setValue(u'height', settings.setValue(u'height',
QtCore.QVariant(self.customHeightValueEdit.text())) QtCore.QVariant(self.customHeightValueEdit.value()))
settings.setValue(u'width', settings.setValue(u'width',
QtCore.QVariant(self.customWidthValueEdit.text())) QtCore.QVariant(self.customWidthValueEdit.value()))
settings.setValue(u'override position', settings.setValue(u'override position',
QtCore.QVariant(self.overrideCheckBox.isChecked())) QtCore.QVariant(self.overrideCheckBox.isChecked()))
settings.endGroup() settings.endGroup()
@ -415,10 +353,10 @@ class GeneralTab(SettingsTab):
# Reset screens after initial definition # Reset screens after initial definition
if self.overrideChanged: if self.overrideChanged:
self.screens.override[u'size'] = QtCore.QRect( self.screens.override[u'size'] = QtCore.QRect(
int(self.customXValueEdit.validText()), self.customXValueEdit.value(),
int(self.customYValueEdit.validText()), self.customYValueEdit.value(),
int(self.customWidthValueEdit.validText()), self.customWidthValueEdit.value(),
int(self.customHeightValueEdit.validText())) self.customHeightValueEdit.value())
if self.overrideCheckBox.isChecked(): if self.overrideCheckBox.isChecked():
self.screens.set_override_display() self.screens.set_override_display()
else: else:

View File

@ -67,6 +67,7 @@ class MainDisplay(DisplayWidget):
self.isLive = live self.isLive = live
self.alertTab = None self.alertTab = None
self.hideMode = None self.hideMode = None
self.videoHide = False
self.override = {} self.override = {}
mainIcon = build_icon(u':/icon/openlp-logo-16x16.png') mainIcon = build_icon(u':/icon/openlp-logo-16x16.png')
self.setWindowIcon(mainIcon) self.setWindowIcon(mainIcon)
@ -110,6 +111,12 @@ class MainDisplay(DisplayWidget):
QtCore.QObject.connect(self.mediaObject, QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'),
self.videoStart) self.videoStart)
QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'finished()'),
self.videoFinished)
QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'tick(qint64)'),
self.videoTick)
log.debug(u'Setup webView for monitor %s' % self.screens.monitor_number) log.debug(u'Setup webView for monitor %s' % self.screens.monitor_number)
self.webView = QtWebKit.QWebView(self) self.webView = QtWebKit.QWebView(self)
self.webView.setGeometry(0, 0, self.webView.setGeometry(0, 0,
@ -143,23 +150,22 @@ class MainDisplay(DisplayWidget):
if not background_color.isValid(): if not background_color.isValid():
background_color = QtCore.Qt.white background_color = QtCore.Qt.white
splash_image = QtGui.QImage(image_file) splash_image = QtGui.QImage(image_file)
initialFrame = QtGui.QImage( self.initialFrame = QtGui.QImage(
self.screens.current[u'size'].width(), self.screens.current[u'size'].width(),
self.screens.current[u'size'].height(), self.screens.current[u'size'].height(),
QtGui.QImage.Format_ARGB32_Premultiplied) QtGui.QImage.Format_ARGB32_Premultiplied)
painter_image = QtGui.QPainter() painter_image = QtGui.QPainter()
painter_image.begin(initialFrame) painter_image.begin(self.initialFrame)
painter_image.fillRect(initialFrame.rect(), background_color) painter_image.fillRect(self.initialFrame.rect(), background_color)
painter_image.drawImage( painter_image.drawImage(
(self.screens.current[u'size'].width() - (self.screens.current[u'size'].width() -
splash_image.width()) / 2, splash_image.width()) / 2,
(self.screens.current[u'size'].height() (self.screens.current[u'size'].height()
- splash_image.height()) / 2, splash_image) - splash_image.height()) / 2, splash_image)
serviceItem = ServiceItem() serviceItem = ServiceItem()
serviceItem.bg_image_bytes = image_to_byte(initialFrame) serviceItem.bg_image_bytes = image_to_byte(self.initialFrame)
self.webView.setHtml(build_html(serviceItem, self.screen, self.webView.setHtml(build_html(serviceItem, self.screen,
self.alertTab, self.isLive, None)) self.alertTab, self.isLive, None))
self.initialFrame = True
self.__hideMouse() self.__hideMouse()
# To display or not to display? # To display or not to display?
if not self.screen[u'primary']: if not self.screen[u'primary']:
@ -181,6 +187,7 @@ class MainDisplay(DisplayWidget):
# Wait for the webview to update before displaying text. # Wait for the webview to update before displaying text.
while not self.webLoaded: while not self.webLoaded:
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
self.setGeometry(self.screen[u'size'])
self.frame.evaluateJavaScript(u'show_text("%s")' % \ self.frame.evaluateJavaScript(u'show_text("%s")' % \
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
return self.preview() return self.preview()
@ -208,12 +215,18 @@ class MainDisplay(DisplayWidget):
else: else:
shrinkItem = self shrinkItem = self
if text: if text:
shrinkItem.resize(self.width(), int(height.toString())) alert_height = int(height.toString())
shrinkItem.resize(self.width(), alert_height)
shrinkItem.setVisible(True) shrinkItem.setVisible(True)
if self.alertTab.location == 1:
shrinkItem.move(self.screen[u'size'].left(),
(self.screen[u'size'].height() - alert_height) / 2)
elif self.alertTab.location == 2:
shrinkItem.move(self.screen[u'size'].left(),
self.screen[u'size'].height() - alert_height)
else: else:
shrinkItem.setVisible(False) shrinkItem.setVisible(False)
shrinkItem.resize(self.screen[u'size'].width(), self.setGeometry(self.screen[u'size'])
self.screen[u'size'].height())
def directImage(self, name, path): def directImage(self, name, path):
""" """
@ -243,6 +256,7 @@ class MainDisplay(DisplayWidget):
""" """
Display an image, as is. Display an image, as is.
""" """
self.setGeometry(self.screen[u'size'])
if image: if image:
js = u'show_image("data:image/png;base64,%s");' % image js = u'show_image("data:image/png;base64,%s");' % image
else: else:
@ -262,6 +276,7 @@ class MainDisplay(DisplayWidget):
self.displayImage(self.serviceItem.bg_image_bytes) self.displayImage(self.serviceItem.bg_image_bytes)
else: else:
self.displayImage(None) self.displayImage(None)
# clear the cache
self.override = {} self.override = {}
# Update the preview frame. # Update the preview frame.
if self.isLive: if self.isLive:
@ -336,6 +351,7 @@ class MainDisplay(DisplayWidget):
""" """
log.debug(u'video') log.debug(u'video')
self.webLoaded = True self.webLoaded = True
self.setGeometry(self.screen[u'size'])
# We are running a background theme # We are running a background theme
self.override[u'theme'] = u'' self.override[u'theme'] = u''
self.override[u'video'] = True self.override[u'video'] = True
@ -349,6 +365,10 @@ class MainDisplay(DisplayWidget):
self.mediaObject.stop() self.mediaObject.stop()
self.mediaObject.clearQueue() self.mediaObject.clearQueue()
self.mediaObject.setCurrentSource(Phonon.MediaSource(videoPath)) self.mediaObject.setCurrentSource(Phonon.MediaSource(videoPath))
# Need the timer to trigger set the trigger to 200ms
# Value taken from web documentation.
if self.serviceItem.start_time != 0:
self.mediaObject.setTickInterval(200)
self.mediaObject.play() self.mediaObject.play()
self.webView.setVisible(False) self.webView.setVisible(False)
self.videoWidget.setVisible(True) self.videoWidget.setVisible(True)
@ -363,8 +383,26 @@ class MainDisplay(DisplayWidget):
Start the video at a predetermined point. Start the video at a predetermined point.
""" """
if newState == Phonon.PlayingState: if newState == Phonon.PlayingState:
# set start time in milliseconds
self.mediaObject.seek(self.serviceItem.start_time * 1000) self.mediaObject.seek(self.serviceItem.start_time * 1000)
def videoFinished(self):
"""
Blank the Video when it has finished so the final frame is not left
hanging
"""
self.videoStop()
self.hideDisplay(HideMode.Blank)
self.phononActive = False
self.videoHide = True
def videoTick(self, tick):
"""
Triggered on video tick every 200 milli seconds
Will be used to manage stop time later
"""
pass
def isWebLoaded(self): def isWebLoaded(self):
""" """
Called by webView event to show display is fully loaded Called by webView event to show display is fully loaded
@ -395,6 +433,13 @@ class MainDisplay(DisplayWidget):
if self.isLive: if self.isLive:
if self.hideMode: if self.hideMode:
self.hideDisplay(self.hideMode) self.hideDisplay(self.hideMode)
else:
# Single screen active
if self.screens.monitor_number == 0:
# Only make visible if setting enabled
if QtCore.QSettings().value(u'general/display on monitor',
QtCore.QVariant(True)).toBool():
self.setVisible(True)
else: else:
self.setVisible(True) self.setVisible(True)
preview = QtGui.QImage(self.screen[u'size'].width(), preview = QtGui.QImage(self.screen[u'size'].width(),
@ -413,7 +458,7 @@ class MainDisplay(DisplayWidget):
""" """
log.debug(u'buildHtml') log.debug(u'buildHtml')
self.webLoaded = False self.webLoaded = False
self.initialFrame = False self.initialFrame = None
self.serviceItem = serviceItem self.serviceItem = serviceItem
background = None background = None
# We have an image override so keep the image till the theme changes # We have an image override so keep the image till the theme changes
@ -422,10 +467,12 @@ class MainDisplay(DisplayWidget):
if u'video' in self.override: if u'video' in self.override:
Receiver.send_message(u'video_background_replaced') Receiver.send_message(u'video_background_replaced')
self.override = {} self.override = {}
# We have a different theme.
elif self.override[u'theme'] != serviceItem.themedata.theme_name: elif self.override[u'theme'] != serviceItem.themedata.theme_name:
Receiver.send_message(u'live_theme_changed') Receiver.send_message(u'live_theme_changed')
self.override = {} self.override = {}
else: else:
# replace the background
background = self.imageManager. \ background = self.imageManager. \
get_image_bytes(self.override[u'image']) get_image_bytes(self.override[u'image'])
if self.serviceItem.themedata.background_filename: if self.serviceItem.themedata.background_filename:
@ -441,6 +488,10 @@ class MainDisplay(DisplayWidget):
# if was hidden keep it hidden # if was hidden keep it hidden
if self.hideMode and self.isLive: if self.hideMode and self.isLive:
self.hideDisplay(self.hideMode) self.hideDisplay(self.hideMode)
# display hidden for video end we have a new item so must be shown
if self.videoHide and self.isLive:
self.videoHide = False
self.showDisplay()
self.__hideMouse() self.__hideMouse()
def footer(self, text): def footer(self, text):

View File

@ -25,6 +25,8 @@
############################################################################### ###############################################################################
import logging import logging
import os
from tempfile import gettempdir
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
@ -215,8 +217,6 @@ class Ui_MainWindow(object):
self.ModeDefaultItem.setChecked(True) self.ModeDefaultItem.setChecked(True)
self.ToolsAddToolItem = icon_action(mainWindow, u'ToolsAddToolItem', self.ToolsAddToolItem = icon_action(mainWindow, u'ToolsAddToolItem',
u':/tools/tools_add.png') u':/tools/tools_add.png')
# Hide the entry, as it does not have any functionality yet.
self.ToolsAddToolItem.setVisible(False)
mainWindow.actionList.add_action(self.ToolsAddToolItem, u'Tools') mainWindow.actionList.add_action(self.ToolsAddToolItem, u'Tools')
self.ToolsOpenDataFolder = icon_action(mainWindow, self.ToolsOpenDataFolder = icon_action(mainWindow,
u'ToolsOpenDataFolder', u':/general/general_open.png') u'ToolsOpenDataFolder', u':/general/general_open.png')
@ -298,6 +298,13 @@ class Ui_MainWindow(object):
QtCore.QObject.connect(self.FileExitItem, QtCore.QObject.connect(self.FileExitItem,
QtCore.SIGNAL(u'triggered()'), mainWindow.close) QtCore.SIGNAL(u'triggered()'), mainWindow.close)
QtCore.QMetaObject.connectSlotsByName(mainWindow) QtCore.QMetaObject.connectSlotsByName(mainWindow)
# Hide the entry, as it does not have any functionality yet.
self.ToolsAddToolItem.setVisible(False)
self.ImportLanguageItem.setVisible(False)
self.ExportLanguageItem.setVisible(False)
self.SettingsShortcutsItem.setVisible(False)
self.HelpDocumentationItem.setVisible(False)
self.HelpOnlineHelpItem.setVisible(False)
def retranslateUi(self, mainWindow): def retranslateUi(self, mainWindow):
""" """
@ -461,14 +468,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
actionList = ActionList() actionList = ActionList()
def __init__(self, screens, applicationVersion, clipboard, firstTime): def __init__(self, screens, applicationVersion, clipboard):
""" """
This constructor sets up the interface, the various managers, and the This constructor sets up the interface, the various managers, and the
plugins. plugins.
""" """
QtGui.QMainWindow.__init__(self) QtGui.QMainWindow.__init__(self)
self.screens = screens self.screens = screens
self.actionList = ActionList()
self.applicationVersion = applicationVersion self.applicationVersion = applicationVersion
self.clipboard = clipboard self.clipboard = clipboard
# Set up settings sections for the main application # Set up settings sections for the main application
@ -478,6 +484,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.serviceSettingsSection = u'servicemanager' self.serviceSettingsSection = u'servicemanager'
self.songsSettingsSection = u'songs' self.songsSettingsSection = u'songs'
self.serviceNotSaved = False self.serviceNotSaved = False
self.actionList = ActionList()
self.settingsmanager = SettingsManager(screens) self.settingsmanager = SettingsManager(screens)
self.aboutForm = AboutForm(self, applicationVersion) self.aboutForm = AboutForm(self, applicationVersion)
self.settingsForm = SettingsForm(self.screens, self, self) self.settingsForm = SettingsForm(self.screens, self, self)
@ -624,10 +631,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.MediaToolBox.setCurrentIndex(savedPlugin) self.MediaToolBox.setCurrentIndex(savedPlugin)
self.settingsForm.postSetUp() self.settingsForm.postSetUp()
Receiver.send_message(u'cursor_normal') Receiver.send_message(u'cursor_normal')
# Import themes if first time
if firstTime:
self.themeManagerContents.firstTime()
def setAutoLanguage(self, value): def setAutoLanguage(self, value):
self.LanguageGroup.setDisabled(value) self.LanguageGroup.setDisabled(value)
@ -670,6 +673,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.setViewMode(False, True, False, False, True) self.setViewMode(False, True, False, False, True)
self.ModeLiveItem.setChecked(True) self.ModeLiveItem.setChecked(True)
def firstTime(self):
# Import themes if first time
Receiver.send_message(u'openlp_process_events')
self.themeManagerContents.firstTime()
for plugin in self.pluginManager.plugins:
if hasattr(plugin, u'firstTime'):
Receiver.send_message(u'openlp_process_events')
plugin.firstTime()
Receiver.send_message(u'openlp_process_events')
temp_dir = os.path.join(unicode(gettempdir()), u'openlp')
for filename in os.listdir(temp_dir):
os.remove(os.path.join(temp_dir, filename))
os.removedirs(temp_dir)
def blankCheck(self): def blankCheck(self):
""" """
Check and display message if screen blank on setup. Check and display message if screen blank on setup.

View File

@ -49,6 +49,19 @@ class ServiceManagerList(QtGui.QTreeWidget):
QtGui.QTreeWidget.__init__(self, parent) QtGui.QTreeWidget.__init__(self, parent)
self.mainwindow = mainwindow self.mainwindow = mainwindow
def keyPressEvent(self, event):
if isinstance(event, QtGui.QKeyEvent):
# here accept the event and do something
if event.key() == QtCore.Qt.Key_Up:
self.mainwindow.onMoveSelectionUp()
event.accept()
elif event.key() == QtCore.Qt.Key_Down:
self.mainwindow.onMoveSelectionDown()
event.accept()
event.ignore()
else:
event.ignore()
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
""" """
Drag and drop event does not care what data is selected Drag and drop event does not care what data is selected
@ -197,13 +210,13 @@ class ServiceManager(QtGui.QWidget):
u':/services/service_expand_all.png', u':/services/service_expand_all.png',
translate('OpenLP.ServiceManager', translate('OpenLP.ServiceManager',
'Expand all the service items.'), 'Expand all the service items.'),
self.onExpandAll) self.onExpandAll, shortcut=QtCore.Qt.Key_Plus)
self.serviceManagerList.collapse = self.orderToolbar.addToolbarButton( self.serviceManagerList.collapse = self.orderToolbar.addToolbarButton(
translate('OpenLP.ServiceManager', '&Collapse all'), translate('OpenLP.ServiceManager', '&Collapse all'),
u':/services/service_collapse_all.png', u':/services/service_collapse_all.png',
translate('OpenLP.ServiceManager', translate('OpenLP.ServiceManager',
'Collapse all the service items.'), 'Collapse all the service items.'),
self.onCollapseAll) self.onCollapseAll, shortcut=QtCore.Qt.Key_Minus)
self.orderToolbar.addSeparator() self.orderToolbar.addSeparator()
self.serviceManagerList.makeLive = self.orderToolbar.addToolbarButton( self.serviceManagerList.makeLive = self.orderToolbar.addToolbarButton(
translate('OpenLP.ServiceManager', 'Go Live'), translate('OpenLP.ServiceManager', 'Go Live'),
@ -293,7 +306,9 @@ class ServiceManager(QtGui.QWidget):
self.serviceManagerList.moveTop, self.serviceManagerList.moveTop,
self.serviceManagerList.moveBottom, self.serviceManagerList.moveBottom,
self.serviceManagerList.up, self.serviceManagerList.up,
self.serviceManagerList.down self.serviceManagerList.down,
self.serviceManagerList.expand,
self.serviceManagerList.collapse
]) ])
self.configUpdated() self.configUpdated()
@ -306,6 +321,9 @@ class ServiceManager(QtGui.QWidget):
actionList.add_action(self.serviceManagerList.makeLive, u'Service') actionList.add_action(self.serviceManagerList.makeLive, u'Service')
actionList.add_action(self.serviceManagerList.up, u'Service') actionList.add_action(self.serviceManagerList.up, u'Service')
actionList.add_action(self.serviceManagerList.down, u'Service') actionList.add_action(self.serviceManagerList.down, u'Service')
actionList.add_action(self.serviceManagerList.expand, u'Service')
actionList.add_action(self.serviceManagerList.collapse, u'Service')
def setModified(self, modified=True): def setModified(self, modified=True):
""" """
@ -600,7 +618,7 @@ class ServiceManager(QtGui.QWidget):
if item.parent() is None: if item.parent() is None:
self.notesAction.setVisible(True) self.notesAction.setVisible(True)
if serviceItem[u'service_item']\ if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsVarableStartTime): .is_capable(ItemCapabilities.AllowsVariableStartTime):
self.timeAction.setVisible(True) self.timeAction.setVisible(True)
self.themeMenu.menuAction().setVisible(False) self.themeMenu.menuAction().setVisible(False)
if serviceItem[u'service_item'].is_text(): if serviceItem[u'service_item'].is_text():
@ -887,7 +905,7 @@ class ServiceManager(QtGui.QWidget):
child.setText(0, text[:40]) child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count))
if item[u'service_item'] \ if item[u'service_item'] \
.is_capable(ItemCapabilities.AllowsVarableStartTime): .is_capable(ItemCapabilities.AllowsVariableStartTime):
tip = item[u'service_item'].get_media_time() tip = item[u'service_item'].get_media_time()
if tip: if tip:
child.setToolTip(0, tip) child.setToolTip(0, tip)

View File

@ -391,7 +391,7 @@ def get_uno_command():
Returns the UNO command to launch an openoffice.org instance. Returns the UNO command to launch an openoffice.org instance.
""" """
COMMAND = u'soffice' COMMAND = u'soffice'
OPTIONS = u'-nologo -norestore -minimized -invisible -nofirststartwizard' OPTIONS = u'-nologo -norestore -minimized -nodefault -nofirststartwizard'
if UNO_CONNECTION_TYPE == u'pipe': if UNO_CONNECTION_TYPE == u'pipe':
CONNECTION = u'"-accept=pipe,name=openlp_pipe;urp;"' CONNECTION = u'"-accept=pipe,name=openlp_pipe;urp;"'
else: else:

View File

@ -50,7 +50,8 @@ class BiblePlugin(Plugin):
self.manager = BibleManager(self) self.manager = BibleManager(self)
Plugin.initialise(self) Plugin.initialise(self)
self.importBibleItem.setVisible(True) self.importBibleItem.setVisible(True)
self.exportBibleItem.setVisible(True) # Set to invisible until we can export bibles
self.exportBibleItem.setVisible(False)
def finalise(self): def finalise(self):
""" """

View File

@ -92,6 +92,10 @@ class BGExtract(object):
cleanup = [(re.compile('\s+'), lambda match: ' ')] cleanup = [(re.compile('\s+'), lambda match: ' ')]
verses = BeautifulSoup(str(soup), markupMassage=cleanup) verses = BeautifulSoup(str(soup), markupMassage=cleanup)
verse_list = {} verse_list = {}
# Cater for inconsistent mark up in the first verse of a chapter.
first_verse = verses.find(u'versenum')
if first_verse:
verse_list[1] = unicode(first_verse.contents[0])
for verse in verses(u'sup', u'versenum'): for verse in verses(u'sup', u'versenum'):
raw_verse_num = verse.next raw_verse_num = verse.next
clean_verse_num = 0 clean_verse_num = 0

View File

@ -132,14 +132,18 @@ class MediaMediaItem(MediaManagerItem):
self.mediaObject.play() self.mediaObject.play()
service_item.title = unicode(self.plugin.nameStrings[u'singular']) service_item.title = unicode(self.plugin.nameStrings[u'singular'])
service_item.add_capability(ItemCapabilities.RequiresMedia) service_item.add_capability(ItemCapabilities.RequiresMedia)
service_item.add_capability(ItemCapabilities.AllowsVarableStartTime)
# force a nonexistent theme # force a nonexistent theme
service_item.theme = -1 service_item.theme = -1
frame = u':/media/image_clapperboard.png' frame = u':/media/image_clapperboard.png'
(path, name) = os.path.split(filename) (path, name) = os.path.split(filename)
file_size = os.path.getsize(filename)
# File too big for processing
if file_size <= 52428800: # 50MiB
while not self.mediaState: while not self.mediaState:
Receiver.send_message(u'openlp_process_events') Receiver.send_message(u'openlp_process_events')
service_item.media_length = self.mediaLength service_item.media_length = self.mediaLength
service_item.add_capability(
ItemCapabilities.AllowsVariableStartTime)
service_item.add_from_command(path, name, frame) service_item.add_from_command(path, name, frame)
return True return True
else: else:

View File

@ -49,11 +49,13 @@ class MediaPlugin(Plugin):
u'audio/ac3': [u'.ac3'], u'audio/ac3': [u'.ac3'],
u'audio/flac': [u'.flac'], u'audio/flac': [u'.flac'],
u'audio/x-m4a': [u'.m4a'], u'audio/x-m4a': [u'.m4a'],
u'audio/midi': [u'.mid', u'.midi'],
u'audio/x-mp3': [u'.mp3'], u'audio/x-mp3': [u'.mp3'],
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'], u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
u'audio/qcelp': [u'.qcp'], u'audio/qcelp': [u'.qcp'],
u'audio/x-wma': [u'.wma'], u'audio/x-wma': [u'.wma'],
u'audio/x-ms-wma': [u'.wma'], u'audio/x-ms-wma': [u'.wma'],
u'video/x-flv': [u'.flv'],
u'video/x-matroska': [u'.mpv', u'.mkv'], u'video/x-matroska': [u'.mpv', u'.mkv'],
u'video/x-wmv': [u'.wmv'], u'video/x-wmv': [u'.wmv'],
u'video/x-ms-wmv': [u'.wmv']} u'video/x-ms-wmv': [u'.wmv']}

View File

@ -80,7 +80,8 @@ class Controller(object):
if self.doc.is_active(): if self.doc.is_active():
return return
if not self.doc.is_loaded(): if not self.doc.is_loaded():
self.doc.load_presentation() if not self.doc.load_presentation():
return
if self.is_live: if self.is_live:
self.doc.start_presentation() self.doc.start_presentation()
if self.doc.slidenumber > 1: if self.doc.slidenumber > 1:

View File

@ -77,6 +77,8 @@ class PowerpointController(PresentationController):
""" """
Loads PowerPoint process Loads PowerPoint process
""" """
log.debug(u'start_process')
if not self.process:
self.process = Dispatch(u'PowerPoint.Application') self.process = Dispatch(u'PowerPoint.Application')
self.process.Visible = True self.process.Visible = True
self.process.WindowState = 2 self.process.WindowState = 2
@ -120,13 +122,14 @@ class PowerpointDocument(PresentationDocument):
``presentation`` ``presentation``
The file name of the presentations to run. The file name of the presentations to run.
""" """
log.debug(u'LoadPresentation') log.debug(u'load_presentation')
if not self.controller.process or not self.controller.process.Visible: if not self.controller.process or not self.controller.process.Visible:
self.controller.start_process() self.controller.start_process()
try: try:
self.controller.process.Presentations.Open(self.filepath, False, self.controller.process.Presentations.Open(self.filepath, False,
False, True) False, True)
except pywintypes.com_error: except pywintypes.com_error:
log.debug(u'PPT open failed')
return False return False
self.presentation = self.controller.process.Presentations( self.presentation = self.controller.process.Presentations(
self.controller.process.Presentations.Count) self.controller.process.Presentations.Count)
@ -145,6 +148,7 @@ class PowerpointDocument(PresentationDocument):
However, for the moment, we want a physical file since it makes life However, for the moment, we want a physical file since it makes life
easier elsewhere. easier elsewhere.
""" """
log.debug(u'create_thumbnails')
if self.check_thumbnails(): if self.check_thumbnails():
return return
for num in range(0, self.presentation.Slides.Count): for num in range(0, self.presentation.Slides.Count):
@ -170,6 +174,7 @@ class PowerpointDocument(PresentationDocument):
""" """
Returns ``True`` if a presentation is loaded. Returns ``True`` if a presentation is loaded.
""" """
log.debug(u'is_loaded')
try: try:
if not self.controller.process.Visible: if not self.controller.process.Visible:
return False return False
@ -186,6 +191,7 @@ class PowerpointDocument(PresentationDocument):
""" """
Returns ``True`` if a presentation is currently active. Returns ``True`` if a presentation is currently active.
""" """
log.debug(u'is_active')
if not self.is_loaded(): if not self.is_loaded():
return False return False
try: try:
@ -201,6 +207,7 @@ class PowerpointDocument(PresentationDocument):
""" """
Unblanks (restores) the presentation. Unblanks (restores) the presentation.
""" """
log.debug(u'unblank_screen')
self.presentation.SlideShowSettings.Run() self.presentation.SlideShowSettings.Run()
self.presentation.SlideShowWindow.View.State = 1 self.presentation.SlideShowWindow.View.State = 1
self.presentation.SlideShowWindow.Activate() self.presentation.SlideShowWindow.Activate()
@ -209,12 +216,14 @@ class PowerpointDocument(PresentationDocument):
""" """
Blanks the screen. Blanks the screen.
""" """
log.debug(u'blank_screen')
self.presentation.SlideShowWindow.View.State = 3 self.presentation.SlideShowWindow.View.State = 3
def is_blank(self): def is_blank(self):
""" """
Returns ``True`` if screen is blank. Returns ``True`` if screen is blank.
""" """
log.debug(u'is_blank')
if self.is_active(): if self.is_active():
return self.presentation.SlideShowWindow.View.State == 3 return self.presentation.SlideShowWindow.View.State == 3
else: else:
@ -224,6 +233,7 @@ class PowerpointDocument(PresentationDocument):
""" """
Stops the current presentation and hides the output. Stops the current presentation and hides the output.
""" """
log.debug(u'stop_presentation')
self.presentation.SlideShowWindow.View.Exit() self.presentation.SlideShowWindow.View.Exit()
if os.name == u'nt': if os.name == u'nt':
@ -231,6 +241,7 @@ class PowerpointDocument(PresentationDocument):
""" """
Starts a presentation from the beginning. Starts a presentation from the beginning.
""" """
log.debug(u'start_presentation')
#SlideShowWindow measures its size/position by points, not pixels #SlideShowWindow measures its size/position by points, not pixels
try: try:
dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88) dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88)
@ -253,30 +264,35 @@ class PowerpointDocument(PresentationDocument):
""" """
Returns the current slide number. Returns the current slide number.
""" """
log.debug(u'get_slide_number')
return self.presentation.SlideShowWindow.View.CurrentShowPosition return self.presentation.SlideShowWindow.View.CurrentShowPosition
def get_slide_count(self): def get_slide_count(self):
""" """
Returns total number of slides. Returns total number of slides.
""" """
log.debug(u'get_slide_count')
return self.presentation.Slides.Count return self.presentation.Slides.Count
def goto_slide(self, slideno): def goto_slide(self, slideno):
""" """
Moves to a specific slide in the presentation. Moves to a specific slide in the presentation.
""" """
log.debug(u'goto_slide')
self.presentation.SlideShowWindow.View.GotoSlide(slideno) self.presentation.SlideShowWindow.View.GotoSlide(slideno)
def next_step(self): def next_step(self):
""" """
Triggers the next effect of slide on the running presentation. Triggers the next effect of slide on the running presentation.
""" """
log.debug(u'next_step')
self.presentation.SlideShowWindow.View.Next() self.presentation.SlideShowWindow.View.Next()
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation. Triggers the previous slide on the running presentation.
""" """
log.debug(u'previous_step')
self.presentation.SlideShowWindow.View.Previous() self.presentation.SlideShowWindow.View.Previous()
def get_slide_text(self, slide_no): def get_slide_text(self, slide_no):

View File

@ -84,7 +84,8 @@ class PptviewController(PresentationController):
dllpath = os.path.join(self.plugin.pluginManager.basepath, dllpath = os.path.join(self.plugin.pluginManager.basepath,
u'presentations', u'lib', u'pptviewlib', u'pptviewlib.dll') u'presentations', u'lib', u'pptviewlib', u'pptviewlib.dll')
self.process = cdll.LoadLibrary(dllpath) self.process = cdll.LoadLibrary(dllpath)
#self.process.SetDebug(1) if log.isEnabledFor(logging.DEBUG):
self.process.SetDebug(1)
def kill(self): def kill(self):
""" """
@ -140,8 +141,10 @@ class PptviewDocument(PresentationDocument):
PPTviewLib creates large BMP's, but we want small PNG's for consistency. PPTviewLib creates large BMP's, but we want small PNG's for consistency.
Convert them here. Convert them here.
""" """
log.debug(u'create_thumbnails')
if self.check_thumbnails(): if self.check_thumbnails():
return return
log.debug(u'create_thumbnails proceeding')
for idx in range(self.get_slide_count()): for idx in range(self.get_slide_count()):
path = u'%s\\slide%s.bmp' % (self.get_temp_folder(), path = u'%s\\slide%s.bmp' % (self.get_temp_folder(),
unicode(idx + 1)) unicode(idx + 1))

View File

@ -30,19 +30,32 @@ from ctypes import *
from ctypes.wintypes import RECT from ctypes.wintypes import RECT
class PPTViewer(QtGui.QWidget): class PPTViewer(QtGui.QWidget):
"""
Standalone Test Harness for the pptviewlib library
"""
def __init__(self, parent=None): def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent) QtGui.QWidget.__init__(self, parent)
self.pptid = -1 self.pptid = -1
self.setWindowTitle(u'PowerPoint Viewer Test') self.setWindowTitle(u'PowerPoint Viewer Test')
PPTLabel = QtGui.QLabel(u'Open PowerPoint file') ppt_label = QtGui.QLabel(u'Open PowerPoint file')
slideLabel = QtGui.QLabel(u'Go to slide #') slide_label = QtGui.QLabel(u'Go to slide #')
self.PPTEdit = QtGui.QLineEdit() self.pptEdit = QtGui.QLineEdit()
self.slideEdit = QtGui.QLineEdit() self.slideEdit = QtGui.QLineEdit()
x_label = QtGui.QLabel(u'X pos')
y_label = QtGui.QLabel(u'Y pos')
width_label = QtGui.QLabel(u'Width')
height_label = QtGui.QLabel(u'Height')
self.xEdit = QtGui.QLineEdit(u'100')
self.yEdit = QtGui.QLineEdit(u'100')
self.widthEdit = QtGui.QLineEdit(u'900')
self.heightEdit = QtGui.QLineEdit(u'700')
self.total = QtGui.QLabel() self.total = QtGui.QLabel()
PPTBtn = QtGui.QPushButton(u'Open') ppt_btn = QtGui.QPushButton(u'Open')
PPTDlgBtn = QtGui.QPushButton(u'...') ppt_dlg_btn = QtGui.QPushButton(u'...')
slideBtn = QtGui.QPushButton(u'Go') folder_label = QtGui.QLabel(u'Slide .bmp path')
self.folderEdit = QtGui.QLineEdit(u'slide')
slide_btn = QtGui.QPushButton(u'Go')
prev = QtGui.QPushButton(u'Prev') prev = QtGui.QPushButton(u'Prev')
next = QtGui.QPushButton(u'Next') next = QtGui.QPushButton(u'Next')
blank = QtGui.QPushButton(u'Blank') blank = QtGui.QPushButton(u'Blank')
@ -51,122 +64,149 @@ class PPTViewer(QtGui.QWidget):
close = QtGui.QPushButton(u'Close') close = QtGui.QPushButton(u'Close')
resume = QtGui.QPushButton(u'Resume') resume = QtGui.QPushButton(u'Resume')
stop = QtGui.QPushButton(u'Stop') stop = QtGui.QPushButton(u'Stop')
pptwindow = QtGui.QWidget()
grid = QtGui.QGridLayout() grid = QtGui.QGridLayout()
grid.addWidget(PPTLabel, 0, 0) row = 0
grid.addWidget(self.PPTEdit, 0, 1) grid.addWidget(folder_label, 0, 0)
grid.addWidget(PPTDlgBtn, 0, 2) grid.addWidget(self.folderEdit, 0, 1)
grid.addWidget(PPTBtn, 0, 3) row = row + 1
grid.addWidget(slideLabel, 1, 0) grid.addWidget(x_label, row, 0)
grid.addWidget(self.slideEdit, 1, 1) grid.addWidget(self.xEdit, row, 1)
grid.addWidget(slideBtn, 1, 3) grid.addWidget(y_label, row, 2)
grid.addWidget(prev, 2, 0) grid.addWidget(self.yEdit, row, 3)
grid.addWidget(next, 2, 1) row = row + 1
grid.addWidget(blank, 3, 0) grid.addWidget(width_label, row, 0)
grid.addWidget(unblank, 3, 1) grid.addWidget(self.widthEdit, row, 1)
grid.addWidget(restart, 4, 0) grid.addWidget(height_label, row, 2)
grid.addWidget(close, 4, 1) grid.addWidget(self.heightEdit, row, 3)
grid.addWidget(stop, 5, 0) row = row + 1
grid.addWidget(resume, 5, 1) grid.addWidget(ppt_label, row, 0)
grid.addWidget(pptwindow, 6, 0, 10, 3) grid.addWidget(self.pptEdit, row, 1)
self.connect(PPTBtn, QtCore.SIGNAL(u'clicked()'), self.OpenClick) grid.addWidget(ppt_dlg_btn, row, 2)
self.connect(PPTDlgBtn, QtCore.SIGNAL(u'clicked()'), self.OpenDialog) grid.addWidget(ppt_btn, row, 3)
self.connect(slideBtn, QtCore.SIGNAL(u'clicked()'), self.GotoClick) row = row + 1
self.connect(prev, QtCore.SIGNAL(u'clicked()'), self.PrevClick) grid.addWidget(slide_label, row, 0)
self.connect(next, QtCore.SIGNAL(u'clicked()'), self.NextClick) grid.addWidget(self.slideEdit, row, 1)
self.connect(blank, QtCore.SIGNAL(u'clicked()'), self.BlankClick) grid.addWidget(slide_btn, row, 2)
self.connect(unblank, QtCore.SIGNAL(u'clicked()'), self.UnblankClick) row = row + 1
self.connect(restart, QtCore.SIGNAL(u'clicked()'), self.RestartClick) grid.addWidget(prev, row, 0)
self.connect(close, QtCore.SIGNAL(u'clicked()'), self.CloseClick) grid.addWidget(next, row, 1)
self.connect(stop, QtCore.SIGNAL(u'clicked()'), self.StopClick) row = row + 1
self.connect(resume, QtCore.SIGNAL(u'clicked()'), self.ResumeClick) grid.addWidget(blank, row, 0)
grid.addWidget(unblank, row, 1)
row = row + 1
grid.addWidget(restart, row, 0)
grid.addWidget(close, row, 1)
row = row + 1
grid.addWidget(stop, row, 0)
grid.addWidget(resume, row, 1)
self.connect(ppt_btn, QtCore.SIGNAL(u'clicked()'), self.openClick)
self.connect(ppt_dlg_btn, QtCore.SIGNAL(u'clicked()'), self.openDialog)
self.connect(slide_btn, QtCore.SIGNAL(u'clicked()'), self.gotoClick)
self.connect(prev, QtCore.SIGNAL(u'clicked()'), self.prevClick)
self.connect(next, QtCore.SIGNAL(u'clicked()'), self.nextClick)
self.connect(blank, QtCore.SIGNAL(u'clicked()'), self.blankClick)
self.connect(unblank, QtCore.SIGNAL(u'clicked()'), self.unblankClick)
self.connect(restart, QtCore.SIGNAL(u'clicked()'), self.restartClick)
self.connect(close, QtCore.SIGNAL(u'clicked()'), self.closeClick)
self.connect(stop, QtCore.SIGNAL(u'clicked()'), self.stopClick)
self.connect(resume, QtCore.SIGNAL(u'clicked()'), self.resumeClick)
self.setLayout(grid) self.setLayout(grid)
self.resize(300, 150) self.resize(300, 150)
def PrevClick(self): def prevClick(self):
if self.pptid<0: return if self.pptid < 0:
pptdll.PrevStep(self.pptid) return
self.UpdateCurrSlide() self.pptdll.PrevStep(self.pptid)
self.updateCurrSlide()
app.processEvents() app.processEvents()
def NextClick(self): def nextClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.NextStep(self.pptid) return
self.UpdateCurrSlide() self.pptdll.NextStep(self.pptid)
self.updateCurrSlide()
app.processEvents() app.processEvents()
def BlankClick(self): def blankClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.Blank(self.pptid) return
self.pptdll.Blank(self.pptid)
app.processEvents() app.processEvents()
def UnblankClick(self): def unblankClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.Unblank(self.pptid) return
self.pptdll.Unblank(self.pptid)
app.processEvents() app.processEvents()
def RestartClick(self): def restartClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.RestartShow(self.pptid) return
self.UpdateCurrSlide() self.pptdll.RestartShow(self.pptid)
self.updateCurrSlide()
app.processEvents() app.processEvents()
def StopClick(self): def stopClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.Stop(self.pptid) return
self.pptdll.Stop(self.pptid)
app.processEvents() app.processEvents()
def ResumeClick(self): def resumeClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.Resume(self.pptid) return
self.pptdll.Resume(self.pptid)
app.processEvents() app.processEvents()
def CloseClick(self): def closeClick(self):
if(self.pptid<0): return if self.pptid < 0:
pptdll.ClosePPT(self.pptid) return
self.pptdll.ClosePPT(self.pptid)
self.pptid = -1 self.pptid = -1
app.processEvents() app.processEvents()
def OpenClick(self): def openClick(self):
oldid = self.pptid; oldid = self.pptid;
rect = RECT(100,100,900,700) rect = RECT(int(self.xEdit.text()), int(self.yEdit.text()),
filename = unicode(self.PPTEdit.text()) int(self.widthEdit.text()), int(self.heightEdit.text()))
print filename filename = str(self.pptEdit.text().replace(u'/', u'\\'))
self.pptid = pptdll.OpenPPT(filename, None, rect, 'c:\\temp\\slide') folder = str(self.folderEdit.text().replace(u'/', u'\\'))
print "id: " + unicode(self.pptid) print filename, folder
if oldid>=0: self.pptid = self.pptdll.OpenPPT(filename, None, rect, folder)
pptdll.ClosePPT(oldid); print u'id: ' + unicode(self.pptid)
slides = pptdll.GetSlideCount(self.pptid) if oldid >= 0:
print "slidecount: " + unicode(slides) self.pptdll.ClosePPT(oldid);
self.total.setNum(pptdll.GetSlideCount(self.pptid)) slides = self.pptdll.GetSlideCount(self.pptid)
self.UpdateCurrSlide() print u'slidecount: ' + unicode(slides)
self.total.setNum(self.pptdll.GetSlideCount(self.pptid))
self.updateCurrSlide()
def UpdateCurrSlide(self): def updateCurrSlide(self):
if(self.pptid<0): return if self.pptid < 0:
slide = unicode(pptdll.GetCurrentSlide(self.pptid)) return
print "currslide: " + slide slide = unicode(self.pptdll.GetCurrentSlide(self.pptid))
print u'currslide: ' + slide
self.slideEdit.setText(slide) self.slideEdit.setText(slide)
app.processEvents() app.processEvents()
def GotoClick(self): def gotoClick(self):
if(self.pptid<0): return if self.pptid < 0:
return
print self.slideEdit.text() print self.slideEdit.text()
pptdll.GotoSlide(self.pptid, int(self.slideEdit.text())) self.pptdll.GotoSlide(self.pptid, int(self.slideEdit.text()))
self.UpdateCurrSlide() self.updateCurrSlide()
app.processEvents() app.processEvents()
def OpenDialog(self): def openDialog(self):
self.PPTEdit.setText(QtGui.QFileDialog.getOpenFileName(self, 'Open file')) self.pptEdit.setText(QtGui.QFileDialog.getOpenFileName(self,
u'Open file'))
if __name__ == '__main__': if __name__ == '__main__':
#pptdll = cdll.LoadLibrary(r'C:\Documents and Settings\jonathan\Desktop\pptviewlib.dll')
pptdll = cdll.LoadLibrary(r'pptviewlib.dll') pptdll = cdll.LoadLibrary(r'pptviewlib.dll')
pptdll.SetDebug(1) pptdll.SetDebug(1)
print "Begin..." print u'Begin...'
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
qb = PPTViewer() window = PPTViewer()
qb.show() window.pptdll = pptdll
window.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,68 @@
/******************************************************************************
* PptViewLib - PowerPoint Viewer 2003/2007 Controller *
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2011 Raoul Snyman *
* Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael *
* Gorven, Scott Guerrieri, Meinert Jordan, Armin Köhler, Andreas Preikschat, *
* Christian Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon *
* Tibble, Carsten Tinggaard, Frode Woldsund *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
#define DllExport extern "C" __declspec( dllexport ) #define DllExport extern "C" __declspec( dllexport )
enum PPTVIEWSTATE { PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED, PPT_CLOSING}; #define DEBUG(...) if (debug) printf(__VA_ARGS__)
DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); enum PPTVIEWSTATE {PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED,
PPT_CLOSING};
DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect,
char *previewPath);
DllExport BOOL CheckInstalled(); DllExport BOOL CheckInstalled();
DllExport void ClosePPT(int id); DllExport void ClosePPT(int id);
DllExport int GetCurrentSlide(int id); DllExport int GetCurrentSlide(int id);
DllExport int GetSlideCount(int id); DllExport int GetSlideCount(int id);
DllExport void NextStep(int id); DllExport void NextStep(int id);
DllExport void PrevStep(int id); DllExport void PrevStep(int id);
DllExport void GotoSlide(int id, int slideno); DllExport void GotoSlide(int id, int slide_no);
DllExport void RestartShow(int id); DllExport void RestartShow(int id);
DllExport void Blank(int id); DllExport void Blank(int id);
DllExport void Unblank(int id); DllExport void Unblank(int id);
DllExport void Stop(int id); DllExport void Stop(int id);
DllExport void Resume(int id); DllExport void Resume(int id);
DllExport void SetDebug(BOOL onoff); DllExport void SetDebug(BOOL onOff);
LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL GetPPTViewerPath(char *pptviewerpath, int strsize); BOOL GetPPTViewerPath(char *pptViewerPath, int stringSize);
HBITMAP CaptureWindow (HWND hWnd); HBITMAP CaptureWindow(HWND hWnd);
VOID SaveBitmap (CHAR* filename, HBITMAP hBmp) ; VOID SaveBitmap(CHAR* filename, HBITMAP hBmp) ;
VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename); VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename);
BOOL GetPPTInfo(int id); BOOL GetPPTInfo(int id);
BOOL SavePPTInfo(int id); BOOL SavePPTInfo(int id);
void Unhook(int id); void Unhook(int id);
#define MAX_PPTOBJS 50 #define MAX_PPTS 16
#define MAX_SLIDES 256
struct PPTVIEWOBJ struct PPTVIEW
{ {
HHOOK hook; HHOOK hook;
HHOOK mhook; HHOOK msgHook;
HWND hWnd; HWND hWnd;
HWND hWnd2; HWND hWnd2;
HWND hParentWnd; HWND hParentWnd;
@ -48,8 +74,11 @@ struct PPTVIEWOBJ
int slideCount; int slideCount;
int currentSlide; int currentSlide;
int firstSlideSteps; int firstSlideSteps;
int lastSlideSteps;
int steps; int steps;
int guess;
char filename[MAX_PATH]; char filename[MAX_PATH];
char previewpath[MAX_PATH]; char previewPath[MAX_PATH];
int slideNos[MAX_SLIDES];
PPTVIEWSTATE state; PPTVIEWSTATE state;
}; };

View File

@ -26,4 +26,68 @@
""" """
The :mod:`remotes` plugin allows OpenLP to be controlled from another machine The :mod:`remotes` plugin allows OpenLP to be controlled from another machine
over a network connection. over a network connection.
Routes:
``/``
Go to the web interface.
``/files/{filename}``
Serve a static file.
``/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``jump``
Jump to a specific slide. Requires an id return in a JSON-encoded
dict like so::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Request the text of the current slide.
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``jump``
Jump to a specific item in the service. Requires an id returned in
a JSON-encoded dict like so::
{"request": {"id": 1}}
``list``
Request a list of items in the service.
""" """

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@ -1,57 +1,73 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <!DOCTYPE html>
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> <head>
<head> <meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>OpenLP 2.0 Remote</title>
<title>OpenLP Remote Controller</title> <link rel="stylesheet" href="/files/jquery.mobile.css" />
<link rel="stylesheet" href="/files/openlp.css" />
<script type="text/javascript" src="/files/jquery.js"></script> <script type="text/javascript" src="/files/jquery.js"></script>
<script type='text/javascript' src="/files/openlp.js"></script> <script type="text/javascript" src="/files/openlp.js"></script>
<script type='text/javascript' src="/files/init.js"></script> <script type="text/javascript" src="/files/jquery.mobile.js"></script>
<link rel="stylesheet" href="/files/style.css" type="text/css" />
</head> </head>
<body> <body>
<h1>OpenLP Controller</h1> <div data-role="page" id="home">
<p>Quick Links: <a href="#service-manager">Service Manager</a> | <a href="#slide-controller">Slide Controller</a> | <a href="#miscellaneous">Miscellaneous</a></p> <div data-role="header">
<h2 id="service-manager">Service Manager</h2> <h1>OpenLP 2.0 Remote</h1>
<div id="service"></div>
<p><em>(Click service item to go live.)</em></p>
<fieldset>
<legend>Controls</legend>
<div id="service-buttons">
<input type="button" value="Refresh Service" id="servicemanager_list_request" />
</div> </div>
<div id="item-buttons"> <div data-role="content">
<input type="button" value="&lt;- Previous Item" id="servicemanager_previous_item" /> <div data-role="controlgroup">
<input type="button" value="Next Item -&gt;" id="servicemanager_next_item" /> <a href="#service-manager" data-role="button" data-icon="arrow-r" data-iconpos="right">Service Manager</a>
<a href="#slide-controller" data-role="button" data-icon="arrow-r" data-iconpos="right">Slide Controller</a>
<a href="#alerts" data-role="button" data-icon="arrow-r" data-iconpos="right">Alerts</a>
</div> </div>
</fieldset>
<hr>
<h2 id="slide-controller">Slide Controller</h2>
<div id="current-item"></div>
<p><em>(Click verse to display.)</em></p>
<fieldset>
<legend>Controls</legend>
<div id="item-buttons">
<input type="button" value="Refresh Item" id="slidecontroller_live_text_request" />
</div> </div>
<div id="slide-buttons"> </div>
<input type="button" value="&lt;- Previous Slide" id="slidecontroller_live_previous" /> <div data-role="page" id="service-manager">
<input type="button" value="Next Slide -&gt;" id="slidecontroller_live_next" /> <div data-role="header">
<a href="#" data-rel="back" data-icon="arrow-l">Back</a>
<h1>Service Manager</h1>
</div> </div>
</fieldset> <div data-role="content">
<hr> <ul data-role="listview" data-inset="true">
<h2 id="miscellaneous">Miscellaneous</h2> </ul>
<div id="display-buttons">
<input type="button" value="Blank" id="slidecontroller_live_blank" />
<input type="button" value="Unblank" id="slidecontroller_live_unblank" />
</div> </div>
<div id="alert-details"> <div data-role="footer" data-theme="b" class="ui-bar">
<label for="alert-text">Alert text:</label> <a href="#" id="service-blank" data-role="button" data-icon="blank">Blank</a>
<input type="text" id="alert-text" /> <a href="#" id="service-unblank" data-role="button" data-icon="unblank">Unblank</a>
<input type="button" value="Send" id="alert-send" /> <a href="#" id="service-refresh" data-role="button" data-icon="refresh">Refresh</a>
<a href="#" id="service-previous" data-role="button" data-icon="arrow-l">Previous</a>
<a href="#" id="service-next" data-role="button" data-icon="arrow-r" data-iconpos="right">Next</a>
</div> </div>
<hr> </div>
<a href="http://openlp.org/">OpenLP website</a> <div data-role="page" id="slide-controller">
<div data-role="header">
<a href="#" data-rel="back" data-icon="arrow-l">Back</a>
<h1>Slide Controller</h1>
</div>
<div data-role="content">
<ul data-role="listview" data-inset="true">
</ul>
</div>
<div data-role="footer" data-theme="b" class="ui-bar">
<a href="#" id="controller-blank" data-role="button" data-icon="blank">Blank</a>
<a href="#" id="controller-unblank" data-role="button" data-icon="unblank">Unblank</a>
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">Refresh</a>
<a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">Previous</a>
<a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">Next</a>
</div>
</div>
<div data-role="page" id="alerts">
<div data-role="header">
<a href="#" data-rel="back" data-icon="arrow-l">Back</a>
<h1>Alerts</h1>
</div>
<div data-role="content">
<div data-role="fieldcontain">
<label for="alert-text">Text:</label>
<input type="text" name="alert-text" id="alert-text" value="" />
</div>
<a href="#" id="alert-submit" data-role="button">Show Alert</a>
</div>
</div>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,121 @@
/*!
* jQuery Mobile v1.0a3
* http://jquerymobile.com/
*
* Copyright 2010, jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function(a,d){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var g=0,e;(e=b[g])!=null;g++)a(e).triggerHandler("remove");c(b)}}else{var f=a.fn.remove;a.fn.remove=function(b,g){return this.each(function(){if(!g)if(!b||a.filter(b,[this]).length)a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return f.call(a(this),b,g)})}}a.widget=function(b,g,e){var i=b.split(".")[0],h;b=b.split(".")[1];h=i+"-"+b;if(!e){e=g;g=a.Widget}a.expr[":"][h]=function(k){return!!a.data(k,
b)};a[i]=a[i]||{};a[i][b]=function(k,j){arguments.length&&this._createWidget(k,j)};g=new g;g.options=a.extend(true,{},g.options);a[i][b].prototype=a.extend(true,g,{namespace:i,widgetName:b,widgetEventPrefix:a[i][b].prototype.widgetEventPrefix||b,widgetBaseClass:h},e);a.widget.bridge(b,a[i][b])};a.widget.bridge=function(b,g){a.fn[b]=function(e){var i=typeof e==="string",h=Array.prototype.slice.call(arguments,1),k=this;e=!i&&h.length?a.extend.apply(null,[true,e].concat(h)):e;if(i&&e.charAt(0)==="_")return k;
i?this.each(function(){var j=a.data(this,b);if(!j)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+e+"'";if(!a.isFunction(j[e]))throw"no such method '"+e+"' for "+b+" widget instance";var o=j[e].apply(j,h);if(o!==j&&o!==d){k=o;return false}}):this.each(function(){var j=a.data(this,b);j?j.option(e||{})._init():a.data(this,b,new g(e,this))});return k}};a.Widget=function(b,g){arguments.length&&this._createWidget(b,g)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",
options:{disabled:false},_createWidget:function(b,g){a.data(g,this.widgetName,this);this.element=a(g);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var e=this;this.element.bind("remove."+this.widgetName,function(){e.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b={};if(a.metadata)b=a.metadata.get(element)[this.widgetName];return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);
this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,g){var e=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(g===d)return this.options[b];e={};e[b]=g}this._setOptions(e);return this},_setOptions:function(b){var g=this;a.each(b,function(e,i){g._setOption(e,i)});return this},_setOption:function(b,g){this.options[b]=g;if(b===
"disabled")this.widget()[g?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",g);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,g,e){var i=this.options[b];g=a.Event(g);g.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();e=e||{};if(g.originalEvent){b=a.event.props.length;for(var h;b;){h=a.event.props[--b];g[h]=g.originalEvent[h]}}this.element.trigger(g,
e);return!(a.isFunction(i)&&i.call(this.element[0],g,e)===false||g.isDefaultPrevented())}}})(jQuery);(function(a,d){a.widget("mobile.widget",{_getCreateOptions:function(){var c=this.element,f={};a.each(this.options,function(b){var g=c.data(b.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()}));if(g!==d)f[b]=g});return f}})})(jQuery);
(function(a){function d(){var g=c.width(),e=[],i=[],h;f.removeClass("min-width-"+b.join("px min-width-")+"px max-width-"+b.join("px max-width-")+"px");a.each(b,function(k,j){g>=j&&e.push("min-width-"+j+"px");g<=j&&i.push("max-width-"+j+"px")});if(e.length)h=e.join(" ");if(i.length)h+=" "+i.join(" ");f.addClass(h)}var c=a(window),f=a("html"),b=[320,480,768,1024];a.mobile.media=function(){var g={},e=a("<div id='jquery-mediatest'>"),i=a("<body>").append(e);return function(h){if(!(h in g)){var k=document.createElement("style"),
j="@media "+h+" { #jquery-mediatest { position:absolute; } }";k.type="text/css";if(k.styleSheet)k.styleSheet.cssText=j;else k.appendChild(document.createTextNode(j));f.prepend(i).prepend(k);g[h]=e.css("position")==="absolute";i.add(k).remove()}return g[h]}}();a.mobile.addResolutionBreakpoints=function(g){if(a.type(g)==="array")b=b.concat(g);else b.push(g);b.sort(function(e,i){return e-i});d()};a(document).bind("mobileinit.htmlclass",function(){c.bind("orientationchange.htmlclass resize.htmlclass",
function(g){g.orientation&&f.removeClass("portrait landscape").addClass(g.orientation);d()})});a(function(){c.trigger("orientationchange.htmlclass")})})(jQuery);
(function(a,d){function c(h){var k=h.charAt(0).toUpperCase()+h.substr(1);h=(h+" "+g.join(k+" ")+k).split(" ");for(var j in h)if(b[j]!==d)return true}var f=a("<body>").prependTo("html"),b=f[0].style,g=["webkit","moz","o"],e=window.palmGetResource||window.PalmServiceBridge,i=window.blackberry;a.extend(a.support,{orientation:"orientation"in window,touch:"ontouchend"in document,cssTransitions:"WebKitTransitionEvent"in window,pushState:!!history.pushState,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!c("content"),
boxShadow:!!c("boxShadow")&&!i,scrollTop:("pageXOffset"in window||"scrollTop"in document.documentElement||"scrollTop"in f[0])&&!e,dynamicBaseTag:function(){var h=location.protocol+"//"+location.host+location.pathname+"ui-dir/",k=a("head base"),j=null,o="";if(k.length)o=k.attr("href");else k=j=a("<base>",{href:h}).appendTo("head");var p=a("<a href='testurl'></a>").prependTo(f)[0].href;k[0].href=o?o:location.pathname;j&&j.remove();return p.indexOf(h)===0}()});f.remove();a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);
(function(a,d){a.each("touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(e,i){a.fn[i]=function(h){return h?this.bind(i,h):this.trigger(i)};a.attrFn[i]=true});var c=a.support.touch,f=c?"touchstart":"mousedown",b=c?"touchend":"mouseup",g=c?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function e(j,o){h=o;var p=j.type;j.type=h?"scrollstart":"scrollstop";a.event.handle.call(i,j);j.type=
p}var i=this,h,k;a(i).bind("touchmove scroll",function(j){if(a.event.special.scrollstart.enabled){h||e(j,true);clearTimeout(k);k=setTimeout(function(){e(j,false)},50)}})}};a.event.special.tap={setup:function(){var e=this,i=a(e);i.bind("mousedown touchstart",function(h){function k(n){if(n.type=="scroll")j=true;else{n=n.type=="touchmove"?n.originalEvent.touches[0]:n;if(Math.abs(v[0]-n.pageX)>10||Math.abs(v[1]-n.pageY)>10)j=true}}if(h.which&&h.which!==1||i.data("prevEvent")&&i.data("prevEvent")!==h.type)return false;
i.data("prevEvent",h.type);setTimeout(function(){i.removeData("prevEvent")},800);var j=false,o=true,p=h.target,t=h.originalEvent,v=h.type=="touchstart"?[t.touches[0].pageX,t.touches[0].pageY]:[h.pageX,h.pageY],m,r;r=setTimeout(function(){if(o&&!j){m=h.type;h.type="taphold";a.event.handle.call(e,h);h.type=m}},750);a(window).one("scroll",k);i.bind("mousemove touchmove",k).one("mouseup touchend",function(n){i.unbind("mousemove touchmove",k);a(window).unbind("scroll",k);clearTimeout(r);o=false;if(!j&&
p==n.target){m=n.type;n.type="tap";a.event.handle.call(e,n);n.type=m}})})}};a.event.special.swipe={setup:function(){var e=a(this);e.bind(f,function(i){function h(p){if(j){var t=p.originalEvent.touches?p.originalEvent.touches[0]:p;o={time:(new Date).getTime(),coords:[t.pageX,t.pageY]};Math.abs(j.coords[0]-o.coords[0])>10&&p.preventDefault()}}var k=i.originalEvent.touches?i.originalEvent.touches[0]:i,j={time:(new Date).getTime(),coords:[k.pageX,k.pageY],origin:a(i.target)},o;e.bind(g,h).one(b,function(){e.unbind(g,
h);if(j&&o)if(o.time-j.time<1E3&&Math.abs(j.coords[0]-o.coords[0])>30&&Math.abs(j.coords[1]-o.coords[1])<75)j.origin.trigger("swipe").trigger(j.coords[0]>o.coords[0]?"swipeleft":"swiperight");j=o=d})})}};(function(e){function i(){var o=k();if(o!==j){j=o;h.trigger("orientationchange")}}var h=e(window),k,j;e.event.special.orientationchange={setup:function(){if(e.support.orientation)return false;j=k();h.bind("resize",i)},teardown:function(){if(e.support.orientation)return false;h.unbind("resize",i)},
add:function(o){var p=o.handler;o.handler=function(t){t.orientation=k();return p.apply(this,arguments)}}};k=function(){var o=document.documentElement;return o&&o.clientWidth/o.clientHeight<1.1?"portrait":"landscape"}})(jQuery);a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(e,i){a.event.special[e]={setup:function(){a(this).bind(i,a.noop)}}})})(jQuery);
(function(a,d,c){function f(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}var b="hashchange",g=document,e,i=a.event.special,h=g.documentMode,k="on"+b in d&&(h===c||h>7);a.fn[b]=function(j){return j?this.bind(b,j):this.trigger(b)};a.fn[b].delay=50;i[b]=a.extend(i[b],{setup:function(){if(k)return false;a(e.start)},teardown:function(){if(k)return false;a(e.stop)}});e=function(){function j(){var n=f(),u=r(t);if(n!==t){m(t=n,u);a(d).trigger(b)}else if(u!==t)location.href=location.href.replace(/#.*/,
"")+u;p=setTimeout(j,a.fn[b].delay)}var o={},p,t=f(),v=function(n){return n},m=v,r=v;o.start=function(){p||j()};o.stop=function(){p&&clearTimeout(p);p=c};a.browser.msie&&!k&&function(){var n,u;o.start=function(){if(!n){u=(u=a.fn[b].src)&&u+f();n=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){u||m(f());j()}).attr("src",u||"javascript:0").insertAfter("body")[0].contentWindow;g.onpropertychange=function(){try{if(event.propertyName==="title")n.document.title=g.title}catch(l){}}}};
o.stop=v;r=function(){return f(n.location.href)};m=function(l,s){var q=n.document,w=a.fn[b].domain;if(l!==s){q.title=g.title;q.open();w&&q.write('<script>document.domain="'+w+'"<\/script>');q.close();n.location.hash=l}}}();return o}()})(jQuery,this);
(function(a){a.widget("mobile.page",a.mobile.widget,{options:{backBtnText:"Back",addBackBtn:true,degradeInputs:{color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:true,tel:false,time:false,url:false,week:false},keepNative:null},_create:function(){var d=this.element,c=this.options;this.keepNative="[data-role='none'], [data-role='nojs']"+(c.keepNative?", "+c.keepNative:"");if(this._trigger("beforeCreate")!==false){d.find("[data-role='page'], [data-role='content']").andSelf().each(function(){a(this).addClass("ui-"+
a(this).data("role"))});d.find("[data-role='nojs']").addClass("ui-nojs");d.find("[data-role]").andSelf().each(function(){var f=a(this),b=f.data("role"),g=f.data("theme");if(b==="header"||b==="footer"){f.addClass("ui-bar-"+(g||f.parent("[data-role=page]").data("theme")||"a"));f.attr("role",b==="header"?"banner":"contentinfo");g=f.children("a");var e=g.hasClass("ui-btn-left"),i=g.hasClass("ui-btn-right");if(!e)e=g.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length;i||g.eq(1).addClass("ui-btn-right");
c.addBackBtn&&b==="header"&&a(".ui-page").length>1&&d.data("url")!==a.mobile.path.stripHash(location.hash)&&!e&&f.data("backbtn")!==false&&a("<a href='#' class='ui-btn-left' data-rel='back' data-icon='arrow-l'>"+c.backBtnText+"</a>").prependTo(f);f.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({tabindex:"0",role:"heading","aria-level":"1"})}else if(b==="content"){g&&f.addClass("ui-body-"+g);f.attr("role","main")}else if(b==="page")f.addClass("ui-body-"+(g||"c"));switch(b){case "header":case "footer":case "page":case "content":f.addClass("ui-"+
b);break;case "collapsible":case "fieldcontain":case "navbar":case "listview":case "dialog":f[b]()}});this._enhanceControls();d.find("[data-role='button'], .ui-bar > a, .ui-header > a, .ui-footer > a").not(".ui-btn").not(this.keepNative).buttonMarkup();d.find("[data-role='controlgroup']").controlgroup();d.find("a:not(.ui-btn):not(.ui-link-inherit)").not(this.keepNative).addClass("ui-link");d.fixHeaderFooter()}},_enhanceControls:function(){var d=this.options;this.element.find("input").not(this.keepNative).each(function(){var b=
this.getAttribute("type"),g=d.degradeInputs[b]||"text";d.degradeInputs[b]&&a(this).replaceWith(a("<div>").html(a(this).clone()).html().replace(/type="([a-zA-Z]+)"/,"type="+g+" data-type='$1'"))});var c=this.element.find("input, textarea, select, button"),f=c.not(this.keepNative);c=c.filter("input[type=text]");c.length&&typeof c[0].autocorrect!=="undefined"&&c.each(function(){this.setAttribute("autocorrect","off");this.setAttribute("autocomplete","off")});f.filter("[type='radio'], [type='checkbox']").checkboxradio();
f.filter("button, [type='button'], [type='submit'], [type='reset'], [type='image']").button();f.filter("input, textarea").not("[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']").textinput();f.filter("input, select").filter("[data-role='slider'], [data-type='range']").slider();f.filter("select:not([data-role='slider'])").selectmenu()}})})(jQuery);
(function(a,d,c){a.extend(a.mobile,{subPageUrlKey:"ui-page",nonHistorySelectors:"dialog",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",ajaxEnabled:true,hashListeningEnabled:true,ajaxLinksEnabled:true,ajaxFormsEnabled:true,defaultTransition:"slide",loadingMessage:"loading",metaViewportContent:"width=device-width, minimum-scale=1, maximum-scale=1",gradeA:function(){return a.support.mediaquery},autoInitialize:true,keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,
COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a(d.document).trigger("mobileinit");if(a.mobile.gradeA()){var f=a(d),b=a("html"),g=a("head"),e=a.mobile.loadingMessage?a("<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1>"+
a.mobile.loadingMessage+"</h1></div>"):c;b.addClass("ui-mobile ui-mobile-rendering");a.mobile.metaViewportContent&&a("<meta>",{name:"viewport",content:a.mobile.metaViewportContent}).prependTo(g);a.extend(a.mobile,{pageLoading:function(i){if(i)b.removeClass("ui-loading");else{if(a.mobile.loadingMessage){i=a("."+a.mobile.activeBtnClass).first();e.appendTo(a.mobile.pageContainer).css({top:a.support.scrollTop&&a(d).scrollTop()+a(d).height()/2||i.length&&i.offset().top||100})}b.addClass("ui-loading")}},
silentScroll:function(i){i=i||0;a.event.special.scrollstart.enabled=false;setTimeout(function(){d.scrollTo(0,i);a(document).trigger("silentscroll",{x:0,y:i})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},initializePage:function(){var i=a("[data-role='page']");i.add("[data-role='dialog']").each(function(){a(this).attr("data-url",a(this).attr("id"))});a.mobile.firstPage=i.first();a.mobile.pageContainer=i.first().parent().addClass("ui-mobile-viewport");a.mobile.pageLoading();
!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage,false,true,false,true):f.trigger("hashchange",[true])}});a.mobile.autoInitialize&&a(a.mobile.initializePage);f.load(a.mobile.silentScroll)}})(jQuery,this);
(function(a,d){function c(l){if(i&&(!i.closest(".ui-page-active").length||l))i.removeClass(a.mobile.activeBtnClass);i=null}var f=a(window),b=a("html"),g=a("head"),e={get:function(l){if(l==d)l=location.hash;return e.stripHash(l).replace(/[^\/]*\.[^\/*]+$/,"")},getFilePath:function(l){var s="&"+a.mobile.subPageUrlKey;return l&&l.split(s)[0].split(t)[0]},set:function(l){location.hash=l},origin:"",setOrigin:function(){e.origin=e.get(location.protocol+"//"+location.host+location.pathname)},makeAbsolute:function(l){return e.get()+
l},clean:function(l){return l.replace(location.protocol+"//"+location.host,"")},stripHash:function(l){return l.replace(/^#/,"")},isExternal:function(l){return e.hasProtocol(e.clean(l))},hasProtocol:function(l){return/^(:?\w+:)/.test(l)},isRelative:function(l){return/^[^\/|#]/.test(l)&&!e.hasProtocol(l)},isEmbeddedPage:function(l){return/^#/.test(l)}},i=null,h={stack:[],activeIndex:0,getActive:function(){return h.stack[h.activeIndex]},getPrev:function(){return h.stack[h.activeIndex-1]},getNext:function(){return h.stack[h.activeIndex+
1]},addNew:function(l,s){h.getNext()&&h.clearForward();h.stack.push({url:l,transition:s});h.activeIndex=h.stack.length-1},clearForward:function(){h.stack=h.stack.slice(0,h.activeIndex+1)},ignoreNextHashChange:true},k="[tabindex],a,button:visible,select:visible,input",j=null,o=[],p=false,t="&ui-state=dialog",v=g.children("base"),m=location.protocol+"//"+location.host,r=e.get(m+location.pathname),n=r;if(v.length){var u=v.attr("href");if(u)n=u.search(/^[^:/]+:\/\/[^/]+\/?/)==-1?u.charAt(0)=="/"?m+u:
r+u:u;n+=n.charAt(n.length-1)=="/"?" ":"/"}base=a.support.dynamicBaseTag?{element:v.length?v:a("<base>",{href:n}).prependTo(g),set:function(l){base.element.attr("href",n+e.get(l))},reset:function(){base.element.attr("href",n)}}:d;e.setOrigin();a.fn.animationComplete=function(l){if(a.support.cssTransitions)return a(this).one("webkitAnimationEnd",l);else setTimeout(l,0)};a.mobile.updateHash=e.set;a.mobile.path=e;a.mobile.base=base;a.mobile.urlstack=h.stack;a.mobile.urlHistory=h;a.mobile.changePage=
function(l,s,q,w,z){function F(){function A(){if(w!==false&&x){h.ignoreNextHashChange=false;e.set(x)}!I&&!K&&h.addNew(x,s);c();a.mobile.silentScroll(l.data("lastScroll"));var G=l,P=G.find(".ui-title:eq(0)");P.length?P.focus():G.find(k).eq(0).focus();y&&y.data("page")._trigger("hide",null,{nextPage:l});l.data("page")._trigger("show",null,{prevPage:y||a("")});a.mobile.activePage=l;L!=null&&L.remove();b.removeClass("ui-mobile-rendering");p=false;o.length>0&&a.mobile.changePage.apply(a.mobile,o.pop())}
function B(G){a.mobile.pageContainer.addClass(G);D.push(G)}a.mobile.silentScroll();var M=f.scrollTop(),J=["flip"],D=[];if(x.indexOf("&"+a.mobile.subPageUrlKey)>-1)l=a("[data-url='"+x+"']");if(y){y.data("lastScroll",M);y.data("page")._trigger("beforehide",{nextPage:l})}l.data("page")._trigger("beforeshow",{prevPage:y||a("")});if(s&&s!=="none"){a.mobile.pageLoading(true);a.inArray(s,J)>=0&&B("ui-mobile-viewport-perspective");B("ui-mobile-viewport-transitioning");if(y)y.addClass(s+" out "+(q?"reverse":
""));l.addClass(a.mobile.activePageClass+" "+s+" in "+(q?"reverse":""));l.animationComplete(function(){y.add(l).removeClass("out in reverse "+s);y&&y.removeClass(a.mobile.activePageClass);A();a.mobile.pageContainer.removeClass(D.join(" "));D=[]})}else{a.mobile.pageLoading(true);y&&y.removeClass(a.mobile.activePageClass);l.addClass(a.mobile.activePageClass);A()}}function Q(){if(j||l.data("role")=="dialog"){x=h.getActive().url+t;if(j){l.attr("data-role",j);j=null}}l.page()}var E=a.type(l)==="array",
H=a.type(l)==="object",y=E?l[0]:a.mobile.activePage;l=E?l[1]:l;var x=fileUrl=a.type(l)==="string"?e.stripHash(l):"",C=d,N="get",R=false,L=null,O=h.getActive(),I=false,K=false;if(!(O&&h.stack.length>1&&O.url===x&&!E&&!H))if(p)o.unshift(arguments);else{p=true;if(z){a.each(h.stack,function(A){if(this.url==x){urlIndex=A;I=A<h.activeIndex;K=!I;h.activeIndex=A}});if(I){q=true;s=s||O.transition}else if(K)s=s||h.getActive().transition}if(H&&l.url){x=l.url;C=l.data;N=l.type;R=true;if(C&&N=="get"){if(a.type(C)==
"object")C=a.param(C);x+="?"+C;C=d}}base&&base.reset();a(window.document.activeElement).add("input:focus, textarea:focus, select:focus").blur();if(x){l=a("[data-url='"+x+"']");fileUrl=e.getFilePath(x)}else{E=l.attr("data-url");H=e.getFilePath(E);if(E!=H)fileUrl=H}if(s===d)s=a.mobile.defaultTransition;if(l.length&&!R){fileUrl&&base&&base.set(fileUrl);Q();F()}else{if(l.length)L=l;a.mobile.pageLoading();a.ajax({url:fileUrl,type:N,data:C,success:function(A){var B=/ data-url="(.*)"/.test(A)&&RegExp.$1;
if(B){base&&base.set(B);x=fileUrl=e.getFilePath(B)}else base&&base.set(fileUrl);B=a("<div></div>");B.get(0).innerHTML=A;l=B.find('[data-role="page"], [data-role="dialog"]').first();if(!a.support.dynamicBaseTag){var M=e.get(fileUrl);l.find("[src],link[href]").each(function(){var J=a(this).is("[href]")?"href":"src",D=a(this).attr(J);D.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(D)||a(this).attr(J,M+D)})}l.attr("data-url",fileUrl).appendTo(a.mobile.pageContainer);
Q();setTimeout(function(){F()},0)},error:function(){a.mobile.pageLoading(true);c(true);base&&base.set(e.get());a("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>Error Loading Page</h1></div>").css({display:"block",opacity:0.96,top:a(window).scrollTop()+100}).appendTo(a.mobile.pageContainer).delay(800).fadeOut(400,function(){a(this).remove()})}})}}};a("form[data-ajax!='false']").live("submit",function(l){if(a.mobile.ajaxEnabled&&a.mobile.ajaxFormsEnabled){var s=a(this).attr("method"),
q=e.clean(a(this).attr("action"));if(!e.isExternal(q)){if(e.isRelative(q))q=e.makeAbsolute(q);a.mobile.changePage({url:q,type:s,data:a(this).serialize()},d,d,true);l.preventDefault()}}});a("a").live("click",function(l){var s=a(this),q=s.attr("href")||"#";q=e.clean(q);var w=s.is("[rel='external']"),z=e.isEmbeddedPage(q);w=e.isExternal(q)||w&&!z;z=s.is("[target]");var F=s.is("[data-ajax='false']");if(s.is("[data-rel='back']")){window.history.back();return false}if(q==="#")return false;i=s.closest(".ui-btn").addClass(a.mobile.activeBtnClass);
if(w||F||z||!a.mobile.ajaxEnabled||!a.mobile.ajaxLinksEnabled){c(true);if(z)window.open(q);else if(F)return;else location.href=q}else{w=s.data("transition");z=(z=s.data("direction"))&&z=="reverse"||s.data("back");j=s.attr("data-rel");if(e.isRelative(q))q=e.makeAbsolute(q);q=e.stripHash(q);a.mobile.changePage(q,w,z)}l.preventDefault()});f.bind("hashchange",function(){var l=e.stripHash(location.hash),s=a.mobile.urlHistory.stack.length===0?false:d;if(!a.mobile.hashListeningEnabled||!h.ignoreNextHashChange||
h.stack.length>1&&l.indexOf(t)>-1&&!a.mobile.activePage.is(".ui-dialog")){if(!h.ignoreNextHashChange)h.ignoreNextHashChange=true}else l?a.mobile.changePage(l,s,d,false,true):a.mobile.changePage(a.mobile.firstPage,s,true,false,true)})})(jQuery);
(function(a,d){a.fn.fixHeaderFooter=function(){if(!a.support.scrollTop)return this;return this.each(function(){var c=a(this);c.data("fullscreen")&&c.addClass("ui-page-fullscreen");c.find('.ui-header[data-position="fixed"]').addClass("ui-header-fixed ui-fixed-inline fade");c.find('.ui-footer[data-position="fixed"]').addClass("ui-footer-fixed ui-fixed-inline fade")})};a.fixedToolbars=function(){function c(){if(!e&&g=="overlay"){i||a.fixedToolbars.hide(true);a.fixedToolbars.startShowTimer()}}function f(m){var r=
0;if(m){var n=m.offsetParent,u=document.body;for(r=m.offsetTop;m&&m!=u;){r+=m.scrollTop||0;if(m==n){r+=n.offsetTop;n=m.offsetParent}m=m.parentNode}}return r}function b(m){var r=a(window).scrollTop(),n=f(m[0]),u=m.css("top")=="auto"?0:parseFloat(m.css("top")),l=window.innerHeight,s=m.outerHeight(),q=m.parents(".ui-page:not(.ui-page-fullscreen)").length;if(m.is(".ui-header-fixed")){u=r-n+u;if(u<n)u=0;return m.css("top",q?u:r)}else{u=r+l-s-(n-u);return m.css("top",q?u:r+l-s)}}if(a.support.scrollTop){var g=
"inline",e=false,i,h,k=a.support.touch,j=k?"touchstart":"mousedown",o=k?"touchend":"mouseup",p=null,t=false,v=true;a(function(){a(document).bind(j,function(m){if(v)a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length||(p=g)}).bind(o,function(m){if(v)if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length)if(!t){a.fixedToolbars.toggle(p);p=null}}).bind("scrollstart",function(m){if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length){t=
true;if(p==null)p=g;if(e=(m=p=="overlay")||!!i){a.fixedToolbars.clearShowTimer();m&&a.fixedToolbars.hide(true)}}}).bind("scrollstop",function(m){if(!a(m.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length){t=false;if(e){e=false;a.fixedToolbars.startShowTimer()}p=null}}).bind("silentscroll",c);a(window).bind("resize",c)});a(".ui-page").live("pagebeforeshow",function(m){m=a(m.target).find('[data-role="footer"]:not(.ui-sticky-footer)');var r=m.data("id");
h=null;if(r){h=a('.ui-footer[data-id="'+r+'"].ui-sticky-footer');if(h.length==0){h=m;m=h.clone();h.addClass("ui-sticky-footer").before(m)}m.addClass("ui-footer-duplicate");h.appendTo(a.pageContainer).css("top",0);b(h)}});a(".ui-page").live("pageshow",function(m){h&&h.length&&h.appendTo(m.target).css("top",0);a.fixedToolbars.show(true,this)});return{show:function(m,r){a.fixedToolbars.clearShowTimer();g="overlay";return(r?a(r):a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var n=
a(this),u=a(window).scrollTop(),l=f(n[0]),s=window.innerHeight,q=n.outerHeight();u=n.is(".ui-header-fixed")&&u<=l+q||n.is(".ui-footer-fixed")&&l<=u+s;n.addClass("ui-fixed-overlay").removeClass("ui-fixed-inline");!u&&!m&&n.animationComplete(function(){n.removeClass("in")}).addClass("in");b(n)})},hide:function(m){g="inline";return(a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var r=a(this),
n=r.css("top");n=n=="auto"?0:parseFloat(n);r.addClass("ui-fixed-inline").removeClass("ui-fixed-overlay");if(n<0||r.is(".ui-header-fixed")&&n!=0)if(m)r.css("top",0);else r.css("top")!=="auto"&&parseFloat(r.css("top"))!==0&&r.animationComplete(function(){r.removeClass("out reverse");r.css("top",0)}).addClass("out reverse")})},startShowTimer:function(){a.fixedToolbars.clearShowTimer();var m=a.makeArray(arguments);i=setTimeout(function(){i=d;a.fixedToolbars.show.apply(null,m)},100)},clearShowTimer:function(){i&&
clearTimeout(i);i=d},toggle:function(m){if(m)g=m;return g=="overlay"?a.fixedToolbars.hide():a.fixedToolbars.show()},setTouchToggleEnabled:function(m){v=m}}}}()})(jQuery);
(function(a,d){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null},_create:function(){var c=this,f=this.element,b=f.closest("form,fieldset,[data-role='page']").find("label[for='"+f.attr("id")+"']"),g=f.attr("type"),e="ui-icon-"+g+"-off";if(!(g!="checkbox"&&g!="radio")){if(!this.options.theme)this.options.theme=this.element.data("theme");b.buttonMarkup({theme:this.options.theme,icon:this.element.parents("[data-type='horizontal']").length?d:e,shadow:false});f.add(b).wrapAll("<div class='ui-"+
g+"'></div>");b.bind({mouseover:function(){if(a(this).parent().is(".ui-disabled"))return false},touchmove:function(i){i=i.originalEvent.touches[0];if(b.data("movestart")){if(Math.abs(b.data("movestart")[0]-i.pageX)>10||Math.abs(abel.data("movestart")[1]-i.pageY)>10)b.data("moved",true)}else b.data("movestart",[parseFloat(i.pageX),parseFloat(i.pageY)])},"touchend mouseup":function(i){b.removeData("movestart");if(b.data("etype")&&b.data("etype")!==i.type||b.data("moved")){b.removeData("etype").removeData("moved");
b.data("moved")&&b.removeData("moved");return false}b.data("etype",i.type);c._cacheVals();f.attr("checked",g==="radio"&&true||!f.is(":checked"));c._updateAll();i.preventDefault()},click:false});f.bind({mousedown:function(){this._cacheVals()},click:function(){c._updateAll()},focus:function(){b.addClass("ui-focus")},blur:function(){b.removeClass("ui-focus")}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){a(this).data("cacheVal",a(this).is(":checked"))})},_getInputSet:function(){return this.element.closest("form,fieldset,[data-role='page']").find("input[name='"+
this.element.attr("name")+"'][type='"+this.element.attr("type")+"']")},_updateAll:function(){this._getInputSet().each(function(){var c=a(this).data("cacheVal");if(c&&c!==a(this).is(":checked")||a(this).is("[type='checkbox']"))a(this).trigger("change")}).checkboxradio("refresh")},refresh:function(){var c=this.element,f=c.closest("form,fieldset,[data-role='page']").find("label[for='"+c.attr("id")+"']"),b=c.attr("type"),g=f.find(".ui-icon"),e="ui-icon-"+b+"-on";b="ui-icon-"+b+"-off";if(c[0].checked){f.addClass("ui-btn-active");
g.addClass(e);g.removeClass(b)}else{f.removeClass("ui-btn-active");g.removeClass(e);g.addClass(b)}c.is(":disabled")?this.disable():this.enable()},disable:function(){this.element.attr("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.attr("disabled",false).parent().removeClass("ui-disabled")}})})(jQuery);
(function(a){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null},_create:function(){var d=this.element,c=this.options,f=c.theme;if(!f){f=this.element.closest("[class*='ui-bar-'],[class*='ui-body-']");f=f.length?/ui-(bar|body)-([a-z])/.exec(f.attr("class"))[2]:"c"}f=" ui-body-"+f;a("label[for="+d.attr("id")+"]").addClass("ui-input-text");d.addClass("ui-input-text ui-body-"+c.theme);var b=d;if(d.is('[type="search"],[data-type="search"]')){b=d.wrap('<div class="ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield'+
f+'"></div>').parent();var g=a('<a href="#" class="ui-input-clear" title="clear text">clear text</a>').tap(function(h){d.val("").focus();d.trigger("change");g.addClass("ui-input-clear-hidden");h.preventDefault()}).appendTo(b).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true});c=function(){d.val()==""?g.addClass("ui-input-clear-hidden"):g.removeClass("ui-input-clear-hidden")};c();d.keyup(c)}else d.addClass("ui-corner-all ui-shadow-inset"+f);d.focus(function(){b.addClass("ui-focus")}).blur(function(){b.removeClass("ui-focus")});
if(d.is("textarea")){var e=function(){var h=d[0].scrollHeight;d[0].clientHeight<h&&d.css({height:h+15})},i;d.keyup(function(){clearTimeout(i);i=setTimeout(e,100)})}},disable:function(){(this.element.attr("disabled",true).is('[type="search"],[data-type="search"]')?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is('[type="search"],[data-type="search"]')?this.element.parent():this.element).removeClass("ui-disabled")}})})(jQuery);
(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:null,corners:true,shadow:true,iconshadow:true,menuPageTheme:"b",overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:false},_create:function(){var d=this,c=this.options,f=this.element.wrap("<div class='ui-select'>"),b=f.attr("id"),g=a("label[for="+b+"]").addClass("ui-select"),e=(d.options.nativeMenu?a("<div/>"):a("<a>",{href:"#",role:"button",id:k,
"aria-haspopup":"true","aria-owns":j})).text(a(f[0].options.item(f[0].selectedIndex)).text()).insertBefore(f).buttonMarkup({theme:c.theme,icon:c.icon,iconpos:c.iconpos,inline:c.inline,corners:c.corners,shadow:c.shadow,iconshadow:c.iconshadow}),i=d.isMultiple=f[0].multiple;c.nativeMenu&&window.opera&&window.opera.version&&f.addClass("ui-select-nativeonly");if(!c.nativeMenu){var h=f.find("option"),k=b+"-button",j=b+"-menu",o=f.closest(".ui-page"),p=/ui-btn-up-([a-z])/.exec(e.attr("class"))[1],t=a("<div data-role='dialog' data-theme='"+
c.menuPageTheme+"'><div data-role='header'><div class='ui-title'>"+g.text()+"</div></div><div data-role='content'></div></div>").appendTo(a.mobile.pageContainer).page(),v=t.find(".ui-content"),m=t.find(".ui-header a"),r=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(o),n=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all pop ui-body-"+c.overlayTheme}).insertAfter(r),u=a("<ul>",{"class":"ui-selectmenu-list",id:j,role:"listbox","aria-labelledby":k,
"data-theme":p}).appendTo(n);p=a("<div>",{"class":"ui-header ui-bar-"+p}).prependTo(n);var l=a("<h1>",{"class":"ui-title"}).appendTo(p),s=a("<a>",{"data-iconpos":"notext","data-icon":"delete",text:c.closeText,href:"#","class":"ui-btn-left"}).appendTo(p).buttonMarkup()}if(i)d.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(e);c.disabled&&this.disable();f.change(function(){d.refresh()});a.extend(d,{select:f,optionElems:h,selectID:b,label:g,buttonId:k,menuId:j,
thisPage:o,button:e,menuPage:t,menuPageContent:v,screen:r,listbox:n,list:u,menuType:void 0,header:p,headerClose:s,headerTitle:l,placeholder:""});if(c.nativeMenu){f.appendTo(e).bind("touchstart mousedown",function(){e.addClass(a.mobile.activeBtnClass)}).bind("focus mouseover",function(){e.trigger("mouseover")}).bind("touchmove",function(){e.removeClass(a.mobile.activeBtnClass)}).bind("change blur mouseout",function(){e.trigger("mouseout").removeClass(a.mobile.activeBtnClass)});e.attr("tabindex","-1")}else{d.refresh();
f.attr("tabindex","-1").focus(function(){a(this).blur();e.focus()});e.bind("touchstart",function(q){a(this).data("startTouches",a.extend({},q.originalEvent.touches[0]))}).bind(a.support.touch?"touchend":"mouseup",function(q){a(this).data("moved")?a(this).removeData("moved"):d.open();q.preventDefault()}).bind("touchmove",function(q){q=q.originalEvent.touches[0];var w=a(this).data("startTouches"),z=Math.abs(q.pageY-w.pageY);if(Math.abs(q.pageX-w.pageX)>10||z>10)a(this).data("moved",true)});u.delegate("li:not(.ui-disabled, .ui-li-divider)",
"click",function(q){if(a(q.target).is("a")){var w=u.find("li:not(.ui-li-divider)").index(this);w=d.optionElems.eq(w)[0];w.selected=i?!w.selected:true;i&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",w.selected).toggleClass("ui-icon-checkbox-off",!w.selected);f.trigger("change");i||d.close();q.preventDefault()}});r.add(s).add(m).bind("click",function(q){d.close();q.preventDefault();a.contains(m[0],q.target)&&q.stopImmediatePropagation()})}},_buildList:function(){var d=this,c=this.options,
f=this.placeholder,b=[],g=[],e=d.isMultiple?"checkbox-off":"false";d.list.empty().filter(".ui-listview").listview("destroy");d.select.find("option").each(function(){var i=a(this),h=i.parent(),k=i.text(),j="<a href='#'>"+k+"</a>",o=[],p=[];if(h.is("optgroup")){h=h.attr("label");if(a.inArray(h,b)===-1){g.push("<li data-role='list-divider'>"+h+"</li>");b.push(h)}}if(!this.getAttribute("value")||k.length==0||i.data("placeholder")){c.hidePlaceholderMenuItems&&o.push("ui-selectmenu-placeholder");f=d.placeholder=
k}if(this.disabled){o.push("ui-disabled");p.push("aria-disabled='true'")}g.push("<li data-icon='"+e+"' class='"+o.join(" ")+"' "+p.join(" ")+">"+j+"</li>")});d.list.html(g.join(" "));this.isMultiple||this.headerClose.hide();!this.isMultiple&&!f.length?this.header.hide():this.headerTitle.text(this.placeholder);d.list.listview()},refresh:function(d){var c=this,f=this.element,b=this.isMultiple,g=this.optionElems=f.find("option"),e=g.filter(":selected"),i=e.map(function(){return g.index(this)}).get();
if(!c.options.nativeMenu&&(d||f[0].options.length>c.list.find("li").length))c._buildList();c.button.find(".ui-btn-text").text(function(){if(!b)return e.text();return e.length?e.map(function(){return a(this).text()}).get().join(", "):c.placeholder});if(b)c.buttonCount[e.length>1?"show":"hide"]().text(e.length);c.options.nativeMenu||c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",false).each(function(h){if(a.inArray(h,i)>-1){h=a(this).addClass(a.mobile.activeBtnClass);
h.find("a").attr("aria-selected",true);b&&h.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on")}})},open:function(){function d(){c.list.find(".ui-btn-active").focus()}if(!(this.options.disabled||this.options.nativeMenu)){var c=this,f=c.list.outerHeight(),b=c.list.outerWidth(),g=a(window).scrollTop(),e=c.button.offset().top,i=window.innerHeight,h=c.list.parents(".ui-dialog").length;c.button.addClass(a.mobile.activeBtnClass);if(h||f>i-80||!a.support.scrollTop){g==0&&
e>i&&c.thisPage.one("pagehide",function(){a(this).data("lastScroll",e)});c.menuPage.one("pageshow",function(){a(window).one("silentscroll",function(){d()})});c.menuType="page";c.menuPageContent.append(c.list);a.mobile.changePage(c.menuPage,"pop",false,true)}else{c.menuType="overlay";c.screen.height(a(document).height()).removeClass("ui-screen-hidden");h=e-g;var k=g+i-e,j=f/2;f=h>f/2&&k>f/2?e+c.button.outerHeight()/2-j:h>k?g+i-f-30:g+30;b=c.button.offset().left+c.button.outerWidth()/2-b/2;c.listbox.append(c.list).removeClass("ui-selectmenu-hidden").css({top:f,
left:b}).addClass("in");d()}setTimeout(function(){c.isOpen=true},400)}},close:function(){function d(){setTimeout(function(){c.button.focus();c.button.removeClass(a.mobile.activeBtnClass)},40);c.listbox.removeAttr("style").append(c.list)}if(!(this.options.disabled||!this.isOpen||this.options.nativeMenu)){var c=this;if(c.menuType=="page"){a.mobile.changePage([c.menuPage,c.thisPage],"pop",true,false);c.menuPage.one("pagehide",d)}else{c.screen.addClass("ui-screen-hidden");c.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in");
d()}this.isOpen=false}},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)}})})(jQuery);
(function(a){a.fn.buttonMarkup=function(c){return this.each(function(){var f=a(this),b=a.extend({},a.fn.buttonMarkup.defaults,f.data(),c),g,e="ui-btn-inner",i;d&&d();if(!b.theme){g=f.closest("[class*='ui-bar-'],[class*='ui-body-']");b.theme=g.length?/ui-(bar|body)-([a-z])/.exec(g.attr("class"))[2]:"c"}g="ui-btn ui-btn-up-"+b.theme;if(b.inline)g+=" ui-btn-inline";if(b.icon){b.icon="ui-icon-"+b.icon;b.iconpos=b.iconpos||"left";i="ui-icon "+b.icon;if(b.shadow)i+=" ui-icon-shadow"}if(b.iconpos){g+=" ui-btn-icon-"+
b.iconpos;b.iconpos=="notext"&&!f.attr("title")&&f.attr("title",f.text())}if(b.corners){g+=" ui-btn-corner-all";e+=" ui-btn-corner-all"}if(b.shadow)g+=" ui-shadow";f.attr("data-theme",b.theme).addClass(g);b=("<D class='"+e+"'><D class='ui-btn-text'></D>"+(b.icon?"<span class='"+i+"'></span>":"")+"</D>").replace(/D/g,b.wrapperEls);f.wrapInner(b)})};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,wrapperEls:"span"};var d=function(){a(".ui-btn:not(.ui-disabled)").live({"touchstart mousedown":function(){var c=
a(this).attr("data-theme");a(this).removeClass("ui-btn-up-"+c).addClass("ui-btn-down-"+c)},"touchmove touchend mouseup":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-down-"+c).addClass("ui-btn-up-"+c)},"mouseover focus":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-up-"+c).addClass("ui-btn-hover-"+c)},"mouseout blur":function(){var c=a(this).attr("data-theme");a(this).removeClass("ui-btn-hover-"+c).addClass("ui-btn-up-"+c)}});d=null}})(jQuery);
(function(a){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:null,corners:true,shadow:true,iconshadow:true},_create:function(){var d=this.element,c=this.options;this.button=a("<div></div>").text(d.text()||d.val()).buttonMarkup({theme:c.theme,icon:c.icon,iconpos:c.iconpos,inline:c.inline,corners:c.corners,shadow:c.shadow,iconshadow:c.iconshadow}).insertBefore(d).append(d.addClass("ui-btn-hidden"));d.attr("type")!=="reset"&&d.click(function(){var f=a("<input>",
{type:"hidden",name:d.attr("name"),value:d.attr("value")}).insertBefore(d);a(document).submit(function(){f.remove()})})},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}})})(jQuery);
(function(a){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false},_create:function(){var d=this,c=this.element,f=c.parents("[class*=ui-bar-],[class*=ui-body-]").eq(0);f=f.length?f.attr("class").match(/ui-(bar|body)-([a-z])/)[2]:"c";var b=this.options.theme?this.options.theme:f,g=this.options.trackTheme?this.options.trackTheme:f,e=c[0].nodeName.toLowerCase();f=e=="select"?"ui-slider-switch":"";var i=c.attr("id"),h=i+"-label";i=a("[for="+i+"]").attr("id",h);
var k=function(){return e=="input"?parseFloat(c.val()):c[0].selectedIndex},j=e=="input"?parseFloat(c.attr("min")):0,o=e=="input"?parseFloat(c.attr("max")):c.find("option").length-1,p=window.parseFloat(c.attr("step")||1),t=a('<div class="ui-slider '+f+" ui-btn-down-"+g+' ui-btn-corner-all" role="application"></div>'),v=a('<a href="#" class="ui-slider-handle"></a>').appendTo(t).buttonMarkup({corners:true,theme:b,shadow:true}).attr({role:"slider","aria-valuemin":j,"aria-valuemax":o,"aria-valuenow":k(),
"aria-valuetext":k(),title:k(),"aria-labelledby":h});a.extend(this,{slider:t,handle:v,dragging:false,beforeStart:null});if(e=="select"){t.wrapInner('<div class="ui-slider-inneroffset"></div>');c.find("option");c.find("option").each(function(m){var r=m==0?"b":"a",n=m==0?"right":"left";m=m==0?" ui-btn-down-"+g:" ui-btn-active";a('<div class="ui-slider-labelbg ui-slider-labelbg-'+r+m+" ui-btn-corner-"+n+'"></div>').prependTo(t);a('<span class="ui-slider-label ui-slider-label-'+r+m+" ui-btn-corner-"+
n+'" role="img">'+a(this).text()+"</span>").prependTo(v)})}i.addClass("ui-slider");c.addClass(e=="input"?"ui-slider-input":"ui-slider-switch").change(function(){d.refresh(k(),true)}).keyup(function(){d.refresh(k(),true,true)}).blur(function(){d.refresh(k(),true)});a(document).bind("touchmove mousemove",function(m){if(d.dragging){d.refresh(m);return false}});t.bind("touchstart mousedown",function(m){d.dragging=true;if(e==="select")d.beforeStart=c[0].selectedIndex;d.refresh(m);return false});t.add(document).bind("touchend mouseup",
function(){if(d.dragging){d.dragging=false;if(e==="select"){if(d.beforeStart===c[0].selectedIndex)d.refresh(d.beforeStart===0?1:0);var m=k();m=Math.round(m/(o-j)*100);v.addClass("ui-slider-handle-snapping").css("left",m+"%").animationComplete(function(){v.removeClass("ui-slider-handle-snapping")})}return false}});t.insertAfter(c);this.handle.bind("touchstart mousedown",function(){a(this).focus()});this.handle.bind("keydown",function(m){var r=k();if(!d.options.disabled){switch(m.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:m.preventDefault();
if(!d._keySliding){d._keySliding=true;a(this).addClass("ui-state-active")}}switch(m.keyCode){case a.mobile.keyCode.HOME:d.refresh(j);break;case a.mobile.keyCode.END:d.refresh(o);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:d.refresh(r+p);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:d.refresh(r-p)}}}).keyup(function(){if(d._keySliding){d._keySliding=false;a(this).removeClass("ui-state-active")}});this.refresh()},
refresh:function(d,c,f){if(!this.options.disabled){var b=this.element,g=b[0].nodeName.toLowerCase(),e=g==="input"?parseFloat(b.attr("min")):0,i=g==="input"?parseFloat(b.attr("max")):b.find("option").length-1;if(typeof d==="object"){d=d.originalEvent.touches?d.originalEvent.touches[0]:d;if(!this.dragging||d.pageX<this.slider.offset().left-8||d.pageX>this.slider.offset().left+this.slider.width()+8)return;d=Math.round((d.pageX-this.slider.offset().left)/this.slider.width()*100)}else{if(d==null)d=g===
"input"?parseFloat(b.val()):b[0].selectedIndex;d=(parseFloat(d)-e)/(i-e)*100}if(!isNaN(d)){if(d<0)d=0;if(d>100)d=100;var h=Math.round(d/100*(i-e))+e;if(h<e)h=e;if(h>i)h=i;this.handle.css("left",d+"%");this.handle.attr({"aria-valuenow":g==="input"?h:b.find("option").eq(h).attr("value"),"aria-valuetext":g==="input"?h:b.find("option").eq(h).text(),title:h});if(g==="select")h===0?this.slider.addClass("ui-slider-switch-a").removeClass("ui-slider-switch-b"):this.slider.addClass("ui-slider-switch-b").removeClass("ui-slider-switch-a");
if(!f){if(g==="input")b.val(h);else b[0].selectedIndex=h;c||b.trigger("change")}}}},enable:function(){this.element.attr("disabled",false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}})})(jQuery);
(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:false,heading:">:header,>legend",theme:null,iconTheme:"d"},_create:function(){var d=this.element,c=this.options,f=d.addClass("ui-collapsible-contain"),b=d.find(c.heading).eq(0),g=f.wrapInner('<div class="ui-collapsible-content"></div>').find(".ui-collapsible-content");d=d.closest('[data-role="collapsible-set"]').addClass("ui-collapsible-set");
if(b.is("legend")){b=a('<div role="heading">'+b.html()+"</div>").insertBefore(b);b.next().remove()}b.insertBefore(g);b.addClass("ui-collapsible-heading").append('<span class="ui-collapsible-heading-status"></span>').wrapInner('<a href="#" class="ui-collapsible-heading-toggle"></a>').find("a:eq(0)").buttonMarkup({shadow:!!!d.length,corners:false,iconPos:"left",icon:"plus",theme:c.theme}).find(".ui-icon").removeAttr("class").buttonMarkup({shadow:true,corners:true,iconPos:"notext",icon:"plus",theme:c.iconTheme});
if(d.length)f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").addClass("ui-corner-bottom");else b.find("a:eq(0)").addClass("ui-corner-all").find(".ui-btn-inner").addClass("ui-corner-all");f.bind("collapse",function(e){if(!e.isDefaultPrevented()){e.preventDefault();b.addClass("ui-collapsible-heading-collapsed").find(".ui-collapsible-heading-status").text(c.expandCueText);b.find(".ui-icon").removeClass("ui-icon-minus").addClass("ui-icon-plus");g.addClass("ui-collapsible-content-collapsed").attr("aria-hidden",
true);f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").addClass("ui-corner-bottom")}}).bind("expand",function(e){if(!e.isDefaultPrevented()){e.preventDefault();b.removeClass("ui-collapsible-heading-collapsed").find(".ui-collapsible-heading-status").text(c.collapseCueText);b.find(".ui-icon").removeClass("ui-icon-plus").addClass("ui-icon-minus");g.removeClass("ui-collapsible-content-collapsed").attr("aria-hidden",false);f.data("collapsible-last")&&b.find("a:eq(0), .ui-btn-inner").removeClass("ui-corner-bottom")}}).trigger(c.collapsed?
"collapse":"expand");if(d.length&&!d.data("collapsiblebound")){d.data("collapsiblebound",true).bind("expand",function(e){a(this).find(".ui-collapsible-contain").not(a(e.target).closest(".ui-collapsible-contain")).not("> .ui-collapsible-contain .ui-collapsible-contain").trigger("collapse")});d=d.find("[data-role=collapsible]");d.first().find("a:eq(0)").addClass("ui-corner-top").find(".ui-btn-inner").addClass("ui-corner-top");d.last().data("collapsible-last",true)}b.bind(a.support.touch?"touchstart":
"click",function(){b.is(".ui-collapsible-heading-collapsed")?f.trigger("expand"):f.trigger("collapse");return false})}})})(jQuery);
(function(a){a.fn.controlgroup=function(d){return this.each(function(){function c(e){e.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(g[0]).end().filter(":last").addClass(g[1]).addClass("ui-controlgroup-last")}var f=a.extend({direction:a(this).data("type")||"vertical",shadow:false},d),b=a(this).find(">legend"),g=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];a(this).find("input:eq(0)").attr("type");if(b.length){a(this).wrapInner('<div class="ui-controlgroup-controls"></div>');
a('<div role="heading" class="ui-controlgroup-label">'+b.html()+"</div>").insertBefore(a(this).children(0));b.remove()}a(this).addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);c(a(this).find(".ui-btn"));c(a(this).find(".ui-btn-inner"));f.shadow&&a(this).addClass("ui-shadow")})}})(jQuery);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")}})(jQuery);
(function(a){a.widget("mobile.listview",a.mobile.widget,{options:{theme:"c",countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:false},_create:function(){var d=this.element,c=this.options;d.addClass("ui-listview").attr("role","listbox");c.inset&&d.addClass("ui-listview-inset ui-corner-all ui-shadow");d.delegate(".ui-li","focusin",function(){a(this).attr("tabindex","0")});this._itemApply(d,d);this.refresh(true);d.keydown(function(f){var b=a(f.target),g=b.closest("li");
switch(f.keyCode){case 38:f=g.prev();if(f.length){b.blur().attr("tabindex","-1");f.find("a").first().focus()}return false;case 40:f=g.next();if(f.length){b.blur().attr("tabindex","-1");f.find("a").first().focus()}return false;case 39:f=g.find("a.ui-li-link-alt");if(f.length){b.blur();f.first().focus()}return false;case 37:f=g.find("a.ui-link-inherit");if(f.length){b.blur();f.first().focus()}return false;case 13:case 32:b.trigger("click");return false}});d.delegate("li","click",function(f){if(!a(f.target).closest("a").length){a(this).find("a").first().trigger("click");
return false}})},_itemApply:function(d,c){c.find(".ui-li-count").addClass("ui-btn-up-"+(d.data("counttheme")||this.options.countTheme)+" ui-btn-corner-all");c.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading");c.find("p, dl").addClass("ui-li-desc");c.find("li").find("img:eq(0)").addClass("ui-li-thumb").each(function(){a(this).closest("li").addClass(a(this).is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb")});var f=c.find(".ui-li-aside");f.length&&f.each(function(b,g){a(g).prependTo(a(g).parent())});
a.support.cssPseudoElement||a.nodeName(c[0],"ol")},_removeCorners:function(d){d.add(d.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb")).removeClass("ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl")},refresh:function(d){this._createSubPages();var c=this.options,f=this.element,b=this,g=f.data("dividertheme")||c.dividerTheme,e=f.children("li"),i=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1;i&&f.find(".ui-li-dec").remove();e.attr({role:"option",tabindex:"-1"});
e.first().attr("tabindex","0");e.each(function(h){var k=a(this),j="ui-li";if(!(!d&&k.hasClass("ui-li"))){var o=k.data("theme")||c.theme,p=k.find("a");if(p.length){var t=k.data("icon");k.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:p.length>1||t===false?false:t||"arrow-r",theme:o});p.first().addClass("ui-link-inherit");if(p.length>1){j+=" ui-li-has-alt";p=p.last();t=f.data("splittheme")||p.data("theme")||c.splitTheme;p.appendTo(k).attr("title",p.text()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,
corners:false,theme:o,icon:false,iconpos:false}).find(".ui-btn-inner").append(a("<span>").buttonMarkup({shadow:true,corners:true,theme:t,iconpos:"notext",icon:f.data("spliticon")||p.data("icon")||c.splitIcon}))}}else if(k.data("role")==="list-divider"){j+=" ui-li-divider ui-btn ui-bar-"+g;k.attr("role","heading");if(i)i=1}else j+=" ui-li-static ui-btn-up-"+o;if(c.inset){if(h===0){j+=" ui-corner-top";k.add(k.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-tr").end().find(".ui-li-thumb").addClass("ui-corner-tl");
k.next().next().length&&b._removeCorners(k.next())}if(h===e.length-1){j+=" ui-corner-bottom";k.add(k.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").addClass("ui-corner-bl");k.prev().prev().length&&b._removeCorners(k.prev())}}i&&j.indexOf("ui-li-divider")<0&&k.find(".ui-link-inherit").first().addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+i++ +". </span>");k.addClass(j);d||b._itemApply(f,k)}})},_idStringEscape:function(d){return d.replace(/[^a-zA-Z0-9]/g,
"-")},_createSubPages:function(){var d=this.element,c=d.closest(".ui-page"),f=c.data("url"),b=this.options,g=this,e=c.find("[data-role='footer']").data("id");a(d.find("ul, ol").toArray().reverse()).each(function(i){var h=a(this),k=h.parent(),j=a.trim(k.contents()[0].nodeValue)||k.find("a:first").text();i=f+"&"+a.mobile.subPageUrlKey+"="+g._idStringEscape(j+" "+i);var o=h.data("theme")||b.theme,p=h.data("counttheme")||d.data("counttheme")||b.countTheme;h.wrap("<div data-role='page'><div data-role='content'></div></div>").parent().before("<div data-role='header' data-theme='"+
b.headerTheme+"'><div class='ui-title'>"+j+"</div></div>").after(e?a("<div>",{"data-role":"footer","data-id":e,"class":"ui-footer-duplicate"}):"").parent().attr({"data-url":i,"data-theme":o,"data-count-theme":p}).appendTo(a.mobile.pageContainer).page();h=k.find("a:first");h.length||(h=a("<a></a>").html(j).prependTo(k.empty()));h.attr("href","#"+i)}).listview()}})})(jQuery);
(function(a){a.mobile.listview.prototype.options.filter=false;a("[data-role='listview']").live("listviewcreate",function(){var d=a(this);if(d.data("listview").options.filter){var c=a("<form>",{"class":"ui-listview-filter ui-bar-c",role:"search"});a("<input>",{placeholder:"Filter results...","data-type":"search"}).bind("keyup change",function(){var f=this.value.toLowerCase();d.children().show();f&&d.children().filter(function(){return a(this).text().toLowerCase().indexOf(f)===-1}).hide()}).appendTo(c).textinput();
c.insertBefore(d)}})})(jQuery);
(function(a){a.widget("mobile.dialog",a.mobile.widget,{options:{},_create:function(){this.element.attr("role","dialog").addClass("ui-page ui-dialog ui-body-a").find("[data-role=header]").addClass("ui-corner-top ui-overlay-shadow").prepend('<a href="#" data-icon="delete" data-rel="back" data-iconpos="notext">Close</a>').end().find('.ui-content:not([class*="ui-body-"])').addClass("ui-body-c").end().find(".ui-content,[data-role=footer]").last().addClass("ui-corner-bottom ui-overlay-shadow");this.element.bind("click submit",
function(d){d=d.type=="click"?a(d.target).closest("a"):a(d.target).closest("form");d.length&&!d.data("transition")&&d.attr("data-transition",a.mobile.urlHistory.getActive().transition).attr("data-direction","reverse")})},close:function(){window.history.back()}})})(jQuery);
(function(a,d){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null},_create:function(){var c=this.element,f=c.find("a"),b=f.filter("[data-icon]").length?this.options.iconpos:d;c.addClass("ui-navbar").attr("role","navigation").find("ul").grid({grid:this.options.grid});b||c.addClass("ui-navbar-noicons");f.buttonMarkup({corners:false,shadow:false,iconpos:b});c.delegate("a","click",function(){f.removeClass("ui-btn-active");a(this).addClass("ui-btn-active")})}})})(jQuery);
(function(a){a.fn.grid=function(d){return this.each(function(){var c=a.extend({grid:null},d),f=a(this).children(),b={a:2,b:3,c:4,d:5};c=c.grid;if(!c)if(f.length<=5)for(var g in b){if(b[g]==f.length)c=g}else c="a";b=b[c];a(this).addClass("ui-grid-"+c);f.filter(":nth-child("+b+"n+1)").addClass("ui-block-a");f.filter(":nth-child("+b+"n+2)").addClass("ui-block-b");b>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");b>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");b>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);

View File

@ -0,0 +1,480 @@
/*
http://www.JSON.org/json2.js
2011-02-23
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false, regexp: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
"use strict";
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' : gap ?
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' : gap ?
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
'{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@ -20,13 +20,10 @@
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
*****************************************************************************/ *****************************************************************************/
/** .ui-icon-blank {
* init.js - In certain browsers (yes, IE, I'm looking at you!), DocumentReady background-image: url(images/ui-icon-blank.png);
* JavaScript functions can only be run very last on the page. This file is the }
* last JavaScript file to be included on the page, and provides a work-around
* for this bug in certain browsers.
*/
$(document).ready(function () { .ui-icon-unblank {
OpenLP.Events.init(); background-image: url(images/ui-icon-unblank.png);
}); }

View File

@ -20,85 +20,7 @@
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
*****************************************************************************/ *****************************************************************************/
window["OpenLP"] = { window.OpenLP = {
Namespace: {
/**
* Create a Javascript namespace.
* Based on: http://code.google.com/p/namespacedotjs/
* Idea behind this is to created nested namespaces that are not ugly.
*/
create: function (name, attributes) {
var parts = name.split('.'),
ns = window,
i = 0;
// find the deepest part of the namespace
// that is already defined
for(; i < parts.length && parts[i] in ns; i++)
ns = ns[parts[i]];
// initialize any remaining parts of the namespace
for(; i < parts.length; i++)
ns = ns[parts[i]] = {};
// copy the attributes into the namespace
for (var attr in attributes)
ns[attr] = attributes[attr];
},
exists: function (namespace) {
/**
* Determine the namespace of a page
*/
page_namespace = $ScribeEngine.Namespace.get_page_namespace();
return (namespace == page_namespace);
},
get_page_namespace: function () {
return $("#content > h2").attr("id");
}
}
};
Array.prototype.append = function (elem) {
this[this.length] = elem;
}
OpenLP.Namespace.create("OpenLP.Events", {
// Local variables
onload_functions: Array(),
// Functions
bindLoad: function (func) {
this.onload_functions.append(func);
},
bindClick: function (selector, func) {
$(selector).bind("click", func);
},
bindChange: function (selector, func) {
$(selector).bind("change", func);
},
bindSubmit: function (selector, func) {
$(selector).bind("submit", func);
},
bindBlur: function (selector, func) {
$(selector).bind("blur", func);
},
bindPaste: function (selector, func) {
$(selector).bind("paste", func);
},
bindKeyUp: function (selector, func) {
$(selector).bind("keyup", func);
},
bindKeyDown: function (selector, func) {
$(selector).bind("keydown", func);
},
bindKeyPress: function (selector, func) {
$(selector).bind("keypress", func);
},
bindMouseEnter: function (selector, func) {
$(selector).bind("mouseenter", func);
},
bindMouseLeave: function (selector, func) {
$(selector).bind("mouseleave", func);
},
liveClick: function (selector, func) {
$(selector).live("click", func);
},
getElement: function(event) { getElement: function(event) {
var targ; var targ;
if (!event) { if (!event) {
@ -116,128 +38,167 @@ OpenLP.Namespace.create("OpenLP.Events", {
} }
return $(targ); return $(targ);
}, },
init: function () { loadService: function (event) {
for (idx in this.onload_functions) { $.getJSON(
func = this.onload_functions[idx]; "/api/service/list",
func(); function (data, status) {
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
ul.html("");
$.each(data.results.items, function (idx, value) {
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(value["title"]));
li.children("a").click(OpenLP.setItem);
ul.append(li);
});
ul.listview("refresh");
} }
} );
});
OpenLP.Namespace.create("OpenLP.Remote", {
sendEvent: function (eventName, eventData)
{
var url = "/";
if (eventName.substr(-8) == "_request")
{
url += "request";
}
else
{
url += "send";
}
url += "/" + eventName;
var args = {};
if (eventData != null && eventData != "")
{
args.q = escape(eventData);
}
$.ajax({
url: url,
dataType: "json",
data: args,
success: function (data)
{
OpenLP.Remote.handleEvent(eventName, data);
}, },
error: function (xhr, textStatus, errorThrown) loadController: function (event) {
{ $.getJSON(
if (eventName == "remotes_poll_request") "/api/controller/live/text",
{ function (data, status) {
OpenLP.Remote.handleEvent("remotes_poll_request"); var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(data.results.slides[idx]["text"]));
if (data.results.slides[idx]["selected"]) {
li.attr("data-theme", "e");
} }
li.children("a").click(OpenLP.setSlide);
ul.append(li);
}
ul.listview("refresh");
}
);
},
setItem: function (event) {
var item = OpenLP.getElement(event);
var id = item.attr("value");
var text = JSON.stringify({"request": {"id": id}});
$.getJSON(
"/api/service/set",
{"data": text},
function (data, status) {
$("#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");
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#service-manager > div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
setSlide: function (event) {
var slide = OpenLP.getElement(event);
console.log(slide);
var id = slide.attr("value");
var text = JSON.stringify({"request": {"id": id}});
$.getJSON(
"/api/controller/live/set",
{"data": text},
function (data, status) {
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
while (slide[0].tagName != "LI") {
slide = slide.parent();
}
slide.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
pollServer: function () {
$.getJSON(
"/api/poll",
function (data, status) {
OpenLP.currentSlide = data.results.slide;
OpenLP.currentItem = data.results.item;
if ($("#service-manager").is(":visible")) {
$("#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);
if (item.text() == OpenLP.currentItem) {
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
} }
}); });
}, $("#service-manager div[data-role=content] ul[data-role=listview]").listview("refresh");
handleEvent: function (eventName, eventData)
{
switch (eventName)
{
case "servicemanager_list_request":
var table = $("<table>");
$.each(eventData, function (row, item) {
var trow = $("<tr>")
.attr("value", parseInt(row))
.click(OpenLP.Remote.sendSetItem);
if (item["selected"])
{
trow.addClass("selected");
} }
trow.append($("<td>").text(parseInt(row) + 1)); if ($("#slide-controller").is(":visible")) {
trow.append($("<td>").text(item["title"])); var idx = 0;
trow.append($("<td>").text(item["plugin"])); $("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
trow.append($("<td>").text("Notes: " + item["notes"])); $("#slide-controller div[data-role=content] ul[data-role=listview] li a").each(function () {
table.append(trow); var item = $(this);
if (idx == OpenLP.currentSlide) {
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
}
idx++;
}); });
$("#service").html(table); $("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
break;
case "slidecontroller_live_text_request":
var table = $("<table>");
$.each(eventData, function (row, item) {
var trow = $("<tr>")
.attr("value", parseInt(row))
.click(OpenLP.Remote.sendLiveSet);
if (item["selected"])
{
trow.addClass("selected");
} }
trow.append($("<td>").text(item["tag"]));
trow.append($("<td>").html(item["text"] ? item["text"].replace(/\\n/g, "<br />") : ""));
table.append(trow);
});
$("#current-item").html(table);
break;
case "remotes_poll_request":
OpenLP.Remote.sendEvent("remotes_poll_request");
OpenLP.Remote.sendEvent("servicemanager_list_request");
OpenLP.Remote.sendEvent("slidecontroller_live_text_request");
break;
} }
);
}, },
sendLiveSet: function (e) nextItem: function (event) {
{ $.getJSON("/api/service/next");
var tr = OpenLP.Events.getElement(e).parent();
if (tr[0].tagName != "TR")
{
tr = tr.parent();
}
OpenLP.Remote.sendEvent("slidecontroller_live_set", tr.attr("value"));
return false; return false;
}, },
sendSetItem: function (e) previousItem: function (event) {
{ $.getJSON("/api/service/previous");
var id = OpenLP.Events.getElement(e).parent().attr("value");
OpenLP.Remote.sendEvent("servicemanager_set_item", id);
return false; return false;
}, },
sendAlert: function (e) nextSlide: function (event) {
{ $.getJSON("/api/controller/live/next");
var alert_text = $("#alert-text").val();
OpenLP.Remote.sendEvent("alerts_text", alert_text);
return false; return false;
}, },
buttonClick: function (e) previousSlide: function (event) {
{ $.getJSON("/api/controller/live/previous");
var id = OpenLP.Events.getElement(e).attr("id"); return false;
OpenLP.Remote.sendEvent(id); },
blankDisplay: function (event) {
$.getJSON("/api/display/hide");
return false;
},
unblankDisplay: function (event) {
$.getJSON("/api/display/show");
return false;
},
showAlert: function (event) {
var text = JSON.stringify({"request": {"text": $("#alert-text").val()}});
$.getJSON(
"/api/alert",
{"data": text},
function () {
$("#alert-text").val("");
}
);
return false; return false;
} }
}); }
// Service Manager
OpenLP.Events.bindLoad(function () { $("#service-manager").live("pagebeforeshow", OpenLP.loadService);
OpenLP.Events.bindClick("input[type=button][id!=alert-send]", OpenLP.Remote.buttonClick); $("#service-refresh").live("click", OpenLP.loadService);
OpenLP.Events.bindClick("#alert-send", OpenLP.Remote.sendAlert); $("#service-next").live("click", OpenLP.nextItem);
OpenLP.Remote.sendEvent("servicemanager_list_request"); $("#service-previous").live("click", OpenLP.previousItem);
OpenLP.Remote.sendEvent("slidecontroller_live_text_request"); $("#service-blank").live("click", OpenLP.blankDisplay);
OpenLP.Remote.sendEvent("remotes_poll_request"); $("#service-unblank").live("click", OpenLP.unblankDisplay);
}); // Slide Controller
$("#slide-controller").live("pagebeforeshow", OpenLP.loadController);
$("#controller-refresh").live("click", OpenLP.loadController);
$("#controller-next").live("click", OpenLP.nextSlide);
$("#controller-previous").live("click", OpenLP.previousSlide);
$("#controller-blank").live("click", OpenLP.blankDisplay);
$("#controller-unblank").live("click", OpenLP.unblankDisplay);
// Alerts
$("#alert-submit").live("click", OpenLP.showAlert);
// Poll the server twice a second to get any updates.
setInterval("OpenLP.pollServer();", 500);
OpenLP.pollServer();

View File

@ -1,44 +0,0 @@
/*****************************************************************************
* OpenLP - Open Source Lyrics Projection *
* ------------------------------------------------------------------------- *
* Copyright (c) 2008-2010 Raoul Snyman *
* Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael *
* Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin *
* Thompson, Jon Tibble, Carsten Tinggaard *
* ------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
* Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
*****************************************************************************/
OpenLP.Namespace.create("OpenLP.Service", {
addServiceItem: function (elem, item)
{
var trow = $("<tr>")
.attr("id", "item-" + item.id)
.addClass("item")
.append($("<td>").text(item.tag))
.append($("<td>").text(item.tag.replace(/\n/g, "<br />")));
return false;
},
sendLive: function (e)
{
var elem = OpenLP.Events.getElement(e);
var row = elem.attr("id").substr(5);
elem.addStyle("font-weight", "bold");
return false;
}
});
OpenLP.Events.load(function (){
OpenLP.Events.liveClick(".item", OpenLP.Service.sendLive);
});

View File

@ -1,45 +0,0 @@
body
{
background-color: #fff;
font-family: Lucida Grande, Lucida Sans, Arial, Helvetica, sans-serif;
font-size: 80%;
}
a
{
color: #3c60a5;
text-decoration: none;
}
a:hover
{
color: #304d85;
text-decoration: underline;
}
fieldset
{
border: 1px solid #ccc;
}
td
{
cursor: pointer;
padding: 3px 5px;
}
tr:hover
{
background-color: #eee;
}
tr.selected:hover
{
background-color: #ccc;
}
.selected
{
background-color: #ddd;
font-weight: bold;
}

View File

@ -26,3 +26,5 @@
from remotetab import RemoteTab from remotetab import RemoteTab
from httpserver import HttpServer from httpserver import HttpServer
__all__ = [u'RemoteTab', u'HttpServer']

View File

@ -24,9 +24,94 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
"""
The :mod:`http` module contains the API web server. This is a lightweight web
server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes.
*Routes:*
``/``
Go to the web interface.
``/files/{filename}``
Serve a static file.
``/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/display/{hide|show}``
Blank or unblank the screen.
``/api/alert``
Sends an alert message to the alerts plugin. This method expects a
JSON-encoded dict like this::
{"request": {"text": "<your alert text>"}}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``set``
Set a specific slide. Requires an id return in a JSON-encoded dict like
this::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Fetches the text of the current song. The output is a JSON-encoded
dict which looks like this::
{"result": {"slides": ["...", "..."]}}
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``set``
Set a specific item in the service. Requires an id returned in a
JSON-encoded dict like this::
{"request": {"id": 1}}
``list``
Request a list of items in the service. Returns a list of items in the
current service in a JSON-encoded dict like this::
{"results": {"items": [{...}, {...}]}}
"""
import logging import logging
import os import os
import urlparse import urlparse
import re
from pprint import pformat
try: try:
import json import json
@ -36,10 +121,29 @@ except ImportError:
from PyQt4 import QtCore, QtNetwork from PyQt4 import QtCore, QtNetwork
from openlp.core.lib import Receiver from openlp.core.lib import Receiver
from openlp.core.ui import HideMode
from openlp.core.utils import AppLocation from openlp.core.utils import AppLocation
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class HttpResponse(object):
"""
A simple object to encapsulate a pseudo-http response.
"""
code = '200 OK'
content = ''
headers = {
'Content-Type': 'text/html; charset="utf-8"\r\n'
}
def __init__(self, content='', headers={}, code=None):
self.content = content
for key, value in headers.iteritems():
self.headers[key] = value
if code:
self.code = code
class HttpServer(object): class HttpServer(object):
""" """
Ability to control OpenLP via a webbrowser Ability to control OpenLP via a webbrowser
@ -90,22 +194,12 @@ class HttpServer(object):
Slide change listener. Store the item and tell the clients Slide change listener. Store the item and tell the clients
""" """
self.current_slide = row self.current_slide = row
self.send_poll()
def item_change(self, items): def item_change(self, items):
""" """
Item (song) change listener. Store the slide and tell the clients Item (song) change listener. Store the slide and tell the clients
""" """
self.current_item = items[0].title self.current_item = items[0]
self.send_poll()
def send_poll(self):
"""
Tell the clients something has changed
"""
Receiver.send_message(u'remotes_poll_response',
{'slide': self.current_slide,
'item': self.current_item})
def new_connection(self): def new_connection(self):
""" """
@ -122,6 +216,7 @@ class HttpServer(object):
The connection has been closed. Clean up The connection has been closed. Clean up
""" """
log.debug(u'close http connection') log.debug(u'close http connection')
if connection in self.connections:
self.connections.remove(connection) self.connections.remove(connection)
def close(self): def close(self):
@ -138,17 +233,44 @@ class HttpConnection(object):
""" """
def __init__(self, parent, socket): def __init__(self, parent, socket):
""" """
Initialise the http connection. Listen out for socket signals Initialise the http connection. Listen out for socket signals.
""" """
log.debug(u'Initialise HttpConnection: %s' % log.debug(u'Initialise HttpConnection: %s' %
socket.peerAddress().toString()) socket.peerAddress().toString())
self.socket = socket self.socket = socket
self.parent = parent self.parent = parent
self.routes = [
(u'^/$', self.serve_file),
(r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll),
(r'^/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service),
(r'^/api/display/(hide|show)$', self.display),
(r'^/api/alert$', self.alert)
]
QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'), QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
self.ready_read) self.ready_read)
QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'), QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
self.disconnected) self.disconnected)
def _get_service_items(self):
service_items = []
service_manager = self.parent.parent.serviceManager
item = service_manager.findServiceItem()[0]
if item >= 0 and item < len(service_manager.serviceItems):
curitem = service_manager.serviceItems[item]
else:
curitem = None
for item in service_manager.serviceItems:
service_item = item[u'service_item']
service_items.append({
u'title': unicode(service_item.get_display_title()),
u'plugin': unicode(service_item.name),
u'notes': unicode(service_item.notes),
u'selected': (item == curitem)
})
return service_items
def ready_read(self): def ready_read(self):
""" """
Data has been sent from the client. Respond to it Data has been sent from the client. Respond to it
@ -158,32 +280,27 @@ class HttpConnection(object):
data = unicode(self.socket.readLine()) data = unicode(self.socket.readLine())
log.debug(u'received: ' + data) log.debug(u'received: ' + data)
words = data.split(u' ') words = data.split(u' ')
html = None response = None
mimetype = None
if words[0] == u'GET': if words[0] == u'GET':
url = urlparse.urlparse(words[1]) url = urlparse.urlparse(words[1])
params = self.load_params(url.query) self.url_params = urlparse.parse_qs(url.query)
folders = url.path.split(u'/') # Loop through the routes we set up earlier and execute them
if folders[1] == u'': for route, func in self.routes:
mimetype, html = self.serve_file(u'') match = re.match(route, url.path)
elif folders[1] == u'files': if match:
mimetype, html = self.serve_file(os.sep.join(folders[2:])) log.debug('Route "%s" matched "%s"', route, url.path)
elif folders[1] == u'send': args = []
html = self.process_event(folders[2], params) for param in match.groups():
elif folders[1] == u'request': args.append(param)
if self.process_request(folders[2], params): response = func(*args)
return break
if html: if response:
if mimetype: self.send_response(response)
self.send_200_ok(mimetype)
else: else:
self.send_200_ok() self.send_response(HttpResponse(code='404 Not Found'))
self.socket.write(html)
else:
self.send_404_not_found()
self.close() self.close()
def serve_file(self, filename): def serve_file(self, filename=None):
""" """
Send a file to the socket. For now, just a subset of file types Send a file to the socket. For now, just a subset of file types
and must be top level inside the html folder. and must be top level inside the html folder.
@ -197,7 +314,7 @@ class HttpConnection(object):
filename = u'index.html' filename = u'index.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename)) path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
if not path.startswith(self.parent.html_dir): if not path.startswith(self.parent.html_dir):
return None return HttpResponse(code=u'404 Not Found')
ext = os.path.splitext(filename)[1] ext = os.path.splitext(filename)[1]
if ext == u'.html': if ext == u'.html':
mimetype = u'text/html' mimetype = u'text/html'
@ -212,126 +329,116 @@ class HttpConnection(object):
elif ext == u'.png': elif ext == u'.png':
mimetype = u'image/png' mimetype = u'image/png'
else: else:
return (None, None) mimetype = u'text/plain'
file_handle = None file_handle = None
try: try:
file_handle = open(path, u'rb') file_handle = open(path, u'rb')
log.debug(u'Opened %s' % path) log.debug(u'Opened %s' % path)
html = file_handle.read() content = file_handle.read()
except IOError: except IOError:
log.exception(u'Failed to open %s' % path) log.exception(u'Failed to open %s' % path)
return None return HttpResponse(code=u'404 Not Found')
finally: finally:
if file_handle: if file_handle:
file_handle.close() file_handle.close()
return (mimetype, html) return HttpResponse(content, {u'Content-Type': mimetype})
def load_params(self, query): def poll(self):
""" """
Decode the query string parameters sent from the browser Poll OpenLP to determine the current slide number and item name.
""" """
log.debug(u'loading params %s' % query) result = {
params = urlparse.parse_qs(query) u'slide': self.parent.current_slide or 0,
if not params: u'item': self.parent.current_item.title \
return None if self.parent.current_item else u''
}
return HttpResponse(json.dumps({u'results': result}),
{u'Content-Type': u'application/json'})
def display(self, action):
"""
Hide or show the display screen.
``action``
This is the action, either ``hide`` or ``show``.
"""
event = u'maindisplay_%s' % action
Receiver.send_message(event, HideMode.Blank)
return HttpResponse(json.dumps({u'results': {u'success': True}}),
{u'Content-Type': u'application/json'})
def alert(self):
"""
Send an alert.
"""
text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
Receiver.send_message(u'alerts_text', [text])
return HttpResponse(json.dumps({u'results': {u'success': True}}),
{u'Content-Type': u'application/json'})
def controller(self, type, action):
"""
Perform an action on the slide controller.
``type``
This is the type of slide controller, either ``preview`` or
``live``.
``action``
The action to perform.
"""
event = u'slidecontroller_%s_%s' % (type, action)
if action == u'text':
current_item = self.parent.current_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
if current_item.is_text():
item[u'tag'] = unicode(frame[u'verseTag'])
item[u'text'] = unicode(frame[u'html'])
else: else:
return params['q'] item[u'tag'] = unicode(index)
item[u'text'] = u''
def process_event(self, event, params): item[u'selected'] = (self.parent.current_slide == index)
""" data.append(item)
Send a signal to openlp to perform an action. json_data = {u'results': {u'slides': data}}
Currently lets anything through. Later we should restrict and perform else:
basic parameter checking, otherwise rogue clients could crash openlp if self.url_params and self.url_params.get(u'data'):
""" data = json.loads(self.url_params[u'data'][0])
log.debug(u'Processing event %s' % event) log.info(data)
if params: # This slot expects an int within a list.
Receiver.send_message(event, params) id = data[u'request'][u'id']
Receiver.send_message(event, [id])
else: else:
Receiver.send_message(event) Receiver.send_message(event)
return json.dumps([u'OK']) json_data = {u'results': {u'success': True}}
return HttpResponse(json.dumps(json_data),
{u'Content-Type': u'application/json'})
def process_request(self, event, params): def service(self, action):
""" event = u'servicemanager_%s' % action
Client has requested data. Send the signal and parameters for openlp if action == u'list':
to handle, then listen out for a corresponding ``_request`` signal return HttpResponse(
which will have the data to return. json.dumps({u'results': {u'items': self._get_service_items()}}),
{u'Content-Type': u'application/json'})
For most events, timeout after 10 seconds (i.e. in case the signal
recipient isn't listening). ``remotes_poll_request`` is a special case
however, this is a ajax long poll which is just waiting for slide
change/song change activity. This can wait longer (one minute).
``event``
The event from the web page.
``params``
Parameters sent with the event.
"""
log.debug(u'Processing request %s' % event)
if not event.endswith(u'_request'):
return False
self.event = event
response = event.replace(u'_request', u'_response')
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(response), self.process_response)
self.timer = QtCore.QTimer()
self.timer.setSingleShot(True)
QtCore.QObject.connect(self.timer,
QtCore.SIGNAL(u'timeout()'), self.timeout)
if event == 'remotes_poll_request':
self.timer.start(60000)
else: else:
self.timer.start(10000) event += u'_item'
if params: if self.url_params and self.url_params.get(u'data'):
Receiver.send_message(event, params) data = json.loads(self.url_params[u'data'][0])
Receiver.send_message(event, data[u'request'][u'id'])
else: else:
Receiver.send_message(event) Receiver.send_message(event)
return True return HttpResponse(json.dumps({u'results': {u'success': True}}),
{u'Content-Type': u'application/json'})
def process_response(self, data): def send_response(self, response):
""" http = u'HTTP/1.1 %s\r\n' % response.code
The recipient of a _request signal has sent data. Convert this to for header, value in response.headers.iteritems():
json and return it to client http += '%s: %s\r\n' % (header, value)
""" http += '\r\n'
log.debug(u'Processing response for %s' % self.event) self.socket.write(http)
if not self.socket: self.socket.write(response.content)
return
self.timer.stop()
html = json.dumps(data)
self.send_200_ok()
self.socket.write(html)
self.close()
def send_200_ok(self, mimetype='text/html; charset="utf-8"'):
"""
Successful request. Send OK headers. Assume html for now.
"""
self.socket.write(u'HTTP/1.1 200 OK\r\n' + \
u'Content-Type: %s\r\n\r\n' % mimetype)
def send_404_not_found(self):
"""
Invalid url. Say so
"""
self.socket.write(u'HTTP/1.1 404 Not Found\r\n'+ \
u'Content-Type: text/html; charset="utf-8"\r\n' + \
u'\r\n')
def send_408_timeout(self):
"""
A _request hasn't returned anything in the timeout period.
Return timeout
"""
self.socket.write(u'HTTP/1.1 408 Request Timeout\r\n')
def timeout(self):
"""
Listener for timeout signal
"""
if not self.socket:
return
self.send_408_timeout()
self.close()
def disconnected(self): def disconnected(self):
""" """

View File

@ -33,7 +33,7 @@ from openlp.core.lib import Receiver, translate
from openlp.core.lib.ui import UiStrings, add_widget_completer, \ from openlp.core.lib.ui import UiStrings, add_widget_completer, \
critical_error_message_box critical_error_message_box
from openlp.plugins.songs.forms import EditVerseForm from openlp.plugins.songs.forms import EditVerseForm
from openlp.plugins.songs.lib import SongXML, VerseType from openlp.plugins.songs.lib import SongXML, VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic from openlp.plugins.songs.lib.db import Book, Song, Author, Topic
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from editsongdialog import Ui_EditSongDialog from editsongdialog import Ui_EditSongDialog
@ -297,7 +297,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseOrderEdit.setText(u' '.join(translated)) self.verseOrderEdit.setText(u' '.join(translated))
else: else:
self.verseOrderEdit.setText(u'') self.verseOrderEdit.setText(u'')
self.verseListWidget.resizeRowsToContents()
self.tagRows() self.tagRows()
# clear the results # clear the results
self.authorsListView.clear() self.authorsListView.clear()
@ -312,10 +311,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id)) topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id))
self.topicsListView.addItem(topic_name) self.topicsListView.addItem(topic_name)
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# if not preview hide the preview button # Hide or show the preview button.
self.previewButton.setVisible(False) self.previewButton.setVisible(preview)
if preview:
self.previewButton.setVisible(True)
def tagRows(self): def tagRows(self):
""" """
@ -329,6 +326,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
row_def = u'%s%s' % (verse_tag, verse_def[1:]) row_def = u'%s%s' % (verse_tag, verse_def[1:])
row_label.append(row_def) row_label.append(row_def)
self.verseListWidget.setVerticalHeaderLabels(row_label) self.verseListWidget.setVerticalHeaderLabels(row_label)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
def onAuthorAddButtonClicked(self): def onAuthorAddButtonClicked(self):
item = int(self.authorsComboBox.currentIndex()) item = int(self.authorsComboBox.currentIndex())
@ -453,9 +453,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.setRowCount( self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1) self.verseListWidget.rowCount() + 1)
self.verseListWidget.setItem( self.verseListWidget.setItem(
int(self.verseListWidget.rowCount() - 1), 0, item) self.verseListWidget.rowCount() - 1, 0, item)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.tagRows() self.tagRows()
def onVerseEditButtonClicked(self): def onVerseEditButtonClicked(self):
@ -482,8 +480,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = QtGui.QTableWidgetItem(tempList[row], 0) item = QtGui.QTableWidgetItem(tempList[row], 0)
item.setData(QtCore.Qt.UserRole, tempId[row]) item.setData(QtCore.Qt.UserRole, tempId[row])
self.verseListWidget.setItem(row, 0, item) self.verseListWidget.setItem(row, 0, item)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
self.tagRows() self.tagRows()
def onVerseEditAllButtonClicked(self): def onVerseEditAllButtonClicked(self):
@ -500,7 +496,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_form.setVerse(verse_list) self.verse_form.setVerse(verse_list)
else: else:
self.verse_form.setVerse(u'') self.verse_form.setVerse(u'')
if self.verse_form.exec_(): if not self.verse_form.exec_():
return
verse_list = self.verse_form.getVerseAll() verse_list = self.verse_form.getVerseAll()
verse_list = unicode(verse_list.replace(u'\r\n', u'\n')) verse_list = unicode(verse_list.replace(u'\r\n', u'\n'))
self.verseListWidget.clear() self.verseListWidget.clear()
@ -508,7 +505,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
for row in self.findVerseSplit.split(verse_list): for row in self.findVerseSplit.split(verse_list):
for match in row.split(u'---['): for match in row.split(u'---['):
for count, parts in enumerate(match.split(u']---\n')): for count, parts in enumerate(match.split(u']---\n')):
if len(parts) > 1: if len(parts) <= 1:
continue
if count == 0: if count == 0:
# handling carefully user inputted versetags # handling carefully user inputted versetags
separator = parts.find(u':') separator = parts.find(u':')
@ -518,8 +516,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
else: else:
verse_name = parts verse_name = parts
verse_num = u'1' verse_num = u'1'
verse_index = \ verse_index = VerseType.from_loose_input(verse_name)
VerseType.from_loose_input(verse_name)
verse_tag = VerseType.Tags[verse_index] verse_tag = VerseType.Tags[verse_index]
# Later we need to handle v1a as well. # Later we need to handle v1a as well.
#regex = re.compile(r'(\d+\w.)') #regex = re.compile(r'(\d+\w.)')
@ -539,11 +536,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.setRowCount( self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1) self.verseListWidget.rowCount() + 1)
self.verseListWidget.setItem( self.verseListWidget.setItem(
int(self.verseListWidget.rowCount() - 1), self.verseListWidget.rowCount() - 1, 0, item)
0, item)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
self.tagRows() self.tagRows()
self.verseEditButton.setEnabled(False) self.verseEditButton.setEnabled(False)
self.verseDeleteButton.setEnabled(False) self.verseDeleteButton.setEnabled(False)
@ -728,17 +721,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.title = unicode(self.titleEdit.text()) self.song.title = unicode(self.titleEdit.text())
self.song.alternate_title = unicode(self.alternativeEdit.text()) self.song.alternate_title = unicode(self.alternativeEdit.text())
self.song.copyright = unicode(self.copyrightEdit.text()) self.song.copyright = unicode(self.copyrightEdit.text())
if self.song.alternate_title: # Values will be set when cleaning the song.
self.song.search_title = self.song.title + u'@' + \ self.song.search_title = u''
self.song.alternate_title self.song.search_lyrics = u''
else: self.song.verse_order = u''
self.song.search_title = self.song.title
self.song.comments = unicode(self.commentsEdit.toPlainText()) self.song.comments = unicode(self.commentsEdit.toPlainText())
ordertext = unicode(self.verseOrderEdit.text()) ordertext = unicode(self.verseOrderEdit.text())
order = [] order = []
for item in ordertext.split(): for item in ordertext.split():
verse_tag = VerseType.Tags[ verse_tag = VerseType.Tags[VerseType.from_translated_tag(item[0])]
VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower() verse_num = item[1:].lower()
order.append(u'%s%s' % (verse_tag, verse_num)) order.append(u'%s%s' % (verse_tag, verse_num))
self.song.verse_order = u' '.join(order) self.song.verse_order = u' '.join(order)
@ -755,8 +746,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.theme_name = theme_name self.song.theme_name = theme_name
else: else:
self.song.theme_name = None self.song.theme_name = None
self.processLyrics() self._processLyrics()
self.processTitle()
self.song.authors = [] self.song.authors = []
for row in range(self.authorsListView.count()): for row in range(self.authorsListView.count()):
item = self.authorsListView.item(row) item = self.authorsListView.item(row)
@ -767,19 +757,19 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = self.topicsListView.item(row) item = self.topicsListView.item(row)
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0] topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.topics.append(self.manager.get_object(Topic, topicId)) self.song.topics.append(self.manager.get_object(Topic, topicId))
clean_song(self.manager, self.song)
self.manager.save_object(self.song) self.manager.save_object(self.song)
if not preview: if not preview:
self.song = None self.song = None
def processLyrics(self): def _processLyrics(self):
""" """
Process the lyric data entered by the user into the OpenLP XML format. Process the lyric data entered by the user into the OpenLP XML format.
""" """
# This method must only be run after the self.song = Song() assignment. # This method must only be run after the self.song = Song() assignment.
log.debug(u'processLyrics') log.debug(u'_processLyrics')
try: try:
sxml = SongXML() sxml = SongXML()
text = u''
multiple = [] multiple = []
for i in range(0, self.verseListWidget.rowCount()): for i in range(0, self.verseListWidget.rowCount()):
item = self.verseListWidget.item(i, 0) item = self.verseListWidget.item(i, 0)
@ -788,11 +778,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_num = verseId[1:] verse_num = verseId[1:]
sxml.add_verse_to_lyrics(verse_tag, verse_num, sxml.add_verse_to_lyrics(verse_tag, verse_num,
unicode(item.text())) unicode(item.text()))
text = text + self.whitespace.sub(u' ', if verse_num > u'1' and verse_tag not in multiple:
unicode(self.verseListWidget.item(i, 0).text())) + u' '
if (verse_num > u'1') and (verse_tag not in multiple):
multiple.append(verse_tag) multiple.append(verse_tag)
self.song.search_lyrics = text.lower()
self.song.lyrics = unicode(sxml.extract_xml(), u'utf-8') self.song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
for verse in multiple: for verse in multiple:
self.song.verse_order = re.sub(u'([' + verse.upper() + self.song.verse_order = re.sub(u'([' + verse.upper() +
@ -801,13 +788,3 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
except: except:
log.exception(u'Problem processing song Lyrics \n%s', log.exception(u'Problem processing song Lyrics \n%s',
sxml.dump_xml()) sxml.dump_xml())
def processTitle(self):
"""
Process the song title entered by the user to remove stray punctuation
characters.
"""
# This method must only be run after the self.song = Song() assignment.
log.debug(u'processTitle')
self.song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(self.song.search_title)).lower().strip()

View File

@ -23,6 +23,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
import re
from PyQt4 import QtGui from PyQt4 import QtGui
@ -244,9 +245,11 @@ def retrieve_windows_encoding(recommendation=None):
return None return None
return filter(lambda item: item[1] == choice[0], encodings)[0][0] return filter(lambda item: item[1] == choice[0], encodings)[0][0]
def add_author_unknown(manager, song): def clean_song(manager, song):
""" """
Add the default author *Author Unknown* to the song. Cleans the search title, rebuilds the search lyrics, adds a default author
if the song does not have one and other clean ups. This should always
called when a new song is added or changed.
``manager`` ``manager``
The song's manager. The song's manager.
@ -254,8 +257,25 @@ def add_author_unknown(manager, song):
``song`` ``song``
The song object. The song object.
""" """
song.title = song.title.strip() if song.title else u''
if song.alternate_title is None:
song.alternate_title = u''
song.alternate_title = song.alternate_title.strip()
whitespace = re.compile(r'\W+', re.UNICODE)
song.search_title = (whitespace.sub(u' ', song.title).strip() + u'@' +
whitespace.sub(u' ', song.alternate_title).strip()).strip().lower()
# Remove the old "language" attribute from lyrics tag (prior to 1.9.5). This
# is not very important, but this keeps the database clean. This can be
# removed when everybody has cleaned his songs.
song.lyrics = song.lyrics.replace(u'<lyrics language="en">', u'<lyrics>')
verses = SongXML().get_verses(song.lyrics)
lyrics = u' '.join([whitespace.sub(u' ', verse[1]) for verse in verses])
song.search_lyrics = lyrics.lower()
# The song does not have any author, add one.
if not song.authors:
name = SongStrings.AuthorUnknown name = SongStrings.AuthorUnknown
author = manager.get_object_filtered(Author, Author.display_name == name) author = manager.get_object_filtered(
Author, Author.display_name == name)
if author is None: if author is None:
author = Author.populate( author = Author.populate(
display_name=name, last_name=u'', first_name=u'') display_name=name, last_name=u'', first_name=u'')

View File

@ -94,7 +94,7 @@ import os
from lxml import etree, objectify from lxml import etree, objectify
from openlp.core.ui.wizard import WizardStrings from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import add_author_unknown, VerseType from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.songimport import SongImport from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
from openlp.plugins.songs.lib.xml import SongXML from openlp.plugins.songs.lib.xml import SongXML
@ -212,9 +212,13 @@ class FoilPresenter(object):
# No xml get out of here. # No xml get out of here.
if not xml: if not xml:
return None return None
song = Song()
if xml[:5] == u'<?xml': if xml[:5] == u'<?xml':
xml = xml[38:] xml = xml[38:]
song = Song()
# Values will be set when cleaning the song.
song.search_lyrics = u''
song.verse_order = u''
song.search_title = u''
# Because "text" seems to be an reserverd word, we have to recompile it. # Because "text" seems to be an reserverd word, we have to recompile it.
xml = re.compile(u'<text>').sub(u'<text_>', xml) xml = re.compile(u'<text>').sub(u'<text_>', xml)
xml = re.compile(u'</text>').sub(u'</text_>', xml) xml = re.compile(u'</text>').sub(u'</text_>', xml)
@ -229,6 +233,7 @@ class FoilPresenter(object):
self._process_authors(foilpresenterfolie, song) self._process_authors(foilpresenterfolie, song)
self._process_songbooks(foilpresenterfolie, song) self._process_songbooks(foilpresenterfolie, song)
self._process_topics(foilpresenterfolie, song) self._process_topics(foilpresenterfolie, song)
clean_song(self.manager, song)
self.manager.save_object(song) self.manager.save_object(song)
return song.id return song.id
@ -348,8 +353,6 @@ class FoilPresenter(object):
first_name = u' '.join(display_name.split(u' ')[:-1])) first_name = u' '.join(display_name.split(u' ')[:-1]))
self.manager.save_object(author) self.manager.save_object(author)
song.authors.append(author) song.authors.append(author)
if not song.authors:
add_author_unknown(self.manager, song)
def _process_cclinumber(self, foilpresenterfolie, song): def _process_cclinumber(self, foilpresenterfolie, song):
""" """
@ -407,7 +410,6 @@ class FoilPresenter(object):
The song object. The song object.
""" """
sxml = SongXML() sxml = SongXML()
search_text = u''
temp_verse_order = {} temp_verse_order = {}
temp_verse_order_backup = [] temp_verse_order_backup = []
temp_sortnr_backup = 1 temp_sortnr_backup = 1
@ -452,7 +454,6 @@ class FoilPresenter(object):
else: else:
verse_type = u'O' verse_type = u'O'
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name) verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
#verse_part = re.compile(u'[0-9]*').sub(u'', verse_name[1:])
# Foilpresenter allows e. g. "C", but we need "C1". # Foilpresenter allows e. g. "C", but we need "C1".
if not verse_number: if not verse_number:
verse_number = unicode(versenumber[verse_type]) verse_number = unicode(versenumber[verse_type])
@ -470,8 +471,6 @@ class FoilPresenter(object):
temp_verse_order_backup.append(u''.join((verse_type[0], temp_verse_order_backup.append(u''.join((verse_type[0],
verse_number))) verse_number)))
sxml.add_verse_to_lyrics(verse_type, verse_number, text) sxml.add_verse_to_lyrics(verse_type, verse_number, text)
search_text = search_text + text
song.search_lyrics = search_text.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8') song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order # Process verse order
verse_order = [] verse_order = []
@ -534,13 +533,9 @@ class FoilPresenter(object):
for titelstring in foilpresenterfolie.titel.titelstring: for titelstring in foilpresenterfolie.titel.titelstring:
if not song.title: if not song.title:
song.title = self._child(titelstring) song.title = self._child(titelstring)
song.search_title = unicode(song.title)
song.alternate_title = u'' song.alternate_title = u''
else: else:
song.alternate_title = self._child(titelstring) song.alternate_title = self._child(titelstring)
song.search_title += u'@' + song.alternate_title
song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(song.search_title)).lower().strip()
def _process_topics(self, foilpresenterfolie, song): def _process_topics(self, foilpresenterfolie, song):
""" """
@ -565,10 +560,3 @@ class FoilPresenter(object):
song.topics.append(topic) song.topics.append(topic)
except AttributeError: except AttributeError:
pass pass
def _dump_xml(self, xml):
"""
Debugging aid to dump XML so that we can see what we have.
"""
return etree.tostring(xml, encoding=u'UTF-8',
xml_declaration=True, pretty_print=True)

View File

@ -166,20 +166,18 @@ class SongMediaItem(MediaManagerItem):
or_(Song.search_title.like(u'%' + self.whitespace.sub(u' ', or_(Song.search_title.like(u'%' + self.whitespace.sub(u' ',
search_keywords.lower()) + u'%'), search_keywords.lower()) + u'%'),
Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'), Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'),
Song.comments.like(u'%' + search_keywords.lower() + u'%')), Song.comments.like(u'%' + search_keywords.lower() + u'%')))
Song.search_title.asc())
self.displayResultsSong(search_results) self.displayResultsSong(search_results)
elif search_type == SongSearch.Titles: elif search_type == SongSearch.Titles:
log.debug(u'Titles Search') log.debug(u'Titles Search')
search_results = self.parent.manager.get_all_objects(Song, search_results = self.parent.manager.get_all_objects(Song,
Song.search_title.like(u'%' + self.whitespace.sub(u' ', Song.search_title.like(u'%' + self.whitespace.sub(u' ',
search_keywords.lower()) + u'%'), Song.search_title.asc()) search_keywords.lower()) + u'%'))
self.displayResultsSong(search_results) self.displayResultsSong(search_results)
elif search_type == SongSearch.Lyrics: elif search_type == SongSearch.Lyrics:
log.debug(u'Lyrics Search') log.debug(u'Lyrics Search')
search_results = self.parent.manager.get_all_objects(Song, search_results = self.parent.manager.get_all_objects(Song,
Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'), Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'))
Song.search_lyrics.asc())
self.displayResultsSong(search_results) self.displayResultsSong(search_results)
elif search_type == SongSearch.Authors: elif search_type == SongSearch.Authors:
log.debug(u'Authors Search') log.debug(u'Authors Search')
@ -190,7 +188,7 @@ class SongMediaItem(MediaManagerItem):
elif search_type == SongSearch.Themes: elif search_type == SongSearch.Themes:
log.debug(u'Theme Search') log.debug(u'Theme Search')
search_results = self.parent.manager.get_all_objects(Song, search_results = self.parent.manager.get_all_objects(Song,
Song.theme_name == search_keywords, Song.search_lyrics.asc()) Song.theme_name == search_keywords)
self.displayResultsSong(search_results) self.displayResultsSong(search_results)
def onSongListLoad(self): def onSongListLoad(self):
@ -268,9 +266,8 @@ class SongMediaItem(MediaManagerItem):
Receiver.send_message(u'songs_load_list') Receiver.send_message(u'songs_load_list')
def onExportClick(self): def onExportClick(self):
if not hasattr(self, u'export_wizard'): export_wizard = SongExportForm(self, self.parent)
self.export_wizard = SongExportForm(self, self.parent) export_wizard.exec_()
self.export_wizard.exec_()
def onNewClick(self): def onNewClick(self):
log.debug(u'onNewClick') log.debug(u'onNewClick')
@ -414,29 +411,32 @@ class SongMediaItem(MediaManagerItem):
def serviceLoad(self, item): def serviceLoad(self, item):
""" """
Triggered by a song being loaded by the service item Triggered by a song being loaded by the service manager.
""" """
log.debug(u'serviceLoad') log.debug(u'serviceLoad')
if self.plugin.status != PluginStatus.Active or not item.data_string: if self.plugin.status != PluginStatus.Active or not item.data_string:
return return
if item.data_string[u'title'].find(u'@') == -1:
# This file seems to be an old one (prior to 1.9.5), which means,
# that the search title (data_string[u'title']) is probably wrong.
# We add "@" to search title and hope that we do not add any
# duplicate. This should work for songs without alternate title.
search_results = self.parent.manager.get_all_objects(Song, search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == re.compile(r'\W+', re.UNICODE).sub(u' ', Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(u' ',
item.data_string[u'title'].split(u'@')[0].lower()).strip(), item.data_string[u'title'].strip()) + u'@').strip().lower(),
Song.search_title.asc())
else:
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == item.data_string[u'title'],
Song.search_title.asc()) Song.search_title.asc())
author_list = item.data_string[u'authors'].split(u', ') author_list = item.data_string[u'authors'].split(u', ')
# The service item always has an author (at least it has u'' as
# author). However, songs saved in the database do not have to have
# an author.
if u'' in author_list:
author_list.remove(u'')
editId = 0 editId = 0
add_song = True add_song = True
if search_results: if search_results:
for song in search_results: for song in search_results:
same_authors = True same_authors = True
# If the author counts are different, we do not have to do any # If the author counts are different, we do not have to do any
# further checking. This is also important when a song does not # further checking.
# have any author (because we can not loop over an empty list).
if len(song.authors) == len(author_list): if len(song.authors) == len(author_list):
for author in song.authors: for author in song.authors:
if author.display_name not in author_list: if author.display_name not in author_list:
@ -461,4 +461,5 @@ class SongMediaItem(MediaManagerItem):
""" """
Locale aware collation of song titles Locale aware collation of song titles
""" """
return locale.strcoll(unicode(song_1.title), unicode(song_2.title)) return locale.strcoll(unicode(song_1.title.lower()),
unicode(song_2.title.lower()))

View File

@ -36,7 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.lib.db import BaseModel from openlp.core.lib.db import BaseModel
from openlp.plugins.songs.lib import add_author_unknown from openlp.plugins.songs.lib import clean_song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile
from songimport import SongImport from songimport import SongImport
@ -151,12 +151,14 @@ class OpenLPSongImport(SongImport):
source_songs = self.source_session.query(OldSong).all() source_songs = self.source_session.query(OldSong).all()
song_total = len(source_songs) song_total = len(source_songs)
if self.import_wizard:
self.import_wizard.progressBar.setMaximum(song_total) self.import_wizard.progressBar.setMaximum(song_total)
song_count = 1 song_count = 1
for song in source_songs: for song in source_songs:
self.import_wizard.incrementProgressBar(unicode(translate( if self.import_wizard:
'SongsPlugin.OpenLPSongImport', 'Importing song %d of %d.')) % self.import_wizard.incrementProgressBar(
(song_count, song_total)) unicode(translate('SongsPlugin.OpenLPSongImport',
'Importing song %d of %d.')) % (song_count, song_total))
new_song = Song() new_song = Song()
new_song.title = song.title new_song.title = song.title
if has_media_files and hasattr(song, 'alternate_title'): if has_media_files and hasattr(song, 'alternate_title'):
@ -165,12 +167,11 @@ class OpenLPSongImport(SongImport):
old_titles = song.search_title.split(u'@') old_titles = song.search_title.split(u'@')
if len(old_titles) > 1: if len(old_titles) > 1:
new_song.alternate_title = old_titles[1] new_song.alternate_title = old_titles[1]
else: # Values will be set when cleaning the song.
new_song.alternate_title = u'' new_song.search_title = u''
new_song.search_title = song.search_title.strip() new_song.search_lyrics = u''
new_song.song_number = song.song_number new_song.song_number = song.song_number
new_song.lyrics = song.lyrics new_song.lyrics = song.lyrics
new_song.search_lyrics = song.search_lyrics
new_song.verse_order = song.verse_order new_song.verse_order = song.verse_order
new_song.copyright = song.copyright new_song.copyright = song.copyright
new_song.comments = song.comments new_song.comments = song.comments
@ -179,31 +180,26 @@ class OpenLPSongImport(SongImport):
for author in song.authors: for author in song.authors:
existing_author = self.manager.get_object_filtered( existing_author = self.manager.get_object_filtered(
Author, Author.display_name == author.display_name) Author, Author.display_name == author.display_name)
if existing_author: if existing_author is None:
new_song.authors.append(existing_author) existing_author = Author.populate(
else:
new_song.authors.append(Author.populate(
first_name=author.first_name, first_name=author.first_name,
last_name=author.last_name, last_name=author.last_name,
display_name=author.display_name)) display_name=author.display_name)
if not new_song.authors: new_song.authors.append(existing_author)
add_author_unknown(self.manager, new_song)
if song.book: if song.book:
existing_song_book = self.manager.get_object_filtered( existing_song_book = self.manager.get_object_filtered(
Book, Book.name == song.book.name) Book, Book.name == song.book.name)
if existing_song_book: if existing_song_book is None:
new_song.book = existing_song_book existing_song_book = Book.populate(name=song.book.name,
else:
new_song.book = Book.populate(name=song.book.name,
publisher=song.book.publisher) publisher=song.book.publisher)
new_song.book = existing_song_book
if song.topics: if song.topics:
for topic in song.topics: for topic in song.topics:
existing_topic = self.manager.get_object_filtered( existing_topic = self.manager.get_object_filtered(
Topic, Topic.name == topic.name) Topic, Topic.name == topic.name)
if existing_topic: if existing_topic is None:
existing_topic = Topic.populate(name=topic.name)
new_song.topics.append(existing_topic) new_song.topics.append(existing_topic)
else:
new_song.topics.append(Topic.populate(name=topic.name))
# if has_media_files: # if has_media_files:
# if song.media_files: # if song.media_files:
# for media_file in song.media_files: # for media_file in song.media_files:
@ -215,6 +211,7 @@ class OpenLPSongImport(SongImport):
# else: # else:
# new_song.media_files.append(MediaFile.populate( # new_song.media_files.append(MediaFile.populate(
# file_name=media_file.file_name)) # file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song) self.manager.save_object(new_song)
song_count += 1 song_count += 1
if self.stop_import_flag: if self.stop_import_flag:

View File

@ -33,7 +33,6 @@
import os import os
import re import re
from songimport import SongImport
from oooimport import OooImport from oooimport import OooImport
if os.name == u'nt': if os.name == u'nt':

View File

@ -29,7 +29,7 @@ import re
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib import Receiver, translate from openlp.core.lib import Receiver, translate
from openlp.plugins.songs.lib import add_author_unknown, VerseType from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import SongXML from openlp.plugins.songs.lib.xml import SongXML
@ -62,6 +62,7 @@ class SongImport(QtCore.QObject):
else: else:
raise KeyError(u'Keyword arguments "filename[s]" not supplied.') raise KeyError(u'Keyword arguments "filename[s]" not supplied.')
log.debug(self.import_source) log.debug(self.import_source)
self.import_wizard = None
self.song = None self.song = None
self.stop_import_flag = False self.stop_import_flag = False
self.set_defaults() self.set_defaults()
@ -244,12 +245,6 @@ class SongImport(QtCore.QObject):
else: else:
return True return True
def remove_punctuation(self, text):
"""
Extracts alphanumeric words for searchable fields
"""
return re.sub(r'\W+', u' ', text, re.UNICODE)
def finish(self): def finish(self):
""" """
All fields have been set to this song. Write the song to disk. All fields have been set to this song. Write the song to disk.
@ -258,11 +253,11 @@ class SongImport(QtCore.QObject):
song = Song() song = Song()
song.title = self.title song.title = self.title
song.alternate_title = self.alternate_title song.alternate_title = self.alternate_title
song.search_title = self.remove_punctuation(self.title).lower() \ # Values will be set when cleaning the song.
+ '@' + self.remove_punctuation(self.alternate_title).lower() song.search_title = u''
song.search_title = song.search_title.strip()
song.song_number = self.song_number
song.search_lyrics = u'' song.search_lyrics = u''
song.verse_order = u''
song.song_number = self.song_number
verses_changed_to_other = {} verses_changed_to_other = {}
sxml = SongXML() sxml = SongXML()
other_count = 1 other_count = 1
@ -279,8 +274,6 @@ class SongImport(QtCore.QObject):
new_verse_def) new_verse_def)
verse_def = new_verse_def verse_def = new_verse_def
sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang) sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
song.search_lyrics += u' ' + self.remove_punctuation(verse_text)
song.search_lyrics = song.search_lyrics.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8') song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
if not len(self.verse_order_list) and \ if not len(self.verse_order_list) and \
self.verse_order_list_generated_useful: self.verse_order_list_generated_useful:
@ -302,9 +295,6 @@ class SongImport(QtCore.QObject):
last_name=authortext.split(u' ')[-1], last_name=authortext.split(u' ')[-1],
first_name=u' '.join(authortext.split(u' ')[:-1])) first_name=u' '.join(authortext.split(u' ')[:-1]))
song.authors.append(author) song.authors.append(author)
# No author, add the default author.
if not song.authors:
add_author_unknown(self.manager, song)
for filename in self.media_files: for filename in self.media_files:
media_file = self.manager.get_object_filtered(MediaFile, media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == filename) MediaFile.file_name == filename)
@ -325,6 +315,7 @@ class SongImport(QtCore.QObject):
if topic is None: if topic is None:
topic = Topic.populate(name=topictext) topic = Topic.populate(name=topictext)
song.topics.append(topic) song.topics.append(topic)
clean_song(self.manager, song)
self.manager.save_object(song) self.manager.save_object(song)
self.set_defaults() self.set_defaults()

View File

@ -154,7 +154,8 @@ class SongShowPlusImport(SongImport):
elif blockKey == COMMENTS: elif blockKey == COMMENTS:
self.comments = unicode(data, u'cp1252') self.comments = unicode(data, u'cp1252')
elif blockKey == VERSE_ORDER: elif blockKey == VERSE_ORDER:
verseTag = self.toOpenLPVerseTag(data) verseTag = self.toOpenLPVerseTag(data, True)
if verseTag:
self.sspVerseOrderList.append(unicode(verseTag, self.sspVerseOrderList.append(unicode(verseTag,
u'cp1252')) u'cp1252'))
elif blockKey == SONG_BOOK: elif blockKey == SONG_BOOK:
@ -174,7 +175,7 @@ class SongShowPlusImport(SongImport):
WizardStrings.ImportingType % file_name) WizardStrings.ImportingType % file_name)
return True return True
def toOpenLPVerseTag(self, verseName): def toOpenLPVerseTag(self, verseName, ignoreUnique=False):
if verseName.find(" ") != -1: if verseName.find(" ") != -1:
verseParts = verseName.split(" ") verseParts = verseName.split(" ")
verseType = verseParts[0] verseType = verseParts[0]
@ -195,6 +196,8 @@ class SongShowPlusImport(SongImport):
verseTag = "B" verseTag = "B"
else: else:
if not self.otherList.has_key(verseName): if not self.otherList.has_key(verseName):
if ignoreUnique:
return None
self.otherCount = self.otherCount + 1 self.otherCount = self.otherCount + 1
self.otherList[verseName] = str(self.otherCount) self.otherList[verseName] = str(self.otherCount)
verseTag = "O" verseTag = "O"

View File

@ -66,7 +66,7 @@ import re
from lxml import etree, objectify from lxml import etree, objectify
from openlp.plugins.songs.lib import add_author_unknown, VerseType from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -89,8 +89,8 @@ class SongXML(object):
Add a verse to the ``<lyrics>`` tag. Add a verse to the ``<lyrics>`` tag.
``type`` ``type``
A string denoting the type of verse. Possible values are *Verse*, A string denoting the type of verse. Possible values are *v*,
*Chorus*, *Bridge*, *Pre-Chorus*, *Intro*, *Ending* and *Other*. *c*, *b*, *p*, *i*, *e* and *o*.
Any other type is **not** allowed, this also includes translated Any other type is **not** allowed, this also includes translated
types. types.
@ -128,8 +128,8 @@ class SongXML(object):
The returned list has the following format:: The returned list has the following format::
[[{'lang': 'en', 'type': 'Verse', 'label': '1'}, u"English verse"], [[{'lang': 'en', 'type': 'v', 'label': '1'}, u"English verse"],
[{'lang': 'en', 'type': 'Chorus', 'label': '1'}, u"English chorus"]] [{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]]
""" """
self.song_xml = None self.song_xml = None
if xml[:5] == u'<?xml': if xml[:5] == u'<?xml':
@ -236,10 +236,9 @@ class OpenLyrics(object):
datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S')) datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S'))
properties = etree.SubElement(song_xml, u'properties') properties = etree.SubElement(song_xml, u'properties')
titles = etree.SubElement(properties, u'titles') titles = etree.SubElement(properties, u'titles')
self._add_text_to_element(u'title', titles, song.title.strip()) self._add_text_to_element(u'title', titles, song.title)
if song.alternate_title: if song.alternate_title:
self._add_text_to_element( self._add_text_to_element(u'title', titles, song.alternate_title)
u'title', titles, song.alternate_title.strip())
if song.comments: if song.comments:
comments = etree.SubElement(properties, u'comments') comments = etree.SubElement(properties, u'comments')
self._add_text_to_element(u'comment', comments, song.comments) self._add_text_to_element(u'comment', comments, song.comments)
@ -303,6 +302,10 @@ class OpenLyrics(object):
else: else:
return None return None
song = Song() song = Song()
# Values will be set when cleaning the song.
song.search_lyrics = u''
song.verse_order = u''
song.search_title = u''
self._process_copyright(properties, song) self._process_copyright(properties, song)
self._process_cclinumber(properties, song) self._process_cclinumber(properties, song)
self._process_titles(properties, song) self._process_titles(properties, song)
@ -312,6 +315,7 @@ class OpenLyrics(object):
self._process_authors(properties, song) self._process_authors(properties, song)
self._process_songbooks(properties, song) self._process_songbooks(properties, song)
self._process_topics(properties, song) self._process_topics(properties, song)
clean_song(self.manager, song)
self.manager.save_object(song) self.manager.save_object(song)
return song.id return song.id
@ -382,8 +386,6 @@ class OpenLyrics(object):
last_name=display_name.split(u' ')[-1], last_name=display_name.split(u' ')[-1],
first_name=u' '.join(display_name.split(u' ')[:-1])) first_name=u' '.join(display_name.split(u' ')[:-1]))
song.authors.append(author) song.authors.append(author)
if not song.authors:
add_author_unknown(self.manager, song)
def _process_cclinumber(self, properties, song): def _process_cclinumber(self, properties, song):
""" """
@ -443,17 +445,18 @@ class OpenLyrics(object):
The song object. The song object.
""" """
sxml = SongXML() sxml = SongXML()
search_text = u''
for verse in lyrics.verse: for verse in lyrics.verse:
text = u'' text = u''
for lines in verse.lines: for lines in verse.lines:
if text: if text:
text += u'\n' text += u'\n'
text += u'\n'.join([unicode(line) for line in lines.line]) text += u'\n'.join([unicode(line) for line in lines.line])
verse_name = self._get(verse, u'name') verse_def = self._get(verse, u'name').lower()
verse_type_index = VerseType.from_tag(verse_name[0]) if verse_def[0] in VerseType.Tags:
verse_type = VerseType.Names[verse_type_index] verse_tag = verse_def[0]
verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_name) else:
verse_tag = VerseType.Tags[VerseType.Other]
verse_number = re.compile(u'[a-zA-Z]*').sub(u'', verse_def)
# OpenLyrics allows e. g. "c", but we need "c1". However, this does # OpenLyrics allows e. g. "c", but we need "c1". However, this does
# not correct the verse order. # not correct the verse order.
if not verse_number: if not verse_number:
@ -461,9 +464,7 @@ class OpenLyrics(object):
lang = None lang = None
if self._get(verse, u'lang'): if self._get(verse, u'lang'):
lang = self._get(verse, u'lang') lang = self._get(verse, u'lang')
sxml.add_verse_to_lyrics(verse_type, verse_number, text, lang) sxml.add_verse_to_lyrics(verse_tag, verse_number, text, lang)
search_text = search_text + text
song.search_lyrics = search_text.lower()
song.lyrics = unicode(sxml.extract_xml(), u'utf-8') song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order # Process verse order
if hasattr(properties, u'verseOrder'): if hasattr(properties, u'verseOrder'):
@ -510,13 +511,9 @@ class OpenLyrics(object):
for title in properties.titles.title: for title in properties.titles.title:
if not song.title: if not song.title:
song.title = self._text(title) song.title = self._text(title)
song.search_title = unicode(song.title)
song.alternate_title = u'' song.alternate_title = u''
else: else:
song.alternate_title = self._text(title) song.alternate_title = self._text(title)
song.search_title += u'@' + song.alternate_title
song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
unicode(song.search_title)).lower().strip()
def _process_topics(self, properties, song): def _process_topics(self, properties, song):
""" """

View File

@ -25,17 +25,19 @@
############################################################################### ###############################################################################
import logging import logging
import re import os
from tempfile import gettempdir
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
from openlp.core.lib.db import Manager from openlp.core.lib.db import Manager
from openlp.core.lib.ui import UiStrings from openlp.core.lib.ui import UiStrings
from openlp.plugins.songs.lib import add_author_unknown, SongMediaItem, \ from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab
SongsTab, SongXML
from openlp.plugins.songs.lib.db import init_schema, Song from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -58,7 +60,6 @@ class SongsPlugin(Plugin):
self.manager = Manager(u'songs', init_schema) self.manager = Manager(u'songs', init_schema)
self.icon_path = u':/plugins/plugin_songs.png' self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path) self.icon = build_icon(self.icon_path)
self.whitespace = re.compile(r'\W+', re.UNICODE)
def initialise(self): def initialise(self):
log.info(u'Songs Initialising') log.info(u'Songs Initialising')
@ -137,38 +138,18 @@ class SongsPlugin(Plugin):
Rebuild each song. Rebuild each song.
""" """
maxSongs = self.manager.get_object_count(Song) maxSongs = self.manager.get_object_count(Song)
if maxSongs == 0:
return
progressDialog = QtGui.QProgressDialog( progressDialog = QtGui.QProgressDialog(
translate('SongsPlugin', 'Reindexing songs...'), UiStrings.Cancel, translate('SongsPlugin', 'Reindexing songs...'), UiStrings.Cancel,
0, maxSongs + 1, self.formparent) 0, maxSongs, self.formparent)
progressDialog.setWindowModality(QtCore.Qt.WindowModal) progressDialog.setWindowModality(QtCore.Qt.WindowModal)
songs = self.manager.get_all_objects(Song) songs = self.manager.get_all_objects(Song)
counter = 0 for number, song in enumerate(songs):
for song in songs: clean_song(self.manager, song)
counter += 1 progressDialog.setValue(number + 1)
# The song does not have any author, add one.
if not song.authors:
add_author_unknown(self.manager, song)
if song.title is None:
song.title = u''
if song.alternate_title is None:
song.alternate_title = u''
song.search_title = self.whitespace.sub(u' ', song.title.lower() +
u' ' + song.alternate_title.lower()).strip()
# Remove the "language" attribute from lyrics tag. This is not very
# important, but this keeps the database clean. This can be removed
# when everybody has run the reindex tool once.
song.lyrics = song.lyrics.replace(
u'<lyrics language="en">', u'<lyrics>')
lyrics = u''
verses = SongXML().get_verses(song.lyrics)
for verse in verses:
lyrics = lyrics + self.whitespace.sub(u' ', verse[1]) + u' '
song.search_lyrics = lyrics.lower()
progressDialog.setValue(counter)
self.manager.save_objects(songs) self.manager.save_objects(songs)
progressDialog.setValue(counter + 1) self.mediaItem.onSearchTextButtonClick()
self.mediaItem.displayResultsSong(
self.manager.get_all_objects(Song, order_by_ref=Song.search_title))
def onSongImportItemClicked(self): def onSongImportItemClicked(self):
if self.mediaItem: if self.mediaItem:
@ -179,10 +160,9 @@ class SongsPlugin(Plugin):
self.mediaItem.onExportClick() self.mediaItem.onExportClick()
def about(self): def about(self):
about_text = translate('SongsPlugin', '<strong>Songs Plugin</strong>' return translate('SongsPlugin', '<strong>Songs Plugin</strong>'
'<br />The songs plugin provides the ability to display and ' '<br />The songs plugin provides the ability to display and '
'manage songs.') 'manage songs.')
return about_text
def usesTheme(self, theme): def usesTheme(self, theme):
""" """
@ -244,6 +224,34 @@ class SongsPlugin(Plugin):
} }
self.setPluginUiTextStrings(tooltips) self.setPluginUiTextStrings(tooltips)
def firstTime(self):
"""
If the first time wizard has run, this function is run to import all the
new songs into the database.
"""
db_dir = unicode(os.path.join(gettempdir(), u'openlp'))
song_dbs = []
for sfile in os.listdir(db_dir):
if sfile.startswith(u'songs_') and sfile.endswith(u'.sqlite'):
song_dbs.append(os.path.join(db_dir, sfile))
self.onToolsReindexItemTriggered()
if len(song_dbs) == 0:
return
progress = QtGui.QProgressDialog(self.formparent)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setLabelText(translate('OpenLP.Ui', 'Starting import...'))
progress.setCancelButton(None)
progress.setRange(0, len(song_dbs))
progress.setMinimumDuration(0)
progress.forceShow()
for idx, db in enumerate(song_dbs):
progress.setValue(idx)
Receiver.send_message(u'openlp_process_events')
importer = OpenLPSongImport(self.manager, filename=db)
importer.do_import()
progress.setValue(len(song_dbs))
self.mediaItem.onSearchTextButtonClick()
def finalise(self): def finalise(self):
""" """
Time to tidy up on exit Time to tidy up on exit