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
: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
self.processEvents()
# start the main app window
self.mainWindow = MainWindow(screens, app_version, self.clipboard(),
not has_run_wizard)
self.mainWindow = MainWindow(screens, app_version, self.clipboard())
self.mainWindow.show()
if show_splash:
# now kill the splashscreen
self.splash.finish(self.mainWindow)
self.mainWindow.repaint()
self.processEvents()
if not has_run_wizard:
self.mainWindow.firstTime()
update_check = QtCore.QSettings().value(
u'general/update check', QtCore.QVariant(True)).toBool()
if update_check:

View File

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

View File

@ -33,8 +33,7 @@ from ConfigParser import SafeConfigParser
from PyQt4 import QtCore, QtGui
from openlp.core.lib import translate, PluginStatus, check_directory_exists, \
Receiver, build_icon
from openlp.core.lib import translate, PluginStatus, Receiver, build_icon
from openlp.core.utils import get_web_page, AppLocation
from firsttimewizard import Ui_FirstTimeWizard, FirstTimePage
@ -95,7 +94,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
language = unicode(self.config.get(
u'bibles_%s' % lang, u'title'), u'utf8')
langItem = QtGui.QTreeWidgetItem(
self.biblesTreeWidget, QtCore.QStringList(language))
self.biblesTreeWidget, QtCore.QStringList(language))
bibles = self.config.get(u'bibles_%s' % lang, u'translations')
bibles = bibles.split(u',')
for bible in bibles:
@ -104,24 +103,27 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
filename = unicode(self.config.get(
u'bible_%s' % bible, u'filename'))
item = QtGui.QTreeWidgetItem(
langItem, QtCore.QStringList(title))
item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(filename))
langItem, QtCore.QStringList(title))
item.setData(0, QtCore.Qt.UserRole,
QtCore.QVariant(filename))
item.setCheckState(0, QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.biblesTreeWidget.expandAll()
themes = self.config.get(u'themes', u'files')
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:
title = self.config.get(u'theme_%s' % theme, u'title')
filename = self.config.get(u'theme_%s' % theme, u'filename')
screenshot = self.config.get(u'theme_%s' % theme, u'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.setData(QtCore.Qt.UserRole,
QtCore.QVariant(filename))
item.setIcon(build_icon(
os.path.join(gettempdir(), screenshot)))
os.path.join(gettempdir(), u'openlp', screenshot)))
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
@ -158,6 +160,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._performWizard()
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):
"""
Update the wizard progress page.
@ -182,19 +194,27 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
max_progress = 2
# Loop through the songs list and increase for each selected item
for i in xrange(self.songsListWidget.count()):
if self.songsListWidget.item(i).checkState() == QtCore.Qt.Checked:
max_progress += 1
item = self.songsListWidget.item(i)
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
iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
while iterator.value():
item = iterator.value()
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
# Loop through the themes list and increase for each selected item
for i in xrange(self.themesListWidget.count()):
if self.themesListWidget.item(i).checkState() == QtCore.Qt.Checked:
max_progress += 1
item = self.themesListWidget.item(i)
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.progressBar.setValue(0)
self.progressBar.setMinimum(0)
@ -222,7 +242,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
'Enabling selected plugins...'))
self._setPluginStatus(self.songsCheckBox, u'songs/status')
self._setPluginStatus(self.bibleCheckBox, u'bibles/status')
self._setPluginStatus(self.presentationCheckBox, u'presentations/status')
self._setPluginStatus(self.presentationCheckBox,
u'presentations/status')
self._setPluginStatus(self.imageCheckBox, u'images/status')
self._setPluginStatus(self.mediaCheckBox, u'media/status')
self._setPluginStatus(self.remoteCheckBox, u'remotes/status')
@ -230,46 +251,41 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self._setPluginStatus(self.songUsageCheckBox, u'songusage/status')
self._setPluginStatus(self.alertCheckBox, u'alerts/status')
# 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')
themes_destination = AppLocation.get_section_data_path(u'themes')
# Install songs
# Download songs
for i in xrange(self.songsListWidget.count()):
item = self.songsListWidget.item(i)
if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole).toString()
self._incrementProgressBar(self.downloading % filename)
destination = os.path.join(songs_destination, u'songs.sqlite')
if os.path.exists(destination):
if QtGui.QMessageBox.question(self,
translate('OpenLP.FirstTimeWizard',
'Overwrite Existing Songs?'),
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
self._incrementProgressBar(self.downloading % filename, 0)
self.previous_size = 0
destination = os.path.join(songs_destination, unicode(filename))
urllib.urlretrieve(u'%s%s' % (self.web, filename), destination,
self._downloadProgress)
# Download Bibles
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
while bibles_iterator.value():
item = bibles_iterator.value()
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
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),
os.path.join(bibles_destination, bible))
os.path.join(bibles_destination, bible),
self._downloadProgress)
bibles_iterator += 1
# Install themes
# Download themes
for i in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(i)
if item.checkState() == QtCore.Qt.Checked:
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),
os.path.join(themes_destination, theme))
os.path.join(themes_destination, theme),
self._downloadProgress)
# Set Default Display
if self.displayComboBox.currentIndex() != -1:
QtCore.QSettings().setValue(u'General/monitor',
@ -285,4 +301,3 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
status = PluginStatus.Active if field.checkState() \
== QtCore.Qt.Checked else PluginStatus.Inactive
QtCore.QSettings().setValue(tag, QtCore.QVariant(status))

View File

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

View File

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

View File

@ -32,27 +32,6 @@ from openlp.core.lib.ui import UiStrings
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):
"""
GeneralTab is the general settings tab in the settings dialog.
@ -164,30 +143,6 @@ class GeneralTab(SettingsTab):
self.displayGroupBox.setObjectName(u'displayGroupBox')
self.displayLayout = QtGui.QGridLayout(self.displayGroupBox)
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.setObjectName(u'overrideCheckBox')
self.displayLayout.addWidget(self.overrideCheckBox, 2, 0, 1, 4)
@ -196,26 +151,30 @@ class GeneralTab(SettingsTab):
self.customXLabel = QtGui.QLabel(self.displayGroupBox)
self.customXLabel.setObjectName(u'customXLabel')
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.setMaximum(9999)
self.displayLayout.addWidget(self.customXValueEdit, 4, 0)
self.customYLabel = QtGui.QLabel(self.displayGroupBox)
self.customYLabel.setObjectName(u'customYLabel')
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.setMaximum(9999)
self.displayLayout.addWidget(self.customYValueEdit, 4, 1)
self.customWidthLabel = QtGui.QLabel(self.displayGroupBox)
self.customWidthLabel.setObjectName(u'customWidthLabel')
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.setMaximum(9999)
self.displayLayout.addWidget(self.customWidthValueEdit, 4, 2)
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
self.customHeightLabel.setObjectName(u'customHeightLabel')
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.setMaximum(9999)
self.displayLayout.addWidget(self.customHeightValueEdit, 4, 3)
self.rightLayout.addWidget(self.displayGroupBox)
self.rightLayout.addStretch()
@ -223,20 +182,22 @@ class GeneralTab(SettingsTab):
QtCore.QObject.connect(self.overrideCheckBox,
QtCore.SIGNAL(u'toggled(bool)'), self.onOverrideCheckBoxToggled)
QtCore.QObject.connect(self.customHeightValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'),
self.onDisplayPositionChanged)
QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customWidthValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'),
self.onDisplayPositionChanged)
QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customYValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'),
self.onDisplayPositionChanged)
QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
QtCore.QObject.connect(self.customXValueEdit,
QtCore.SIGNAL(u'textEdited(const QString&)'),
self.onDisplayPositionChanged)
QtCore.SIGNAL(u'valueChanged(int)'), self.onDisplayPositionChanged)
# Reload the tab, as the screen resolution/count may have changed.
QtCore.QObject.connect(Receiver.get_receiver(),
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):
"""
@ -267,8 +228,7 @@ class GeneralTab(SettingsTab):
'Automatically preview next item in service'))
self.timeoutLabel.setText(translate('OpenLP.GeneralTab',
'Slide loop delay:'))
self.timeoutSpinBox.setSuffix(
translate('OpenLP.GeneralTab', ' sec'))
self.timeoutSpinBox.setSuffix(translate('OpenLP.GeneralTab', ' sec'))
self.ccliGroupBox.setTitle(
translate('OpenLP.GeneralTab', 'CCLI Details'))
self.numberLabel.setText(UiStrings.CCLINumberLabel)
@ -279,22 +239,11 @@ class GeneralTab(SettingsTab):
# Moved from display tab
self.displayGroupBox.setTitle(
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',
'Override display position'))
self.customXLabel.setText(translate('OpenLP.GeneralTab', 'X'))
self.customYLabel.setText(translate('OpenLP.GeneralTab', 'Y'))
self.customHeightLabel.setText(
translate('OpenLP.GeneralTab', 'Height'))
self.customHeightLabel.setText(translate('OpenLP.GeneralTab', 'Height'))
self.customWidthLabel.setText(translate('OpenLP.GeneralTab', 'Width'))
def load(self):
@ -304,8 +253,7 @@ class GeneralTab(SettingsTab):
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
self.monitorComboBox.clear()
for screen in self.screens.get_screen_list():
self.monitorComboBox.addItem(screen)
self.monitorComboBox.addItems(self.screens.get_screen_list())
self.numberEdit.setText(unicode(settings.value(
u'ccli number', QtCore.QVariant(u'')).toString()))
self.usernameEdit.setText(unicode(settings.value(
@ -328,26 +276,16 @@ class GeneralTab(SettingsTab):
QtCore.QVariant(False)).toBool())
self.timeoutSpinBox.setValue(settings.value(u'loop delay',
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',
QtCore.QVariant(False)).toBool())
self.customXValueEdit.setText(settings.value(u'x position',
QtCore.QVariant(self.screens.current[u'size'].x())).toString())
self.customYValueEdit.setText(settings.value(u'y position',
QtCore.QVariant(self.screens.current[u'size'].y())).toString())
self.customHeightValueEdit.setText(
settings.value(u'height', QtCore.QVariant(
self.screens.current[u'size'].height())).toString())
self.customWidthValueEdit.setText(
settings.value(u'width', QtCore.QVariant(
self.screens.current[u'size'].width())).toString())
self.customXValueEdit.setValue(settings.value(u'x position',
QtCore.QVariant(self.screens.current[u'size'].x())).toInt()[0])
self.customYValueEdit.setValue(settings.value(u'y position',
QtCore.QVariant(self.screens.current[u'size'].y())).toInt()[0])
self.customHeightValueEdit.setValue(settings.value(u'height',
QtCore.QVariant(self.screens.current[u'size'].height())).toInt()[0])
self.customWidthValueEdit.setValue(settings.value(u'width',
QtCore.QVariant(self.screens.current[u'size'].width())).toInt()[0])
settings.endGroup()
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
@ -385,13 +323,13 @@ class GeneralTab(SettingsTab):
settings.setValue(u'songselect password',
QtCore.QVariant(self.passwordEdit.displayText()))
settings.setValue(u'x position',
QtCore.QVariant(self.customXValueEdit.text()))
QtCore.QVariant(self.customXValueEdit.value()))
settings.setValue(u'y position',
QtCore.QVariant(self.customYValueEdit.text()))
QtCore.QVariant(self.customYValueEdit.value()))
settings.setValue(u'height',
QtCore.QVariant(self.customHeightValueEdit.text()))
QtCore.QVariant(self.customHeightValueEdit.value()))
settings.setValue(u'width',
QtCore.QVariant(self.customWidthValueEdit.text()))
QtCore.QVariant(self.customWidthValueEdit.value()))
settings.setValue(u'override position',
QtCore.QVariant(self.overrideCheckBox.isChecked()))
settings.endGroup()
@ -415,10 +353,10 @@ class GeneralTab(SettingsTab):
# Reset screens after initial definition
if self.overrideChanged:
self.screens.override[u'size'] = QtCore.QRect(
int(self.customXValueEdit.validText()),
int(self.customYValueEdit.validText()),
int(self.customWidthValueEdit.validText()),
int(self.customHeightValueEdit.validText()))
self.customXValueEdit.value(),
self.customYValueEdit.value(),
self.customWidthValueEdit.value(),
self.customHeightValueEdit.value())
if self.overrideCheckBox.isChecked():
self.screens.set_override_display()
else:

View File

@ -67,6 +67,7 @@ class MainDisplay(DisplayWidget):
self.isLive = live
self.alertTab = None
self.hideMode = None
self.videoHide = False
self.override = {}
mainIcon = build_icon(u':/icon/openlp-logo-16x16.png')
self.setWindowIcon(mainIcon)
@ -110,6 +111,12 @@ class MainDisplay(DisplayWidget):
QtCore.QObject.connect(self.mediaObject,
QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'),
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)
self.webView = QtWebKit.QWebView(self)
self.webView.setGeometry(0, 0,
@ -143,23 +150,22 @@ class MainDisplay(DisplayWidget):
if not background_color.isValid():
background_color = QtCore.Qt.white
splash_image = QtGui.QImage(image_file)
initialFrame = QtGui.QImage(
self.initialFrame = QtGui.QImage(
self.screens.current[u'size'].width(),
self.screens.current[u'size'].height(),
QtGui.QImage.Format_ARGB32_Premultiplied)
painter_image = QtGui.QPainter()
painter_image.begin(initialFrame)
painter_image.fillRect(initialFrame.rect(), background_color)
painter_image.begin(self.initialFrame)
painter_image.fillRect(self.initialFrame.rect(), background_color)
painter_image.drawImage(
(self.screens.current[u'size'].width() -
splash_image.width()) / 2,
(self.screens.current[u'size'].height()
- splash_image.height()) / 2, splash_image)
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.alertTab, self.isLive, None))
self.initialFrame = True
self.__hideMouse()
# To display or not to display?
if not self.screen[u'primary']:
@ -181,6 +187,7 @@ class MainDisplay(DisplayWidget):
# Wait for the webview to update before displaying text.
while not self.webLoaded:
Receiver.send_message(u'openlp_process_events')
self.setGeometry(self.screen[u'size'])
self.frame.evaluateJavaScript(u'show_text("%s")' % \
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
return self.preview()
@ -208,12 +215,18 @@ class MainDisplay(DisplayWidget):
else:
shrinkItem = self
if text:
shrinkItem.resize(self.width(), int(height.toString()))
alert_height = int(height.toString())
shrinkItem.resize(self.width(), alert_height)
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:
shrinkItem.setVisible(False)
shrinkItem.resize(self.screen[u'size'].width(),
self.screen[u'size'].height())
self.setGeometry(self.screen[u'size'])
def directImage(self, name, path):
"""
@ -243,6 +256,7 @@ class MainDisplay(DisplayWidget):
"""
Display an image, as is.
"""
self.setGeometry(self.screen[u'size'])
if image:
js = u'show_image("data:image/png;base64,%s");' % image
else:
@ -262,6 +276,7 @@ class MainDisplay(DisplayWidget):
self.displayImage(self.serviceItem.bg_image_bytes)
else:
self.displayImage(None)
# clear the cache
self.override = {}
# Update the preview frame.
if self.isLive:
@ -336,6 +351,7 @@ class MainDisplay(DisplayWidget):
"""
log.debug(u'video')
self.webLoaded = True
self.setGeometry(self.screen[u'size'])
# We are running a background theme
self.override[u'theme'] = u''
self.override[u'video'] = True
@ -349,6 +365,10 @@ class MainDisplay(DisplayWidget):
self.mediaObject.stop()
self.mediaObject.clearQueue()
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.webView.setVisible(False)
self.videoWidget.setVisible(True)
@ -363,8 +383,26 @@ class MainDisplay(DisplayWidget):
Start the video at a predetermined point.
"""
if newState == Phonon.PlayingState:
# set start time in milliseconds
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):
"""
Called by webView event to show display is fully loaded
@ -396,7 +434,14 @@ class MainDisplay(DisplayWidget):
if self.hideMode:
self.hideDisplay(self.hideMode)
else:
self.setVisible(True)
# 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:
self.setVisible(True)
preview = QtGui.QImage(self.screen[u'size'].width(),
self.screen[u'size'].height(),
QtGui.QImage.Format_ARGB32_Premultiplied)
@ -413,7 +458,7 @@ class MainDisplay(DisplayWidget):
"""
log.debug(u'buildHtml')
self.webLoaded = False
self.initialFrame = False
self.initialFrame = None
self.serviceItem = serviceItem
background = None
# 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:
Receiver.send_message(u'video_background_replaced')
self.override = {}
# We have a different theme.
elif self.override[u'theme'] != serviceItem.themedata.theme_name:
Receiver.send_message(u'live_theme_changed')
self.override = {}
else:
# replace the background
background = self.imageManager. \
get_image_bytes(self.override[u'image'])
if self.serviceItem.themedata.background_filename:
@ -441,6 +488,10 @@ class MainDisplay(DisplayWidget):
# if was hidden keep it hidden
if self.hideMode and self.isLive:
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()
def footer(self, text):

View File

@ -25,6 +25,8 @@
###############################################################################
import logging
import os
from tempfile import gettempdir
from PyQt4 import QtCore, QtGui
@ -215,8 +217,6 @@ class Ui_MainWindow(object):
self.ModeDefaultItem.setChecked(True)
self.ToolsAddToolItem = icon_action(mainWindow, u'ToolsAddToolItem',
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')
self.ToolsOpenDataFolder = icon_action(mainWindow,
u'ToolsOpenDataFolder', u':/general/general_open.png')
@ -298,6 +298,13 @@ class Ui_MainWindow(object):
QtCore.QObject.connect(self.FileExitItem,
QtCore.SIGNAL(u'triggered()'), mainWindow.close)
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):
"""
@ -461,14 +468,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
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
plugins.
"""
QtGui.QMainWindow.__init__(self)
self.screens = screens
self.actionList = ActionList()
self.applicationVersion = applicationVersion
self.clipboard = clipboard
# Set up settings sections for the main application
@ -478,6 +484,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.serviceSettingsSection = u'servicemanager'
self.songsSettingsSection = u'songs'
self.serviceNotSaved = False
self.actionList = ActionList()
self.settingsmanager = SettingsManager(screens)
self.aboutForm = AboutForm(self, applicationVersion)
self.settingsForm = SettingsForm(self.screens, self, self)
@ -624,10 +631,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.MediaToolBox.setCurrentIndex(savedPlugin)
self.settingsForm.postSetUp()
Receiver.send_message(u'cursor_normal')
# Import themes if first time
if firstTime:
self.themeManagerContents.firstTime()
def setAutoLanguage(self, value):
self.LanguageGroup.setDisabled(value)
@ -670,6 +673,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.setViewMode(False, True, False, False, 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):
"""
Check and display message if screen blank on setup.

View File

@ -49,6 +49,19 @@ class ServiceManagerList(QtGui.QTreeWidget):
QtGui.QTreeWidget.__init__(self, parent)
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):
"""
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',
translate('OpenLP.ServiceManager',
'Expand all the service items.'),
self.onExpandAll)
self.onExpandAll, shortcut=QtCore.Qt.Key_Plus)
self.serviceManagerList.collapse = self.orderToolbar.addToolbarButton(
translate('OpenLP.ServiceManager', '&Collapse all'),
u':/services/service_collapse_all.png',
translate('OpenLP.ServiceManager',
'Collapse all the service items.'),
self.onCollapseAll)
self.onCollapseAll, shortcut=QtCore.Qt.Key_Minus)
self.orderToolbar.addSeparator()
self.serviceManagerList.makeLive = self.orderToolbar.addToolbarButton(
translate('OpenLP.ServiceManager', 'Go Live'),
@ -293,7 +306,9 @@ class ServiceManager(QtGui.QWidget):
self.serviceManagerList.moveTop,
self.serviceManagerList.moveBottom,
self.serviceManagerList.up,
self.serviceManagerList.down
self.serviceManagerList.down,
self.serviceManagerList.expand,
self.serviceManagerList.collapse
])
self.configUpdated()
@ -306,6 +321,9 @@ class ServiceManager(QtGui.QWidget):
actionList.add_action(self.serviceManagerList.makeLive, u'Service')
actionList.add_action(self.serviceManagerList.up, 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):
"""
@ -600,7 +618,7 @@ class ServiceManager(QtGui.QWidget):
if item.parent() is None:
self.notesAction.setVisible(True)
if serviceItem[u'service_item']\
.is_capable(ItemCapabilities.AllowsVarableStartTime):
.is_capable(ItemCapabilities.AllowsVariableStartTime):
self.timeAction.setVisible(True)
self.themeMenu.menuAction().setVisible(False)
if serviceItem[u'service_item'].is_text():
@ -887,7 +905,7 @@ class ServiceManager(QtGui.QWidget):
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count))
if item[u'service_item'] \
.is_capable(ItemCapabilities.AllowsVarableStartTime):
.is_capable(ItemCapabilities.AllowsVariableStartTime):
tip = item[u'service_item'].get_media_time()
if 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.
"""
COMMAND = u'soffice'
OPTIONS = u'-nologo -norestore -minimized -invisible -nofirststartwizard'
OPTIONS = u'-nologo -norestore -minimized -nodefault -nofirststartwizard'
if UNO_CONNECTION_TYPE == u'pipe':
CONNECTION = u'"-accept=pipe,name=openlp_pipe;urp;"'
else:

View File

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

View File

@ -92,6 +92,10 @@ class BGExtract(object):
cleanup = [(re.compile('\s+'), lambda match: ' ')]
verses = BeautifulSoup(str(soup), markupMassage=cleanup)
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'):
raw_verse_num = verse.next
clean_verse_num = 0

View File

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

View File

@ -49,11 +49,13 @@ class MediaPlugin(Plugin):
u'audio/ac3': [u'.ac3'],
u'audio/flac': [u'.flac'],
u'audio/x-m4a': [u'.m4a'],
u'audio/midi': [u'.mid', u'.midi'],
u'audio/x-mp3': [u'.mp3'],
u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
u'audio/qcelp': [u'.qcp'],
u'audio/x-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-wmv': [u'.wmv'],
u'video/x-ms-wmv': [u'.wmv']}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,84 @@
/******************************************************************************
* 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 )
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 void ClosePPT(int id);
DllExport int GetCurrentSlide(int id);
DllExport int GetSlideCount(int id);
DllExport void NextStep(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 Blank(int id);
DllExport void Unblank(int id);
DllExport void Stop(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 CwpProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL GetPPTViewerPath(char *pptviewerpath, int strsize);
HBITMAP CaptureWindow (HWND hWnd);
VOID SaveBitmap (CHAR* filename, HBITMAP hBmp) ;
BOOL GetPPTViewerPath(char *pptViewerPath, int stringSize);
HBITMAP CaptureWindow(HWND hWnd);
VOID SaveBitmap(CHAR* filename, HBITMAP hBmp) ;
VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename);
BOOL GetPPTInfo(int id);
BOOL SavePPTInfo(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 mhook;
HWND hWnd;
HWND hWnd2;
HWND hParentWnd;
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
RECT rect;
int slideCount;
int currentSlide;
int firstSlideSteps;
int steps;
char filename[MAX_PATH];
char previewpath[MAX_PATH];
PPTVIEWSTATE state;
HHOOK hook;
HHOOK msgHook;
HWND hWnd;
HWND hWnd2;
HWND hParentWnd;
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
RECT rect;
int slideCount;
int currentSlide;
int firstSlideSteps;
int lastSlideSteps;
int steps;
int guess;
char filename[MAX_PATH];
char previewPath[MAX_PATH];
int slideNos[MAX_SLIDES];
PPTVIEWSTATE state;
};

View File

@ -26,4 +26,68 @@
"""
The :mod:`remotes` plugin allows OpenLP to be controlled from another machine
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"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>OpenLP Remote Controller</title>
<script type="text/javascript" src="/files/jquery.js"></script>
<script type='text/javascript' src="/files/openlp.js"></script>
<script type='text/javascript' src="/files/init.js"></script>
<link rel="stylesheet" href="/files/style.css" type="text/css" />
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>OpenLP 2.0 Remote</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/openlp.js"></script>
<script type="text/javascript" src="/files/jquery.mobile.js"></script>
</head>
<body>
<h1>OpenLP Controller</h1>
<p>Quick Links: <a href="#service-manager">Service Manager</a> | <a href="#slide-controller">Slide Controller</a> | <a href="#miscellaneous">Miscellaneous</a></p>
<h2 id="service-manager">Service Manager</h2>
<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 id="item-buttons">
<input type="button" value="&lt;- Previous Item" id="servicemanager_previous_item" />
<input type="button" value="Next Item -&gt;" id="servicemanager_next_item" />
</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 id="slide-buttons">
<input type="button" value="&lt;- Previous Slide" id="slidecontroller_live_previous" />
<input type="button" value="Next Slide -&gt;" id="slidecontroller_live_next" />
</div>
</fieldset>
<hr>
<h2 id="miscellaneous">Miscellaneous</h2>
<div id="display-buttons">
<input type="button" value="Blank" id="slidecontroller_live_blank" />
<input type="button" value="Unblank" id="slidecontroller_live_unblank" />
<div data-role="page" id="home">
<div data-role="header">
<h1>OpenLP 2.0 Remote</h1>
</div>
<div data-role="content">
<div data-role="controlgroup">
<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 id="alert-details">
<label for="alert-text">Alert text:</label>
<input type="text" id="alert-text" />
<input type="button" value="Send" id="alert-send" />
</div>
</div>
<div data-role="page" id="service-manager">
<div data-role="header">
<a href="#" data-rel="back" data-icon="arrow-l">Back</a>
<h1>Service Manager</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="service-blank" data-role="button" data-icon="blank">Blank</a>
<a href="#" id="service-unblank" data-role="button" data-icon="unblank">Unblank</a>
<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>
<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>
<hr>
<a href="http://openlp.org/">OpenLP website</a>
<a href="#" id="alert-submit" data-role="button">Show Alert</a>
</div>
</div>
</body>
</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 *
*****************************************************************************/
/**
* init.js - In certain browsers (yes, IE, I'm looking at you!), DocumentReady
* 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.
*/
.ui-icon-blank {
background-image: url(images/ui-icon-blank.png);
}
$(document).ready(function () {
OpenLP.Events.init();
});
.ui-icon-unblank {
background-image: url(images/ui-icon-unblank.png);
}

View File

@ -20,85 +20,7 @@
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
*****************************************************************************/
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);
},
window.OpenLP = {
getElement: function(event) {
var targ;
if (!event) {
@ -116,128 +38,167 @@ OpenLP.Namespace.create("OpenLP.Events", {
}
return $(targ);
},
init: function () {
for (idx in this.onload_functions) {
func = this.onload_functions[idx];
func();
}
}
});
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)
{
if (eventName == "remotes_poll_request")
{
OpenLP.Remote.handleEvent("remotes_poll_request");
}
}
loadService: function (event) {
$.getJSON(
"/api/service/list",
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);
});
},
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));
trow.append($("<td>").text(item["title"]));
trow.append($("<td>").text(item["plugin"]));
trow.append($("<td>").text("Notes: " + item["notes"]));
table.append(trow);
});
$("#service").html(table);
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;
ul.listview("refresh");
}
);
},
loadController: function (event) {
$.getJSON(
"/api/controller/live/text",
function (data, status) {
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);
}
},
sendLiveSet: function (e)
{
var tr = OpenLP.Events.getElement(e).parent();
if (tr[0].tagName != "TR")
{
tr = tr.parent();
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();
}
OpenLP.Remote.sendEvent("slidecontroller_live_set", tr.attr("value"));
return false;
},
sendSetItem: function (e)
{
var id = OpenLP.Events.getElement(e).parent().attr("value");
OpenLP.Remote.sendEvent("servicemanager_set_item", id);
return false;
},
sendAlert: function (e)
{
var alert_text = $("#alert-text").val();
OpenLP.Remote.sendEvent("alerts_text", alert_text);
return false;
},
buttonClick: function (e)
{
var id = OpenLP.Events.getElement(e).attr("id");
OpenLP.Remote.sendEvent(id);
return false;
}
});
OpenLP.Events.bindLoad(function () {
OpenLP.Events.bindClick("input[type=button][id!=alert-send]", OpenLP.Remote.buttonClick);
OpenLP.Events.bindClick("#alert-send", OpenLP.Remote.sendAlert);
OpenLP.Remote.sendEvent("servicemanager_list_request");
OpenLP.Remote.sendEvent("slidecontroller_live_text_request");
OpenLP.Remote.sendEvent("remotes_poll_request");
});
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");
}
if ($("#slide-controller").is(":visible")) {
var idx = 0;
$("#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");
$("#slide-controller div[data-role=content] ul[data-role=listview] li a").each(function () {
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++;
});
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
}
);
},
nextItem: function (event) {
$.getJSON("/api/service/next");
return false;
},
previousItem: function (event) {
$.getJSON("/api/service/previous");
return false;
},
nextSlide: function (event) {
$.getJSON("/api/controller/live/next");
return false;
},
previousSlide: function (event) {
$.getJSON("/api/controller/live/previous");
return false;
},
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;
}
}
// Service Manager
$("#service-manager").live("pagebeforeshow", OpenLP.loadService);
$("#service-refresh").live("click", OpenLP.loadService);
$("#service-next").live("click", OpenLP.nextItem);
$("#service-previous").live("click", OpenLP.previousItem);
$("#service-blank").live("click", OpenLP.blankDisplay);
$("#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 httpserver import HttpServer
__all__ = [u'RemoteTab', u'HttpServer']

View File

@ -24,9 +24,94 @@
# 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 os
import urlparse
import re
from pprint import pformat
try:
import json
@ -36,10 +121,29 @@ except ImportError:
from PyQt4 import QtCore, QtNetwork
from openlp.core.lib import Receiver
from openlp.core.ui import HideMode
from openlp.core.utils import AppLocation
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):
"""
Ability to control OpenLP via a webbrowser
@ -90,22 +194,12 @@ class HttpServer(object):
Slide change listener. Store the item and tell the clients
"""
self.current_slide = row
self.send_poll()
def item_change(self, items):
"""
Item (song) change listener. Store the slide and tell the clients
"""
self.current_item = items[0].title
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})
self.current_item = items[0]
def new_connection(self):
"""
@ -122,7 +216,8 @@ class HttpServer(object):
The connection has been closed. Clean up
"""
log.debug(u'close http connection')
self.connections.remove(connection)
if connection in self.connections:
self.connections.remove(connection)
def close(self):
"""
@ -138,17 +233,44 @@ class HttpConnection(object):
"""
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' %
socket.peerAddress().toString())
self.socket = socket
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()'),
self.ready_read)
QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'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):
"""
Data has been sent from the client. Respond to it
@ -158,32 +280,27 @@ class HttpConnection(object):
data = unicode(self.socket.readLine())
log.debug(u'received: ' + data)
words = data.split(u' ')
html = None
mimetype = None
response = None
if words[0] == u'GET':
url = urlparse.urlparse(words[1])
params = self.load_params(url.query)
folders = url.path.split(u'/')
if folders[1] == u'':
mimetype, html = self.serve_file(u'')
elif folders[1] == u'files':
mimetype, html = self.serve_file(os.sep.join(folders[2:]))
elif folders[1] == u'send':
html = self.process_event(folders[2], params)
elif folders[1] == u'request':
if self.process_request(folders[2], params):
return
if html:
if mimetype:
self.send_200_ok(mimetype)
else:
self.send_200_ok()
self.socket.write(html)
self.url_params = urlparse.parse_qs(url.query)
# Loop through the routes we set up earlier and execute them
for route, func in self.routes:
match = re.match(route, url.path)
if match:
log.debug('Route "%s" matched "%s"', route, url.path)
args = []
for param in match.groups():
args.append(param)
response = func(*args)
break
if response:
self.send_response(response)
else:
self.send_404_not_found()
self.send_response(HttpResponse(code='404 Not Found'))
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
and must be top level inside the html folder.
@ -197,7 +314,7 @@ class HttpConnection(object):
filename = u'index.html'
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
if not path.startswith(self.parent.html_dir):
return None
return HttpResponse(code=u'404 Not Found')
ext = os.path.splitext(filename)[1]
if ext == u'.html':
mimetype = u'text/html'
@ -212,126 +329,116 @@ class HttpConnection(object):
elif ext == u'.png':
mimetype = u'image/png'
else:
return (None, None)
mimetype = u'text/plain'
file_handle = None
try:
file_handle = open(path, u'rb')
log.debug(u'Opened %s' % path)
html = file_handle.read()
content = file_handle.read()
except IOError:
log.exception(u'Failed to open %s' % path)
return None
return HttpResponse(code=u'404 Not Found')
finally:
if file_handle:
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)
params = urlparse.parse_qs(query)
if not params:
return None
result = {
u'slide': self.parent.current_slide or 0,
u'item': self.parent.current_item.title \
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:
item[u'tag'] = unicode(index)
item[u'text'] = u''
item[u'selected'] = (self.parent.current_slide == index)
data.append(item)
json_data = {u'results': {u'slides': data}}
else:
return params['q']
if self.url_params and self.url_params.get(u'data'):
data = json.loads(self.url_params[u'data'][0])
log.info(data)
# This slot expects an int within a list.
id = data[u'request'][u'id']
Receiver.send_message(event, [id])
else:
Receiver.send_message(event)
json_data = {u'results': {u'success': True}}
return HttpResponse(json.dumps(json_data),
{u'Content-Type': u'application/json'})
def process_event(self, event, params):
"""
Send a signal to openlp to perform an action.
Currently lets anything through. Later we should restrict and perform
basic parameter checking, otherwise rogue clients could crash openlp
"""
log.debug(u'Processing event %s' % event)
if params:
Receiver.send_message(event, params)
def service(self, action):
event = u'servicemanager_%s' % action
if action == u'list':
return HttpResponse(
json.dumps({u'results': {u'items': self._get_service_items()}}),
{u'Content-Type': u'application/json'})
else:
event += u'_item'
if self.url_params and self.url_params.get(u'data'):
data = json.loads(self.url_params[u'data'][0])
Receiver.send_message(event, data[u'request'][u'id'])
else:
Receiver.send_message(event)
return json.dumps([u'OK'])
return HttpResponse(json.dumps({u'results': {u'success': True}}),
{u'Content-Type': u'application/json'})
def process_request(self, event, params):
"""
Client has requested data. Send the signal and parameters for openlp
to handle, then listen out for a corresponding ``_request`` signal
which will have the data to return.
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:
self.timer.start(10000)
if params:
Receiver.send_message(event, params)
else:
Receiver.send_message(event)
return True
def process_response(self, data):
"""
The recipient of a _request signal has sent data. Convert this to
json and return it to client
"""
log.debug(u'Processing response for %s' % self.event)
if not self.socket:
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 send_response(self, response):
http = u'HTTP/1.1 %s\r\n' % response.code
for header, value in response.headers.iteritems():
http += '%s: %s\r\n' % (header, value)
http += '\r\n'
self.socket.write(http)
self.socket.write(response.content)
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, \
critical_error_message_box
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.ui import SongStrings
from editsongdialog import Ui_EditSongDialog
@ -297,7 +297,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseOrderEdit.setText(u' '.join(translated))
else:
self.verseOrderEdit.setText(u'')
self.verseListWidget.resizeRowsToContents()
self.tagRows()
# clear the results
self.authorsListView.clear()
@ -312,10 +311,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id))
self.topicsListView.addItem(topic_name)
self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# if not preview hide the preview button
self.previewButton.setVisible(False)
if preview:
self.previewButton.setVisible(True)
# Hide or show the preview button.
self.previewButton.setVisible(preview)
def tagRows(self):
"""
@ -329,6 +326,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
row_def = u'%s%s' % (verse_tag, verse_def[1:])
row_label.append(row_def)
self.verseListWidget.setVerticalHeaderLabels(row_label)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
def onAuthorAddButtonClicked(self):
item = int(self.authorsComboBox.currentIndex())
@ -453,9 +453,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1)
self.verseListWidget.setItem(
int(self.verseListWidget.rowCount() - 1), 0, item)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.rowCount() - 1, 0, item)
self.tagRows()
def onVerseEditButtonClicked(self):
@ -482,8 +480,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = QtGui.QTableWidgetItem(tempList[row], 0)
item.setData(QtCore.Qt.UserRole, tempId[row])
self.verseListWidget.setItem(row, 0, item)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
self.tagRows()
def onVerseEditAllButtonClicked(self):
@ -500,53 +496,50 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_form.setVerse(verse_list)
else:
self.verse_form.setVerse(u'')
if self.verse_form.exec_():
verse_list = self.verse_form.getVerseAll()
verse_list = unicode(verse_list.replace(u'\r\n', u'\n'))
self.verseListWidget.clear()
self.verseListWidget.setRowCount(0)
for row in self.findVerseSplit.split(verse_list):
for match in row.split(u'---['):
for count, parts in enumerate(match.split(u']---\n')):
if len(parts) > 1:
if count == 0:
# handling carefully user inputted versetags
separator = parts.find(u':')
if separator >= 0:
verse_name = parts[0:separator].strip()
verse_num = parts[separator+1:].strip()
else:
verse_name = parts
verse_num = u'1'
verse_index = \
VerseType.from_loose_input(verse_name)
verse_tag = VerseType.Tags[verse_index]
# Later we need to handle v1a as well.
#regex = re.compile(r'(\d+\w.)')
regex = re.compile(r'\D*(\d+)\D*')
match = regex.match(verse_num)
if match:
verse_num = match.group(1)
else:
verse_num = u'1'
verse_def = u'%s%s' % (verse_tag, verse_num)
else:
if parts.endswith(u'\n'):
parts = parts.rstrip(u'\n')
item = QtGui.QTableWidgetItem(parts)
item.setData(QtCore.Qt.UserRole,
QtCore.QVariant(verse_def))
self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1)
self.verseListWidget.setItem(
int(self.verseListWidget.rowCount() - 1),
0, item)
self.verseListWidget.setColumnWidth(0, self.width)
self.verseListWidget.resizeRowsToContents()
self.verseListWidget.repaint()
self.tagRows()
self.verseEditButton.setEnabled(False)
self.verseDeleteButton.setEnabled(False)
if not self.verse_form.exec_():
return
verse_list = self.verse_form.getVerseAll()
verse_list = unicode(verse_list.replace(u'\r\n', u'\n'))
self.verseListWidget.clear()
self.verseListWidget.setRowCount(0)
for row in self.findVerseSplit.split(verse_list):
for match in row.split(u'---['):
for count, parts in enumerate(match.split(u']---\n')):
if len(parts) <= 1:
continue
if count == 0:
# handling carefully user inputted versetags
separator = parts.find(u':')
if separator >= 0:
verse_name = parts[0:separator].strip()
verse_num = parts[separator+1:].strip()
else:
verse_name = parts
verse_num = u'1'
verse_index = VerseType.from_loose_input(verse_name)
verse_tag = VerseType.Tags[verse_index]
# Later we need to handle v1a as well.
#regex = re.compile(r'(\d+\w.)')
regex = re.compile(r'\D*(\d+)\D*')
match = regex.match(verse_num)
if match:
verse_num = match.group(1)
else:
verse_num = u'1'
verse_def = u'%s%s' % (verse_tag, verse_num)
else:
if parts.endswith(u'\n'):
parts = parts.rstrip(u'\n')
item = QtGui.QTableWidgetItem(parts)
item.setData(QtCore.Qt.UserRole,
QtCore.QVariant(verse_def))
self.verseListWidget.setRowCount(
self.verseListWidget.rowCount() + 1)
self.verseListWidget.setItem(
self.verseListWidget.rowCount() - 1, 0, item)
self.tagRows()
self.verseEditButton.setEnabled(False)
self.verseDeleteButton.setEnabled(False)
def onVerseDeleteButtonClicked(self):
self.verseListWidget.removeRow(self.verseListWidget.currentRow())
@ -728,17 +721,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.title = unicode(self.titleEdit.text())
self.song.alternate_title = unicode(self.alternativeEdit.text())
self.song.copyright = unicode(self.copyrightEdit.text())
if self.song.alternate_title:
self.song.search_title = self.song.title + u'@' + \
self.song.alternate_title
else:
self.song.search_title = self.song.title
# Values will be set when cleaning the song.
self.song.search_title = u''
self.song.search_lyrics = u''
self.song.verse_order = u''
self.song.comments = unicode(self.commentsEdit.toPlainText())
ordertext = unicode(self.verseOrderEdit.text())
order = []
for item in ordertext.split():
verse_tag = VerseType.Tags[
VerseType.from_translated_tag(item[0])]
verse_tag = VerseType.Tags[VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower()
order.append(u'%s%s' % (verse_tag, verse_num))
self.song.verse_order = u' '.join(order)
@ -755,8 +746,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.theme_name = theme_name
else:
self.song.theme_name = None
self.processLyrics()
self.processTitle()
self._processLyrics()
self.song.authors = []
for row in range(self.authorsListView.count()):
item = self.authorsListView.item(row)
@ -767,19 +757,19 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = self.topicsListView.item(row)
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.song.topics.append(self.manager.get_object(Topic, topicId))
clean_song(self.manager, self.song)
self.manager.save_object(self.song)
if not preview:
self.song = None
def processLyrics(self):
def _processLyrics(self):
"""
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.
log.debug(u'processLyrics')
log.debug(u'_processLyrics')
try:
sxml = SongXML()
text = u''
multiple = []
for i in range(0, self.verseListWidget.rowCount()):
item = self.verseListWidget.item(i, 0)
@ -788,11 +778,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_num = verseId[1:]
sxml.add_verse_to_lyrics(verse_tag, verse_num,
unicode(item.text()))
text = text + self.whitespace.sub(u' ',
unicode(self.verseListWidget.item(i, 0).text())) + u' '
if (verse_num > u'1') and (verse_tag not in multiple):
if verse_num > u'1' and verse_tag not in multiple:
multiple.append(verse_tag)
self.song.search_lyrics = text.lower()
self.song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
for verse in multiple:
self.song.verse_order = re.sub(u'([' + verse.upper() +
@ -801,13 +788,3 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
except:
log.exception(u'Problem processing song Lyrics \n%s',
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 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import re
from PyQt4 import QtGui
@ -244,9 +245,11 @@ def retrieve_windows_encoding(recommendation=None):
return None
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``
The song's manager.
@ -254,12 +257,29 @@ def add_author_unknown(manager, song):
``song``
The song object.
"""
name = SongStrings.AuthorUnknown
author = manager.get_object_filtered(Author, Author.display_name == name)
if author is None:
author = Author.populate(
display_name=name, last_name=u'', first_name=u'')
song.authors.append(author)
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
author = manager.get_object_filtered(
Author, Author.display_name == name)
if author is None:
author = Author.populate(
display_name=name, last_name=u'', first_name=u'')
song.authors.append(author)
from xml import OpenLyrics, SongXML
from songstab import SongsTab

View File

@ -94,7 +94,7 @@ import os
from lxml import etree, objectify
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.db import Author, Book, Song, Topic
from openlp.plugins.songs.lib.xml import SongXML
@ -212,9 +212,13 @@ class FoilPresenter(object):
# No xml get out of here.
if not xml:
return None
song = Song()
if xml[:5] == u'<?xml':
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.
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_songbooks(foilpresenterfolie, song)
self._process_topics(foilpresenterfolie, song)
clean_song(self.manager, song)
self.manager.save_object(song)
return song.id
@ -348,8 +353,6 @@ class FoilPresenter(object):
first_name = u' '.join(display_name.split(u' ')[:-1]))
self.manager.save_object(author)
song.authors.append(author)
if not song.authors:
add_author_unknown(self.manager, song)
def _process_cclinumber(self, foilpresenterfolie, song):
"""
@ -407,7 +410,6 @@ class FoilPresenter(object):
The song object.
"""
sxml = SongXML()
search_text = u''
temp_verse_order = {}
temp_verse_order_backup = []
temp_sortnr_backup = 1
@ -452,7 +454,6 @@ class FoilPresenter(object):
else:
verse_type = u'O'
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".
if not verse_number:
verse_number = unicode(versenumber[verse_type])
@ -470,8 +471,6 @@ class FoilPresenter(object):
temp_verse_order_backup.append(u''.join((verse_type[0],
verse_number)))
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')
# Process verse order
verse_order = []
@ -534,13 +533,9 @@ class FoilPresenter(object):
for titelstring in foilpresenterfolie.titel.titelstring:
if not song.title:
song.title = self._child(titelstring)
song.search_title = unicode(song.title)
song.alternate_title = u''
else:
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):
"""
@ -565,10 +560,3 @@ class FoilPresenter(object):
song.topics.append(topic)
except AttributeError:
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' ',
search_keywords.lower()) + u'%'),
Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'),
Song.comments.like(u'%' + search_keywords.lower() + u'%')),
Song.search_title.asc())
Song.comments.like(u'%' + search_keywords.lower() + u'%')))
self.displayResultsSong(search_results)
elif search_type == SongSearch.Titles:
log.debug(u'Titles Search')
search_results = self.parent.manager.get_all_objects(Song,
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)
elif search_type == SongSearch.Lyrics:
log.debug(u'Lyrics Search')
search_results = self.parent.manager.get_all_objects(Song,
Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'),
Song.search_lyrics.asc())
Song.search_lyrics.like(u'%' + search_keywords.lower() + u'%'))
self.displayResultsSong(search_results)
elif search_type == SongSearch.Authors:
log.debug(u'Authors Search')
@ -190,7 +188,7 @@ class SongMediaItem(MediaManagerItem):
elif search_type == SongSearch.Themes:
log.debug(u'Theme Search')
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)
def onSongListLoad(self):
@ -268,9 +266,8 @@ class SongMediaItem(MediaManagerItem):
Receiver.send_message(u'songs_load_list')
def onExportClick(self):
if not hasattr(self, u'export_wizard'):
self.export_wizard = SongExportForm(self, self.parent)
self.export_wizard.exec_()
export_wizard = SongExportForm(self, self.parent)
export_wizard.exec_()
def onNewClick(self):
log.debug(u'onNewClick')
@ -414,29 +411,32 @@ class SongMediaItem(MediaManagerItem):
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')
if self.plugin.status != PluginStatus.Active or not item.data_string:
return
search_results = self.parent.manager.get_all_objects(Song,
Song.search_title == re.compile(r'\W+', re.UNICODE).sub(u' ',
item.data_string[u'title'].split(u'@')[0].lower()).strip(),
Song.search_title.asc())
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,
Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(u' ',
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())
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
add_song = True
if search_results:
for song in search_results:
same_authors = True
# If the author counts are different, we do not have to do any
# further checking. This is also important when a song does not
# have any author (because we can not loop over an empty list).
# further checking.
if len(song.authors) == len(author_list):
for author in song.authors:
if author.display_name not in author_list:
@ -461,4 +461,5 @@ class SongMediaItem(MediaManagerItem):
"""
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.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 songimport import SongImport
@ -151,12 +151,14 @@ class OpenLPSongImport(SongImport):
source_songs = self.source_session.query(OldSong).all()
song_total = len(source_songs)
self.import_wizard.progressBar.setMaximum(song_total)
if self.import_wizard:
self.import_wizard.progressBar.setMaximum(song_total)
song_count = 1
for song in source_songs:
self.import_wizard.incrementProgressBar(unicode(translate(
'SongsPlugin.OpenLPSongImport', 'Importing song %d of %d.')) %
(song_count, song_total))
if self.import_wizard:
self.import_wizard.incrementProgressBar(
unicode(translate('SongsPlugin.OpenLPSongImport',
'Importing song %d of %d.')) % (song_count, song_total))
new_song = Song()
new_song.title = song.title
if has_media_files and hasattr(song, 'alternate_title'):
@ -165,12 +167,11 @@ class OpenLPSongImport(SongImport):
old_titles = song.search_title.split(u'@')
if len(old_titles) > 1:
new_song.alternate_title = old_titles[1]
else:
new_song.alternate_title = u''
new_song.search_title = song.search_title.strip()
# Values will be set when cleaning the song.
new_song.search_title = u''
new_song.search_lyrics = u''
new_song.song_number = song.song_number
new_song.lyrics = song.lyrics
new_song.search_lyrics = song.search_lyrics
new_song.verse_order = song.verse_order
new_song.copyright = song.copyright
new_song.comments = song.comments
@ -179,31 +180,26 @@ class OpenLPSongImport(SongImport):
for author in song.authors:
existing_author = self.manager.get_object_filtered(
Author, Author.display_name == author.display_name)
if existing_author:
new_song.authors.append(existing_author)
else:
new_song.authors.append(Author.populate(
if existing_author is None:
existing_author = Author.populate(
first_name=author.first_name,
last_name=author.last_name,
display_name=author.display_name))
if not new_song.authors:
add_author_unknown(self.manager, new_song)
display_name=author.display_name)
new_song.authors.append(existing_author)
if song.book:
existing_song_book = self.manager.get_object_filtered(
Book, Book.name == song.book.name)
if existing_song_book:
new_song.book = existing_song_book
else:
new_song.book = Book.populate(name=song.book.name,
if existing_song_book is None:
existing_song_book = Book.populate(name=song.book.name,
publisher=song.book.publisher)
new_song.book = existing_song_book
if song.topics:
for topic in song.topics:
existing_topic = self.manager.get_object_filtered(
Topic, Topic.name == topic.name)
if existing_topic:
new_song.topics.append(existing_topic)
else:
new_song.topics.append(Topic.populate(name=topic.name))
if existing_topic is None:
existing_topic = Topic.populate(name=topic.name)
new_song.topics.append(existing_topic)
# if has_media_files:
# if song.media_files:
# for media_file in song.media_files:
@ -215,6 +211,7 @@ class OpenLPSongImport(SongImport):
# else:
# new_song.media_files.append(MediaFile.populate(
# file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song)
song_count += 1
if self.stop_import_flag:

View File

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

View File

@ -29,7 +29,7 @@ import re
from PyQt4 import QtCore
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.ui import SongStrings
from openlp.plugins.songs.lib.xml import SongXML
@ -62,6 +62,7 @@ class SongImport(QtCore.QObject):
else:
raise KeyError(u'Keyword arguments "filename[s]" not supplied.')
log.debug(self.import_source)
self.import_wizard = None
self.song = None
self.stop_import_flag = False
self.set_defaults()
@ -244,12 +245,6 @@ class SongImport(QtCore.QObject):
else:
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):
"""
All fields have been set to this song. Write the song to disk.
@ -258,11 +253,11 @@ class SongImport(QtCore.QObject):
song = Song()
song.title = self.title
song.alternate_title = self.alternate_title
song.search_title = self.remove_punctuation(self.title).lower() \
+ '@' + self.remove_punctuation(self.alternate_title).lower()
song.search_title = song.search_title.strip()
song.song_number = self.song_number
# Values will be set when cleaning the song.
song.search_title = u''
song.search_lyrics = u''
song.verse_order = u''
song.song_number = self.song_number
verses_changed_to_other = {}
sxml = SongXML()
other_count = 1
@ -279,8 +274,6 @@ class SongImport(QtCore.QObject):
new_verse_def)
verse_def = new_verse_def
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')
if not len(self.verse_order_list) and \
self.verse_order_list_generated_useful:
@ -302,9 +295,6 @@ class SongImport(QtCore.QObject):
last_name=authortext.split(u' ')[-1],
first_name=u' '.join(authortext.split(u' ')[:-1]))
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:
media_file = self.manager.get_object_filtered(MediaFile,
MediaFile.file_name == filename)
@ -325,6 +315,7 @@ class SongImport(QtCore.QObject):
if topic is None:
topic = Topic.populate(name=topictext)
song.topics.append(topic)
clean_song(self.manager, song)
self.manager.save_object(song)
self.set_defaults()

View File

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

View File

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

View File

@ -25,17 +25,19 @@
###############################################################################
import logging
import re
import os
from tempfile import gettempdir
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.ui import UiStrings
from openlp.plugins.songs.lib import add_author_unknown, SongMediaItem, \
SongsTab, SongXML
from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab
from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
log = logging.getLogger(__name__)
@ -58,7 +60,6 @@ class SongsPlugin(Plugin):
self.manager = Manager(u'songs', init_schema)
self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path)
self.whitespace = re.compile(r'\W+', re.UNICODE)
def initialise(self):
log.info(u'Songs Initialising')
@ -137,38 +138,18 @@ class SongsPlugin(Plugin):
Rebuild each song.
"""
maxSongs = self.manager.get_object_count(Song)
if maxSongs == 0:
return
progressDialog = QtGui.QProgressDialog(
translate('SongsPlugin', 'Reindexing songs...'), UiStrings.Cancel,
0, maxSongs + 1, self.formparent)
0, maxSongs, self.formparent)
progressDialog.setWindowModality(QtCore.Qt.WindowModal)
songs = self.manager.get_all_objects(Song)
counter = 0
for song in songs:
counter += 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)
for number, song in enumerate(songs):
clean_song(self.manager, song)
progressDialog.setValue(number + 1)
self.manager.save_objects(songs)
progressDialog.setValue(counter + 1)
self.mediaItem.displayResultsSong(
self.manager.get_all_objects(Song, order_by_ref=Song.search_title))
self.mediaItem.onSearchTextButtonClick()
def onSongImportItemClicked(self):
if self.mediaItem:
@ -179,10 +160,9 @@ class SongsPlugin(Plugin):
self.mediaItem.onExportClick()
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 '
'manage songs.')
return about_text
def usesTheme(self, theme):
"""
@ -244,6 +224,34 @@ class SongsPlugin(Plugin):
}
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):
"""
Time to tidy up on exit