forked from openlp/openlp
head
This commit is contained in:
commit
4afd104965
41
openlp.pyw
41
openlp.pyw
|
@ -29,12 +29,14 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
from traceback import format_exception
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.lib import Receiver
|
from openlp.core.lib import Receiver
|
||||||
from openlp.core.resources import qInitResources
|
from openlp.core.resources import qInitResources
|
||||||
from openlp.core.ui.mainwindow import MainWindow
|
from openlp.core.ui.mainwindow import MainWindow
|
||||||
|
from openlp.core.ui.exceptionform import ExceptionForm
|
||||||
from openlp.core.ui import SplashScreen, ScreenList
|
from openlp.core.ui import SplashScreen, ScreenList
|
||||||
from openlp.core.utils import AppLocation, LanguageManager, VersionThread
|
from openlp.core.utils import AppLocation, LanguageManager, VersionThread
|
||||||
|
|
||||||
|
@ -129,11 +131,11 @@ class OpenLP(QtGui.QApplication):
|
||||||
screens = ScreenList()
|
screens = ScreenList()
|
||||||
# Decide how many screens we have and their size
|
# Decide how many screens we have and their size
|
||||||
for screen in xrange(0, self.desktop().numScreens()):
|
for screen in xrange(0, self.desktop().numScreens()):
|
||||||
|
size = self.desktop().screenGeometry(screen);
|
||||||
screens.add_screen({u'number': screen,
|
screens.add_screen({u'number': screen,
|
||||||
u'size': self.desktop().availableGeometry(screen),
|
u'size': size,
|
||||||
u'primary': (self.desktop().primaryScreen() == screen)})
|
u'primary': (self.desktop().primaryScreen() == screen)})
|
||||||
log.info(u'Screen %d found with resolution %s',
|
log.info(u'Screen %d found with resolution %s', screen, size)
|
||||||
screen, self.desktop().availableGeometry(screen))
|
|
||||||
# start the main app window
|
# start the main app window
|
||||||
self.mainWindow = MainWindow(screens, app_version)
|
self.mainWindow = MainWindow(screens, app_version)
|
||||||
self.mainWindow.show()
|
self.mainWindow.show()
|
||||||
|
@ -144,6 +146,16 @@ class OpenLP(QtGui.QApplication):
|
||||||
VersionThread(self.mainWindow, app_version).start()
|
VersionThread(self.mainWindow, app_version).start()
|
||||||
return self.exec_()
|
return self.exec_()
|
||||||
|
|
||||||
|
def hookException(self, exctype, value, traceback):
|
||||||
|
if not hasattr(self, u'mainWindow'):
|
||||||
|
log.exception(''.join(format_exception(exctype, value, traceback)))
|
||||||
|
return
|
||||||
|
if not hasattr(self, u'exceptionForm'):
|
||||||
|
self.exceptionForm = ExceptionForm(self.mainWindow)
|
||||||
|
self.exceptionForm.exceptionTextEdit.setPlainText(
|
||||||
|
''.join(format_exception(exctype, value, traceback)))
|
||||||
|
self.exceptionForm.exec_()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
The main function which parses command line options and then runs
|
The main function which parses command line options and then runs
|
||||||
|
@ -152,16 +164,16 @@ def main():
|
||||||
# Set up command line options.
|
# Set up command line options.
|
||||||
usage = u'Usage: %prog [options] [qt-options]'
|
usage = u'Usage: %prog [options] [qt-options]'
|
||||||
parser = OptionParser(usage=usage)
|
parser = OptionParser(usage=usage)
|
||||||
parser.add_option("-l", "--log-level", dest="loglevel",
|
parser.add_option(u'-e', u'--no-error-form', dest=u'no_error_form',
|
||||||
default="warning", metavar="LEVEL",
|
action=u'store_true', help=u'Disable the error notification form.')
|
||||||
help="Set logging to LEVEL level. Valid values are "
|
parser.add_option(u'-l', u'--log-level', dest=u'loglevel',
|
||||||
"\"debug\", \"info\", \"warning\".")
|
default=u'warning', metavar=u'LEVEL', help=u'Set logging to LEVEL '
|
||||||
parser.add_option("-p", "--portable", dest="portable",
|
u'level. Valid values are "debug", "info", "warning".')
|
||||||
action="store_true",
|
parser.add_option(u'-p', u'--portable', dest=u'portable',
|
||||||
help="Specify if this should be run as a portable app, "
|
action=u'store_true', help=u'Specify if this should be run as a '
|
||||||
"off a USB flash drive.")
|
u'portable app, off a USB flash drive (not implemented).')
|
||||||
parser.add_option("-s", "--style", dest="style",
|
parser.add_option(u'-s', u'--style', dest=u'style',
|
||||||
help="Set the Qt4 style (passed directly to Qt4).")
|
help=u'Set the Qt4 style (passed directly to Qt4).')
|
||||||
# Set up logging
|
# Set up logging
|
||||||
log_path = AppLocation.get_directory(AppLocation.CacheDir)
|
log_path = AppLocation.get_directory(AppLocation.CacheDir)
|
||||||
if not os.path.exists(log_path):
|
if not os.path.exists(log_path):
|
||||||
|
@ -194,7 +206,8 @@ def main():
|
||||||
language = LanguageManager.get_language()
|
language = LanguageManager.get_language()
|
||||||
appTranslator = LanguageManager.get_translator(language)
|
appTranslator = LanguageManager.get_translator(language)
|
||||||
app.installTranslator(appTranslator)
|
app.installTranslator(appTranslator)
|
||||||
|
if not options.no_error_form:
|
||||||
|
sys.excepthook = app.hookException
|
||||||
sys.exit(app.run())
|
sys.exit(app.run())
|
||||||
|
|
||||||
if __name__ == u'__main__':
|
if __name__ == u'__main__':
|
||||||
|
|
|
@ -38,62 +38,47 @@ log = logging.getLogger(__name__)
|
||||||
# TODO make external and configurable in alpha 4 via a settings dialog
|
# TODO make external and configurable in alpha 4 via a settings dialog
|
||||||
html_expands = []
|
html_expands = []
|
||||||
|
|
||||||
html_expands.append({u'desc':u'Red', u'start tag':u'{r}', \
|
html_expands.append({u'desc':u'Red', u'start tag':u'{r}',
|
||||||
u'start html':u'<font color=red>', \
|
u'start html':u'<span style="-webkit-text-fill-color:red">',
|
||||||
u'end tag':u'{/r}', u'end html':u'</font>', \
|
u'end tag':u'{/r}', u'end html':u'</span>', u'protected':False})
|
||||||
u'protected':False})
|
html_expands.append({u'desc':u'Black', u'start tag':u'{b}',
|
||||||
html_expands.append({u'desc':u'Black', u'start tag':u'{b}', \
|
u'start html':u'<span style="-webkit-text-fill-color:black">',
|
||||||
u'start html':u'<font color=black>', \
|
u'end tag':u'{/b}', u'end html':u'</span>', u'protected':False})
|
||||||
u'end tag':u'{/b}', u'end html':u'</font>', \
|
html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}',
|
||||||
u'protected':False})
|
u'start html':u'<span style="-webkit-text-fill-color:blue">',
|
||||||
html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}', \
|
u'end tag':u'{/bl}', u'end html':u'</span>', u'protected':False})
|
||||||
u'start html':u'<font color=blue>', \
|
html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}',
|
||||||
u'end tag':u'{/bl}', u'end html':u'</font>', \
|
u'start html':u'<span style="-webkit-text-fill-color:yellow">',
|
||||||
u'protected':False})
|
u'end tag':u'{/y}', u'end html':u'</span>', u'protected':False})
|
||||||
html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}', \
|
html_expands.append({u'desc':u'Green', u'start tag':u'{g}',
|
||||||
u'start html':u'<font color=yellow>', \
|
u'start html':u'<span style="-webkit-text-fill-color:green">',
|
||||||
u'end tag':u'{/y}', u'end html':u'</font>', \
|
u'end tag':u'{/g}', u'end html':u'</span>', u'protected':False})
|
||||||
u'protected':False})
|
html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}',
|
||||||
html_expands.append({u'desc':u'Green', u'start tag':u'{g}', \
|
u'start html':u'<span style="-webkit-text-fill-color:#CC33CC">',
|
||||||
u'start html':u'<font color=green>', \
|
u'end tag':u'{/pk}', u'end html':u'</span>', u'protected':False})
|
||||||
u'end tag':u'{/g}', u'end html':u'</font>', \
|
html_expands.append({u'desc':u'Orange', u'start tag':u'{o}',
|
||||||
u'protected':False})
|
u'start html':u'<span style="-webkit-text-fill-color:#CC0033">',
|
||||||
html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}', \
|
u'end tag':u'{/o}', u'end html':u'</span>', u'protected':False})
|
||||||
u'start html':u'<font color=#CC33CC>', \
|
html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}',
|
||||||
u'end tag':u'{/pk}', u'end html':u'</font>', \
|
u'start html':u'<span style="-webkit-text-fill-color:#9900FF">',
|
||||||
u'protected':False})
|
u'end tag':u'{/pp}', u'end html':u'</span>', u'protected':False})
|
||||||
html_expands.append({u'desc':u'Orange', u'start tag':u'{o}', \
|
html_expands.append({u'desc':u'White', u'start tag':u'{w}',
|
||||||
u'start html':u'<font color=#CC0033>', \
|
u'start html':u'<span style="-webkit-text-fill-color:white">',
|
||||||
u'end tag':u'{/o}', u'end html':u'</font>', \
|
u'end tag':u'{/w}', u'end html':u'</span>', u'protected':False})
|
||||||
u'protected':False})
|
html_expands.append({u'desc':u'Superscript', u'start tag':u'{su}',
|
||||||
html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}', \
|
u'start html':u'<sup>', u'end tag':u'{/su}', u'end html':u'</sup>',
|
||||||
u'start html':u'<font color=#9900FF>', \
|
|
||||||
u'end tag':u'{/pp}', u'end html':u'</font>', \
|
|
||||||
u'protected':False})
|
|
||||||
html_expands.append({u'desc':u'White', u'start tag':u'{w}', \
|
|
||||||
u'start html':u'<font color=white>', \
|
|
||||||
u'end tag':u'{/w}', u'end html':u'</font>', \
|
|
||||||
u'protected':False})
|
|
||||||
html_expands.append({u'desc':u'Superscript', u'start tag':u'{su}', \
|
|
||||||
u'start html':u'<sup>', \
|
|
||||||
u'end tag':u'{/su}', u'end html':u'</sup>', \
|
|
||||||
u'protected':True})
|
u'protected':True})
|
||||||
html_expands.append({u'desc':u'Subscript', u'start tag':u'{sb}', \
|
html_expands.append({u'desc':u'Subscript', u'start tag':u'{sb}',
|
||||||
u'start html':u'<sub>', \
|
u'start html':u'<sub>', u'end tag':u'{/sb}', u'end html':u'</sub>',
|
||||||
u'end tag':u'{/sb}', u'end html':u'</sub>', \
|
|
||||||
u'protected':True})
|
u'protected':True})
|
||||||
html_expands.append({u'desc':u'Paragraph', u'start tag':u'{p}', \
|
html_expands.append({u'desc':u'Paragraph', u'start tag':u'{p}',
|
||||||
u'start html':u'<p>', \
|
u'start html':u'<p>', u'end tag':u'{/p}', u'end html':u'</p>',
|
||||||
u'end tag':u'{/p}', u'end html':u'</p>', \
|
|
||||||
u'protected':True})
|
u'protected':True})
|
||||||
html_expands.append({u'desc':u'Bold', u'start tag':u'{st}', \
|
html_expands.append({u'desc':u'Bold', u'start tag':u'{st}',
|
||||||
u'start html':u'<strong>', \
|
u'start html':u'<strong>', u'end tag':u'{/st}', u'end html':u'</strong>',
|
||||||
u'end tag':u'{/st}', \
|
|
||||||
u'end html':u'</strong>', \
|
|
||||||
u'protected':True})
|
u'protected':True})
|
||||||
html_expands.append({u'desc':u'Italics', u'start tag':u'{it}', \
|
html_expands.append({u'desc':u'Italics', u'start tag':u'{it}',
|
||||||
u'start html':u'<em>', \
|
u'start html':u'<em>', u'end tag':u'{/it}', u'end html':u'</em>',
|
||||||
u'end tag':u'{/it}', u'end html':u'</em>', \
|
|
||||||
u'protected':True})
|
u'protected':True})
|
||||||
|
|
||||||
def translate(context, text, comment=None):
|
def translate(context, text, comment=None):
|
||||||
|
@ -316,6 +301,7 @@ def expand_tags(text):
|
||||||
text = text.replace(tag[u'end tag'], tag[u'end html'])
|
text = text.replace(tag[u'end tag'], tag[u'end html'])
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
from spelltextedit import SpellTextEdit
|
||||||
from eventreceiver import Receiver
|
from eventreceiver import Receiver
|
||||||
from settingsmanager import SettingsManager
|
from settingsmanager import SettingsManager
|
||||||
from plugin import PluginStatus, Plugin
|
from plugin import PluginStatus, Plugin
|
||||||
|
@ -324,7 +310,8 @@ from settingstab import SettingsTab
|
||||||
from serviceitem import ServiceItem
|
from serviceitem import ServiceItem
|
||||||
from serviceitem import ServiceItemType
|
from serviceitem import ServiceItemType
|
||||||
from serviceitem import ItemCapabilities
|
from serviceitem import ItemCapabilities
|
||||||
from htmlbuilder import build_html
|
from htmlbuilder import build_html, build_lyrics_format_css, \
|
||||||
|
build_lyrics_outline_css
|
||||||
from toolbar import OpenLPToolbar
|
from toolbar import OpenLPToolbar
|
||||||
from dockwidget import OpenLPDockWidget
|
from dockwidget import OpenLPDockWidget
|
||||||
from theme import ThemeLevel, ThemeXML
|
from theme import ThemeLevel, ThemeXML
|
||||||
|
|
|
@ -24,8 +24,13 @@
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from PyQt4 import QtWebKit
|
||||||
|
|
||||||
from openlp.core.lib import image_to_byte
|
from openlp.core.lib import image_to_byte
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
HTMLSRC = u"""
|
HTMLSRC = u"""
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -35,11 +40,12 @@ HTMLSRC = u"""
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-color: black;
|
%s;
|
||||||
}
|
}
|
||||||
.dim {
|
.size {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
|
@ -51,6 +57,9 @@ body {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#image {
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
#video {
|
#video {
|
||||||
z-index:2;
|
z-index:2;
|
||||||
}
|
}
|
||||||
|
@ -72,17 +81,14 @@ body {
|
||||||
</style>
|
</style>
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
var timer = null;
|
var timer = null;
|
||||||
|
var video_timer = null;
|
||||||
var transition = %s;
|
var transition = %s;
|
||||||
|
|
||||||
function show_video(state, path, volume, loop){
|
function show_video(state, path, volume, loop){
|
||||||
var vid = document.getElementById('video');
|
var vid = document.getElementById('video');
|
||||||
if(path != null)
|
if(path != null){
|
||||||
vid.src = path;
|
vid.src = path;
|
||||||
if(loop != null){
|
vid.load();
|
||||||
if(loop)
|
|
||||||
vid.loop = 'loop';
|
|
||||||
else
|
|
||||||
vid.loop = '';
|
|
||||||
}
|
}
|
||||||
if(volume != null){
|
if(volume != null){
|
||||||
vid.volume = volume;
|
vid.volume = volume;
|
||||||
|
@ -91,23 +97,55 @@ body {
|
||||||
case 'play':
|
case 'play':
|
||||||
vid.play();
|
vid.play();
|
||||||
vid.style.display = 'block';
|
vid.style.display = 'block';
|
||||||
|
if(loop)
|
||||||
|
video_timer = setInterval('video_loop()', 200);
|
||||||
break;
|
break;
|
||||||
case 'pause':
|
case 'pause':
|
||||||
|
if(video_timer!=null){
|
||||||
|
clearInterval(video_timer);
|
||||||
|
video_timer = null;
|
||||||
|
}
|
||||||
vid.pause();
|
vid.pause();
|
||||||
vid.style.display = 'block';
|
vid.style.display = 'block';
|
||||||
break;
|
break;
|
||||||
case 'stop':
|
case 'stop':
|
||||||
|
if(video_timer!=null){
|
||||||
|
clearInterval(video_timer);
|
||||||
|
video_timer = null;
|
||||||
|
}
|
||||||
vid.pause();
|
vid.pause();
|
||||||
vid.style.display = 'none';
|
vid.style.display = 'none';
|
||||||
|
vid.load();
|
||||||
break;
|
break;
|
||||||
case 'close':
|
case 'close':
|
||||||
|
if(video_timer!=null){
|
||||||
|
clearInterval(video_timer);
|
||||||
|
video_timer = null;
|
||||||
|
}
|
||||||
vid.pause();
|
vid.pause();
|
||||||
vid.style.display = 'none';
|
vid.style.display = 'none';
|
||||||
vid.src = '';
|
vid.src = '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function video_loop(){
|
||||||
|
// The preferred method would be to use the video tag loop attribute
|
||||||
|
// But QtWebKit doesn't support this. Neither does it support the
|
||||||
|
// onended event, hence the setInterval()
|
||||||
|
// In addition, setting the currentTime attribute to zero to restart
|
||||||
|
// the video raises an INDEX_SIZE_ERROR: DOM Exception 1
|
||||||
|
// To complicate it further, sometimes vid.currentTime stops
|
||||||
|
// slightly short of vid.duration and vid.ended is intermittent!
|
||||||
|
//
|
||||||
|
// Note, currently the background may go black between loops. Not
|
||||||
|
// desirable. Need to investigate using two <video>'s, and hiding/
|
||||||
|
// preloading one, and toggle between the two when looping.
|
||||||
|
var vid = document.getElementById('video');
|
||||||
|
if(vid.ended||vid.currentTime+0.2>=vid.duration){
|
||||||
|
vid.load();
|
||||||
|
vid.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
function show_image(src){
|
function show_image(src){
|
||||||
var img = document.getElementById('image');
|
var img = document.getElementById('image');
|
||||||
img.src = src;
|
img.src = src;
|
||||||
|
@ -136,8 +174,13 @@ body {
|
||||||
}
|
}
|
||||||
document.getElementById('black').style.display = black;
|
document.getElementById('black').style.display = black;
|
||||||
document.getElementById('lyricsmain').style.visibility = lyrics;
|
document.getElementById('lyricsmain').style.visibility = lyrics;
|
||||||
document.getElementById('lyricsoutline').style.visibility = lyrics;
|
document.getElementById('image').style.visibility = lyrics;
|
||||||
document.getElementById('lyricsshadow').style.visibility = lyrics;
|
outline = document.getElementById('lyricsoutline')
|
||||||
|
if(outline!=null)
|
||||||
|
outline.style.visibility = lyrics;
|
||||||
|
shadow = document.getElementById('lyricsshadow')
|
||||||
|
if(shadow!=null)
|
||||||
|
shadow.style.visibility = lyrics;
|
||||||
document.getElementById('footer').style.visibility = lyrics;
|
document.getElementById('footer').style.visibility = lyrics;
|
||||||
var vid = document.getElementById('video');
|
var vid = document.getElementById('video');
|
||||||
if(vid.src != ''){
|
if(vid.src != ''){
|
||||||
|
@ -156,7 +199,7 @@ body {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(position == ''){
|
if(position == ''){
|
||||||
position = window.getComputedStyle(text, '').verticalAlign;
|
position = getComputedStyle(text, '').verticalAlign;
|
||||||
}
|
}
|
||||||
switch(position)
|
switch(position)
|
||||||
{
|
{
|
||||||
|
@ -181,116 +224,67 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_text(newtext){
|
function show_text(newtext){
|
||||||
var text1 = document.getElementById('lyricsmain');
|
|
||||||
var texto1 = document.getElementById('lyricsoutline');
|
|
||||||
var texts1 = document.getElementById('lyricsshadow');
|
|
||||||
if(!transition){
|
|
||||||
text1.innerHTML = newtext;
|
|
||||||
texto1.innerHTML = newtext;
|
|
||||||
texts1.innerHTML = newtext;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var text2 = document.getElementById('lyricsmain2');
|
|
||||||
var texto2 = document.getElementById('lyricsoutline2');
|
|
||||||
var texts2 = document.getElementById('lyricsshadow2');
|
|
||||||
if((text2.style.opacity == '')||(parseFloat(text2.style.opacity) < 0.5))
|
|
||||||
{
|
|
||||||
text2.innerHTML = text1.innerHTML;
|
|
||||||
text2.style.opacity = text1.style.opacity;
|
|
||||||
texto2.innerHTML = text1.innerHTML;
|
|
||||||
texto2.style.opacity = text1.style.opacity;
|
|
||||||
texts2.innerHTML = text1.innerHTML;
|
|
||||||
texts2.style.opacity = text1.style.opacity;
|
|
||||||
}
|
|
||||||
text1.style.opacity = 0;
|
|
||||||
text1.innerHTML = newtext;
|
|
||||||
texto1.style.opacity = 0;
|
|
||||||
texto1.innerHTML = newtext;
|
|
||||||
texts1.style.opacity = 0;
|
|
||||||
texts1.innerHTML = newtext;
|
|
||||||
// For performance reasons, we'll not animate the shadow for now
|
|
||||||
texts2.style.opacity = 0;
|
|
||||||
if(timer != null)
|
if(timer != null)
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = setTimeout('text_fade()', 50);
|
text_fade('lyricsmain', newtext);
|
||||||
|
text_fade('lyricsoutline', newtext);
|
||||||
|
text_fade('lyricsshadow', newtext);
|
||||||
|
if(text_opacity()==1) return;
|
||||||
|
timer = setTimeout(function(){
|
||||||
|
show_text(newtext);
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function text_fade(){
|
function text_fade(id, newtext){
|
||||||
var text1 = document.getElementById('lyricsmain');
|
/*
|
||||||
var texto1 = document.getElementById('lyricsoutline');
|
Using -webkit-transition: opacity 1s linear; would have been preferred
|
||||||
var texts1 = document.getElementById('lyricsshadow');
|
but it isn't currently quick enough when animating multiple layers of
|
||||||
var text2 = document.getElementById('lyricsmain2');
|
large areas of large text. Therefore do it manually as best we can.
|
||||||
var texto2 = document.getElementById('lyricsoutline2');
|
Hopefully in the future we can revisit and do more interesting
|
||||||
var texts2 = document.getElementById('lyricsshadow2');
|
transitions using -webkit-transition and -webkit-transform.
|
||||||
if(parseFloat(text1.style.opacity) < 1){
|
However we need to ensure interrupted transitions (quickly change 2
|
||||||
text1.style.opacity = parseFloat(text1.style.opacity) + 0.1;
|
slides) still looks pretty and is zippy.
|
||||||
texto1.style.opacity = parseFloat(texto1.style.opacity) + 0.1;
|
*/
|
||||||
// Don't animate shadow (performance)
|
var text = document.getElementById(id);
|
||||||
//texts1.style.opacity = parseFloat(texts1.style.opacity) + 0.1;
|
if(text==null) return;
|
||||||
|
if(!transition){
|
||||||
|
text.innerHTML = newtext;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if(parseFloat(text2.style.opacity) > 0){
|
if(newtext==text.innerHTML){
|
||||||
text2.style.opacity = parseFloat(text2.style.opacity) - 0.1;
|
text.style.opacity = parseFloat(text.style.opacity) + 0.3;
|
||||||
texto2.style.opacity = parseFloat(texto2.style.opacity) - 0.1;
|
if(text.style.opacity>0.7)
|
||||||
// Don't animate shadow (performance)
|
text.style.opacity = 1;
|
||||||
//texts2.style.opacity = parseFloat(texts2.style.opacity) - 0.1;
|
|
||||||
}
|
|
||||||
if((parseFloat(text1.style.opacity) < 1) ||
|
|
||||||
(parseFloat(text2.style.opacity) > 0)){
|
|
||||||
t = setTimeout('text_fade()', 50);
|
|
||||||
} else {
|
} else {
|
||||||
text1.style.opacity = 1;
|
text.style.opacity = parseFloat(text.style.opacity) - 0.3;
|
||||||
texto1.style.opacity = 1;
|
if(text.style.opacity<=0.1){
|
||||||
texts1.style.opacity = 1;
|
text.innerHTML = newtext;
|
||||||
text2.style.opacity = 0;
|
|
||||||
texto2.style.opacity = 0;
|
|
||||||
texts2.style.opacity = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function text_opacity(){
|
||||||
|
var text = document.getElementById('lyricsmain');
|
||||||
|
return getComputedStyle(text, '').opacity;
|
||||||
|
}
|
||||||
|
|
||||||
function show_text_complete(){
|
function show_text_complete(){
|
||||||
return (document.getElementById('lyricsmain').style.opacity == 1);
|
return (text_opacity()==1);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<img id="image" class="size" src="%s" />
|
||||||
Using tables, rather than div's to make use of the vertical-align style that
|
<video id="video" class="size"></video>
|
||||||
doesn't work on div's. This avoids the need to do positioning manually which
|
%s
|
||||||
could get messy when changing verses esp. with transitions
|
|
||||||
|
|
||||||
Would prefer to use a single table and make use of -webkit-text-fill-color
|
|
||||||
-webkit-text-stroke and text-shadow styles, but they have problems working/
|
|
||||||
co-operating in qwebkit. https://bugs.webkit.org/show_bug.cgi?id=43187
|
|
||||||
Therefore one table for text, one for outline and one for shadow.
|
|
||||||
-->
|
|
||||||
<table class="lyricstable lyricscommon">
|
|
||||||
<tr><td id="lyricsmain" class="lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<table class="lyricsoutlinetable lyricscommon">
|
|
||||||
<tr><td id="lyricsoutline" class="lyricsoutline lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<table class="lyricsshadowtable lyricscommon">
|
|
||||||
<tr><td id="lyricsshadow" class="lyricsshadow lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<table class="lyricstable lyricscommon">
|
|
||||||
<tr><td id="lyricsmain2" class="lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<table class="lyricsoutlinetable lyricscommon">
|
|
||||||
<tr><td id="lyricsoutline2" class="lyricsoutline lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<table class="lyricsshadowtable lyricscommon">
|
|
||||||
<tr><td id="lyricsshadow2" class="lyricsshadow lyrics"></td></tr>
|
|
||||||
</table>
|
|
||||||
<div id="alert" style="visibility:hidden;"></div>
|
|
||||||
<div id="footer" class="footer"></div>
|
<div id="footer" class="footer"></div>
|
||||||
<video class="dim" id="video"></video>
|
<div id="black" class="size"></div>
|
||||||
<div class="dim" id="black"></div>
|
<div id="alert" style="visibility:hidden;"></div>
|
||||||
<img class="dim" id="image" src="%s" />
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def build_html(item, screen, alert):
|
def build_html(item, screen, alert, islive):
|
||||||
"""
|
"""
|
||||||
Build the full web paged structure for display
|
Build the full web paged structure for display
|
||||||
|
|
||||||
|
@ -300,92 +294,245 @@ def build_html(item, screen, alert):
|
||||||
Current display information
|
Current display information
|
||||||
`alert`
|
`alert`
|
||||||
Alert display display information
|
Alert display display information
|
||||||
|
`islive`
|
||||||
|
Item is going live, rather than preview/theme building
|
||||||
"""
|
"""
|
||||||
width = screen[u'size'].width()
|
width = screen[u'size'].width()
|
||||||
height = screen[u'size'].height()
|
height = screen[u'size'].height()
|
||||||
theme = item.themedata
|
theme = item.themedata
|
||||||
|
webkitvers = webkit_version()
|
||||||
if item.bg_frame:
|
if item.bg_frame:
|
||||||
image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame)
|
image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame)
|
||||||
else:
|
else:
|
||||||
image = u''
|
image = u''
|
||||||
html = HTMLSRC % (width, height,
|
html = HTMLSRC % (build_background_css(item, width, height),
|
||||||
build_alert(alert, width),
|
width, height,
|
||||||
build_footer(item),
|
build_alert_css(alert, width),
|
||||||
build_lyrics(item),
|
build_footer_css(item, height),
|
||||||
u'true' if theme and theme.display_slideTransition \
|
build_lyrics_css(item, webkitvers),
|
||||||
|
u'true' if theme and theme.display_slideTransition and islive \
|
||||||
else u'false',
|
else u'false',
|
||||||
image)
|
image,
|
||||||
|
build_lyrics_html(item, webkitvers))
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def build_lyrics(item):
|
def webkit_version():
|
||||||
"""
|
"""
|
||||||
Build the video display div
|
Return the Webkit version in use.
|
||||||
|
Note method added relatively recently, so return 0 if prior to this
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
webkitvers = float(QtWebKit.qWebKitVersion())
|
||||||
|
log.debug(u'Webkit version = %s' % webkitvers)
|
||||||
|
except AttributeError:
|
||||||
|
webkitvers = 0
|
||||||
|
return webkitvers
|
||||||
|
|
||||||
|
def build_background_css(item, width, height):
|
||||||
|
"""
|
||||||
|
Build the background css
|
||||||
|
|
||||||
`item`
|
`item`
|
||||||
Service Item containing theme and location information
|
Service Item containing theme and location information
|
||||||
|
|
||||||
|
"""
|
||||||
|
width = int(width) / 2
|
||||||
|
theme = item.themedata
|
||||||
|
background = u'background-color: black'
|
||||||
|
if theme:
|
||||||
|
if theme.background_type == u'solid':
|
||||||
|
background = u'background-color: %s' % theme.background_color
|
||||||
|
else:
|
||||||
|
if theme.background_direction == u'horizontal':
|
||||||
|
background = \
|
||||||
|
u'background: ' \
|
||||||
|
u'-webkit-gradient(linear, left top, left bottom, ' \
|
||||||
|
'from(%s), to(%s))' % (theme.background_startColor,
|
||||||
|
theme.background_endColor)
|
||||||
|
elif theme.background_direction == u'vertical':
|
||||||
|
background = \
|
||||||
|
u'background: -webkit-gradient(linear, left top, ' \
|
||||||
|
u'right top, from(%s), to(%s))' % \
|
||||||
|
(theme.background_startColor, theme.background_endColor)
|
||||||
|
else:
|
||||||
|
background = \
|
||||||
|
u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \
|
||||||
|
u'50%%, %s, from(%s), to(%s))' % (width, width, width,
|
||||||
|
theme.background_startColor, theme.background_endColor)
|
||||||
|
return background
|
||||||
|
|
||||||
|
def build_lyrics_css(item, webkitvers):
|
||||||
|
"""
|
||||||
|
Build the lyrics display css
|
||||||
|
|
||||||
|
`item`
|
||||||
|
Service Item containing theme and location information
|
||||||
|
|
||||||
|
`webkitvers`
|
||||||
|
The version of qtwebkit we're using
|
||||||
|
|
||||||
"""
|
"""
|
||||||
style = """
|
style = """
|
||||||
.lyricscommon { position: absolute; %s }
|
.lyricstable {
|
||||||
.lyricstable { z-index:4; %s }
|
z-index:4;
|
||||||
.lyricsoutlinetable { z-index:3; %s }
|
position: absolute;
|
||||||
.lyricsshadowtable { z-index:2; %s }
|
display: table;
|
||||||
.lyrics { %s }
|
%s
|
||||||
.lyricsoutline { %s }
|
}
|
||||||
.lyricsshadow { %s }
|
.lyricscell {
|
||||||
|
display:table-cell;
|
||||||
|
word-wrap: break-word;
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsmain {
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsoutline {
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsshadow {
|
||||||
|
%s
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
theme = item.themedata
|
theme = item.themedata
|
||||||
lyricscommon = u''
|
|
||||||
lyricstable = u''
|
lyricstable = u''
|
||||||
outlinetable = u''
|
|
||||||
shadowtable = u''
|
|
||||||
lyrics = u''
|
lyrics = u''
|
||||||
outline = u'display: none;'
|
lyricsmain = u''
|
||||||
shadow = u'display: none;'
|
outline = u''
|
||||||
if theme:
|
shadow = u''
|
||||||
lyricscommon = u'width: %spx; height: %spx; word-wrap: break-word; ' \
|
if theme and item.main:
|
||||||
u'font-family: %s; font-size: %spx; color: %s; line-height: %d%%;' \
|
|
||||||
% (item.main.width(), item.main.height(), theme.font_main_name,
|
|
||||||
theme.font_main_proportion, theme.font_main_color,
|
|
||||||
100 + int(theme.font_main_line_adjustment))
|
|
||||||
lyricstable = u'left: %spx; top: %spx;' % \
|
lyricstable = u'left: %spx; top: %spx;' % \
|
||||||
(item.main.x(), item.main.y())
|
(item.main.x(), item.main.y())
|
||||||
outlinetable = u'left: %spx; top: %spx;' % \
|
lyrics = build_lyrics_format_css(theme, item.main.width(),
|
||||||
(item.main.x(), item.main.y())
|
item.main.height())
|
||||||
shadowtable = u'left: %spx; top: %spx;' % \
|
# For performance reasons we want to show as few DIV's as possible,
|
||||||
(item.main.x() + float(theme.display_shadow_size),
|
# especially when animating/transitions.
|
||||||
item.main.y() + float(theme.display_shadow_size))
|
# However some bugs in older versions of qtwebkit mean we need to
|
||||||
align = u''
|
# perform workarounds and add extra divs. Only do these when needed.
|
||||||
if theme.display_horizontalAlign == 2:
|
#
|
||||||
align = u'text-align:center;'
|
# Before 533.3 the webkit-text-fill colour wasn't displayed, only the
|
||||||
elif theme.display_horizontalAlign == 1:
|
# stroke (outline) color. So put stroke layer underneath the main text.
|
||||||
align = u'text-align:right;'
|
#
|
||||||
|
# Before 534.4 the webkit-text-stroke was sometimes out of alignment
|
||||||
|
# with the fill, or normal text. letter-spacing=1 is workaround
|
||||||
|
# https://bugs.webkit.org/show_bug.cgi?id=44403
|
||||||
|
#
|
||||||
|
# Before 534.4 the text-shadow didn't get displayed when
|
||||||
|
# webkit-text-stroke was used. So use an offset text layer underneath.
|
||||||
|
# https://bugs.webkit.org/show_bug.cgi?id=19728
|
||||||
|
if webkitvers >= 533.3:
|
||||||
|
lyricsmain += build_lyrics_outline_css(theme)
|
||||||
else:
|
else:
|
||||||
align = u'text-align:left;'
|
outline = build_lyrics_outline_css(theme)
|
||||||
if theme.display_verticalAlign == 2:
|
|
||||||
valign = u'vertical-align:bottom;'
|
|
||||||
elif theme.display_verticalAlign == 1:
|
|
||||||
valign = u'vertical-align:middle;'
|
|
||||||
else:
|
|
||||||
valign = u'vertical-align:top;'
|
|
||||||
lyrics = u'%s %s' % (align, valign)
|
|
||||||
if theme.display_outline:
|
|
||||||
lyricscommon += u' letter-spacing: 1px;'
|
|
||||||
outline = u'-webkit-text-stroke: %sem %s; ' % \
|
|
||||||
(float(theme.display_outline_size) / 16,
|
|
||||||
theme.display_outline_color)
|
|
||||||
if theme.display_shadow:
|
if theme.display_shadow:
|
||||||
shadow = u'-webkit-text-stroke: %sem %s; ' \
|
if theme.display_outline and webkitvers < 534.3:
|
||||||
u'-webkit-text-fill-color: %s; ' % \
|
shadow = u'padding-left: %spx; padding-top: %spx ' % \
|
||||||
(float(theme.display_outline_size) / 16,
|
(theme.display_shadow_size, theme.display_shadow_size)
|
||||||
theme.display_shadow_color, theme.display_shadow_color)
|
shadow += build_lyrics_outline_css(theme, True)
|
||||||
else:
|
else:
|
||||||
if theme.display_shadow:
|
lyricsmain += u' text-shadow: %s %spx %spx;' % \
|
||||||
shadow = u'color: %s;' % (theme.display_shadow_color)
|
(theme.display_shadow_color, theme.display_shadow_size,
|
||||||
lyrics_html = style % (lyricscommon, lyricstable, outlinetable,
|
theme.display_shadow_size)
|
||||||
shadowtable, lyrics, outline, shadow)
|
lyrics_css = style % (lyricstable, lyrics, lyricsmain, outline, shadow)
|
||||||
return lyrics_html
|
return lyrics_css
|
||||||
|
|
||||||
def build_footer(item):
|
def build_lyrics_outline_css(theme, is_shadow=False):
|
||||||
|
"""
|
||||||
|
Build the css which controls the theme outline
|
||||||
|
Also used by renderer for splitting verses
|
||||||
|
|
||||||
|
`theme`
|
||||||
|
Object containing theme information
|
||||||
|
|
||||||
|
`is_shadow`
|
||||||
|
If true, use the shadow colors instead
|
||||||
|
"""
|
||||||
|
if theme.display_outline:
|
||||||
|
size = float(theme.display_outline_size) / 16
|
||||||
|
if is_shadow:
|
||||||
|
fill_color = theme.display_shadow_color
|
||||||
|
outline_color = theme.display_shadow_color
|
||||||
|
else:
|
||||||
|
fill_color = theme.font_main_color
|
||||||
|
outline_color = theme.display_outline_color
|
||||||
|
return u' -webkit-text-stroke: %sem %s; ' \
|
||||||
|
u'-webkit-text-fill-color: %s; ' % (size, outline_color, fill_color)
|
||||||
|
else:
|
||||||
|
return u''
|
||||||
|
|
||||||
|
def build_lyrics_format_css(theme, width, height):
|
||||||
|
"""
|
||||||
|
Build the css which controls the theme format
|
||||||
|
Also used by renderer for splitting verses
|
||||||
|
|
||||||
|
`theme`
|
||||||
|
Object containing theme information
|
||||||
|
|
||||||
|
`width`
|
||||||
|
Width of the lyrics block
|
||||||
|
|
||||||
|
`height`
|
||||||
|
Height of the lyrics block
|
||||||
|
|
||||||
|
"""
|
||||||
|
if theme.display_horizontalAlign == 2:
|
||||||
|
align = u'center'
|
||||||
|
elif theme.display_horizontalAlign == 1:
|
||||||
|
align = u'right'
|
||||||
|
else:
|
||||||
|
align = u'left'
|
||||||
|
if theme.display_verticalAlign == 2:
|
||||||
|
valign = u'bottom'
|
||||||
|
elif theme.display_verticalAlign == 1:
|
||||||
|
valign = u'middle'
|
||||||
|
else:
|
||||||
|
valign = u'top'
|
||||||
|
lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \
|
||||||
|
'text-align: %s; vertical-align: %s; font-family: %s; ' \
|
||||||
|
'font-size: %spt; color: %s; line-height: %d%%; ' \
|
||||||
|
'margin:0; padding:0; width: %spx; height: %spx; ' % \
|
||||||
|
(align, valign, theme.font_main_name, theme.font_main_proportion,
|
||||||
|
theme.font_main_color, 100 + int(theme.font_main_line_adjustment),
|
||||||
|
width, height)
|
||||||
|
if theme.display_outline:
|
||||||
|
if webkit_version() < 534.3:
|
||||||
|
lyrics += u' letter-spacing: 1px;'
|
||||||
|
if theme.font_main_italics:
|
||||||
|
lyrics += u' font-style:italic; '
|
||||||
|
if theme.font_main_weight == u'Bold':
|
||||||
|
lyrics += u' font-weight:bold; '
|
||||||
|
return lyrics
|
||||||
|
|
||||||
|
def build_lyrics_html(item, webkitvers):
|
||||||
|
"""
|
||||||
|
Build the HTML required to show the lyrics
|
||||||
|
|
||||||
|
`item`
|
||||||
|
Service Item containing theme and location information
|
||||||
|
|
||||||
|
`webkitvers`
|
||||||
|
The version of qtwebkit we're using
|
||||||
|
"""
|
||||||
|
# Bugs in some versions of QtWebKit mean we sometimes need additional
|
||||||
|
# divs for outline and shadow, since the CSS doesn't work.
|
||||||
|
# To support vertical alignment middle and bottom, nested div's using
|
||||||
|
# display:table/display:table-cell are required for each lyric block.
|
||||||
|
lyrics = u''
|
||||||
|
theme = item.themedata
|
||||||
|
if webkitvers < 534.4 and theme and theme.display_outline:
|
||||||
|
lyrics += u'<div class="lyricstable">' \
|
||||||
|
u'<div id="lyricsshadow" style="opacity:1" ' \
|
||||||
|
u'class="lyricscell lyricsshadow"></div></div>'
|
||||||
|
if webkitvers < 533.3:
|
||||||
|
lyrics += u'<div class="lyricstable">' \
|
||||||
|
u'<div id="lyricsoutline" style="opacity:1" ' \
|
||||||
|
u'class="lyricscell lyricsoutline"></div></div>'
|
||||||
|
lyrics += u'<div class="lyricstable">' \
|
||||||
|
u'<div id="lyricsmain" style="opacity:1" ' \
|
||||||
|
u'class="lyricscell lyricsmain"></div></div>'
|
||||||
|
return lyrics
|
||||||
|
|
||||||
|
def build_footer_css(item, height):
|
||||||
"""
|
"""
|
||||||
Build the display of the item footer
|
Build the display of the item footer
|
||||||
|
|
||||||
|
@ -394,29 +541,24 @@ def build_footer(item):
|
||||||
"""
|
"""
|
||||||
style = """
|
style = """
|
||||||
left: %spx;
|
left: %spx;
|
||||||
top: %spx;
|
bottom: %spx;
|
||||||
width: %spx;
|
width: %spx;
|
||||||
height: %spx;
|
|
||||||
font-family: %s;
|
font-family: %s;
|
||||||
font-size: %spx;
|
font-size: %spt;
|
||||||
color: %s;
|
color: %s;
|
||||||
text-align: %s;
|
text-align: left;
|
||||||
|
white-space:nowrap;
|
||||||
"""
|
"""
|
||||||
theme = item.themedata
|
theme = item.themedata
|
||||||
if not theme:
|
if not theme or not item.footer:
|
||||||
return u''
|
return u''
|
||||||
if theme.display_horizontalAlign == 2:
|
bottom = height - int(item.footer.y()) - int(item.footer.height())
|
||||||
align = u'center'
|
lyrics_html = style % (item.footer.x(), bottom,
|
||||||
elif theme.display_horizontalAlign == 1:
|
item.footer.width(), theme.font_footer_name,
|
||||||
align = u'right'
|
theme.font_footer_proportion, theme.font_footer_color)
|
||||||
else:
|
|
||||||
align = u'left'
|
|
||||||
lyrics_html = style % (item.footer.x(), item.footer.y(),
|
|
||||||
item.footer.width(), item.footer.height(), theme.font_footer_name,
|
|
||||||
theme.font_footer_proportion, theme.font_footer_color, align)
|
|
||||||
return lyrics_html
|
return lyrics_html
|
||||||
|
|
||||||
def build_alert(alertTab, width):
|
def build_alert_css(alertTab, width):
|
||||||
"""
|
"""
|
||||||
Build the display of the footer
|
Build the display of the footer
|
||||||
|
|
||||||
|
@ -424,10 +566,10 @@ def build_alert(alertTab, width):
|
||||||
Details from the Alert tab for fonts etc
|
Details from the Alert tab for fonts etc
|
||||||
"""
|
"""
|
||||||
style = """
|
style = """
|
||||||
width: %s;
|
width: %spx;
|
||||||
vertical-align: %s;
|
vertical-align: %s;
|
||||||
font-family: %s;
|
font-family: %s;
|
||||||
font-size: %spx;
|
font-size: %spt;
|
||||||
color: %s;
|
color: %s;
|
||||||
background-color: %s;
|
background-color: %s;
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -432,7 +432,7 @@ class MediaManagerItem(QtGui.QWidget):
|
||||||
raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to '
|
raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to '
|
||||||
u'be defined by the plugin')
|
u'be defined by the plugin')
|
||||||
|
|
||||||
def generateSlideData(self, service_item, item):
|
def generateSlideData(self, service_item, item=None):
|
||||||
raise NotImplementedError(u'MediaManagerItem.generateSlideData needs '
|
raise NotImplementedError(u'MediaManagerItem.generateSlideData needs '
|
||||||
u'to be defined by the plugin')
|
u'to be defined by the plugin')
|
||||||
|
|
||||||
|
|
|
@ -29,9 +29,10 @@ format it for the output display.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore, QtWebKit
|
||||||
|
|
||||||
from openlp.core.lib import resize_image, expand_tags
|
from openlp.core.lib import resize_image, expand_tags, \
|
||||||
|
build_lyrics_format_css, build_lyrics_outline_css
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -47,26 +48,13 @@ class Renderer(object):
|
||||||
Initialise the renderer.
|
Initialise the renderer.
|
||||||
"""
|
"""
|
||||||
self._rect = None
|
self._rect = None
|
||||||
self._debug = False
|
|
||||||
self._display_shadow_size_footer = 0
|
|
||||||
self._display_outline_size_footer = 0
|
|
||||||
self.theme_name = None
|
self.theme_name = None
|
||||||
self._theme = None
|
self._theme = None
|
||||||
self._bg_image_filename = None
|
self._bg_image_filename = None
|
||||||
self.frame = None
|
self.frame = None
|
||||||
self.frame_opaque = None
|
|
||||||
self.bg_frame = None
|
self.bg_frame = None
|
||||||
self.bg_image = None
|
self.bg_image = None
|
||||||
|
|
||||||
def set_debug(self, debug):
|
|
||||||
"""
|
|
||||||
Set the debug mode of the renderer.
|
|
||||||
|
|
||||||
``debug``
|
|
||||||
The debug mode.
|
|
||||||
"""
|
|
||||||
self._debug = debug
|
|
||||||
|
|
||||||
def set_theme(self, theme):
|
def set_theme(self, theme):
|
||||||
"""
|
"""
|
||||||
Set the theme to be used.
|
Set the theme to be used.
|
||||||
|
@ -82,17 +70,7 @@ class Renderer(object):
|
||||||
self.theme_name = theme.theme_name
|
self.theme_name = theme.theme_name
|
||||||
if theme.background_type == u'image':
|
if theme.background_type == u'image':
|
||||||
if theme.background_filename:
|
if theme.background_filename:
|
||||||
self.set_bg_image(theme.background_filename)
|
self._bg_image_filename = unicode(theme.background_filename)
|
||||||
|
|
||||||
def set_bg_image(self, filename):
|
|
||||||
"""
|
|
||||||
Set a background image.
|
|
||||||
|
|
||||||
``filename``
|
|
||||||
The name of the image file.
|
|
||||||
"""
|
|
||||||
log.debug(u'set bg image %s', filename)
|
|
||||||
self._bg_image_filename = unicode(filename)
|
|
||||||
if self.frame:
|
if self.frame:
|
||||||
self.bg_image = resize_image(self._bg_image_filename,
|
self.bg_image = resize_image(self._bg_image_filename,
|
||||||
self.frame.width(),
|
self.frame.width(),
|
||||||
|
@ -112,7 +90,7 @@ class Renderer(object):
|
||||||
self._rect = rect_main
|
self._rect = rect_main
|
||||||
self._rect_footer = rect_footer
|
self._rect_footer = rect_footer
|
||||||
|
|
||||||
def set_frame_dest(self, frame_width, frame_height, preview=False):
|
def set_frame_dest(self, frame_width, frame_height):
|
||||||
"""
|
"""
|
||||||
Set the size of the slide.
|
Set the size of the slide.
|
||||||
|
|
||||||
|
@ -122,11 +100,7 @@ class Renderer(object):
|
||||||
``frame_height``
|
``frame_height``
|
||||||
The height of the slide.
|
The height of the slide.
|
||||||
|
|
||||||
``preview``
|
|
||||||
Defaults to *False*. Whether or not to generate a preview.
|
|
||||||
"""
|
"""
|
||||||
if preview:
|
|
||||||
self.bg_frame = None
|
|
||||||
log.debug(u'set frame dest (frame) w %d h %d', frame_width,
|
log.debug(u'set frame dest (frame) w %d h %d', frame_width,
|
||||||
frame_height)
|
frame_height)
|
||||||
self.frame = QtGui.QImage(frame_width, frame_height,
|
self.frame = QtGui.QImage(frame_width, frame_height,
|
||||||
|
@ -134,8 +108,17 @@ class Renderer(object):
|
||||||
if self._bg_image_filename and not self.bg_image:
|
if self._bg_image_filename and not self.bg_image:
|
||||||
self.bg_image = resize_image(self._bg_image_filename,
|
self.bg_image = resize_image(self._bg_image_filename,
|
||||||
self.frame.width(), self.frame.height())
|
self.frame.width(), self.frame.height())
|
||||||
if self.bg_frame is None:
|
if self._theme.background_type == u'image':
|
||||||
self._generate_background_frame()
|
self.bg_frame = QtGui.QImage(self.frame.width(),
|
||||||
|
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
|
painter = QtGui.QPainter()
|
||||||
|
painter.begin(self.bg_frame)
|
||||||
|
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
||||||
|
if self.bg_image:
|
||||||
|
painter.drawImage(0, 0, self.bg_image)
|
||||||
|
painter.end()
|
||||||
|
else:
|
||||||
|
self.bg_frame = None
|
||||||
|
|
||||||
def format_slide(self, words, line_break):
|
def format_slide(self, words, line_break):
|
||||||
"""
|
"""
|
||||||
|
@ -156,91 +139,33 @@ class Renderer(object):
|
||||||
lines = verse.split(u'\n')
|
lines = verse.split(u'\n')
|
||||||
for line in lines:
|
for line in lines:
|
||||||
text.append(line)
|
text.append(line)
|
||||||
doc = QtGui.QTextDocument()
|
web = QtWebKit.QWebView()
|
||||||
doc.setPageSize(QtCore.QSizeF(self._rect.width(), self._rect.height()))
|
web.resize(self._rect.width(), self._rect.height())
|
||||||
df = doc.defaultFont()
|
web.setVisible(False)
|
||||||
df.setPixelSize(self._theme.font_main_proportion)
|
frame = web.page().mainFrame()
|
||||||
df.setFamily(self._theme.font_main_name)
|
# Adjust width and height to account for shadow. outline done in css
|
||||||
main_weight = 50
|
width = self._rect.width() - int(self._theme.display_shadow_size)
|
||||||
if self._theme.font_main_weight == u'Bold':
|
height = self._rect.height() - int(self._theme.display_shadow_size)
|
||||||
main_weight = 75
|
shell = u'<html><head><style>#main {%s %s}</style><body>' \
|
||||||
df.setWeight(main_weight)
|
u'<div id="main">' % \
|
||||||
doc.setDefaultFont(df)
|
(build_lyrics_format_css(self._theme, width, height),
|
||||||
layout = doc.documentLayout()
|
build_lyrics_outline_css(self._theme))
|
||||||
formatted = []
|
formatted = []
|
||||||
if self._theme.font_main_weight == u'Bold' and \
|
html_text = u''
|
||||||
self._theme.font_main_italics:
|
styled_text = u''
|
||||||
shell = u'{p}{st}{it}%s{/it}{/st}{/p}'
|
js_height = 'document.getElementById("main").scrollHeight'
|
||||||
elif self._theme.font_main_weight == u'Bold' and \
|
|
||||||
not self._theme.font_main_italics:
|
|
||||||
shell = u'{p}{st}%s{/st}{/p}'
|
|
||||||
elif self._theme.font_main_italics:
|
|
||||||
shell = u'{p}{it}%s{/it}{/p}'
|
|
||||||
else:
|
|
||||||
shell = u'{p}%s{/p}'
|
|
||||||
temp_text = u''
|
|
||||||
old_html_text = u''
|
|
||||||
for line in text:
|
for line in text:
|
||||||
# mark line ends
|
styled_line = expand_tags(line) + line_end
|
||||||
temp_text = temp_text + line + line_end
|
styled_text += styled_line
|
||||||
html_text = shell % expand_tags(temp_text)
|
html = shell + styled_text + u'</div></body></html>'
|
||||||
doc.setHtml(html_text)
|
web.setHtml(html)
|
||||||
# Text too long so gone to next mage
|
# Text too long so go to next page
|
||||||
if layout.pageCount() != 1:
|
text_height = int(frame.evaluateJavaScript(js_height).toString())
|
||||||
formatted.append(shell % old_html_text)
|
if text_height > height:
|
||||||
temp_text = line
|
formatted.append(html_text)
|
||||||
old_html_text = temp_text
|
html_text = u''
|
||||||
formatted.append(shell % old_html_text)
|
styled_text = styled_line
|
||||||
|
html_text += line + line_end
|
||||||
|
formatted.append(html_text)
|
||||||
log.debug(u'format_slide - End')
|
log.debug(u'format_slide - End')
|
||||||
return formatted
|
return formatted
|
||||||
|
|
||||||
def _generate_background_frame(self):
|
|
||||||
"""
|
|
||||||
Generate a background frame to the same size as the frame to be used.
|
|
||||||
Results are cached for performance reasons.
|
|
||||||
"""
|
|
||||||
assert(self._theme)
|
|
||||||
self.bg_frame = QtGui.QImage(self.frame.width(),
|
|
||||||
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
|
||||||
log.debug(u'render background %s start', self._theme.background_type)
|
|
||||||
painter = QtGui.QPainter()
|
|
||||||
painter.begin(self.bg_frame)
|
|
||||||
if self._theme.background_type == u'solid':
|
|
||||||
painter.fillRect(self.frame.rect(),
|
|
||||||
QtGui.QColor(self._theme.background_color))
|
|
||||||
elif self._theme.background_type == u'gradient':
|
|
||||||
# gradient
|
|
||||||
gradient = None
|
|
||||||
if self._theme.background_direction == u'horizontal':
|
|
||||||
w = int(self.frame.width()) / 2
|
|
||||||
# vertical
|
|
||||||
gradient = QtGui.QLinearGradient(w, 0, w, self.frame.height())
|
|
||||||
elif self._theme.background_direction == u'vertical':
|
|
||||||
h = int(self.frame.height()) / 2
|
|
||||||
# Horizontal
|
|
||||||
gradient = QtGui.QLinearGradient(0, h, self.frame.width(), h)
|
|
||||||
else:
|
|
||||||
w = int(self.frame.width()) / 2
|
|
||||||
h = int(self.frame.height()) / 2
|
|
||||||
# Circular
|
|
||||||
gradient = QtGui.QRadialGradient(w, h, w)
|
|
||||||
gradient.setColorAt(0,
|
|
||||||
QtGui.QColor(self._theme.background_startColor))
|
|
||||||
gradient.setColorAt(1,
|
|
||||||
QtGui.QColor(self._theme.background_endColor))
|
|
||||||
painter.setBrush(QtGui.QBrush(gradient))
|
|
||||||
rect_path = QtGui.QPainterPath()
|
|
||||||
max_x = self.frame.width()
|
|
||||||
max_y = self.frame.height()
|
|
||||||
rect_path.moveTo(0, 0)
|
|
||||||
rect_path.lineTo(0, max_y)
|
|
||||||
rect_path.lineTo(max_x, max_y)
|
|
||||||
rect_path.lineTo(max_x, 0)
|
|
||||||
rect_path.closeSubpath()
|
|
||||||
painter.drawPath(rect_path)
|
|
||||||
elif self._theme.background_type == u'image':
|
|
||||||
# image
|
|
||||||
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
|
||||||
if self.bg_image:
|
|
||||||
painter.drawImage(0, 0, self.bg_image)
|
|
||||||
painter.end()
|
|
||||||
|
|
|
@ -93,6 +93,9 @@ class RenderManager(object):
|
||||||
"""
|
"""
|
||||||
self.global_theme = global_theme
|
self.global_theme = global_theme
|
||||||
self.theme_level = theme_level
|
self.theme_level = theme_level
|
||||||
|
self.global_theme_data = \
|
||||||
|
self.theme_manager.getThemeData(self.global_theme)
|
||||||
|
self.themedata = None
|
||||||
|
|
||||||
def set_service_theme(self, service_theme):
|
def set_service_theme(self, service_theme):
|
||||||
"""
|
"""
|
||||||
|
@ -102,6 +105,7 @@ class RenderManager(object):
|
||||||
The service-level theme to be set.
|
The service-level theme to be set.
|
||||||
"""
|
"""
|
||||||
self.service_theme = service_theme
|
self.service_theme = service_theme
|
||||||
|
self.themedata = None
|
||||||
|
|
||||||
def set_override_theme(self, theme, overrideLevels=False):
|
def set_override_theme(self, theme, overrideLevels=False):
|
||||||
"""
|
"""
|
||||||
|
@ -111,6 +115,10 @@ class RenderManager(object):
|
||||||
``theme``
|
``theme``
|
||||||
The name of the song-level theme. None means the service
|
The name of the song-level theme. None means the service
|
||||||
item wants to use the given value.
|
item wants to use the given value.
|
||||||
|
|
||||||
|
``overrideLevels``
|
||||||
|
Used to force the theme data passed in to be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug(u'set override theme to %s', theme)
|
log.debug(u'set override theme to %s', theme)
|
||||||
theme_level = self.theme_level
|
theme_level = self.theme_level
|
||||||
|
@ -137,6 +145,7 @@ class RenderManager(object):
|
||||||
if self.theme != self.renderer.theme_name or self.themedata is None \
|
if self.theme != self.renderer.theme_name or self.themedata is None \
|
||||||
or overrideLevels:
|
or overrideLevels:
|
||||||
log.debug(u'theme is now %s', self.theme)
|
log.debug(u'theme is now %s', self.theme)
|
||||||
|
# Force the theme to be the one passed in.
|
||||||
if overrideLevels:
|
if overrideLevels:
|
||||||
self.themedata = theme
|
self.themedata = theme
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -160,9 +160,9 @@ class ServiceItem(object):
|
||||||
self.themedata = self.render_manager.renderer._theme
|
self.themedata = self.render_manager.renderer._theme
|
||||||
for slide in self._raw_frames:
|
for slide in self._raw_frames:
|
||||||
before = time.time()
|
before = time.time()
|
||||||
formated = self.render_manager \
|
formatted = self.render_manager \
|
||||||
.format_slide(slide[u'raw_slide'], line_break)
|
.format_slide(slide[u'raw_slide'], line_break)
|
||||||
for page in formated:
|
for page in formatted:
|
||||||
self._display_frames.append(
|
self._display_frames.append(
|
||||||
{u'title': clean_tags(page),
|
{u'title': clean_tags(page),
|
||||||
u'text': clean_tags(page.rstrip()),
|
u'text': clean_tags(page.rstrip()),
|
||||||
|
@ -170,6 +170,7 @@ class ServiceItem(object):
|
||||||
u'verseTag': slide[u'verseTag'] })
|
u'verseTag': slide[u'verseTag'] })
|
||||||
log.log(15, u'Formatting took %4s' % (time.time() - before))
|
log.log(15, u'Formatting took %4s' % (time.time() - before))
|
||||||
elif self.service_item_type == ServiceItemType.Image:
|
elif self.service_item_type == ServiceItemType.Image:
|
||||||
|
self.themedata = self.render_manager.global_theme_data
|
||||||
for slide in self._raw_frames:
|
for slide in self._raw_frames:
|
||||||
slide[u'image'] = resize_image(slide[u'image'],
|
slide[u'image'] = resize_image(slide[u'image'],
|
||||||
self.render_manager.width, self.render_manager.height)
|
self.render_manager.width, self.render_manager.height)
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
import enchant
|
||||||
|
enchant_available = True
|
||||||
|
except ImportError:
|
||||||
|
enchant_available = False
|
||||||
|
|
||||||
|
# based on code from
|
||||||
|
# http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from openlp.core.lib import html_expands, translate, context_menu_action
|
||||||
|
|
||||||
|
class SpellTextEdit(QtGui.QPlainTextEdit):
|
||||||
|
def __init__(self, *args):
|
||||||
|
QtGui.QPlainTextEdit.__init__(self, *args)
|
||||||
|
# Default dictionary based on the current locale.
|
||||||
|
if enchant_available:
|
||||||
|
self.dict = enchant.Dict()
|
||||||
|
self.highlighter = Highlighter(self.document())
|
||||||
|
self.highlighter.setDict(self.dict)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.button() == QtCore.Qt.RightButton:
|
||||||
|
# Rewrite the mouse event to a left button event so the cursor is
|
||||||
|
# moved to the location of the pointer.
|
||||||
|
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
|
||||||
|
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton,
|
||||||
|
QtCore.Qt.NoModifier)
|
||||||
|
QtGui.QPlainTextEdit.mousePressEvent(self, event)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event):
|
||||||
|
popup_menu = self.createStandardContextMenu()
|
||||||
|
# Select the word under the cursor.
|
||||||
|
cursor = self.textCursor()
|
||||||
|
# only select text if not already selected
|
||||||
|
if not cursor.hasSelection():
|
||||||
|
cursor.select(QtGui.QTextCursor.WordUnderCursor)
|
||||||
|
self.setTextCursor(cursor)
|
||||||
|
# Check if the selected word is misspelled and offer spelling
|
||||||
|
# suggestions if it is.
|
||||||
|
if enchant_available and self.textCursor().hasSelection():
|
||||||
|
text = unicode(self.textCursor().selectedText())
|
||||||
|
if not self.dict.check(text):
|
||||||
|
spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||||
|
'Spelling Suggestions'))
|
||||||
|
for word in self.dict.suggest(text):
|
||||||
|
action = SpellAction(word, spell_menu)
|
||||||
|
action.correct.connect(self.correctWord)
|
||||||
|
spell_menu.addAction(action)
|
||||||
|
# Only add the spelling suggests to the menu if there are
|
||||||
|
# suggestions.
|
||||||
|
if len(spell_menu.actions()) != 0:
|
||||||
|
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||||
|
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
|
||||||
|
tag_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||||
|
'Formatting Tags'))
|
||||||
|
for html in html_expands:
|
||||||
|
action = SpellAction( html[u'desc'], tag_menu)
|
||||||
|
action.correct.connect(self.htmlTag)
|
||||||
|
tag_menu.addAction(action)
|
||||||
|
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||||
|
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
|
||||||
|
popup_menu.exec_(event.globalPos())
|
||||||
|
|
||||||
|
def correctWord(self, word):
|
||||||
|
"""
|
||||||
|
Replaces the selected text with word.
|
||||||
|
"""
|
||||||
|
cursor = self.textCursor()
|
||||||
|
cursor.beginEditBlock()
|
||||||
|
cursor.removeSelectedText()
|
||||||
|
cursor.insertText(word)
|
||||||
|
cursor.endEditBlock()
|
||||||
|
|
||||||
|
def htmlTag(self, tag):
|
||||||
|
"""
|
||||||
|
Replaces the selected text with word.
|
||||||
|
"""
|
||||||
|
for html in html_expands:
|
||||||
|
if tag == html[u'desc']:
|
||||||
|
cursor = self.textCursor()
|
||||||
|
if self.textCursor().hasSelection():
|
||||||
|
text = cursor.selectedText()
|
||||||
|
cursor.beginEditBlock()
|
||||||
|
cursor.removeSelectedText()
|
||||||
|
cursor.insertText(html[u'start tag'])
|
||||||
|
cursor.insertText(text)
|
||||||
|
cursor.insertText(html[u'end tag'])
|
||||||
|
cursor.endEditBlock()
|
||||||
|
else:
|
||||||
|
cursor = self.textCursor()
|
||||||
|
cursor.insertText(html[u'start tag'])
|
||||||
|
cursor.insertText(html[u'end tag'])
|
||||||
|
|
||||||
|
class Highlighter(QtGui.QSyntaxHighlighter):
|
||||||
|
|
||||||
|
WORDS = u'(?iu)[\w\']+'
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QtGui.QSyntaxHighlighter.__init__(self, *args)
|
||||||
|
self.dict = None
|
||||||
|
|
||||||
|
def setDict(self, dict):
|
||||||
|
self.dict = dict
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
if not self.dict:
|
||||||
|
return
|
||||||
|
text = unicode(text)
|
||||||
|
format = QtGui.QTextCharFormat()
|
||||||
|
format.setUnderlineColor(QtCore.Qt.red)
|
||||||
|
format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
|
||||||
|
for word_object in re.finditer(self.WORDS, text):
|
||||||
|
if not self.dict.check(word_object.group()):
|
||||||
|
self.setFormat(word_object.start(),
|
||||||
|
word_object.end() - word_object.start(), format)
|
||||||
|
|
||||||
|
class SpellAction(QtGui.QAction):
|
||||||
|
"""
|
||||||
|
A special QAction that returns the text in a signal.
|
||||||
|
"""
|
||||||
|
correct = QtCore.pyqtSignal(unicode)
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QtGui.QAction.__init__(self, *args)
|
||||||
|
self.triggered.connect(lambda x: self.correct.emit(
|
||||||
|
unicode(self.text())))
|
|
@ -56,7 +56,7 @@ BLANK_THEME_XML = \
|
||||||
<weight>Normal</weight>
|
<weight>Normal</weight>
|
||||||
<italics>False</italics>
|
<italics>False</italics>
|
||||||
<line_adjustment>0</line_adjustment>
|
<line_adjustment>0</line_adjustment>
|
||||||
<location override="False" x="10" y="10" width="1004" height="730"/>
|
<location override="False" x="10" y="10" width="1004" height="690"/>
|
||||||
</font>
|
</font>
|
||||||
<font type="footer">
|
<font type="footer">
|
||||||
<name>Arial</name>
|
<name>Arial</name>
|
||||||
|
@ -65,7 +65,7 @@ BLANK_THEME_XML = \
|
||||||
<weight>Normal</weight>
|
<weight>Normal</weight>
|
||||||
<italics>False</italics>
|
<italics>False</italics>
|
||||||
<line_adjustment>0</line_adjustment>
|
<line_adjustment>0</line_adjustment>
|
||||||
<location override="False" x="10" y="730" width="1004" height="38"/>
|
<location override="False" x="10" y="690" width="1004" height="78"/>
|
||||||
</font>
|
</font>
|
||||||
<display>
|
<display>
|
||||||
<shadow color="#000000" size="5">True</shadow>
|
<shadow color="#000000" size="5">True</shadow>
|
||||||
|
|
|
@ -27,141 +27,6 @@
|
||||||
The :mod:`ui` module provides the core user interface for OpenLP
|
The :mod:`ui` module provides the core user interface for OpenLP
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
try:
|
|
||||||
import enchant
|
|
||||||
enchant_available = True
|
|
||||||
except ImportError:
|
|
||||||
enchant_available = False
|
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from openlp.core.lib import html_expands, translate, context_menu_action
|
|
||||||
|
|
||||||
class SpellTextEdit(QtGui.QPlainTextEdit):
|
|
||||||
def __init__(self, *args):
|
|
||||||
QtGui.QPlainTextEdit.__init__(self, *args)
|
|
||||||
# Default dictionary based on the current locale.
|
|
||||||
if enchant_available:
|
|
||||||
self.dict = enchant.Dict()
|
|
||||||
self.highlighter = Highlighter(self.document())
|
|
||||||
self.highlighter.setDict(self.dict)
|
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
|
||||||
if event.button() == QtCore.Qt.RightButton:
|
|
||||||
# Rewrite the mouse event to a left button event so the cursor is
|
|
||||||
# moved to the location of the pointer.
|
|
||||||
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
|
|
||||||
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton,
|
|
||||||
QtCore.Qt.NoModifier)
|
|
||||||
QtGui.QPlainTextEdit.mousePressEvent(self, event)
|
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
|
||||||
popup_menu = self.createStandardContextMenu()
|
|
||||||
# Select the word under the cursor.
|
|
||||||
cursor = self.textCursor()
|
|
||||||
cursor.select(QtGui.QTextCursor.WordUnderCursor)
|
|
||||||
self.setTextCursor(cursor)
|
|
||||||
# Check if the selected word is misspelled and offer spelling
|
|
||||||
# suggestions if it is.
|
|
||||||
if enchant_available and self.textCursor().hasSelection():
|
|
||||||
text = unicode(self.textCursor().selectedText())
|
|
||||||
if not self.dict.check(text):
|
|
||||||
spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
|
||||||
'Spelling Suggestions'))
|
|
||||||
for word in self.dict.suggest(text):
|
|
||||||
action = SpellAction(word, spell_menu)
|
|
||||||
action.correct.connect(self.correctWord)
|
|
||||||
spell_menu.addAction(action)
|
|
||||||
# Only add the spelling suggests to the menu if there are
|
|
||||||
# suggestions.
|
|
||||||
if len(spell_menu.actions()) != 0:
|
|
||||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
|
||||||
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
|
|
||||||
tag_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
|
||||||
'Formatting Tags'))
|
|
||||||
for html in html_expands:
|
|
||||||
action = SpellAction( html[u'desc'], tag_menu)
|
|
||||||
action.correct.connect(self.htmlTag)
|
|
||||||
tag_menu.addAction(action)
|
|
||||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
|
||||||
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
|
|
||||||
|
|
||||||
popup_menu.exec_(event.globalPos())
|
|
||||||
|
|
||||||
def correctWord(self, word):
|
|
||||||
"""
|
|
||||||
Replaces the selected text with word.
|
|
||||||
"""
|
|
||||||
cursor = self.textCursor()
|
|
||||||
cursor.beginEditBlock()
|
|
||||||
|
|
||||||
cursor.removeSelectedText()
|
|
||||||
cursor.insertText(word)
|
|
||||||
|
|
||||||
cursor.endEditBlock()
|
|
||||||
|
|
||||||
def htmlTag(self, tag):
|
|
||||||
"""
|
|
||||||
Replaces the selected text with word.
|
|
||||||
"""
|
|
||||||
for html in html_expands:
|
|
||||||
if tag == html[u'desc']:
|
|
||||||
cursor = self.textCursor()
|
|
||||||
if self.textCursor().hasSelection():
|
|
||||||
text = cursor.selectedText()
|
|
||||||
cursor.beginEditBlock()
|
|
||||||
cursor.removeSelectedText()
|
|
||||||
cursor.insertText(html[u'start tag'])
|
|
||||||
cursor.insertText(text)
|
|
||||||
cursor.insertText(html[u'end tag'])
|
|
||||||
cursor.endEditBlock()
|
|
||||||
else:
|
|
||||||
cursor = self.textCursor()
|
|
||||||
cursor.insertText(html[u'start tag'])
|
|
||||||
cursor.insertText(html[u'end tag'])
|
|
||||||
|
|
||||||
class Highlighter(QtGui.QSyntaxHighlighter):
|
|
||||||
|
|
||||||
WORDS = u'(?iu)[\w\']+'
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QtGui.QSyntaxHighlighter.__init__(self, *args)
|
|
||||||
|
|
||||||
self.dict = None
|
|
||||||
|
|
||||||
def setDict(self, dict):
|
|
||||||
self.dict = dict
|
|
||||||
|
|
||||||
def highlightBlock(self, text):
|
|
||||||
if not self.dict:
|
|
||||||
return
|
|
||||||
|
|
||||||
text = unicode(text)
|
|
||||||
|
|
||||||
format = QtGui.QTextCharFormat()
|
|
||||||
format.setUnderlineColor(QtCore.Qt.red)
|
|
||||||
format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
|
|
||||||
|
|
||||||
for word_object in re.finditer(self.WORDS, text):
|
|
||||||
if not self.dict.check(word_object.group()):
|
|
||||||
self.setFormat(word_object.start(),
|
|
||||||
word_object.end() - word_object.start(), format)
|
|
||||||
|
|
||||||
class SpellAction(QtGui.QAction):
|
|
||||||
"""
|
|
||||||
A special QAction that returns the text in a signal.
|
|
||||||
"""
|
|
||||||
correct = QtCore.pyqtSignal(unicode)
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QtGui.QAction.__init__(self, *args)
|
|
||||||
|
|
||||||
self.triggered.connect(lambda x: self.correct.emit(
|
|
||||||
unicode(self.text())))
|
|
||||||
|
|
||||||
class HideMode(object):
|
class HideMode(object):
|
||||||
"""
|
"""
|
||||||
This is basically an enumeration class which specifies the mode of a Bible.
|
This is basically an enumeration class which specifies the mode of a Bible.
|
||||||
|
|
|
@ -450,7 +450,7 @@ class Ui_AboutDialog(object):
|
||||||
'\n'
|
'\n'
|
||||||
'Each version is given a distinguishing version number. If the '
|
'Each version is given a distinguishing version number. If the '
|
||||||
'Program specifies a version number of this License which applies '
|
'Program specifies a version number of this License which applies '
|
||||||
'to it and \"any later version\', you have the option of '
|
'to it and "any later version", you have the option of '
|
||||||
'following the terms and conditions either of that version or of '
|
'following the terms and conditions either of that version or of '
|
||||||
'any later version published by the Free Software Foundation. If '
|
'any later version published by the Free Software Foundation. If '
|
||||||
'the Program does not specify a version number of this License, '
|
'the Program does not specify a version number of this License, '
|
||||||
|
@ -565,3 +565,4 @@ class Ui_AboutDialog(object):
|
||||||
self.contributeButton.setText(translate('OpenLP.AboutForm',
|
self.contributeButton.setText(translate('OpenLP.AboutForm',
|
||||||
'Contribute'))
|
'Contribute'))
|
||||||
self.closeButton.setText(translate('OpenLP.AboutForm', 'Close'))
|
self.closeButton.setText(translate('OpenLP.AboutForm', 'Close'))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
|
class Ui_ExceptionDialog(object):
|
||||||
|
def setupUi(self, exceptionDialog):
|
||||||
|
exceptionDialog.setObjectName(u'exceptionDialog')
|
||||||
|
exceptionDialog.resize(580, 407)
|
||||||
|
self.exceptionLayout = QtGui.QVBoxLayout(exceptionDialog)
|
||||||
|
self.exceptionLayout.setSpacing(8)
|
||||||
|
self.exceptionLayout.setMargin(8)
|
||||||
|
self.exceptionLayout.setObjectName(u'exceptionLayout')
|
||||||
|
self.messageLayout = QtGui.QHBoxLayout()
|
||||||
|
self.messageLayout.setSpacing(0)
|
||||||
|
self.messageLayout.setContentsMargins(0, -1, 0, -1)
|
||||||
|
self.messageLayout.setObjectName(u'messageLayout')
|
||||||
|
self.bugLabel = QtGui.QLabel(exceptionDialog)
|
||||||
|
self.bugLabel.setMinimumSize(QtCore.QSize(64, 64))
|
||||||
|
self.bugLabel.setMaximumSize(QtCore.QSize(64, 64))
|
||||||
|
self.bugLabel.setText(u'')
|
||||||
|
self.bugLabel.setPixmap(QtGui.QPixmap(u':/graphics/exception.png'))
|
||||||
|
self.bugLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.bugLabel.setObjectName(u'bugLabel')
|
||||||
|
self.messageLayout.addWidget(self.bugLabel)
|
||||||
|
self.messageLabel = QtGui.QLabel(exceptionDialog)
|
||||||
|
self.messageLabel.setWordWrap(True)
|
||||||
|
self.messageLabel.setObjectName(u'messageLabel')
|
||||||
|
self.messageLayout.addWidget(self.messageLabel)
|
||||||
|
self.exceptionLayout.addLayout(self.messageLayout)
|
||||||
|
self.exceptionTextEdit = QtGui.QPlainTextEdit(exceptionDialog)
|
||||||
|
self.exceptionTextEdit.setReadOnly(True)
|
||||||
|
self.exceptionTextEdit.setBackgroundVisible(False)
|
||||||
|
self.exceptionTextEdit.setObjectName(u'exceptionTextEdit')
|
||||||
|
self.exceptionLayout.addWidget(self.exceptionTextEdit)
|
||||||
|
self.exceptionButtonBox = QtGui.QDialogButtonBox(exceptionDialog)
|
||||||
|
self.exceptionButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||||
|
self.exceptionButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Close)
|
||||||
|
self.exceptionButtonBox.setObjectName(u'exceptionButtonBox')
|
||||||
|
self.exceptionLayout.addWidget(self.exceptionButtonBox)
|
||||||
|
|
||||||
|
self.retranslateUi(exceptionDialog)
|
||||||
|
QtCore.QObject.connect(self.exceptionButtonBox,
|
||||||
|
QtCore.SIGNAL(u'accepted()'), exceptionDialog.accept)
|
||||||
|
QtCore.QObject.connect(self.exceptionButtonBox,
|
||||||
|
QtCore.SIGNAL(u'rejected()'), exceptionDialog.reject)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(exceptionDialog)
|
||||||
|
|
||||||
|
def retranslateUi(self, exceptionDialog):
|
||||||
|
exceptionDialog.setWindowTitle(
|
||||||
|
translate('OpenLP.ExceptionDialog', 'Error Occurred'))
|
||||||
|
self.messageLabel.setText(translate('OpenLP.ExceptionDialog', 'Oops! '
|
||||||
|
'OpenLP hit a problem, and couldn\'t recover. The text in the box '
|
||||||
|
'below contains information that might be helpful to the OpenLP '
|
||||||
|
'developers, so please e-mail it to bugs@openlp.org, along with a '
|
||||||
|
'detailed description of what you were doing when the problem '
|
||||||
|
'occurred.'))
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
from exceptiondialog import Ui_ExceptionDialog
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
|
class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
|
||||||
|
"""
|
||||||
|
The exception dialog
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
QtGui.QDialog.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
|
@ -195,6 +195,19 @@ class GeneralTab(SettingsTab):
|
||||||
self.currentYValueLabel.setObjectName(u'currentYValueLabel')
|
self.currentYValueLabel.setObjectName(u'currentYValueLabel')
|
||||||
self.currentYLayout.addWidget(self.currentYValueLabel)
|
self.currentYLayout.addWidget(self.currentYValueLabel)
|
||||||
self.currentLayout.addLayout(self.currentYLayout)
|
self.currentLayout.addLayout(self.currentYLayout)
|
||||||
|
self.currentWidthLayout = QtGui.QVBoxLayout()
|
||||||
|
self.currentWidthLayout.setSpacing(0)
|
||||||
|
self.currentWidthLayout.setMargin(0)
|
||||||
|
self.currentWidthLayout.setObjectName(u'currentWidthLayout')
|
||||||
|
self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox)
|
||||||
|
self.currentWidthLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.currentWidthLabel.setObjectName(u'currentWidthLabel')
|
||||||
|
self.currentWidthLayout.addWidget(self.currentWidthLabel)
|
||||||
|
self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox)
|
||||||
|
self.currentWidthValueLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel')
|
||||||
|
self.currentWidthLayout.addWidget(self.currentWidthValueLabel)
|
||||||
|
self.currentLayout.addLayout(self.currentWidthLayout)
|
||||||
self.currentHeightLayout = QtGui.QVBoxLayout()
|
self.currentHeightLayout = QtGui.QVBoxLayout()
|
||||||
self.currentHeightLayout.setSpacing(0)
|
self.currentHeightLayout.setSpacing(0)
|
||||||
self.currentHeightLayout.setMargin(0)
|
self.currentHeightLayout.setMargin(0)
|
||||||
|
@ -209,19 +222,6 @@ class GeneralTab(SettingsTab):
|
||||||
self.currentHeightValueLabel.setObjectName(u'Height')
|
self.currentHeightValueLabel.setObjectName(u'Height')
|
||||||
self.currentHeightLayout.addWidget(self.currentHeightValueLabel)
|
self.currentHeightLayout.addWidget(self.currentHeightValueLabel)
|
||||||
self.currentLayout.addLayout(self.currentHeightLayout)
|
self.currentLayout.addLayout(self.currentHeightLayout)
|
||||||
self.currentWidthLayout = QtGui.QVBoxLayout()
|
|
||||||
self.currentWidthLayout.setSpacing(0)
|
|
||||||
self.currentWidthLayout.setMargin(0)
|
|
||||||
self.currentWidthLayout.setObjectName(u'currentWidthLayout')
|
|
||||||
self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox)
|
|
||||||
self.currentWidthLabel.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.currentWidthLabel.setObjectName(u'currentWidthLabel')
|
|
||||||
self.currentWidthLayout.addWidget(self.currentWidthLabel)
|
|
||||||
self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox)
|
|
||||||
self.currentWidthValueLabel.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel')
|
|
||||||
self.currentWidthLayout.addWidget(self.currentWidthValueLabel)
|
|
||||||
self.currentLayout.addLayout(self.currentWidthLayout)
|
|
||||||
self.displayLayout.addLayout(self.currentLayout)
|
self.displayLayout.addLayout(self.currentLayout)
|
||||||
self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox)
|
self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox)
|
||||||
self.overrideCheckBox.setObjectName(u'overrideCheckBox')
|
self.overrideCheckBox.setObjectName(u'overrideCheckBox')
|
||||||
|
@ -256,18 +256,6 @@ class GeneralTab(SettingsTab):
|
||||||
self.customYValueEdit.setObjectName(u'customYValueEdit')
|
self.customYValueEdit.setObjectName(u'customYValueEdit')
|
||||||
self.customYLayout.addWidget(self.customYValueEdit)
|
self.customYLayout.addWidget(self.customYValueEdit)
|
||||||
self.customLayout.addLayout(self.customYLayout)
|
self.customLayout.addLayout(self.customYLayout)
|
||||||
self.customHeightLayout = QtGui.QVBoxLayout()
|
|
||||||
self.customHeightLayout.setSpacing(0)
|
|
||||||
self.customHeightLayout.setMargin(0)
|
|
||||||
self.customHeightLayout.setObjectName(u'customHeightLayout')
|
|
||||||
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
|
|
||||||
self.customHeightLabel.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.customHeightLabel.setObjectName(u'customHeightLabel')
|
|
||||||
self.customHeightLayout.addWidget(self.customHeightLabel)
|
|
||||||
self.customHeightValueEdit = QtGui.QLineEdit(self.displayGroupBox)
|
|
||||||
self.customHeightValueEdit.setObjectName(u'customHeightValueEdit')
|
|
||||||
self.customHeightLayout.addWidget(self.customHeightValueEdit)
|
|
||||||
self.customLayout.addLayout(self.customHeightLayout)
|
|
||||||
self.customWidthLayout = QtGui.QVBoxLayout()
|
self.customWidthLayout = QtGui.QVBoxLayout()
|
||||||
self.customWidthLayout.setSpacing(0)
|
self.customWidthLayout.setSpacing(0)
|
||||||
self.customWidthLayout.setMargin(0)
|
self.customWidthLayout.setMargin(0)
|
||||||
|
@ -281,6 +269,18 @@ class GeneralTab(SettingsTab):
|
||||||
self.customWidthValueEdit.setObjectName(u'customWidthValueEdit')
|
self.customWidthValueEdit.setObjectName(u'customWidthValueEdit')
|
||||||
self.customWidthLayout.addWidget(self.customWidthValueEdit)
|
self.customWidthLayout.addWidget(self.customWidthValueEdit)
|
||||||
self.customLayout.addLayout(self.customWidthLayout)
|
self.customLayout.addLayout(self.customWidthLayout)
|
||||||
|
self.customHeightLayout = QtGui.QVBoxLayout()
|
||||||
|
self.customHeightLayout.setSpacing(0)
|
||||||
|
self.customHeightLayout.setMargin(0)
|
||||||
|
self.customHeightLayout.setObjectName(u'customHeightLayout')
|
||||||
|
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
|
||||||
|
self.customHeightLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.customHeightLabel.setObjectName(u'customHeightLabel')
|
||||||
|
self.customHeightLayout.addWidget(self.customHeightLabel)
|
||||||
|
self.customHeightValueEdit = QtGui.QLineEdit(self.displayGroupBox)
|
||||||
|
self.customHeightValueEdit.setObjectName(u'customHeightValueEdit')
|
||||||
|
self.customHeightLayout.addWidget(self.customHeightValueEdit)
|
||||||
|
self.customLayout.addLayout(self.customHeightLayout)
|
||||||
self.displayLayout.addLayout(self.customLayout)
|
self.displayLayout.addLayout(self.customLayout)
|
||||||
# Bottom spacer
|
# Bottom spacer
|
||||||
self.generalRightSpacer = QtGui.QSpacerItem(20, 40,
|
self.generalRightSpacer = QtGui.QSpacerItem(20, 40,
|
||||||
|
@ -476,7 +476,6 @@ class GeneralTab(SettingsTab):
|
||||||
# Order is important so be careful if you change
|
# Order is important so be careful if you change
|
||||||
if self.overrideChanged or postUpdate:
|
if self.overrideChanged or postUpdate:
|
||||||
Receiver.send_message(u'config_screen_changed')
|
Receiver.send_message(u'config_screen_changed')
|
||||||
Receiver.send_message(u'config_updated')
|
|
||||||
self.overrideChanged = False
|
self.overrideChanged = False
|
||||||
|
|
||||||
def onOverrideCheckBoxToggled(self, checked):
|
def onOverrideCheckBoxToggled(self, checked):
|
||||||
|
|
|
@ -97,6 +97,7 @@ class MainDisplay(DisplayWidget):
|
||||||
self.screens = screens
|
self.screens = screens
|
||||||
self.isLive = live
|
self.isLive = live
|
||||||
self.alertTab = None
|
self.alertTab = None
|
||||||
|
self.hide_mode = None
|
||||||
self.setWindowTitle(u'OpenLP Display')
|
self.setWindowTitle(u'OpenLP Display')
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
||||||
QtCore.Qt.WindowStaysOnTopHint)
|
QtCore.Qt.WindowStaysOnTopHint)
|
||||||
|
@ -115,13 +116,18 @@ class MainDisplay(DisplayWidget):
|
||||||
self.screen = self.screens.current
|
self.screen = self.screens.current
|
||||||
self.setVisible(False)
|
self.setVisible(False)
|
||||||
self.setGeometry(self.screen[u'size'])
|
self.setGeometry(self.screen[u'size'])
|
||||||
self.webView = QtWebKit.QWebView(self)
|
self.scene = QtGui.QGraphicsScene()
|
||||||
self.webView.setGeometry(0, 0, self.screen[u'size'].width(), \
|
self.setScene(self.scene)
|
||||||
|
self.webView = QtWebKit.QGraphicsWebView()
|
||||||
|
self.scene.addItem(self.webView)
|
||||||
|
self.webView.resize(self.screen[u'size'].width(),
|
||||||
self.screen[u'size'].height())
|
self.screen[u'size'].height())
|
||||||
self.page = self.webView.page()
|
self.page = self.webView.page()
|
||||||
self.frame = self.page.mainFrame()
|
self.frame = self.page.mainFrame()
|
||||||
QtCore.QObject.connect(self.webView,
|
QtCore.QObject.connect(self.webView,
|
||||||
QtCore.SIGNAL(u'loadFinished(bool)'), self.isLoaded)
|
QtCore.SIGNAL(u'loadFinished(bool)'), self.isLoaded)
|
||||||
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
|
self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
|
||||||
QtCore.Qt.ScrollBarAlwaysOff)
|
QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||||
|
@ -152,12 +158,12 @@ class MainDisplay(DisplayWidget):
|
||||||
splash_image)
|
splash_image)
|
||||||
serviceItem = ServiceItem()
|
serviceItem = ServiceItem()
|
||||||
serviceItem.bg_frame = initialFrame
|
serviceItem.bg_frame = initialFrame
|
||||||
self.webView.setHtml(build_html(serviceItem, self.screen, \
|
self.webView.setHtml(build_html(serviceItem, self.screen,
|
||||||
self.parent.alertTab))
|
self.parent.alertTab, self.isLive))
|
||||||
self.initialFrame = True
|
self.initialFrame = True
|
||||||
self.show()
|
|
||||||
# To display or not to display?
|
# To display or not to display?
|
||||||
if not self.screen[u'primary']:
|
if not self.screen[u'primary']:
|
||||||
|
self.show()
|
||||||
self.primary = False
|
self.primary = False
|
||||||
else:
|
else:
|
||||||
self.primary = True
|
self.primary = True
|
||||||
|
@ -297,6 +303,10 @@ class MainDisplay(DisplayWidget):
|
||||||
Generates a preview of the image displayed.
|
Generates a preview of the image displayed.
|
||||||
"""
|
"""
|
||||||
log.debug(u'preview for %s', self.isLive)
|
log.debug(u'preview for %s', self.isLive)
|
||||||
|
# We must have a service item to preview
|
||||||
|
if not hasattr(self, u'serviceItem'):
|
||||||
|
return
|
||||||
|
if self.isLive:
|
||||||
# Wait for the fade to finish before geting the preview.
|
# Wait for the fade to finish before geting the preview.
|
||||||
# Important otherwise preview will have incorrect text if at all !
|
# Important otherwise preview will have incorrect text if at all !
|
||||||
if self.serviceItem.themedata and \
|
if self.serviceItem.themedata and \
|
||||||
|
@ -318,9 +328,6 @@ class MainDisplay(DisplayWidget):
|
||||||
# Make display show up if in single screen mode
|
# Make display show up if in single screen mode
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
# save preview for debugging
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
|
||||||
preview.save(u'temp.png', u'png')
|
|
||||||
return preview
|
return preview
|
||||||
|
|
||||||
def buildHtml(self, serviceItem):
|
def buildHtml(self, serviceItem):
|
||||||
|
@ -332,10 +339,14 @@ class MainDisplay(DisplayWidget):
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
self.initialFrame = False
|
self.initialFrame = False
|
||||||
self.serviceItem = serviceItem
|
self.serviceItem = serviceItem
|
||||||
html = build_html(self.serviceItem, self.screen, self.parent.alertTab)
|
html = build_html(self.serviceItem, self.screen, self.parent.alertTab,
|
||||||
|
self.isLive)
|
||||||
self.webView.setHtml(html)
|
self.webView.setHtml(html)
|
||||||
if serviceItem.foot_text and serviceItem.foot_text:
|
if serviceItem.foot_text and serviceItem.foot_text:
|
||||||
self.footer(serviceItem.foot_text)
|
self.footer(serviceItem.foot_text)
|
||||||
|
# if was hidden keep it hidden
|
||||||
|
if self.hide_mode and self.isLive:
|
||||||
|
self.hideDisplay(self.hide_mode)
|
||||||
|
|
||||||
def footer(self, text):
|
def footer(self, text):
|
||||||
"""
|
"""
|
||||||
|
@ -361,6 +372,7 @@ class MainDisplay(DisplayWidget):
|
||||||
self.frame.evaluateJavaScript(u'show_blank("theme");')
|
self.frame.evaluateJavaScript(u'show_blank("theme");')
|
||||||
if mode != HideMode.Screen and self.isHidden():
|
if mode != HideMode.Screen and self.isHidden():
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
|
self.hide_mode = mode
|
||||||
|
|
||||||
def showDisplay(self):
|
def showDisplay(self):
|
||||||
"""
|
"""
|
||||||
|
@ -374,6 +386,7 @@ class MainDisplay(DisplayWidget):
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
# Trigger actions when display is active again
|
# Trigger actions when display is active again
|
||||||
Receiver.send_message(u'maindisplay_active')
|
Receiver.send_message(u'maindisplay_active')
|
||||||
|
self.hide_mode = None
|
||||||
|
|
||||||
class AudioPlayer(QtCore.QObject):
|
class AudioPlayer(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -250,7 +250,7 @@ class Ui_MainWindow(object):
|
||||||
self.LanguageGroup = QtGui.QActionGroup(MainWindow)
|
self.LanguageGroup = QtGui.QActionGroup(MainWindow)
|
||||||
qmList = LanguageManager.get_qm_list()
|
qmList = LanguageManager.get_qm_list()
|
||||||
savedLanguage = LanguageManager.get_language()
|
savedLanguage = LanguageManager.get_language()
|
||||||
self.AutoLanguageItem.setChecked(LanguageManager.AutoLanguage)
|
self.AutoLanguageItem.setChecked(LanguageManager.auto_language)
|
||||||
for key in sorted(qmList.keys()):
|
for key in sorted(qmList.keys()):
|
||||||
languageItem = QtGui.QAction(MainWindow)
|
languageItem = QtGui.QAction(MainWindow)
|
||||||
languageItem.setObjectName(key)
|
languageItem.setObjectName(key)
|
||||||
|
@ -258,7 +258,7 @@ class Ui_MainWindow(object):
|
||||||
if qmList[key] == savedLanguage:
|
if qmList[key] == savedLanguage:
|
||||||
languageItem.setChecked(True)
|
languageItem.setChecked(True)
|
||||||
add_actions(self.LanguageGroup, [languageItem])
|
add_actions(self.LanguageGroup, [languageItem])
|
||||||
self.LanguageGroup.setDisabled(LanguageManager.AutoLanguage)
|
self.LanguageGroup.setDisabled(LanguageManager.auto_language)
|
||||||
self.ToolsAddToolItem = QtGui.QAction(MainWindow)
|
self.ToolsAddToolItem = QtGui.QAction(MainWindow)
|
||||||
self.ToolsAddToolItem.setIcon(build_icon(u':/tools/tools_add.png'))
|
self.ToolsAddToolItem.setIcon(build_icon(u':/tools/tools_add.png'))
|
||||||
self.ToolsAddToolItem.setObjectName(u'ToolsAddToolItem')
|
self.ToolsAddToolItem.setObjectName(u'ToolsAddToolItem')
|
||||||
|
@ -640,7 +640,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
def setAutoLanguage(self, value):
|
def setAutoLanguage(self, value):
|
||||||
self.LanguageGroup.setDisabled(value)
|
self.LanguageGroup.setDisabled(value)
|
||||||
LanguageManager.AutoLanguage = value
|
LanguageManager.auto_language = value
|
||||||
LanguageManager.set_language(self.LanguageGroup.checkedAction())
|
LanguageManager.set_language(self.LanguageGroup.checkedAction())
|
||||||
|
|
||||||
def versionNotice(self, version):
|
def versionNotice(self, version):
|
||||||
|
|
|
@ -93,7 +93,7 @@ class Ui_PluginViewDialog(object):
|
||||||
self.pluginListButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
|
self.pluginListButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
|
||||||
self.pluginListButtonBox.setObjectName(u'pluginListButtonBox')
|
self.pluginListButtonBox.setObjectName(u'pluginListButtonBox')
|
||||||
self.pluginLayout.addWidget(self.pluginListButtonBox)
|
self.pluginLayout.addWidget(self.pluginListButtonBox)
|
||||||
|
self.versionNumberLabel.setText(u'')
|
||||||
self.retranslateUi(pluginViewDialog)
|
self.retranslateUi(pluginViewDialog)
|
||||||
QtCore.QObject.connect(self.pluginListButtonBox,
|
QtCore.QObject.connect(self.pluginListButtonBox,
|
||||||
QtCore.SIGNAL(u'accepted()'), pluginViewDialog.close)
|
QtCore.SIGNAL(u'accepted()'), pluginViewDialog.close)
|
||||||
|
@ -106,8 +106,6 @@ class Ui_PluginViewDialog(object):
|
||||||
translate('OpenLP.PluginForm', 'Plugin Details'))
|
translate('OpenLP.PluginForm', 'Plugin Details'))
|
||||||
self.versionLabel.setText(
|
self.versionLabel.setText(
|
||||||
translate('OpenLP.PluginForm', 'Version:'))
|
translate('OpenLP.PluginForm', 'Version:'))
|
||||||
self.versionNumberLabel.setText(
|
|
||||||
translate('OpenLP.PluginForm', 'TextLabel'))
|
|
||||||
self.aboutLabel.setText(
|
self.aboutLabel.setText(
|
||||||
translate('OpenLP.PluginForm', 'About:'))
|
translate('OpenLP.PluginForm', 'About:'))
|
||||||
self.statusLabel.setText(
|
self.statusLabel.setText(
|
||||||
|
@ -116,3 +114,4 @@ class Ui_PluginViewDialog(object):
|
||||||
translate('OpenLP.PluginForm', 'Active'))
|
translate('OpenLP.PluginForm', 'Active'))
|
||||||
self.statusComboBox.setItemText(1,
|
self.statusComboBox.setItemText(1,
|
||||||
translate('OpenLP.PluginForm', 'Inactive'))
|
translate('OpenLP.PluginForm', 'Inactive'))
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
|
||||||
Load the plugin details into the screen
|
Load the plugin details into the screen
|
||||||
"""
|
"""
|
||||||
self.pluginListWidget.clear()
|
self.pluginListWidget.clear()
|
||||||
|
self.programaticChange = True
|
||||||
|
self._clearDetails()
|
||||||
|
self.programaticChange = True
|
||||||
for plugin in self.parent.plugin_manager.plugins:
|
for plugin in self.parent.plugin_manager.plugins:
|
||||||
item = QtGui.QListWidgetItem(self.pluginListWidget)
|
item = QtGui.QListWidgetItem(self.pluginListWidget)
|
||||||
# We do this just to make 100% sure the status is an integer as
|
# We do this just to make 100% sure the status is an integer as
|
||||||
|
|
|
@ -279,7 +279,8 @@ class ServiceManager(QtGui.QWidget):
|
||||||
self.editAction.setVisible(False)
|
self.editAction.setVisible(False)
|
||||||
self.maintainAction.setVisible(False)
|
self.maintainAction.setVisible(False)
|
||||||
self.notesAction.setVisible(False)
|
self.notesAction.setVisible(False)
|
||||||
if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit):
|
if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit) \
|
||||||
|
and hasattr(serviceItem[u'service_item'], u'editId'):
|
||||||
self.editAction.setVisible(True)
|
self.editAction.setVisible(True)
|
||||||
if serviceItem[u'service_item']\
|
if serviceItem[u'service_item']\
|
||||||
.is_capable(ItemCapabilities.AllowsMaintain):
|
.is_capable(ItemCapabilities.AllowsMaintain):
|
||||||
|
@ -574,7 +575,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
* An osd which is a pickle of the service items
|
* An osd which is a pickle of the service items
|
||||||
* All image, presentation and video files needed to run the service.
|
* All image, presentation and video files needed to run the service.
|
||||||
"""
|
"""
|
||||||
log.debug(u'onSaveService')
|
log.debug(u'onSaveService %s' % quick)
|
||||||
if not quick or self.isNew:
|
if not quick or self.isNew:
|
||||||
filename = QtGui.QFileDialog.getSaveFileName(self,
|
filename = QtGui.QFileDialog.getSaveFileName(self,
|
||||||
translate('OpenLP.ServiceManager', 'Save Service'),
|
translate('OpenLP.ServiceManager', 'Save Service'),
|
||||||
|
@ -632,6 +633,8 @@ class ServiceManager(QtGui.QWidget):
|
||||||
|
|
||||||
def onLoadService(self, lastService=False):
|
def onLoadService(self, lastService=False):
|
||||||
if lastService:
|
if lastService:
|
||||||
|
if not self.parent.recentFiles:
|
||||||
|
return
|
||||||
filename = self.parent.recentFiles[0]
|
filename = self.parent.recentFiles[0]
|
||||||
else:
|
else:
|
||||||
filename = QtGui.QFileDialog.getOpenFileName(
|
filename = QtGui.QFileDialog.getOpenFileName(
|
||||||
|
@ -755,6 +758,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Set the theme for the current service
|
Set the theme for the current service
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'onThemeComboBoxSelected')
|
||||||
self.service_theme = unicode(self.themeComboBox.currentText())
|
self.service_theme = unicode(self.themeComboBox.currentText())
|
||||||
self.parent.RenderManager.set_service_theme(self.service_theme)
|
self.parent.RenderManager.set_service_theme(self.service_theme)
|
||||||
QtCore.QSettings().setValue(
|
QtCore.QSettings().setValue(
|
||||||
|
@ -767,6 +771,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
The theme may have changed in the settings dialog so make
|
The theme may have changed in the settings dialog so make
|
||||||
sure the theme combo box is in the correct state.
|
sure the theme combo box is in the correct state.
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'themeChange')
|
||||||
if self.parent.RenderManager.theme_level == ThemeLevel.Global:
|
if self.parent.RenderManager.theme_level == ThemeLevel.Global:
|
||||||
self.toolbar.actions[u'ThemeLabel'].setVisible(False)
|
self.toolbar.actions[u'ThemeLabel'].setVisible(False)
|
||||||
self.toolbar.actions[u'ThemeWidget'].setVisible(False)
|
self.toolbar.actions[u'ThemeWidget'].setVisible(False)
|
||||||
|
@ -779,6 +784,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
Rebuild the service list as things have changed and a
|
Rebuild the service list as things have changed and a
|
||||||
repaint is the easiest way to do this.
|
repaint is the easiest way to do this.
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'regenerateServiceItems')
|
||||||
# force reset of renderer as theme data has changed
|
# force reset of renderer as theme data has changed
|
||||||
self.parent.RenderManager.themedata = None
|
self.parent.RenderManager.themedata = None
|
||||||
if self.serviceItems:
|
if self.serviceItems:
|
||||||
|
@ -800,6 +806,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
``item``
|
``item``
|
||||||
Service Item to be added
|
Service Item to be added
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'addServiceItem')
|
||||||
sitem = self.findServiceItem()[0]
|
sitem = self.findServiceItem()[0]
|
||||||
item.render()
|
item.render()
|
||||||
if replace:
|
if replace:
|
||||||
|
|
|
@ -30,6 +30,7 @@ import logging
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
|
from openlp.core.lib import Receiver
|
||||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||||
from settingsdialog import Ui_SettingsDialog
|
from settingsdialog import Ui_SettingsDialog
|
||||||
|
|
||||||
|
@ -87,6 +88,8 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
|
||||||
"""
|
"""
|
||||||
for tabIndex in range(0, self.settingsTabWidget.count()):
|
for tabIndex in range(0, self.settingsTabWidget.count()):
|
||||||
self.settingsTabWidget.widget(tabIndex).save()
|
self.settingsTabWidget.widget(tabIndex).save()
|
||||||
|
# Must go after all settings are save
|
||||||
|
Receiver.send_message(u'config_updated')
|
||||||
return QtGui.QDialog.accept(self)
|
return QtGui.QDialog.accept(self)
|
||||||
|
|
||||||
def postSetUp(self):
|
def postSetUp(self):
|
||||||
|
|
|
@ -162,11 +162,6 @@ class SlideController(QtGui.QWidget):
|
||||||
sizeToolbarPolicy.setHeightForWidth(
|
sizeToolbarPolicy.setHeightForWidth(
|
||||||
self.Toolbar.sizePolicy().hasHeightForWidth())
|
self.Toolbar.sizePolicy().hasHeightForWidth())
|
||||||
self.Toolbar.setSizePolicy(sizeToolbarPolicy)
|
self.Toolbar.setSizePolicy(sizeToolbarPolicy)
|
||||||
# if self.isLive:
|
|
||||||
# self.Toolbar.addToolbarButton(
|
|
||||||
# u'First Slide', u':/slides/slide_first.png',
|
|
||||||
# translate('OpenLP.SlideController', 'Move to first'),
|
|
||||||
# self.onSlideSelectedFirst)
|
|
||||||
self.Toolbar.addToolbarButton(
|
self.Toolbar.addToolbarButton(
|
||||||
u'Previous Slide', u':/slides/slide_previous.png',
|
u'Previous Slide', u':/slides/slide_previous.png',
|
||||||
translate('OpenLP.SlideController', 'Move to previous'),
|
translate('OpenLP.SlideController', 'Move to previous'),
|
||||||
|
@ -175,11 +170,6 @@ class SlideController(QtGui.QWidget):
|
||||||
u'Next Slide', u':/slides/slide_next.png',
|
u'Next Slide', u':/slides/slide_next.png',
|
||||||
translate('OpenLP.SlideController', 'Move to next'),
|
translate('OpenLP.SlideController', 'Move to next'),
|
||||||
self.onSlideSelectedNext)
|
self.onSlideSelectedNext)
|
||||||
# if self.isLive:
|
|
||||||
# self.Toolbar.addToolbarButton(
|
|
||||||
# u'Last Slide', u':/slides/slide_last.png',
|
|
||||||
# translate('OpenLP.SlideController', 'Move to last'),
|
|
||||||
# self.onSlideSelectedLast)
|
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
||||||
self.HideMenu = QtGui.QToolButton(self.Toolbar)
|
self.HideMenu = QtGui.QToolButton(self.Toolbar)
|
||||||
|
@ -219,7 +209,7 @@ class SlideController(QtGui.QWidget):
|
||||||
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
||||||
self.Toolbar.addToolbarButton(
|
self.Toolbar.addToolbarButton(
|
||||||
u'Edit Song', u':/general/general_edit.png',
|
u'Edit Song', u':/general/general_edit.png',
|
||||||
translate('OpenLP.SlideController', 'Edit and re-preview Song'),
|
translate('OpenLP.SlideController', 'Edit and re-preview song'),
|
||||||
self.onEditSong)
|
self.onEditSong)
|
||||||
if isLive:
|
if isLive:
|
||||||
self.Toolbar.addToolbarSeparator(u'Loop Separator')
|
self.Toolbar.addToolbarSeparator(u'Loop Separator')
|
||||||
|
@ -279,11 +269,11 @@ class SlideController(QtGui.QWidget):
|
||||||
if isLive:
|
if isLive:
|
||||||
self.SongMenu = QtGui.QToolButton(self.Toolbar)
|
self.SongMenu = QtGui.QToolButton(self.Toolbar)
|
||||||
self.SongMenu.setText(translate('OpenLP.SlideController',
|
self.SongMenu.setText(translate('OpenLP.SlideController',
|
||||||
'Go to Verse'))
|
'Go To'))
|
||||||
self.SongMenu.setPopupMode(QtGui.QToolButton.InstantPopup)
|
self.SongMenu.setPopupMode(QtGui.QToolButton.InstantPopup)
|
||||||
self.Toolbar.addToolbarWidget(u'Song Menu', self.SongMenu)
|
self.Toolbar.addToolbarWidget(u'Song Menu', self.SongMenu)
|
||||||
self.SongMenu.setMenu(QtGui.QMenu(
|
self.SongMenu.setMenu(QtGui.QMenu(
|
||||||
translate('OpenLP.SlideController', 'Go to Verse'),
|
translate('OpenLP.SlideController', 'Go To'),
|
||||||
self.Toolbar))
|
self.Toolbar))
|
||||||
self.Toolbar.makeWidgetsInvisible([u'Song Menu'])
|
self.Toolbar.makeWidgetsInvisible([u'Song Menu'])
|
||||||
# Screen preview area
|
# Screen preview area
|
||||||
|
|
|
@ -295,7 +295,7 @@ class ThemeManager(QtGui.QWidget):
|
||||||
path = unicode(path)
|
path = unicode(path)
|
||||||
if path:
|
if path:
|
||||||
SettingsManager.set_last_dir(self.settingsSection, path, 1)
|
SettingsManager.set_last_dir(self.settingsSection, path, 1)
|
||||||
themePath = os.path.join(path, theme + u'.thz')
|
themePath = os.path.join(path, theme + u'.otz')
|
||||||
zip = None
|
zip = None
|
||||||
try:
|
try:
|
||||||
zip = zipfile.ZipFile(themePath, u'w')
|
zip = zipfile.ZipFile(themePath, u'w')
|
||||||
|
|
|
@ -102,6 +102,7 @@ class AppLocation(object):
|
||||||
PluginsDir = 4
|
PluginsDir = 4
|
||||||
VersionDir = 5
|
VersionDir = 5
|
||||||
CacheDir = 6
|
CacheDir = 6
|
||||||
|
LanguageDir = 7
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_directory(dir_type=1):
|
def get_directory(dir_type=1):
|
||||||
|
@ -112,7 +113,11 @@ class AppLocation(object):
|
||||||
The directory type you want, for instance the data directory.
|
The directory type you want, for instance the data directory.
|
||||||
"""
|
"""
|
||||||
if dir_type == AppLocation.AppDir:
|
if dir_type == AppLocation.AppDir:
|
||||||
return os.path.abspath(os.path.split(sys.argv[0])[0])
|
if hasattr(sys, u'frozen') and sys.frozen == 1:
|
||||||
|
app_path = os.path.abspath(os.path.split(sys.argv[0])[0])
|
||||||
|
else:
|
||||||
|
app_path = os.path.split(openlp.__file__)[0]
|
||||||
|
return app_path
|
||||||
elif dir_type == AppLocation.ConfigDir:
|
elif dir_type == AppLocation.ConfigDir:
|
||||||
if sys.platform == u'win32':
|
if sys.platform == u'win32':
|
||||||
path = os.path.join(os.getenv(u'APPDATA'), u'openlp')
|
path = os.path.join(os.getenv(u'APPDATA'), u'openlp')
|
||||||
|
@ -169,6 +174,13 @@ class AppLocation(object):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
path = os.path.join(os.getenv(u'HOME'), u'.openlp')
|
path = os.path.join(os.getenv(u'HOME'), u'.openlp')
|
||||||
return path
|
return path
|
||||||
|
if dir_type == AppLocation.LanguageDir:
|
||||||
|
if hasattr(sys, u'frozen') and sys.frozen == 1:
|
||||||
|
app_path = os.path.abspath(os.path.split(sys.argv[0])[0])
|
||||||
|
else:
|
||||||
|
app_path = os.path.split(openlp.__file__)[0]
|
||||||
|
return os.path.join(app_path, u'i18n')
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_data_path():
|
def get_data_path():
|
||||||
|
|
|
@ -35,14 +35,14 @@ from PyQt4 import QtCore, QtGui
|
||||||
from openlp.core.utils import AppLocation
|
from openlp.core.utils import AppLocation
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class LanguageManager(object):
|
class LanguageManager(object):
|
||||||
"""
|
"""
|
||||||
Helper for Language selection
|
Helper for Language selection
|
||||||
"""
|
"""
|
||||||
__qmList__ = None
|
__qm_list__ = {}
|
||||||
AutoLanguage = False
|
auto_language = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_translator(language):
|
def get_translator(language):
|
||||||
|
@ -52,12 +52,11 @@ class LanguageManager(object):
|
||||||
``language``
|
``language``
|
||||||
The language to load into the translator
|
The language to load into the translator
|
||||||
"""
|
"""
|
||||||
if LanguageManager.AutoLanguage:
|
if LanguageManager.auto_language:
|
||||||
language = QtCore.QLocale.system().name()
|
language = QtCore.QLocale.system().name()
|
||||||
lang_path = AppLocation.get_directory(AppLocation.AppDir)
|
lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
|
||||||
lang_path = os.path.join(lang_path, u'resources', u'i18n')
|
|
||||||
app_translator = QtCore.QTranslator()
|
app_translator = QtCore.QTranslator()
|
||||||
if app_translator.load("openlp_" + language, lang_path):
|
if app_translator.load(language, lang_path):
|
||||||
return app_translator
|
return app_translator
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -65,8 +64,8 @@ class LanguageManager(object):
|
||||||
"""
|
"""
|
||||||
Find all available language files in this OpenLP install
|
Find all available language files in this OpenLP install
|
||||||
"""
|
"""
|
||||||
trans_dir = AppLocation.get_directory(AppLocation.AppDir)
|
trans_dir = QtCore.QDir(AppLocation.get_directory(
|
||||||
trans_dir = QtCore.QDir(os.path.join(trans_dir, u'resources', u'i18n'))
|
AppLocation.LanguageDir))
|
||||||
file_names = trans_dir.entryList(QtCore.QStringList("*.qm"),
|
file_names = trans_dir.entryList(QtCore.QStringList("*.qm"),
|
||||||
QtCore.QDir.Files, QtCore.QDir.Name)
|
QtCore.QDir.Files, QtCore.QDir.Name)
|
||||||
for name in file_names:
|
for name in file_names:
|
||||||
|
@ -96,7 +95,7 @@ class LanguageManager(object):
|
||||||
log.info(u'Language file: \'%s\' Loaded from conf file' % language)
|
log.info(u'Language file: \'%s\' Loaded from conf file' % language)
|
||||||
reg_ex = QtCore.QRegExp("^\[(.*)\]")
|
reg_ex = QtCore.QRegExp("^\[(.*)\]")
|
||||||
if reg_ex.exactMatch(language):
|
if reg_ex.exactMatch(language):
|
||||||
LanguageManager.AutoLanguage = True
|
LanguageManager.auto_language = True
|
||||||
language = reg_ex.cap(1)
|
language = reg_ex.cap(1)
|
||||||
return language
|
return language
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ class LanguageManager(object):
|
||||||
"""
|
"""
|
||||||
action_name = u'%s' % action.objectName()
|
action_name = u'%s' % action.objectName()
|
||||||
qm_list = LanguageManager.get_qm_list()
|
qm_list = LanguageManager.get_qm_list()
|
||||||
if LanguageManager.AutoLanguage:
|
if LanguageManager.auto_language:
|
||||||
language = u'[%s]' % qm_list[action_name]
|
language = u'[%s]' % qm_list[action_name]
|
||||||
else:
|
else:
|
||||||
language = u'%s' % qm_list[action_name]
|
language = u'%s' % qm_list[action_name]
|
||||||
|
@ -127,20 +126,18 @@ class LanguageManager(object):
|
||||||
"""
|
"""
|
||||||
Initialise the list of available translations
|
Initialise the list of available translations
|
||||||
"""
|
"""
|
||||||
LanguageManager.__qmList__ = {}
|
LanguageManager.__qm_list__ = {}
|
||||||
qm_files = LanguageManager.find_qm_files()
|
qm_files = LanguageManager.find_qm_files()
|
||||||
for i, qmf in enumerate(qm_files):
|
for counter, qmf in enumerate(qm_files):
|
||||||
reg_ex = QtCore.QRegExp("^.*openlp_(.*).qm")
|
name = unicode(qmf).split(u'.')[0]
|
||||||
if reg_ex.exactMatch(qmf):
|
LanguageManager.__qm_list__[u'%#2i %s' % (counter + 1,
|
||||||
lang_name = reg_ex.cap(1)
|
LanguageManager.language_name(qmf))] = name
|
||||||
LanguageManager.__qmList__[u'%#2i %s' % (i+1,
|
|
||||||
LanguageManager.language_name(qmf))] = lang_name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_qm_list():
|
def get_qm_list():
|
||||||
"""
|
"""
|
||||||
Return the list of available translations
|
Return the list of available translations
|
||||||
"""
|
"""
|
||||||
if LanguageManager.__qmList__ is None:
|
if not LanguageManager.__qm_list__:
|
||||||
LanguageManager.init_qm_list()
|
LanguageManager.init_qm_list()
|
||||||
return LanguageManager.__qmList__
|
return LanguageManager.__qm_list__
|
||||||
|
|
|
@ -196,10 +196,10 @@ class BiblesTab(SettingsTab):
|
||||||
self.show_new_chapters = True
|
self.show_new_chapters = True
|
||||||
|
|
||||||
def onBibleDualCheckBox(self, check_state):
|
def onBibleDualCheckBox(self, check_state):
|
||||||
self.duel_bibles = False
|
self.dual_bibles = False
|
||||||
# we have a set value convert to True/False
|
# we have a set value convert to True/False
|
||||||
if check_state == QtCore.Qt.Checked:
|
if check_state == QtCore.Qt.Checked:
|
||||||
self.duel_bibles = True
|
self.dual_bibles = True
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
settings = QtCore.QSettings()
|
settings = QtCore.QSettings()
|
||||||
|
@ -212,12 +212,12 @@ class BiblesTab(SettingsTab):
|
||||||
u'verse layout style', QtCore.QVariant(0)).toInt()[0]
|
u'verse layout style', QtCore.QVariant(0)).toInt()[0]
|
||||||
self.bible_theme = unicode(
|
self.bible_theme = unicode(
|
||||||
settings.value(u'bible theme', QtCore.QVariant(u'')).toString())
|
settings.value(u'bible theme', QtCore.QVariant(u'')).toString())
|
||||||
self.duel_bibles = settings.value(
|
self.dual_bibles = settings.value(
|
||||||
u'dual bibles', QtCore.QVariant(True)).toBool()
|
u'dual bibles', QtCore.QVariant(True)).toBool()
|
||||||
self.NewChaptersCheckBox.setChecked(self.show_new_chapters)
|
self.NewChaptersCheckBox.setChecked(self.show_new_chapters)
|
||||||
self.DisplayStyleComboBox.setCurrentIndex(self.display_style)
|
self.DisplayStyleComboBox.setCurrentIndex(self.display_style)
|
||||||
self.LayoutStyleComboBox.setCurrentIndex(self.layout_style)
|
self.LayoutStyleComboBox.setCurrentIndex(self.layout_style)
|
||||||
self.BibleDualCheckBox.setChecked(self.duel_bibles)
|
self.BibleDualCheckBox.setChecked(self.dual_bibles)
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -229,7 +229,7 @@ class BiblesTab(SettingsTab):
|
||||||
QtCore.QVariant(self.display_style))
|
QtCore.QVariant(self.display_style))
|
||||||
settings.setValue(u'verse layout style',
|
settings.setValue(u'verse layout style',
|
||||||
QtCore.QVariant(self.layout_style))
|
QtCore.QVariant(self.layout_style))
|
||||||
settings.setValue(u'dual bibles', QtCore.QVariant(self.duel_bibles))
|
settings.setValue(u'dual bibles', QtCore.QVariant(self.dual_bibles))
|
||||||
settings.setValue(u'bible theme', QtCore.QVariant(self.bible_theme))
|
settings.setValue(u'bible theme', QtCore.QVariant(self.bible_theme))
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
|
|
|
@ -51,9 +51,9 @@ class CSVBible(BibleDB):
|
||||||
if u'booksfile' not in kwargs:
|
if u'booksfile' not in kwargs:
|
||||||
raise KeyError(u'You have to supply a file to import books from.')
|
raise KeyError(u'You have to supply a file to import books from.')
|
||||||
self.booksfile = kwargs[u'booksfile']
|
self.booksfile = kwargs[u'booksfile']
|
||||||
if u'versesfile' not in kwargs:
|
if u'versefile' not in kwargs:
|
||||||
raise KeyError(u'You have to supply a file to import verses from.')
|
raise KeyError(u'You have to supply a file to import verses from.')
|
||||||
self.versesfile = kwargs[u'versesfile']
|
self.versesfile = kwargs[u'versefile']
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'bibles_stop_import'), self.stop_import)
|
QtCore.SIGNAL(u'bibles_stop_import'), self.stop_import)
|
||||||
|
|
||||||
|
|
|
@ -275,8 +275,9 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
self.SearchProgress.setObjectName(u'SearchProgress')
|
self.SearchProgress.setObjectName(u'SearchProgress')
|
||||||
|
|
||||||
def configUpdated(self):
|
def configUpdated(self):
|
||||||
|
log.debug(u'configUpdated')
|
||||||
if QtCore.QSettings().value(self.settingsSection + u'/dual bibles',
|
if QtCore.QSettings().value(self.settingsSection + u'/dual bibles',
|
||||||
QtCore.QVariant(False)).toBool():
|
QtCore.QVariant(True)).toBool():
|
||||||
self.AdvancedSecondBibleLabel.setVisible(True)
|
self.AdvancedSecondBibleLabel.setVisible(True)
|
||||||
self.AdvancedSecondBibleComboBox.setVisible(True)
|
self.AdvancedSecondBibleComboBox.setVisible(True)
|
||||||
self.QuickSecondVersionLabel.setVisible(True)
|
self.QuickSecondVersionLabel.setVisible(True)
|
||||||
|
@ -677,7 +678,8 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
self.dual_search_results[count].text)
|
self.dual_search_results[count].text)
|
||||||
}
|
}
|
||||||
bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name,
|
bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name,
|
||||||
verse.chapter, verse.verse, version.value, dual_version.value)
|
verse.chapter, verse.verse, version.value,
|
||||||
|
dual_version.value)
|
||||||
else:
|
else:
|
||||||
vdict = {
|
vdict = {
|
||||||
'book': QtCore.QVariant(verse.book.name),
|
'book': QtCore.QVariant(verse.book.name),
|
||||||
|
|
|
@ -89,7 +89,7 @@ class OpenSongBible(BibleDB):
|
||||||
Receiver.send_message(u'openlp_process_events')
|
Receiver.send_message(u'openlp_process_events')
|
||||||
self.wizard.incrementProgressBar(
|
self.wizard.incrementProgressBar(
|
||||||
QtCore.QString('%s %s %s' % (
|
QtCore.QString('%s %s %s' % (
|
||||||
translate('BiblesPlugin.Opensong', 'Importing'), \
|
translate('BiblesPlugin.Opensong', 'Importing'),
|
||||||
db_book.name, chapter.attrib[u'n'])))
|
db_book.name, chapter.attrib[u'n'])))
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.lib import build_icon, translate
|
from openlp.core.lib import build_icon, translate, SpellTextEdit
|
||||||
from openlp.core.ui import SpellTextEdit
|
|
||||||
|
|
||||||
class Ui_CustomEditDialog(object):
|
class Ui_CustomEditDialog(object):
|
||||||
def setupUi(self, customEditDialog):
|
def setupUi(self, customEditDialog):
|
||||||
|
|
|
@ -212,7 +212,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
self, translate('PresentationPlugin.MediaItem',
|
self, translate('PresentationPlugin.MediaItem',
|
||||||
'Unsupported File'),
|
'Unsupported File'),
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'This type of presentation is not supported'))
|
'This type of presentation is not supported.'))
|
||||||
continue
|
continue
|
||||||
item_name = QtGui.QListWidgetItem(filename)
|
item_name = QtGui.QListWidgetItem(filename)
|
||||||
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
|
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
|
||||||
|
|
|
@ -1,119 +1,57 @@
|
||||||
<html>
|
<!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>
|
<head>
|
||||||
<title>OpenLP Controller</title>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<script type='text/javascript'>
|
<title>OpenLP Remote Controller</title>
|
||||||
|
<script type="text/javascript" src="/files/jquery.js"></script>
|
||||||
function send_event(eventname, data){
|
<script type='text/javascript' src="/files/openlp.js"></script>
|
||||||
var req = new XMLHttpRequest();
|
<script type='text/javascript' src="/files/init.js"></script>
|
||||||
req.onreadystatechange = function() {
|
<link rel="stylesheet" href="/files/style.css" type="text/css" />
|
||||||
if(req.readyState==4)
|
|
||||||
response(eventname, req);
|
|
||||||
}
|
|
||||||
var url = '';
|
|
||||||
if(eventname.substr(-8) == '_request')
|
|
||||||
url = 'request';
|
|
||||||
else
|
|
||||||
url = 'send';
|
|
||||||
url += '/' + eventname;
|
|
||||||
if(data!=null)
|
|
||||||
url += '?q=' + escape(data);
|
|
||||||
req.open('GET', url, true);
|
|
||||||
req.send();
|
|
||||||
}
|
|
||||||
function failed_response(eventname, req){
|
|
||||||
switch(eventname){
|
|
||||||
case 'remotes_poll_request':
|
|
||||||
if(req.status==408)
|
|
||||||
send_event("remotes_poll_request");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function response(eventname, req){
|
|
||||||
if(req.status!=200){
|
|
||||||
failed_response(eventname, req);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
text = req.responseText;
|
|
||||||
switch(eventname){
|
|
||||||
case 'servicemanager_list_request':
|
|
||||||
var data = eval('(' + text + ')');
|
|
||||||
var html = '<table>';
|
|
||||||
for(row in data){
|
|
||||||
html += '<tr onclick="send_event('
|
|
||||||
html += "'servicemanager_set_item', " + row + ')"';
|
|
||||||
if(data[row]['selected'])
|
|
||||||
html += ' style="font-weight: bold"';
|
|
||||||
html += '>'
|
|
||||||
html += '<td>' + (parseInt(row)+1) + '</td>'
|
|
||||||
html += '<td>' + data[row]['title'] + '</td>'
|
|
||||||
html += '<td>' + data[row]['plugin'] + '</td>'
|
|
||||||
html += '<td>' + data[row]['notes'] + '</td>'
|
|
||||||
html += '</tr>';
|
|
||||||
}
|
|
||||||
html += '</table>';
|
|
||||||
document.getElementById('service').innerHTML = html;
|
|
||||||
break;
|
|
||||||
case 'slidecontroller_live_text_request':
|
|
||||||
var data = eval('(' + text + ')');
|
|
||||||
var html = '<table>';
|
|
||||||
for(row in data){
|
|
||||||
html += '<tr onclick="send_event('
|
|
||||||
html += "'slidecontroller_live_set', " + row + ')"';
|
|
||||||
if(data[row]['selected'])
|
|
||||||
html += ' style="font-weight: bold"';
|
|
||||||
html += '>';
|
|
||||||
html += '<td>' + data[row]['tag'] + '</td>';
|
|
||||||
html += '<td>' + data[row]['text'].replace(/\n/g, '<br>');
|
|
||||||
html += '</td></tr>';
|
|
||||||
}
|
|
||||||
html += '</table>';
|
|
||||||
document.getElementById('currentitem').innerHTML = html;
|
|
||||||
break;
|
|
||||||
case 'remotes_poll_request':
|
|
||||||
send_event("remotes_poll_request");
|
|
||||||
send_event("servicemanager_list_request");
|
|
||||||
send_event("slidecontroller_live_text_request");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
send_event("servicemanager_list_request");
|
|
||||||
send_event("slidecontroller_live_text_request");
|
|
||||||
send_event("remotes_poll_request");
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>OpenLP Controller</h1>
|
<h1>OpenLP Controller</h1>
|
||||||
<input type='button' value='<- Previous Slide'
|
<p>Quick Links: <a href="#service-manager">Service Manager</a> | <a href="#slide-controller">Slide Controller</a> | <a href="#miscellaneous">Miscellaneous</a></p>
|
||||||
onclick='send_event("slidecontroller_live_previous");' />
|
<h2 id="service-manager">Service Manager</h2>
|
||||||
<input type='button' value='Next Slide ->'
|
<div id="service"></div>
|
||||||
onclick='send_event("slidecontroller_live_next");' />
|
<p><em>(Click service item to go live.)</em></p>
|
||||||
<br/>
|
<fieldset>
|
||||||
<input type='button' value='<- Previous Item'
|
<legend>Controls</legend>
|
||||||
onclick='send_event("servicemanager_previous_item");' />
|
<div id="service-buttons">
|
||||||
<input type='button' value='Next Item ->'
|
<input type="button" value="Refresh Service" id="servicemanager_list_request" />
|
||||||
onclick='send_event("servicemanager_next_item");' />
|
</div>
|
||||||
<br/>
|
<div id="item-buttons">
|
||||||
<input type='button' value='Blank'
|
<input type="button" value="<- Previous Item" id="servicemanager_previous_item" />
|
||||||
onclick='send_event("slidecontroller_live_blank");' />
|
<input type="button" value="Next Item ->" id="servicemanager_next_item" />
|
||||||
<input type='button' value='Unblank'
|
</div>
|
||||||
onclick='send_event("slidecontroller_live_unblank");' />
|
</fieldset>
|
||||||
<br/>
|
|
||||||
<label>Alert text</label><input id='alert' type='text' />
|
|
||||||
<input type='button' value='Send'
|
|
||||||
onclick='send_event("alerts_text",
|
|
||||||
document.getElementById("alert").value);' />
|
|
||||||
<hr>
|
<hr>
|
||||||
<input type='button' value='Order of service'
|
<h2 id="slide-controller">Slide Controller</h2>
|
||||||
onclick='send_event("servicemanager_list_request");'>
|
<div id="current-item"></div>
|
||||||
<i>(Click service item to go live.)</i>
|
<p><em>(Click verse to display.)</em></p>
|
||||||
<div id='service'></div>
|
<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="<- Previous Slide" id="slidecontroller_live_previous" />
|
||||||
|
<input type="button" value="Next Slide ->" id="slidecontroller_live_next" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
<hr>
|
<hr>
|
||||||
<input type='button' value='Current item'
|
<h2 id="miscellaneous">Miscellaneous</h2>
|
||||||
onclick='send_event("slidecontroller_live_text_request");'>
|
<div id="display-buttons">
|
||||||
<i>(Click verse to display.)</i>
|
<input type="button" value="Blank" id="slidecontroller_live_blank" />
|
||||||
<div id='currentitem'></div>
|
<input type="button" value="Unblank" id="slidecontroller_live_unblank" />
|
||||||
|
</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>
|
||||||
<hr>
|
<hr>
|
||||||
<a href="http://www.openlp.org/">OpenLP website</a>
|
<a href="http://openlp.org/">OpenLP website</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* 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 *
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
OpenLP.Events.init();
|
||||||
|
});
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*!
|
||||||
|
* jQuery JavaScript Library v1.4.2
|
||||||
|
* http://jquery.com/
|
||||||
|
*
|
||||||
|
* Copyright 2010, John Resig
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* Includes Sizzle.js
|
||||||
|
* http://sizzlejs.com/
|
||||||
|
* Copyright 2010, The Dojo Foundation
|
||||||
|
* Released under the MIT, BSD, and GPL Licenses.
|
||||||
|
*
|
||||||
|
* Date: Sat Feb 13 22:33:48 2010 -0500
|
||||||
|
*/
|
||||||
|
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
|
||||||
|
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
|
||||||
|
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
|
||||||
|
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
|
||||||
|
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
|
||||||
|
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
|
||||||
|
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
|
||||||
|
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
|
||||||
|
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
|
||||||
|
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
|
||||||
|
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
|
||||||
|
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
|
||||||
|
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
|
||||||
|
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
|
||||||
|
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
|
||||||
|
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
|
||||||
|
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
|
||||||
|
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
|
||||||
|
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
|
||||||
|
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
|
||||||
|
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
|
||||||
|
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
|
||||||
|
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
|
||||||
|
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
|
||||||
|
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
|
||||||
|
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
|
||||||
|
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
|
||||||
|
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
|
||||||
|
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
|
||||||
|
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
|
||||||
|
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
|
||||||
|
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
|
||||||
|
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
|
||||||
|
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
|
||||||
|
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
|
||||||
|
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
|
||||||
|
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
|
||||||
|
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
|
||||||
|
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
|
||||||
|
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
|
||||||
|
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
|
||||||
|
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
|
||||||
|
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
|
||||||
|
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
|
||||||
|
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
|
||||||
|
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
|
||||||
|
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
|
||||||
|
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
|
||||||
|
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
|
||||||
|
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
|
||||||
|
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
|
||||||
|
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
|
||||||
|
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
|
||||||
|
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
|
||||||
|
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
|
||||||
|
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
|
||||||
|
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
|
||||||
|
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
|
||||||
|
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
|
||||||
|
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
|
||||||
|
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
|
||||||
|
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
|
||||||
|
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
|
||||||
|
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
|
||||||
|
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
|
||||||
|
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
|
||||||
|
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
|
||||||
|
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
|
||||||
|
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
|
||||||
|
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
|
||||||
|
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
|
||||||
|
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
|
||||||
|
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
|
||||||
|
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
|
||||||
|
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
|
||||||
|
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
|
||||||
|
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
|
||||||
|
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
|
||||||
|
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
|
||||||
|
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
|
||||||
|
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
|
||||||
|
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
|
||||||
|
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
|
||||||
|
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
|
||||||
|
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
|
||||||
|
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
|
||||||
|
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
|
||||||
|
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
|
||||||
|
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
|
||||||
|
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
|
||||||
|
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
|
||||||
|
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
|
||||||
|
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
|
||||||
|
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
|
||||||
|
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
|
||||||
|
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
|
||||||
|
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
|
||||||
|
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
|
||||||
|
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
|
||||||
|
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
|
||||||
|
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
|
||||||
|
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
|
||||||
|
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
|
||||||
|
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
|
||||||
|
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
|
||||||
|
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
|
||||||
|
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
|
||||||
|
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
|
||||||
|
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
|
||||||
|
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
|
||||||
|
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
|
||||||
|
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
|
||||||
|
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
|
||||||
|
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
|
||||||
|
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
|
||||||
|
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
|
||||||
|
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
|
||||||
|
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
|
||||||
|
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
|
||||||
|
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
|
||||||
|
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
|
||||||
|
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
|
||||||
|
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
|
||||||
|
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
|
||||||
|
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
|
||||||
|
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
|
||||||
|
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
|
||||||
|
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
|
||||||
|
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
|
||||||
|
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
|
||||||
|
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
|
||||||
|
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
|
||||||
|
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
|
||||||
|
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
|
||||||
|
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
|
||||||
|
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
|
||||||
|
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
|
||||||
|
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
|
||||||
|
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
|
|
@ -0,0 +1,239 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* 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 *
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
window["OpenLP"] = {
|
||||||
|
Namespace: {
|
||||||
|
/**
|
||||||
|
* Create a Javascript namespace.
|
||||||
|
* Based on: http://code.google.com/p/namespacedotjs/
|
||||||
|
* Idea behind this is to created nested namespaces that are not ugly.
|
||||||
|
*/
|
||||||
|
create: function (name, attributes) {
|
||||||
|
var parts = name.split('.'),
|
||||||
|
ns = window,
|
||||||
|
i = 0;
|
||||||
|
// find the deepest part of the namespace
|
||||||
|
// that is already defined
|
||||||
|
for(; i < parts.length && parts[i] in ns; i++)
|
||||||
|
ns = ns[parts[i]];
|
||||||
|
// initialize any remaining parts of the namespace
|
||||||
|
for(; i < parts.length; i++)
|
||||||
|
ns = ns[parts[i]] = {};
|
||||||
|
// copy the attributes into the namespace
|
||||||
|
for (var attr in attributes)
|
||||||
|
ns[attr] = attributes[attr];
|
||||||
|
},
|
||||||
|
exists: function (namespace) {
|
||||||
|
/**
|
||||||
|
* Determine the namespace of a page
|
||||||
|
*/
|
||||||
|
page_namespace = $ScribeEngine.Namespace.get_page_namespace();
|
||||||
|
return (namespace == page_namespace);
|
||||||
|
},
|
||||||
|
get_page_namespace: function () {
|
||||||
|
return $("#content > h2").attr("id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.append = function (elem) {
|
||||||
|
this[this.length] = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenLP.Namespace.create("OpenLP.Events", {
|
||||||
|
// Local variables
|
||||||
|
onload_functions: Array(),
|
||||||
|
// Functions
|
||||||
|
bindLoad: function (func) {
|
||||||
|
this.onload_functions.append(func);
|
||||||
|
},
|
||||||
|
bindClick: function (selector, func) {
|
||||||
|
$(selector).bind("click", func);
|
||||||
|
},
|
||||||
|
bindChange: function (selector, func) {
|
||||||
|
$(selector).bind("change", func);
|
||||||
|
},
|
||||||
|
bindSubmit: function (selector, func) {
|
||||||
|
$(selector).bind("submit", func);
|
||||||
|
},
|
||||||
|
bindBlur: function (selector, func) {
|
||||||
|
$(selector).bind("blur", func);
|
||||||
|
},
|
||||||
|
bindPaste: function (selector, func) {
|
||||||
|
$(selector).bind("paste", func);
|
||||||
|
},
|
||||||
|
bindKeyUp: function (selector, func) {
|
||||||
|
$(selector).bind("keyup", func);
|
||||||
|
},
|
||||||
|
bindKeyDown: function (selector, func) {
|
||||||
|
$(selector).bind("keydown", func);
|
||||||
|
},
|
||||||
|
bindKeyPress: function (selector, func) {
|
||||||
|
$(selector).bind("keypress", func);
|
||||||
|
},
|
||||||
|
bindMouseEnter: function (selector, func) {
|
||||||
|
$(selector).bind("mouseenter", func);
|
||||||
|
},
|
||||||
|
bindMouseLeave: function (selector, func) {
|
||||||
|
$(selector).bind("mouseleave", func);
|
||||||
|
},
|
||||||
|
liveClick: function (selector, func) {
|
||||||
|
$(selector).live("click", func);
|
||||||
|
},
|
||||||
|
getElement: function(event) {
|
||||||
|
var targ;
|
||||||
|
if (!event) {
|
||||||
|
var event = window.event;
|
||||||
|
}
|
||||||
|
if (event.target) {
|
||||||
|
targ = event.target;
|
||||||
|
}
|
||||||
|
else if (event.srcElement) {
|
||||||
|
targ = event.srcElement;
|
||||||
|
}
|
||||||
|
if (targ.nodeType == 3) {
|
||||||
|
// defeat Safari bug
|
||||||
|
targ = targ.parentNode;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendLiveSet: function (e)
|
||||||
|
{
|
||||||
|
var id = OpenLP.Events.getElement(e).parent().attr("value");
|
||||||
|
OpenLP.Remote.sendEvent("slidecontroller_live_set", id);
|
||||||
|
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");
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
|
@ -0,0 +1,45 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -26,9 +26,13 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtNetwork
|
from PyQt4 import QtCore, QtNetwork
|
||||||
|
|
||||||
from openlp.core.lib import Receiver
|
from openlp.core.lib import Receiver
|
||||||
|
@ -242,18 +246,24 @@ class HttpConnection(object):
|
||||||
Receiver.send_message(event, params)
|
Receiver.send_message(event, params)
|
||||||
else:
|
else:
|
||||||
Receiver.send_message(event)
|
Receiver.send_message(event)
|
||||||
return u'OK'
|
return json.dumps([u'OK'])
|
||||||
|
|
||||||
def process_request(self, event, params):
|
def process_request(self, event, params):
|
||||||
"""
|
"""
|
||||||
Client has requested data. Send the signal and parameters for openlp
|
Client has requested data. Send the signal and parameters for openlp
|
||||||
to handle, then listen out for a corresponding _request signal
|
to handle, then listen out for a corresponding ``_request`` signal
|
||||||
which will have the data to return.
|
which will have the data to return.
|
||||||
For most event timeout after 10 seconds (i.e. incase the signal
|
|
||||||
recipient isn't listening)
|
For most events, timeout after 10 seconds (i.e. in case the signal
|
||||||
remotes_poll_request is a special case, this is a ajax long poll which
|
recipient isn't listening). ``remotes_poll_request`` is a special case
|
||||||
is just waiting for slide change/song change activity. This can wait
|
however, this is a ajax long poll which is just waiting for slide
|
||||||
longer (one minute)
|
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)
|
log.debug(u'Processing request %s' % event)
|
||||||
if not event.endswith(u'_request'):
|
if not event.endswith(u'_request'):
|
||||||
|
|
|
@ -258,10 +258,11 @@ class Ui_EditSongDialog(object):
|
||||||
self.TopicBookLayout.addWidget(self.TopicGroupBox)
|
self.TopicBookLayout.addWidget(self.TopicGroupBox)
|
||||||
self.SongBookGroup = QtGui.QGroupBox(self.TopicBookWidget)
|
self.SongBookGroup = QtGui.QGroupBox(self.TopicBookWidget)
|
||||||
self.SongBookGroup.setObjectName(u'SongBookGroup')
|
self.SongBookGroup.setObjectName(u'SongBookGroup')
|
||||||
self.SongbookLayout = QtGui.QGridLayout(self.SongBookGroup)
|
self.SongbookLayout = QtGui.QFormLayout(self.SongBookGroup)
|
||||||
self.SongbookLayout.setMargin(8)
|
self.SongbookLayout.setMargin(8)
|
||||||
self.SongbookLayout.setSpacing(8)
|
self.SongbookLayout.setSpacing(8)
|
||||||
self.SongbookLayout.setObjectName(u'SongbookLayout')
|
self.SongbookLayout.setObjectName(u'SongbookLayout')
|
||||||
|
self.SongbookNameLabel = QtGui.QLabel(self.SongBookGroup)
|
||||||
self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup)
|
self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup)
|
||||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
|
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
|
||||||
QtGui.QSizePolicy.Fixed)
|
QtGui.QSizePolicy.Fixed)
|
||||||
|
@ -272,13 +273,11 @@ class Ui_EditSongDialog(object):
|
||||||
self.SongbookCombo.setEditable(True)
|
self.SongbookCombo.setEditable(True)
|
||||||
self.SongbookCombo.setSizePolicy(sizePolicy)
|
self.SongbookCombo.setSizePolicy(sizePolicy)
|
||||||
self.SongbookCombo.setObjectName(u'SongbookCombo')
|
self.SongbookCombo.setObjectName(u'SongbookCombo')
|
||||||
self.SongbookLayout.addWidget(self.SongbookCombo, 0, 0, 1, 1)
|
self.SongbookLayout.addRow(self.SongbookNameLabel, self.SongbookCombo)
|
||||||
self.songBookNumberLabel = QtGui.QLabel(self.SongBookGroup)
|
self.songBookNumberLabel = QtGui.QLabel(self.SongBookGroup)
|
||||||
self.SongbookLayout.addWidget(self.songBookNumberLabel, 0, 1, 1, 1)
|
|
||||||
self.songBookNumberEdit = QtGui.QLineEdit(self.SongBookGroup)
|
self.songBookNumberEdit = QtGui.QLineEdit(self.SongBookGroup)
|
||||||
self.songBookNumberLabel.setBuddy(self.songBookNumberEdit)
|
self.SongbookLayout.addRow(self.songBookNumberLabel,
|
||||||
self.songBookNumberEdit.setMaximumWidth(35)
|
self.songBookNumberEdit)
|
||||||
self.SongbookLayout.addWidget(self.songBookNumberEdit, 0, 2, 1, 1)
|
|
||||||
self.TopicBookLayout.addWidget(self.SongBookGroup)
|
self.TopicBookLayout.addWidget(self.SongBookGroup)
|
||||||
self.AuthorsTabLayout.addWidget(self.TopicBookWidget)
|
self.AuthorsTabLayout.addWidget(self.TopicBookWidget)
|
||||||
self.SongTabWidget.addTab(self.AuthorsTab, u'')
|
self.SongTabWidget.addTab(self.AuthorsTab, u'')
|
||||||
|
@ -446,8 +445,10 @@ class Ui_EditSongDialog(object):
|
||||||
translate('SongsPlugin.EditSongForm', 'R&emove'))
|
translate('SongsPlugin.EditSongForm', 'R&emove'))
|
||||||
self.SongBookGroup.setTitle(
|
self.SongBookGroup.setTitle(
|
||||||
translate('SongsPlugin.EditSongForm', 'Song Book'))
|
translate('SongsPlugin.EditSongForm', 'Song Book'))
|
||||||
|
self.SongbookNameLabel.setText(translate('SongsPlugin.EditSongForm',
|
||||||
|
'Book:'))
|
||||||
self.songBookNumberLabel.setText(translate('SongsPlugin.EditSongForm',
|
self.songBookNumberLabel.setText(translate('SongsPlugin.EditSongForm',
|
||||||
'Song No.:'))
|
'Number:'))
|
||||||
self.SongTabWidget.setTabText(
|
self.SongTabWidget.setTabText(
|
||||||
self.SongTabWidget.indexOf(self.AuthorsTab),
|
self.SongTabWidget.indexOf(self.AuthorsTab),
|
||||||
translate('SongsPlugin.EditSongForm',
|
translate('SongsPlugin.EditSongForm',
|
||||||
|
|
|
@ -274,7 +274,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
item = self.VerseListWidget.item(row, 0)
|
item = self.VerseListWidget.item(row, 0)
|
||||||
data = unicode(item.data(QtCore.Qt.UserRole).toString())
|
data = unicode(item.data(QtCore.Qt.UserRole).toString())
|
||||||
bit = data.split(u':')
|
bit = data.split(u':')
|
||||||
rowTag = u'%s\n%s' % (bit[0][0:1], bit[1])
|
rowTag = u'%s%s' % (bit[0][0:1], bit[1])
|
||||||
rowLabel.append(rowTag)
|
rowLabel.append(rowTag)
|
||||||
self.VerseListWidget.setVerticalHeaderLabels(rowLabel)
|
self.VerseListWidget.setVerticalHeaderLabels(rowLabel)
|
||||||
|
|
||||||
|
@ -395,9 +395,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
self.VerseDeleteButton.setEnabled(True)
|
self.VerseDeleteButton.setEnabled(True)
|
||||||
|
|
||||||
def onVerseAddButtonClicked(self):
|
def onVerseAddButtonClicked(self):
|
||||||
# Allow insert button as you do not know if multiple verses will
|
self.verse_form.setVerse(u'', True)
|
||||||
# be entered.
|
|
||||||
self.verse_form.setVerse(u'')
|
|
||||||
if self.verse_form.exec_():
|
if self.verse_form.exec_():
|
||||||
afterText, verse, subVerse = self.verse_form.getVerse()
|
afterText, verse, subVerse = self.verse_form.getVerse()
|
||||||
data = u'%s:%s' % (verse, subVerse)
|
data = u'%s:%s' % (verse, subVerse)
|
||||||
|
@ -623,6 +621,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def saveSong(self):
|
def saveSong(self):
|
||||||
|
"""
|
||||||
|
Get all the data from the widgets on the form, and then save it to the
|
||||||
|
database.
|
||||||
|
"""
|
||||||
self.song.title = unicode(self.TitleEditItem.text())
|
self.song.title = unicode(self.TitleEditItem.text())
|
||||||
self.song.alternate_title = unicode(self.AlternativeEdit.text())
|
self.song.alternate_title = unicode(self.AlternativeEdit.text())
|
||||||
self.song.copyright = unicode(self.CopyrightEditItem.text())
|
self.song.copyright = unicode(self.CopyrightEditItem.text())
|
||||||
|
@ -648,6 +650,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
self.song.topics.append(self.songmanager.get_object(Topic,
|
self.song.topics.append(self.songmanager.get_object(Topic,
|
||||||
topicId))
|
topicId))
|
||||||
self.songmanager.save_object(self.song)
|
self.songmanager.save_object(self.song)
|
||||||
|
self.song = None
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.lib import build_icon, translate
|
from openlp.core.lib import build_icon, translate, SpellTextEdit
|
||||||
from openlp.core.ui import SpellTextEdit
|
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
|
|
||||||
class Ui_EditVerseDialog(object):
|
class Ui_EditVerseDialog(object):
|
||||||
|
|
|
@ -31,7 +31,6 @@ from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from songimportwizard import Ui_SongImportWizard
|
from songimportwizard import Ui_SongImportWizard
|
||||||
from openlp.core.lib import Receiver, SettingsManager, translate
|
from openlp.core.lib import Receiver, SettingsManager, translate
|
||||||
#from openlp.core.utils import AppLocation
|
|
||||||
from openlp.plugins.songs.lib.importer import SongFormat
|
from openlp.plugins.songs.lib.importer import SongFormat
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -58,6 +57,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
self.registerFields()
|
self.registerFields()
|
||||||
self.finishButton = self.button(QtGui.QWizard.FinishButton)
|
self.finishButton = self.button(QtGui.QWizard.FinishButton)
|
||||||
self.cancelButton = self.button(QtGui.QWizard.CancelButton)
|
self.cancelButton = self.button(QtGui.QWizard.CancelButton)
|
||||||
|
if not SongFormat.get_availability(SongFormat.OpenLP1):
|
||||||
|
self.openLP1DisabledWidget.setVisible(True)
|
||||||
|
self.openLP1ImportWidget.setVisible(False)
|
||||||
|
if not SongFormat.get_availability(SongFormat.SongsOfFellowship):
|
||||||
|
self.songsOfFellowshipDisabledWidget.setVisible(True)
|
||||||
|
self.songsOfFellowshipImportWidget.setVisible(False)
|
||||||
|
if not SongFormat.get_availability(SongFormat.Generic):
|
||||||
|
self.genericDisabledWidget.setVisible(True)
|
||||||
|
self.genericImportWidget.setVisible(False)
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
QtCore.QObject.connect(self.openLP2BrowseButton,
|
QtCore.QObject.connect(self.openLP2BrowseButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
|
@ -65,12 +73,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
QtCore.QObject.connect(self.openLP1BrowseButton,
|
QtCore.QObject.connect(self.openLP1BrowseButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onOpenLP1BrowseButtonClicked)
|
self.onOpenLP1BrowseButtonClicked)
|
||||||
QtCore.QObject.connect(self.openLyricsAddButton,
|
#QtCore.QObject.connect(self.openLyricsAddButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
# QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onOpenLyricsAddButtonClicked)
|
# self.onOpenLyricsAddButtonClicked)
|
||||||
QtCore.QObject.connect(self.openLyricsRemoveButton,
|
#QtCore.QObject.connect(self.openLyricsRemoveButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
# QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onOpenLyricsRemoveButtonClicked)
|
# self.onOpenLyricsRemoveButtonClicked)
|
||||||
QtCore.QObject.connect(self.openSongAddButton,
|
QtCore.QObject.connect(self.openSongAddButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onOpenSongAddButtonClicked)
|
self.onOpenSongAddButtonClicked)
|
||||||
|
@ -83,6 +91,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
QtCore.QObject.connect(self.wordsOfWorshipRemoveButton,
|
QtCore.QObject.connect(self.wordsOfWorshipRemoveButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onWordsOfWorshipRemoveButtonClicked)
|
self.onWordsOfWorshipRemoveButtonClicked)
|
||||||
|
QtCore.QObject.connect(self.ccliAddButton,
|
||||||
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
|
self.onCCLIAddButtonClicked)
|
||||||
|
QtCore.QObject.connect(self.ccliRemoveButton,
|
||||||
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
|
self.onCCLIRemoveButtonClicked)
|
||||||
QtCore.QObject.connect(self.songsOfFellowshipAddButton,
|
QtCore.QObject.connect(self.songsOfFellowshipAddButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
self.onSongsOfFellowshipAddButtonClicked)
|
self.onSongsOfFellowshipAddButtonClicked)
|
||||||
|
@ -130,7 +144,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
self.openLP2BrowseButton.setFocus()
|
self.openLP2BrowseButton.setFocus()
|
||||||
return False
|
return False
|
||||||
elif source_format == SongFormat.OpenLP1:
|
elif source_format == SongFormat.OpenLP1:
|
||||||
if self.openSongFilenameEdit.text().isEmpty():
|
if self.openLP1FilenameEdit.text().isEmpty():
|
||||||
QtGui.QMessageBox.critical(self,
|
QtGui.QMessageBox.critical(self,
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
translate('SongsPlugin.ImportWizardForm',
|
||||||
'No openlp.org 1.x Song Database Selected'),
|
'No openlp.org 1.x Song Database Selected'),
|
||||||
|
@ -140,14 +154,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
self.openLP1BrowseButton.setFocus()
|
self.openLP1BrowseButton.setFocus()
|
||||||
return False
|
return False
|
||||||
elif source_format == SongFormat.OpenLyrics:
|
elif source_format == SongFormat.OpenLyrics:
|
||||||
if self.openLyricsFileListWidget.count() == 0:
|
#if self.openLyricsFileListWidget.count() == 0:
|
||||||
QtGui.QMessageBox.critical(self,
|
# QtGui.QMessageBox.critical(self,
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
# translate('SongsPlugin.ImportWizardForm',
|
||||||
'No OpenLyrics Files Selected'),
|
# 'No OpenLyrics Files Selected'),
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
# translate('SongsPlugin.ImportWizardForm',
|
||||||
'You need to add at least one OpenLyrics '
|
# 'You need to add at least one OpenLyrics '
|
||||||
'song file to import from.'))
|
# 'song file to import from.'))
|
||||||
self.openLyricsAddButton.setFocus()
|
# self.openLyricsAddButton.setFocus()
|
||||||
|
# return False
|
||||||
return False
|
return False
|
||||||
elif source_format == SongFormat.OpenSong:
|
elif source_format == SongFormat.OpenSong:
|
||||||
if self.openSongFileListWidget.count() == 0:
|
if self.openSongFileListWidget.count() == 0:
|
||||||
|
@ -160,7 +175,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
self.openSongAddButton.setFocus()
|
self.openSongAddButton.setFocus()
|
||||||
return False
|
return False
|
||||||
elif source_format == SongFormat.WordsOfWorship:
|
elif source_format == SongFormat.WordsOfWorship:
|
||||||
if self.wordsOfWorshipListWidget.count() == 0:
|
if self.wordsOfWorshipFileListWidget.count() == 0:
|
||||||
QtGui.QMessageBox.critical(self,
|
QtGui.QMessageBox.critical(self,
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
translate('SongsPlugin.ImportWizardForm',
|
||||||
'No Words of Worship Files Selected'),
|
'No Words of Worship Files Selected'),
|
||||||
|
@ -247,15 +262,15 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
self.openLP1FilenameEdit
|
self.openLP1FilenameEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
def onOpenLyricsAddButtonClicked(self):
|
#def onOpenLyricsAddButtonClicked(self):
|
||||||
self.getFiles(
|
# self.getFiles(
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
# translate('SongsPlugin.ImportWizardForm',
|
||||||
'Select OpenLyrics Files'),
|
# 'Select OpenLyrics Files'),
|
||||||
self.openLyricsFileListWidget
|
# self.openLyricsFileListWidget
|
||||||
)
|
# )
|
||||||
|
|
||||||
def onOpenLyricsRemoveButtonClicked(self):
|
#def onOpenLyricsRemoveButtonClicked(self):
|
||||||
self.removeSelectedItems(self.openLyricsFileListWidget)
|
# self.removeSelectedItems(self.openLyricsFileListWidget)
|
||||||
|
|
||||||
def onOpenSongAddButtonClicked(self):
|
def onOpenSongAddButtonClicked(self):
|
||||||
self.getFiles(
|
self.getFiles(
|
||||||
|
@ -277,6 +292,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
def onWordsOfWorshipRemoveButtonClicked(self):
|
def onWordsOfWorshipRemoveButtonClicked(self):
|
||||||
self.removeSelectedItems(self.wordsOfWorshipFileListWidget)
|
self.removeSelectedItems(self.wordsOfWorshipFileListWidget)
|
||||||
|
|
||||||
|
def onCCLIAddButtonClicked(self):
|
||||||
|
self.getFiles(
|
||||||
|
translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Select CCLI Files'),
|
||||||
|
self.ccliFileListWidget
|
||||||
|
)
|
||||||
|
|
||||||
|
def onCCLIRemoveButtonClicked(self):
|
||||||
|
self.removeSelectedItems(self.ccliFileListWidget)
|
||||||
|
|
||||||
def onSongsOfFellowshipAddButtonClicked(self):
|
def onSongsOfFellowshipAddButtonClicked(self):
|
||||||
self.getFiles(
|
self.getFiles(
|
||||||
translate('SongsPlugin.ImportWizardForm',
|
translate('SongsPlugin.ImportWizardForm',
|
||||||
|
@ -315,10 +340,11 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setDefaults(self):
|
def setDefaults(self):
|
||||||
|
self.restart()
|
||||||
self.formatComboBox.setCurrentIndex(0)
|
self.formatComboBox.setCurrentIndex(0)
|
||||||
self.openLP2FilenameEdit.setText(u'')
|
self.openLP2FilenameEdit.setText(u'')
|
||||||
self.openLP1FilenameEdit.setText(u'')
|
self.openLP1FilenameEdit.setText(u'')
|
||||||
self.openLyricsFileListWidget.clear()
|
#self.openLyricsFileListWidget.clear()
|
||||||
self.openSongFileListWidget.clear()
|
self.openSongFileListWidget.clear()
|
||||||
self.wordsOfWorshipFileListWidget.clear()
|
self.wordsOfWorshipFileListWidget.clear()
|
||||||
self.ccliFileListWidget.clear()
|
self.ccliFileListWidget.clear()
|
||||||
|
@ -357,11 +383,11 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
importer = self.plugin.importSongs(SongFormat.OpenLP2,
|
importer = self.plugin.importSongs(SongFormat.OpenLP2,
|
||||||
filename=unicode(self.openLP2FilenameEdit.text())
|
filename=unicode(self.openLP2FilenameEdit.text())
|
||||||
)
|
)
|
||||||
#elif source_format == SongFormat.OpenLP1:
|
elif source_format == SongFormat.OpenLP1:
|
||||||
# # Import an openlp.org database
|
# Import an openlp.org database
|
||||||
# importer = self.plugin.importSongs(SongFormat.OpenLP1,
|
importer = self.plugin.importSongs(SongFormat.OpenLP1,
|
||||||
# filename=unicode(self.field(u'openlp1_filename').toString())
|
filename=unicode(self.openLP1FilenameEdit.text())
|
||||||
# )
|
)
|
||||||
elif source_format == SongFormat.OpenLyrics:
|
elif source_format == SongFormat.OpenLyrics:
|
||||||
# Import OpenLyrics songs
|
# Import OpenLyrics songs
|
||||||
importer = self.plugin.importSongs(SongFormat.OpenLyrics,
|
importer = self.plugin.importSongs(SongFormat.OpenLyrics,
|
||||||
|
|
|
@ -131,26 +131,43 @@ class Ui_SongImportWizard(object):
|
||||||
# openlp.org 1.x
|
# openlp.org 1.x
|
||||||
self.openLP1Page = QtGui.QWidget()
|
self.openLP1Page = QtGui.QWidget()
|
||||||
self.openLP1Page.setObjectName(u'openLP1Page')
|
self.openLP1Page.setObjectName(u'openLP1Page')
|
||||||
self.openLP1Layout = QtGui.QFormLayout(self.openLP1Page)
|
self.openLP1Layout = QtGui.QVBoxLayout(self.openLP1Page)
|
||||||
self.openLP1Layout.setMargin(0)
|
self.openLP1Layout.setMargin(0)
|
||||||
self.openLP1Layout.setSpacing(8)
|
self.openLP1Layout.setSpacing(0)
|
||||||
self.openLP1Layout.setObjectName(u'openLP1Layout')
|
self.openLP1Layout.setObjectName(u'openLP1Layout')
|
||||||
self.openLP1FilenameLabel = QtGui.QLabel(self.openLP1Page)
|
self.openLP1DisabledWidget = QtGui.QWidget(self.openLP1Page)
|
||||||
|
self.openLP1DisabledLayout = QtGui.QVBoxLayout(self.openLP1DisabledWidget)
|
||||||
|
self.openLP1DisabledLayout.setMargin(0)
|
||||||
|
self.openLP1DisabledLayout.setSpacing(8)
|
||||||
|
self.openLP1DisabledLayout.setObjectName(u'openLP1DisabledLayout')
|
||||||
|
self.openLP1DisabledLabel = QtGui.QLabel(self.openLP1DisabledWidget)
|
||||||
|
self.openLP1DisabledLabel.setWordWrap(True)
|
||||||
|
self.openLP1DisabledLabel.setObjectName(u'openLP1DisabledLabel')
|
||||||
|
self.openLP1DisabledLayout.addWidget(self.openLP1DisabledLabel)
|
||||||
|
self.openLP1DisabledWidget.setVisible(False)
|
||||||
|
self.openLP1Layout.addWidget(self.openLP1DisabledWidget)
|
||||||
|
self.openLP1ImportWidget = QtGui.QWidget(self.openLP1Page)
|
||||||
|
self.openLP1ImportLayout = QtGui.QFormLayout(self.openLP1ImportWidget)
|
||||||
|
self.openLP1ImportLayout.setMargin(0)
|
||||||
|
self.openLP1ImportLayout.setSpacing(8)
|
||||||
|
self.openLP1ImportLayout.setObjectName(u'openLP1ImportLayout')
|
||||||
|
self.openLP1FilenameLabel = QtGui.QLabel(self.openLP1ImportWidget)
|
||||||
self.openLP1FilenameLabel.setObjectName(u'openLP1FilenameLabel')
|
self.openLP1FilenameLabel.setObjectName(u'openLP1FilenameLabel')
|
||||||
self.openLP1Layout.setWidget(0, QtGui.QFormLayout.LabelRole,
|
self.openLP1ImportLayout.setWidget(0, QtGui.QFormLayout.LabelRole,
|
||||||
self.openLP1FilenameLabel)
|
self.openLP1FilenameLabel)
|
||||||
self.openLP1FileLayout = QtGui.QHBoxLayout()
|
self.openLP1FileLayout = QtGui.QHBoxLayout()
|
||||||
self.openLP1FileLayout.setSpacing(8)
|
self.openLP1FileLayout.setSpacing(8)
|
||||||
self.openLP1FileLayout.setObjectName(u'openLP1FileLayout')
|
self.openLP1FileLayout.setObjectName(u'openLP1FileLayout')
|
||||||
self.openLP1FilenameEdit = QtGui.QLineEdit(self.openLP1Page)
|
self.openLP1FilenameEdit = QtGui.QLineEdit(self.openLP1ImportWidget)
|
||||||
self.openLP1FilenameEdit.setObjectName(u'openLP1FilenameEdit')
|
self.openLP1FilenameEdit.setObjectName(u'openLP1FilenameEdit')
|
||||||
self.openLP1FileLayout.addWidget(self.openLP1FilenameEdit)
|
self.openLP1FileLayout.addWidget(self.openLP1FilenameEdit)
|
||||||
self.openLP1BrowseButton = QtGui.QToolButton(self.openLP1Page)
|
self.openLP1BrowseButton = QtGui.QToolButton(self.openLP1ImportWidget)
|
||||||
self.openLP1BrowseButton.setIcon(openIcon)
|
self.openLP1BrowseButton.setIcon(openIcon)
|
||||||
self.openLP1BrowseButton.setObjectName(u'openLP1BrowseButton')
|
self.openLP1BrowseButton.setObjectName(u'openLP1BrowseButton')
|
||||||
self.openLP1FileLayout.addWidget(self.openLP1BrowseButton)
|
self.openLP1FileLayout.addWidget(self.openLP1BrowseButton)
|
||||||
self.openLP1Layout.setLayout(0, QtGui.QFormLayout.FieldRole,
|
self.openLP1ImportLayout.setLayout(0, QtGui.QFormLayout.FieldRole,
|
||||||
self.openLP1FileLayout)
|
self.openLP1FileLayout)
|
||||||
|
self.openLP1Layout.addWidget(self.openLP1ImportWidget)
|
||||||
self.formatStackedWidget.addWidget(self.openLP1Page)
|
self.formatStackedWidget.addWidget(self.openLP1Page)
|
||||||
# OpenLyrics
|
# OpenLyrics
|
||||||
self.openLyricsPage = QtGui.QWidget()
|
self.openLyricsPage = QtGui.QWidget()
|
||||||
|
@ -159,26 +176,31 @@ class Ui_SongImportWizard(object):
|
||||||
self.openLyricsLayout.setSpacing(8)
|
self.openLyricsLayout.setSpacing(8)
|
||||||
self.openLyricsLayout.setMargin(0)
|
self.openLyricsLayout.setMargin(0)
|
||||||
self.openLyricsLayout.setObjectName(u'OpenLyricsLayout')
|
self.openLyricsLayout.setObjectName(u'OpenLyricsLayout')
|
||||||
self.openLyricsFileListWidget = QtGui.QListWidget(self.openLyricsPage)
|
self.openLyricsDisabledLabel = QtGui.QLabel(self.openLyricsPage)
|
||||||
self.openLyricsFileListWidget.setSelectionMode(
|
self.openLyricsDisabledLabel.setWordWrap(True)
|
||||||
QtGui.QAbstractItemView.ExtendedSelection)
|
self.openLyricsDisabledLabel.setObjectName(u'openLyricsDisabledLabel')
|
||||||
self.openLyricsFileListWidget.setObjectName(u'OpenLyricsFileListWidget')
|
self.openLyricsLayout.addWidget(self.openLyricsDisabledLabel)
|
||||||
self.openLyricsLayout.addWidget(self.openLyricsFileListWidget)
|
# Commented out for future use.
|
||||||
self.openLyricsButtonLayout = QtGui.QHBoxLayout()
|
#self.openLyricsFileListWidget = QtGui.QListWidget(self.openLyricsPage)
|
||||||
self.openLyricsButtonLayout.setSpacing(8)
|
#self.openLyricsFileListWidget.setSelectionMode(
|
||||||
self.openLyricsButtonLayout.setObjectName(u'OpenLyricsButtonLayout')
|
# QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
self.openLyricsAddButton = QtGui.QPushButton(self.openLyricsPage)
|
#self.openLyricsFileListWidget.setObjectName(u'OpenLyricsFileListWidget')
|
||||||
self.openLyricsAddButton.setIcon(openIcon)
|
#self.openLyricsLayout.addWidget(self.openLyricsFileListWidget)
|
||||||
self.openLyricsAddButton.setObjectName(u'OpenLyricsAddButton')
|
#self.openLyricsButtonLayout = QtGui.QHBoxLayout()
|
||||||
self.openLyricsButtonLayout.addWidget(self.openLyricsAddButton)
|
#self.openLyricsButtonLayout.setSpacing(8)
|
||||||
self.openLyricsButtonSpacer = QtGui.QSpacerItem(40, 20,
|
#self.openLyricsButtonLayout.setObjectName(u'OpenLyricsButtonLayout')
|
||||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
#self.openLyricsAddButton = QtGui.QPushButton(self.openLyricsPage)
|
||||||
self.openLyricsButtonLayout.addItem(self.openLyricsButtonSpacer)
|
#self.openLyricsAddButton.setIcon(openIcon)
|
||||||
self.openLyricsRemoveButton = QtGui.QPushButton(self.openLyricsPage)
|
#self.openLyricsAddButton.setObjectName(u'OpenLyricsAddButton')
|
||||||
self.openLyricsRemoveButton.setIcon(deleteIcon)
|
#self.openLyricsButtonLayout.addWidget(self.openLyricsAddButton)
|
||||||
self.openLyricsRemoveButton.setObjectName(u'OpenLyricsRemoveButton')
|
#self.openLyricsButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||||
self.openLyricsButtonLayout.addWidget(self.openLyricsRemoveButton)
|
# QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
self.openLyricsLayout.addLayout(self.openLyricsButtonLayout)
|
#self.openLyricsButtonLayout.addItem(self.openLyricsButtonSpacer)
|
||||||
|
#self.openLyricsRemoveButton = QtGui.QPushButton(self.openLyricsPage)
|
||||||
|
#self.openLyricsRemoveButton.setIcon(deleteIcon)
|
||||||
|
#self.openLyricsRemoveButton.setObjectName(u'OpenLyricsRemoveButton')
|
||||||
|
#self.openLyricsButtonLayout.addWidget(self.openLyricsRemoveButton)
|
||||||
|
#self.openLyricsLayout.addLayout(self.openLyricsButtonLayout)
|
||||||
self.formatStackedWidget.addWidget(self.openLyricsPage)
|
self.formatStackedWidget.addWidget(self.openLyricsPage)
|
||||||
# Open Song
|
# Open Song
|
||||||
self.openSongPage = QtGui.QWidget()
|
self.openSongPage = QtGui.QWidget()
|
||||||
|
@ -277,22 +299,52 @@ class Ui_SongImportWizard(object):
|
||||||
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(
|
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(
|
||||||
self.songsOfFellowshipPage)
|
self.songsOfFellowshipPage)
|
||||||
self.songsOfFellowshipLayout.setMargin(0)
|
self.songsOfFellowshipLayout.setMargin(0)
|
||||||
self.songsOfFellowshipLayout.setSpacing(8)
|
self.songsOfFellowshipLayout.setSpacing(0)
|
||||||
self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout')
|
self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout')
|
||||||
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(
|
self.songsOfFellowshipDisabledWidget = QtGui.QWidget(
|
||||||
self.songsOfFellowshipPage)
|
self.songsOfFellowshipPage)
|
||||||
|
self.songsOfFellowshipDisabledWidget.setVisible(False)
|
||||||
|
self.songsOfFellowshipDisabledWidget.setObjectName(
|
||||||
|
u'songsOfFellowshipDisabledWidget')
|
||||||
|
self.songsOfFellowshipDisabledLayout = QtGui.QVBoxLayout(
|
||||||
|
self.songsOfFellowshipDisabledWidget)
|
||||||
|
self.songsOfFellowshipDisabledLayout.setMargin(0)
|
||||||
|
self.songsOfFellowshipDisabledLayout.setSpacing(8)
|
||||||
|
self.songsOfFellowshipDisabledLayout.setObjectName(
|
||||||
|
u'songsOfFellowshipDisabledLayout')
|
||||||
|
self.songsOfFellowshipDisabledLabel = QtGui.QLabel(
|
||||||
|
self.songsOfFellowshipDisabledWidget)
|
||||||
|
self.songsOfFellowshipDisabledLabel.setWordWrap(True)
|
||||||
|
self.songsOfFellowshipDisabledLabel.setObjectName(
|
||||||
|
u'songsOfFellowshipDisabledLabel')
|
||||||
|
self.songsOfFellowshipDisabledLayout.addWidget(
|
||||||
|
self.songsOfFellowshipDisabledLabel)
|
||||||
|
self.songsOfFellowshipLayout.addWidget(
|
||||||
|
self.songsOfFellowshipDisabledWidget)
|
||||||
|
self.songsOfFellowshipImportWidget = QtGui.QWidget(
|
||||||
|
self.songsOfFellowshipPage)
|
||||||
|
self.songsOfFellowshipImportWidget.setObjectName(
|
||||||
|
u'songsOfFellowshipImportWidget')
|
||||||
|
self.songsOfFellowshipImportLayout = QtGui.QVBoxLayout(
|
||||||
|
self.songsOfFellowshipImportWidget)
|
||||||
|
self.songsOfFellowshipImportLayout.setMargin(0)
|
||||||
|
self.songsOfFellowshipImportLayout.setSpacing(8)
|
||||||
|
self.songsOfFellowshipImportLayout.setObjectName(
|
||||||
|
u'songsOfFellowshipImportLayout')
|
||||||
|
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(
|
||||||
|
self.songsOfFellowshipImportWidget)
|
||||||
self.songsOfFellowshipFileListWidget.setSelectionMode(
|
self.songsOfFellowshipFileListWidget.setSelectionMode(
|
||||||
QtGui.QAbstractItemView.ExtendedSelection)
|
QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
self.songsOfFellowshipFileListWidget.setObjectName(
|
self.songsOfFellowshipFileListWidget.setObjectName(
|
||||||
u'songsOfFellowshipFileListWidget')
|
u'songsOfFellowshipFileListWidget')
|
||||||
self.songsOfFellowshipLayout.addWidget(
|
self.songsOfFellowshipImportLayout.addWidget(
|
||||||
self.songsOfFellowshipFileListWidget)
|
self.songsOfFellowshipFileListWidget)
|
||||||
self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout()
|
self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout()
|
||||||
self.songsOfFellowshipButtonLayout.setSpacing(8)
|
self.songsOfFellowshipButtonLayout.setSpacing(8)
|
||||||
self.songsOfFellowshipButtonLayout.setObjectName(
|
self.songsOfFellowshipButtonLayout.setObjectName(
|
||||||
u'songsOfFellowshipButtonLayout')
|
u'songsOfFellowshipButtonLayout')
|
||||||
self.songsOfFellowshipAddButton = QtGui.QPushButton(
|
self.songsOfFellowshipAddButton = QtGui.QPushButton(
|
||||||
self.songsOfFellowshipPage)
|
self.songsOfFellowshipImportWidget)
|
||||||
self.songsOfFellowshipAddButton.setIcon(openIcon)
|
self.songsOfFellowshipAddButton.setIcon(openIcon)
|
||||||
self.songsOfFellowshipAddButton.setObjectName(
|
self.songsOfFellowshipAddButton.setObjectName(
|
||||||
u'songsOfFellowshipAddButton')
|
u'songsOfFellowshipAddButton')
|
||||||
|
@ -303,42 +355,63 @@ class Ui_SongImportWizard(object):
|
||||||
self.songsOfFellowshipButtonLayout.addItem(
|
self.songsOfFellowshipButtonLayout.addItem(
|
||||||
self.songsOfFellowshipButtonSpacer)
|
self.songsOfFellowshipButtonSpacer)
|
||||||
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(
|
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(
|
||||||
self.songsOfFellowshipPage)
|
self.songsOfFellowshipImportWidget)
|
||||||
self.songsOfFellowshipRemoveButton.setIcon(deleteIcon)
|
self.songsOfFellowshipRemoveButton.setIcon(deleteIcon)
|
||||||
self.songsOfFellowshipRemoveButton.setObjectName(
|
self.songsOfFellowshipRemoveButton.setObjectName(
|
||||||
u'songsOfFellowshipRemoveButton')
|
u'songsOfFellowshipRemoveButton')
|
||||||
self.songsOfFellowshipButtonLayout.addWidget(
|
self.songsOfFellowshipButtonLayout.addWidget(
|
||||||
self.songsOfFellowshipRemoveButton)
|
self.songsOfFellowshipRemoveButton)
|
||||||
self.songsOfFellowshipLayout.addLayout(
|
self.songsOfFellowshipImportLayout.addLayout(
|
||||||
self.songsOfFellowshipButtonLayout)
|
self.songsOfFellowshipButtonLayout)
|
||||||
|
self.songsOfFellowshipLayout.addWidget(
|
||||||
|
self.songsOfFellowshipImportWidget)
|
||||||
self.formatStackedWidget.addWidget(self.songsOfFellowshipPage)
|
self.formatStackedWidget.addWidget(self.songsOfFellowshipPage)
|
||||||
# Generic Document/Presentation import
|
# Generic Document/Presentation import
|
||||||
self.genericPage = QtGui.QWidget()
|
self.genericPage = QtGui.QWidget()
|
||||||
self.genericPage.setObjectName(u'genericPage')
|
self.genericPage.setObjectName(u'genericPage')
|
||||||
self.genericLayout = QtGui.QVBoxLayout(self.genericPage)
|
self.genericLayout = QtGui.QVBoxLayout(self.genericPage)
|
||||||
self.genericLayout.setMargin(0)
|
self.genericLayout.setMargin(0)
|
||||||
self.genericLayout.setSpacing(8)
|
self.genericLayout.setSpacing(0)
|
||||||
self.genericLayout.setObjectName(u'genericLayout')
|
self.genericLayout.setObjectName(u'genericLayout')
|
||||||
self.genericFileListWidget = QtGui.QListWidget(self.genericPage)
|
self.genericDisabledWidget = QtGui.QWidget(self.genericPage)
|
||||||
|
self.genericDisabledWidget.setObjectName(u'genericDisabledWidget')
|
||||||
|
self.genericDisabledLayout = QtGui.QVBoxLayout(self.genericDisabledWidget)
|
||||||
|
self.genericDisabledLayout.setMargin(0)
|
||||||
|
self.genericDisabledLayout.setSpacing(8)
|
||||||
|
self.genericDisabledLayout.setObjectName(u'genericDisabledLayout')
|
||||||
|
self.genericDisabledLabel = QtGui.QLabel(self.genericDisabledWidget)
|
||||||
|
self.genericDisabledLabel.setWordWrap(True)
|
||||||
|
self.genericDisabledLabel.setObjectName(u'genericDisabledLabel')
|
||||||
|
self.genericDisabledWidget.setVisible(False)
|
||||||
|
self.genericDisabledLayout.addWidget(self.genericDisabledLabel)
|
||||||
|
self.genericLayout.addWidget(self.genericDisabledWidget)
|
||||||
|
self.genericImportWidget = QtGui.QWidget(self.genericPage)
|
||||||
|
self.genericImportWidget.setObjectName(u'genericImportWidget')
|
||||||
|
self.genericImportLayout = QtGui.QVBoxLayout(self.genericImportWidget)
|
||||||
|
self.genericImportLayout.setMargin(0)
|
||||||
|
self.genericImportLayout.setSpacing(8)
|
||||||
|
self.genericImportLayout.setObjectName(u'genericImportLayout')
|
||||||
|
self.genericFileListWidget = QtGui.QListWidget(self.genericImportWidget)
|
||||||
self.genericFileListWidget.setSelectionMode(
|
self.genericFileListWidget.setSelectionMode(
|
||||||
QtGui.QAbstractItemView.ExtendedSelection)
|
QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
self.genericFileListWidget.setObjectName(u'genericFileListWidget')
|
self.genericFileListWidget.setObjectName(u'genericFileListWidget')
|
||||||
self.genericLayout.addWidget(self.genericFileListWidget)
|
self.genericImportLayout.addWidget(self.genericFileListWidget)
|
||||||
self.genericButtonLayout = QtGui.QHBoxLayout()
|
self.genericButtonLayout = QtGui.QHBoxLayout()
|
||||||
self.genericButtonLayout.setSpacing(8)
|
self.genericButtonLayout.setSpacing(8)
|
||||||
self.genericButtonLayout.setObjectName(u'genericButtonLayout')
|
self.genericButtonLayout.setObjectName(u'genericButtonLayout')
|
||||||
self.genericAddButton = QtGui.QPushButton(self.genericPage)
|
self.genericAddButton = QtGui.QPushButton(self.genericImportWidget)
|
||||||
self.genericAddButton.setIcon(openIcon)
|
self.genericAddButton.setIcon(openIcon)
|
||||||
self.genericAddButton.setObjectName(u'genericAddButton')
|
self.genericAddButton.setObjectName(u'genericAddButton')
|
||||||
self.genericButtonLayout.addWidget(self.genericAddButton)
|
self.genericButtonLayout.addWidget(self.genericAddButton)
|
||||||
self.genericButtonSpacer = QtGui.QSpacerItem(40, 20,
|
self.genericButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
self.genericButtonLayout.addItem(self.genericButtonSpacer)
|
self.genericButtonLayout.addItem(self.genericButtonSpacer)
|
||||||
self.genericRemoveButton = QtGui.QPushButton(self.genericPage)
|
self.genericRemoveButton = QtGui.QPushButton(self.genericImportWidget)
|
||||||
self.genericRemoveButton.setIcon(deleteIcon)
|
self.genericRemoveButton.setIcon(deleteIcon)
|
||||||
self.genericRemoveButton.setObjectName(u'genericRemoveButton')
|
self.genericRemoveButton.setObjectName(u'genericRemoveButton')
|
||||||
self.genericButtonLayout.addWidget(self.genericRemoveButton)
|
self.genericButtonLayout.addWidget(self.genericRemoveButton)
|
||||||
self.genericLayout.addLayout(self.genericButtonLayout)
|
self.genericImportLayout.addLayout(self.genericButtonLayout)
|
||||||
|
self.genericLayout.addWidget(self.genericImportWidget)
|
||||||
self.formatStackedWidget.addWidget(self.genericPage)
|
self.formatStackedWidget.addWidget(self.genericPage)
|
||||||
# Commented out for future use.
|
# Commented out for future use.
|
||||||
# self.csvPage = QtGui.QWidget()
|
# self.csvPage = QtGui.QWidget()
|
||||||
|
@ -434,10 +507,20 @@ class Ui_SongImportWizard(object):
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Filename:'))
|
translate('SongsPlugin.ImportWizardForm', 'Filename:'))
|
||||||
self.openLP1BrowseButton.setText(
|
self.openLP1BrowseButton.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Browse...'))
|
translate('SongsPlugin.ImportWizardForm', 'Browse...'))
|
||||||
self.openLyricsAddButton.setText(
|
self.openLP1DisabledLabel.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
translate('SongsPlugin.ImportWizardForm', 'The openlp.org 1.x '
|
||||||
self.openLyricsRemoveButton.setText(
|
'importer has been disabled due to a missing Python module. If '
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
'you want to use this importer, you will need to install the '
|
||||||
|
'"python-sqlite" module.'))
|
||||||
|
#self.openLyricsAddButton.setText(
|
||||||
|
# translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
||||||
|
#self.openLyricsRemoveButton.setText(
|
||||||
|
# translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||||
|
self.openLyricsDisabledLabel.setText(
|
||||||
|
translate('SongsPlugin.ImportWizardForm', 'The OpenLyrics '
|
||||||
|
'importer has not yet been developed, but as you can see, we are '
|
||||||
|
'still intending to do so. Hopefully it will be in the next '
|
||||||
|
'release.'))
|
||||||
self.openSongAddButton.setText(
|
self.openSongAddButton.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
||||||
self.openSongRemoveButton.setText(
|
self.openSongRemoveButton.setText(
|
||||||
|
@ -454,10 +537,18 @@ class Ui_SongImportWizard(object):
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
||||||
self.songsOfFellowshipRemoveButton.setText(
|
self.songsOfFellowshipRemoveButton.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||||
|
self.songsOfFellowshipDisabledLabel.setText(
|
||||||
|
translate('SongsPlugin.ImportWizardForm', 'The Songs of '
|
||||||
|
'Fellowship importer has been disabled because OpenLP cannot '
|
||||||
|
'find OpenOffice.org on your computer.'))
|
||||||
self.genericAddButton.setText(
|
self.genericAddButton.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
||||||
self.genericRemoveButton.setText(
|
self.genericRemoveButton.setText(
|
||||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||||
|
self.genericDisabledLabel.setText(
|
||||||
|
translate('SongsPlugin.ImportWizardForm', 'The generic document/'
|
||||||
|
'presentation importer has been disabled because OpenLP cannot '
|
||||||
|
'find OpenOffice.org on your computer.'))
|
||||||
# self.csvFilenameLabel.setText(
|
# self.csvFilenameLabel.setText(
|
||||||
# translate('SongsPlugin.ImportWizardForm', 'Filename:'))
|
# translate('SongsPlugin.ImportWizardForm', 'Filename:'))
|
||||||
# self.csvBrowseButton.setText(
|
# self.csvBrowseButton.setText(
|
||||||
|
|
|
@ -324,8 +324,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||||
QtGui.QMessageBox.critical(self,
|
QtGui.QMessageBox.critical(self,
|
||||||
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
|
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
|
||||||
translate('SongsPlugin.SongMaintenanceForm',
|
translate('SongsPlugin.SongMaintenanceForm',
|
||||||
'Could not save your modified author, because he '
|
'Could not save your modified author, because the '
|
||||||
'already exists.'))
|
'author already exists.'))
|
||||||
|
|
||||||
def onTopicEditButtonClick(self):
|
def onTopicEditButtonClick(self):
|
||||||
topic_id = self._getCurrentItemId(self.TopicsListWidget)
|
topic_id = self._getCurrentItemId(self.TopicsListWidget)
|
||||||
|
|
|
@ -0,0 +1,311 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian #
|
||||||
|
# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
|
||||||
|
# Carsten Tinggaard, Frode Woldsund, Derek Scotney #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 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 #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import chardet
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
from songimport import SongImport
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class CCLIFileImportError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CCLIFileImport(SongImport):
|
||||||
|
"""
|
||||||
|
The :class:`CCLIFileImport` class provides OpenLP with the
|
||||||
|
ability to import CCLI SongSelect song files in both .txt and
|
||||||
|
.usr formats. See http://www.ccli.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, manager, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialise the import.
|
||||||
|
|
||||||
|
``manager``
|
||||||
|
The song manager for the running OpenLP installation.
|
||||||
|
``filenames``
|
||||||
|
The files to be imported.
|
||||||
|
"""
|
||||||
|
SongImport.__init__(self, manager)
|
||||||
|
if u'filenames' in kwargs:
|
||||||
|
self.filenames = kwargs[u'filenames']
|
||||||
|
log.debug(self.filenames)
|
||||||
|
else:
|
||||||
|
raise KeyError(u'Keyword argument "filenames" not supplied.')
|
||||||
|
|
||||||
|
def do_import(self):
|
||||||
|
"""
|
||||||
|
Import either a .usr or a .txt SongSelect file
|
||||||
|
"""
|
||||||
|
log.debug(u'Starting CCLI File Import')
|
||||||
|
song_total = len(self.filenames)
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(song_total)
|
||||||
|
song_count = 1
|
||||||
|
for filename in self.filenames:
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
u'Importing song %s of %s' % (song_count, song_total))
|
||||||
|
filename = unicode(filename)
|
||||||
|
log.debug(u'Importing CCLI File: %s', filename)
|
||||||
|
lines = []
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
detect_file = open(filename, u'r')
|
||||||
|
details = chardet.detect(detect_file.read(2048))
|
||||||
|
detect_file.close()
|
||||||
|
infile = codecs.open(filename, u'r', details['encoding'])
|
||||||
|
lines = infile.readlines()
|
||||||
|
ext = os.path.splitext(filename)[1]
|
||||||
|
if ext.lower() == ".usr":
|
||||||
|
log.info(u'SongSelect .usr format file found %s: ',
|
||||||
|
filename)
|
||||||
|
self.do_import_usr_file(lines)
|
||||||
|
elif ext.lower() == ".txt":
|
||||||
|
log.info(u'SongSelect .txt format file found %s: ',
|
||||||
|
filename)
|
||||||
|
self.do_import_txt_file(lines)
|
||||||
|
else:
|
||||||
|
log.info(u'Extension %s is not valid', filename)
|
||||||
|
song_count += 1
|
||||||
|
if self.stop_import_flag:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def do_import_usr_file(self, textList):
|
||||||
|
"""
|
||||||
|
The :method:`do_import_usr_file` method provides OpenLP
|
||||||
|
with the ability to import CCLI SongSelect songs in
|
||||||
|
*USR* file format
|
||||||
|
|
||||||
|
``textList``
|
||||||
|
An array of strings containing the usr file content.
|
||||||
|
|
||||||
|
**SongSelect .usr file format**
|
||||||
|
``[File]``
|
||||||
|
USR file format first line
|
||||||
|
``Type=``
|
||||||
|
Indicates the file type
|
||||||
|
e.g. *Type=SongSelect Import File*
|
||||||
|
``Version=3.0``
|
||||||
|
File format version
|
||||||
|
``[S A2672885]``
|
||||||
|
Contains the CCLI Song number e.g. *2672885*
|
||||||
|
``Title=``
|
||||||
|
Contains the song title (e.g. *Title=Above All*)
|
||||||
|
``Author=``
|
||||||
|
Contains a | delimited list of the song authors
|
||||||
|
e.g. *Author=LeBlanc, Lenny | Baloche, Paul*
|
||||||
|
``Copyright=``
|
||||||
|
Contains a | delimited list of the song copyrights
|
||||||
|
e.g. Copyright=1999 Integrity's Hosanna! Music |
|
||||||
|
LenSongs Publishing (Verwaltet von Gerth Medien
|
||||||
|
Musikverlag)
|
||||||
|
``Admin=``
|
||||||
|
Contains the song administrator
|
||||||
|
e.g. *Admin=Gerth Medien Musikverlag*
|
||||||
|
``Themes=``
|
||||||
|
Contains a /t delimited list of the song themes
|
||||||
|
e.g. *Themes=Cross/tKingship/tMajesty/tRedeemer*
|
||||||
|
``Keys=``
|
||||||
|
Contains the keys in which the music is played??
|
||||||
|
e.g. *Keys=A*
|
||||||
|
``Fields=``
|
||||||
|
Contains a list of the songs fields in order /t delimited
|
||||||
|
e.g. *Fields=Vers 1/tVers 2/tChorus 1/tAndere 1*
|
||||||
|
``Words=``
|
||||||
|
Contains the songs various lyrics in order as shown by the
|
||||||
|
*Fields* description
|
||||||
|
e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF]
|
||||||
|
"""
|
||||||
|
log.debug(u'USR file text: %s', textList)
|
||||||
|
lyrics = []
|
||||||
|
self.set_defaults()
|
||||||
|
for line in textList:
|
||||||
|
if line.startswith(u'Title='):
|
||||||
|
song_name = line[6:].strip()
|
||||||
|
elif line.startswith(u'Author='):
|
||||||
|
song_author = line[7:].strip()
|
||||||
|
elif line.startswith(u'Copyright='):
|
||||||
|
song_copyright = line[10:].strip()
|
||||||
|
elif line.startswith(u'[S A'):
|
||||||
|
song_ccli = line[4:-3].strip()
|
||||||
|
elif line.startswith(u'Fields='):
|
||||||
|
#Fields contain single line indicating verse, chorus, etc,
|
||||||
|
#/t delimited, same as with words field. store seperately
|
||||||
|
#and process at end.
|
||||||
|
song_fields = line[7:].strip()
|
||||||
|
elif line.startswith(u'Words='):
|
||||||
|
song_words = line[6:].strip()
|
||||||
|
#Unhandled usr keywords:Type,Version,Admin,Themes,Keys
|
||||||
|
#Process Fields and words sections
|
||||||
|
field_list = song_fields.split(u'/t')
|
||||||
|
words_list = song_words.split(u'/t')
|
||||||
|
for counter in range(0, len(field_list)):
|
||||||
|
if field_list[counter].startswith(u'Ver'):
|
||||||
|
verse_type = u'V'
|
||||||
|
elif field_list[counter].startswith(u'Ch'):
|
||||||
|
verse_type = u'C'
|
||||||
|
elif field_list[counter].startswith(u'Br'):
|
||||||
|
verse_type = u'B'
|
||||||
|
else: #Other
|
||||||
|
verse_type = u'O'
|
||||||
|
verse_text = unicode(words_list[counter])
|
||||||
|
verse_text = verse_text.replace("/n", "\n")
|
||||||
|
if len(verse_text) > 0:
|
||||||
|
self.add_verse(verse_text, verse_type)
|
||||||
|
#Handle multiple authors
|
||||||
|
author_list = song_author.split(u'/')
|
||||||
|
if len(author_list) < 2:
|
||||||
|
author_list = song_author.split(u'|')
|
||||||
|
for author in author_list:
|
||||||
|
seperated = author.split(u',')
|
||||||
|
self.add_author(seperated[1].strip() + " " + seperated[0].strip())
|
||||||
|
self.title = song_name
|
||||||
|
self.copyright = song_copyright
|
||||||
|
self.ccli_number = song_ccli
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
def do_import_txt_file(self, textList):
|
||||||
|
"""
|
||||||
|
The :method:`do_import_txt_file` method provides OpenLP
|
||||||
|
with the ability to import CCLI SongSelect songs in
|
||||||
|
*TXT* file format
|
||||||
|
|
||||||
|
``textList``
|
||||||
|
An array of strings containing the txt file content.
|
||||||
|
|
||||||
|
**SongSelect .txt file format**
|
||||||
|
|
||||||
|
``Song Title``
|
||||||
|
Contains the song title
|
||||||
|
|
||||||
|
<Empty line>
|
||||||
|
|
||||||
|
``Title of following verse/chorus and number``
|
||||||
|
e.g. Verse 1, Chorus 1
|
||||||
|
|
||||||
|
``Verse/Chorus lyrics``
|
||||||
|
|
||||||
|
<Empty line>
|
||||||
|
|
||||||
|
<Empty line>
|
||||||
|
|
||||||
|
``Title of next verse/chorus (repeats)``
|
||||||
|
|
||||||
|
``Verse/Chorus lyrics``
|
||||||
|
|
||||||
|
<Empty line>
|
||||||
|
|
||||||
|
<Empty line>
|
||||||
|
|
||||||
|
``Song CCLI Number``
|
||||||
|
e.g. CCLI Number (e.g.CCLI-Liednummer: 2672885)
|
||||||
|
``Song Copyright``
|
||||||
|
e.g. © 1999 Integrity's Hosanna! Music | LenSongs Publishing
|
||||||
|
``Song Authors``
|
||||||
|
e.g. Lenny LeBlanc | Paul Baloche
|
||||||
|
``Licencing info``
|
||||||
|
e.g. For use solely with the SongSelect Terms of Use.
|
||||||
|
All rights Reserved. www.ccli.com
|
||||||
|
``CCLI Licence number of user``
|
||||||
|
e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14
|
||||||
|
"""
|
||||||
|
log.debug(u'TXT file text: %s', textList)
|
||||||
|
self.set_defaults()
|
||||||
|
line_number = 0
|
||||||
|
verse_text = u''
|
||||||
|
song_comments = u''
|
||||||
|
song_copyright = u''
|
||||||
|
verse_start = False
|
||||||
|
for line in textList:
|
||||||
|
clean_line = line.strip()
|
||||||
|
if not clean_line:
|
||||||
|
if line_number == 0:
|
||||||
|
continue
|
||||||
|
elif verse_start:
|
||||||
|
if verse_text:
|
||||||
|
self.add_verse(verse_text, verse_type)
|
||||||
|
verse_text = ''
|
||||||
|
verse_start = False
|
||||||
|
else:
|
||||||
|
#line_number=0, song title
|
||||||
|
if line_number == 0:
|
||||||
|
song_name = clean_line
|
||||||
|
line_number += 1
|
||||||
|
#line_number=1, verses
|
||||||
|
elif line_number == 1:
|
||||||
|
#line_number=1, ccli number, first line after verses
|
||||||
|
if clean_line.startswith(u'CCLI'):
|
||||||
|
line_number += 1
|
||||||
|
ccli_parts = clean_line.split(' ')
|
||||||
|
song_ccli = ccli_parts[len(ccli_parts)-1]
|
||||||
|
elif not verse_start:
|
||||||
|
# We have the verse descriptor
|
||||||
|
verse_desc_parts = clean_line.split(' ')
|
||||||
|
if len(verse_desc_parts) == 2:
|
||||||
|
if verse_desc_parts[0].startswith(u'Ver'):
|
||||||
|
verse_type = u'V'
|
||||||
|
elif verse_desc_parts[0].startswith(u'Ch'):
|
||||||
|
verse_type = u'C'
|
||||||
|
elif verse_desc_parts[0].startswith(u'Br'):
|
||||||
|
verse_type = u'B'
|
||||||
|
else:
|
||||||
|
verse_type = u'O'
|
||||||
|
verse_number = verse_desc_parts[1]
|
||||||
|
else:
|
||||||
|
verse_type = u'O'
|
||||||
|
verse_number = 1
|
||||||
|
verse_start = True
|
||||||
|
else:
|
||||||
|
# We have verse content or the start of the
|
||||||
|
# last part. Add l so as to keep the CRLF
|
||||||
|
verse_text = verse_text + line
|
||||||
|
else:
|
||||||
|
#line_number=2, copyright
|
||||||
|
if line_number == 2:
|
||||||
|
line_number += 1
|
||||||
|
song_copyright = clean_line
|
||||||
|
#n=3, authors
|
||||||
|
elif line_number == 3:
|
||||||
|
line_number += 1
|
||||||
|
song_author = clean_line
|
||||||
|
#line_number=4, comments lines before last line
|
||||||
|
elif (line_number == 4) and \
|
||||||
|
(not clean_line.startswith(u'CCL')):
|
||||||
|
song_comments = song_comments + clean_line
|
||||||
|
# split on known separators
|
||||||
|
author_list = song_author.split(u'/')
|
||||||
|
if len(author_list) < 2:
|
||||||
|
author_list = song_author.split(u'|')
|
||||||
|
#Clean spaces before and after author names
|
||||||
|
for author_name in author_list:
|
||||||
|
self.add_author(author_name.strip())
|
||||||
|
self.title = song_name
|
||||||
|
self.copyright = song_copyright
|
||||||
|
self.ccli_number = song_ccli
|
||||||
|
self.comments = song_comments
|
||||||
|
self.finish()
|
|
@ -26,11 +26,24 @@
|
||||||
|
|
||||||
from opensongimport import OpenSongImport
|
from opensongimport import OpenSongImport
|
||||||
from olpimport import OpenLPSongImport
|
from olpimport import OpenLPSongImport
|
||||||
|
from wowimport import WowImport
|
||||||
|
from cclifileimport import CCLIFileImport
|
||||||
|
# Imports that might fail
|
||||||
|
try:
|
||||||
|
from olp1import import OpenLP1SongImport
|
||||||
|
has_openlp1 = True
|
||||||
|
except ImportError:
|
||||||
|
has_openlp1 = False
|
||||||
try:
|
try:
|
||||||
from sofimport import SofImport
|
from sofimport import SofImport
|
||||||
from oooimport import OooImport
|
has_sof = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
has_sof = False
|
||||||
|
try:
|
||||||
|
from oooimport import OooImport
|
||||||
|
has_ooo = True
|
||||||
|
except ImportError:
|
||||||
|
has_ooo = False
|
||||||
|
|
||||||
class SongFormat(object):
|
class SongFormat(object):
|
||||||
"""
|
"""
|
||||||
|
@ -38,6 +51,7 @@ class SongFormat(object):
|
||||||
plus a few helper functions to facilitate generic handling of song types
|
plus a few helper functions to facilitate generic handling of song types
|
||||||
for importing.
|
for importing.
|
||||||
"""
|
"""
|
||||||
|
_format_availability = {}
|
||||||
Unknown = -1
|
Unknown = -1
|
||||||
OpenLP2 = 0
|
OpenLP2 = 0
|
||||||
OpenLP1 = 1
|
OpenLP1 = 1
|
||||||
|
@ -59,12 +73,18 @@ class SongFormat(object):
|
||||||
"""
|
"""
|
||||||
if format == SongFormat.OpenLP2:
|
if format == SongFormat.OpenLP2:
|
||||||
return OpenLPSongImport
|
return OpenLPSongImport
|
||||||
|
if format == SongFormat.OpenLP1:
|
||||||
|
return OpenLP1SongImport
|
||||||
elif format == SongFormat.OpenSong:
|
elif format == SongFormat.OpenSong:
|
||||||
return OpenSongImport
|
return OpenSongImport
|
||||||
elif format == SongFormat.SongsOfFellowship:
|
elif format == SongFormat.SongsOfFellowship:
|
||||||
return SofImport
|
return SofImport
|
||||||
|
elif format == SongFormat.WordsOfWorship:
|
||||||
|
return WowImport
|
||||||
elif format == SongFormat.Generic:
|
elif format == SongFormat.Generic:
|
||||||
return OooImport
|
return OooImport
|
||||||
|
elif format == SongFormat.CCLI:
|
||||||
|
return CCLIFileImport
|
||||||
# else:
|
# else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -84,4 +104,16 @@ class SongFormat(object):
|
||||||
SongFormat.Generic
|
SongFormat.Generic
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_availability(format, available):
|
||||||
|
SongFormat._format_availability[format] = available
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_availability(format):
|
||||||
|
return SongFormat._format_availability.get(format, True)
|
||||||
|
|
||||||
|
SongFormat.set_availability(SongFormat.OpenLP1, has_openlp1)
|
||||||
|
SongFormat.set_availability(SongFormat.SongsOfFellowship, has_sof)
|
||||||
|
SongFormat.set_availability(SongFormat.Generic, has_ooo)
|
||||||
|
|
||||||
__all__ = [u'SongFormat']
|
__all__ = [u'SongFormat']
|
||||||
|
|
|
@ -359,16 +359,13 @@ class SongMediaItem(MediaManagerItem):
|
||||||
author_list = author_list + u', '
|
author_list = author_list + u', '
|
||||||
author_list = author_list + unicode(author.display_name)
|
author_list = author_list + unicode(author.display_name)
|
||||||
author_audit.append(unicode(author.display_name))
|
author_audit.append(unicode(author.display_name))
|
||||||
if song.ccli_number is None or len(song.ccli_number) == 0:
|
|
||||||
ccli = QtCore.QSettings().value(u'general/ccli number',
|
|
||||||
QtCore.QVariant(u'')).toString()
|
|
||||||
else:
|
|
||||||
ccli = unicode(song.ccli_number)
|
|
||||||
raw_footer.append(song.title)
|
raw_footer.append(song.title)
|
||||||
raw_footer.append(author_list)
|
raw_footer.append(author_list)
|
||||||
raw_footer.append(song.copyright )
|
raw_footer.append(song.copyright )
|
||||||
raw_footer.append(unicode(
|
raw_footer.append(unicode(
|
||||||
translate('SongsPlugin.MediaItem', 'CCLI Licence: ') + ccli))
|
translate('SongsPlugin.MediaItem', 'CCLI Licence: ') +
|
||||||
|
QtCore.QSettings().value(u'general/ccli number',
|
||||||
|
QtCore.QVariant(u'')).toString()))
|
||||||
service_item.raw_footer = raw_footer
|
service_item.raw_footer = raw_footer
|
||||||
service_item.audit = [
|
service_item.audit = [
|
||||||
song.title, author_audit, song.copyright, unicode(song.ccli_number)
|
song.title, author_audit, song.copyright, unicode(song.ccli_number)
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`olp1import` module provides the functionality for importing
|
||||||
|
openlp.org 1.x song databases into the current installation database.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import chardet
|
||||||
|
import sqlite
|
||||||
|
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
from songimport import SongImport
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OpenLP1SongImport(SongImport):
|
||||||
|
"""
|
||||||
|
The :class:`OpenLP1SongImport` class provides OpenLP with the ability to
|
||||||
|
import song databases from installations of openlp.org 1.x.
|
||||||
|
"""
|
||||||
|
last_encoding = u'windows-1252'
|
||||||
|
|
||||||
|
def __init__(self, manager, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialise the import.
|
||||||
|
|
||||||
|
``manager``
|
||||||
|
The song manager for the running OpenLP installation.
|
||||||
|
|
||||||
|
``filename``
|
||||||
|
The database providing the data to import.
|
||||||
|
"""
|
||||||
|
SongImport.__init__(self, manager)
|
||||||
|
self.import_source = kwargs[u'filename']
|
||||||
|
|
||||||
|
def decode_string(self, raw, guess):
|
||||||
|
"""
|
||||||
|
Use chardet to detect the encoding of the raw string, and convert it
|
||||||
|
to unicode.
|
||||||
|
|
||||||
|
``raw``
|
||||||
|
The raw bytestring to decode.
|
||||||
|
``guess``
|
||||||
|
What chardet guessed the encoding to be.
|
||||||
|
"""
|
||||||
|
if guess[u'confidence'] < 0.8:
|
||||||
|
codec = u'windows-1252'
|
||||||
|
else:
|
||||||
|
codec = guess[u'encoding']
|
||||||
|
try:
|
||||||
|
decoded = unicode(raw, codec)
|
||||||
|
self.last_encoding = codec
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
log.exception(u'Error in detecting openlp.org 1.x database encoding.')
|
||||||
|
try:
|
||||||
|
decoded = unicode(raw, self.last_encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# possibly show an error form
|
||||||
|
#self.import_wizard.showError(u'There was a problem '
|
||||||
|
# u'detecting the encoding of a string')
|
||||||
|
decoded = raw
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
def do_import(self):
|
||||||
|
"""
|
||||||
|
Run the import for an openlp.org 1.x song database.
|
||||||
|
"""
|
||||||
|
# Connect to the database
|
||||||
|
connection = sqlite.connect(self.import_source)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
# Determine if we're using a new or an old DB
|
||||||
|
cursor.execute(u'SELECT name FROM sqlite_master '
|
||||||
|
u'WHERE type = \'table\' AND name = \'tracks\'')
|
||||||
|
table_list = cursor.fetchall()
|
||||||
|
new_db = len(table_list) > 0
|
||||||
|
# Count the number of records we need to import, for the progress bar
|
||||||
|
cursor.execute(u'SELECT COUNT(songid) FROM songs')
|
||||||
|
count = int(cursor.fetchone()[0])
|
||||||
|
success = True
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(count)
|
||||||
|
# "cache" our list of authors
|
||||||
|
cursor.execute(u'SELECT authorid, authorname FROM authors')
|
||||||
|
authors = cursor.fetchall()
|
||||||
|
if new_db:
|
||||||
|
# "cache" our list of tracks
|
||||||
|
cursor.execute(u'SELECT trackid, fulltrackname FROM tracks')
|
||||||
|
tracks = cursor.fetchall()
|
||||||
|
# Import the songs
|
||||||
|
cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, '
|
||||||
|
u'copyrightinfo FROM songs')
|
||||||
|
songs = cursor.fetchall()
|
||||||
|
for song in songs:
|
||||||
|
self.set_defaults()
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
song_id = song[0]
|
||||||
|
guess = chardet.detect(song[2])
|
||||||
|
title = self.decode_string(song[1], guess)
|
||||||
|
lyrics = self.decode_string(song[2], guess).replace(u'\r', u'')
|
||||||
|
copyright = self.decode_string(song[3], guess)
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
unicode(translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Importing "%s"...')) % title)
|
||||||
|
self.title = title
|
||||||
|
self.process_song_text(lyrics)
|
||||||
|
self.add_copyright(copyright)
|
||||||
|
cursor.execute(u'SELECT authorid FROM songauthors '
|
||||||
|
u'WHERE songid = %s' % song_id)
|
||||||
|
author_ids = cursor.fetchall()
|
||||||
|
for author_id in author_ids:
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
for author in authors:
|
||||||
|
if author[0] == author_id[0]:
|
||||||
|
self.parse_author(self.decode_string(author[1], guess))
|
||||||
|
break
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
if new_db:
|
||||||
|
cursor.execute(u'SELECT trackid FROM songtracks '
|
||||||
|
u'WHERE songid = %s ORDER BY listindex' % song_id)
|
||||||
|
track_ids = cursor.fetchall()
|
||||||
|
for track_id in track_ids:
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
for track in tracks:
|
||||||
|
if track[0] == track_id[0]:
|
||||||
|
self.add_media_file(self.decode_string(track[1], guess))
|
||||||
|
break
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
self.finish()
|
||||||
|
return success
|
||||||
|
|
|
@ -28,6 +28,7 @@ import os
|
||||||
|
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
|
from openlp.core.lib import Receiver
|
||||||
from songimport import SongImport
|
from songimport import SongImport
|
||||||
|
|
||||||
if os.name == u'nt':
|
if os.name == u'nt':
|
||||||
|
@ -43,23 +44,32 @@ else:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class OooImport(object):
|
class OooImport(SongImport):
|
||||||
"""
|
"""
|
||||||
Import songs from Impress/Powerpoint docs using Impress
|
Import songs from Impress/Powerpoint docs using Impress
|
||||||
"""
|
"""
|
||||||
def __init__(self, songmanager):
|
def __init__(self, master_manager, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialise the class. Requires a songmanager class which is passed
|
Initialise the class. Requires a songmanager class which is passed
|
||||||
to SongImport for writing song to disk
|
to SongImport for writing song to disk
|
||||||
"""
|
"""
|
||||||
|
SongImport.__init__(self, master_manager)
|
||||||
self.song = None
|
self.song = None
|
||||||
self.manager = songmanager
|
self.master_manager = master_manager
|
||||||
self.document = None
|
self.document = None
|
||||||
self.process_started = False
|
self.process_started = False
|
||||||
|
self.filenames = kwargs[u'filenames']
|
||||||
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
|
QtCore.SIGNAL(u'song_stop_import'), self.stop_import)
|
||||||
|
|
||||||
def import_docs(self, filenames):
|
def do_import(self):
|
||||||
|
self.abort = False
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(0)
|
||||||
self.start_ooo()
|
self.start_ooo()
|
||||||
for filename in filenames:
|
for filename in self.filenames:
|
||||||
|
if self.abort:
|
||||||
|
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||||
|
return
|
||||||
filename = unicode(filename)
|
filename = unicode(filename)
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
self.open_ooo_file(filename)
|
self.open_ooo_file(filename)
|
||||||
|
@ -72,6 +82,12 @@ class OooImport(object):
|
||||||
self.process_doc()
|
self.process_doc()
|
||||||
self.close_ooo_file()
|
self.close_ooo_file()
|
||||||
self.close_ooo()
|
self.close_ooo()
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(1)
|
||||||
|
self.import_wizard.incrementProgressBar(u'', 1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop_import(self):
|
||||||
|
self.abort = True
|
||||||
|
|
||||||
def start_ooo(self):
|
def start_ooo(self):
|
||||||
"""
|
"""
|
||||||
|
@ -135,6 +151,9 @@ class OooImport(object):
|
||||||
"com.sun.star.presentation.PresentationDocument") and not \
|
"com.sun.star.presentation.PresentationDocument") and not \
|
||||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||||
self.close_ooo_file()
|
self.close_ooo_file()
|
||||||
|
else:
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
u'Processing file ' + filepath, 0)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
@ -161,6 +180,9 @@ class OooImport(object):
|
||||||
slides = doc.getDrawPages()
|
slides = doc.getDrawPages()
|
||||||
text = u''
|
text = u''
|
||||||
for slide_no in range(slides.getCount()):
|
for slide_no in range(slides.getCount()):
|
||||||
|
if self.abort:
|
||||||
|
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||||
|
return
|
||||||
slide = slides.getByIndex(slide_no)
|
slide = slides.getByIndex(slide_no)
|
||||||
slidetext = u''
|
slidetext = u''
|
||||||
for idx in range(slide.getCount()):
|
for idx in range(slide.getCount()):
|
||||||
|
|
|
@ -28,7 +28,10 @@ import logging
|
||||||
import os
|
import os
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from lxml import objectify
|
from lxml import objectify
|
||||||
|
from lxml.etree import Error, LxmlError
|
||||||
|
import re
|
||||||
|
|
||||||
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -36,24 +39,35 @@ log = logging.getLogger(__name__)
|
||||||
class OpenSongImportError(Exception):
|
class OpenSongImportError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class OpenSongImport(object):
|
class OpenSongImport(SongImport):
|
||||||
"""
|
"""
|
||||||
Import songs exported from OpenSong - the format is described loosly here:
|
Import songs exported from OpenSong
|
||||||
http://www.opensong.org/d/manual/song_file_format_specification
|
|
||||||
|
|
||||||
However, it doesn't describe the <lyrics> section, so here's an attempt:
|
The format is described loosly on the `OpenSong File Format Specification
|
||||||
|
<http://www.opensong.org/d/manual/song_file_format_specification>`_ page on
|
||||||
|
the OpenSong web site. However, it doesn't describe the <lyrics> section,
|
||||||
|
so here's an attempt:
|
||||||
|
|
||||||
|
Verses can be expressed in one of 2 ways, either in complete verses, or by
|
||||||
|
line grouping, i.e. grouping all line 1's of a verse together, all line 2's
|
||||||
|
of a verse together, and so on.
|
||||||
|
|
||||||
|
An example of complete verses::
|
||||||
|
|
||||||
Verses can be expressed in one of 2 ways:
|
|
||||||
<lyrics>
|
<lyrics>
|
||||||
[v1]List of words
|
[v1]
|
||||||
|
List of words
|
||||||
Another Line
|
Another Line
|
||||||
|
|
||||||
[v2]Some words for the 2nd verse
|
[v2]
|
||||||
|
Some words for the 2nd verse
|
||||||
etc...
|
etc...
|
||||||
</lyrics>
|
</lyrics>
|
||||||
|
|
||||||
The 'v' can be left out - it is implied
|
The 'v' in the verse specifiers above can be left out, it is implied.
|
||||||
or:
|
|
||||||
|
An example of line grouping::
|
||||||
|
|
||||||
<lyrics>
|
<lyrics>
|
||||||
[V]
|
[V]
|
||||||
1List of words
|
1List of words
|
||||||
|
@ -63,101 +77,141 @@ class OpenSongImport(object):
|
||||||
2etc...
|
2etc...
|
||||||
</lyrics>
|
</lyrics>
|
||||||
|
|
||||||
Either or both forms can be used in one song. The Number does not
|
Either or both forms can be used in one song. The number does not
|
||||||
necessarily appear at the start of the line
|
necessarily appear at the start of the line. Additionally, the [v1] labels
|
||||||
|
can have either upper or lower case Vs.
|
||||||
|
|
||||||
The [v1] labels can have either upper or lower case Vs
|
|
||||||
Other labels can be used also:
|
Other labels can be used also:
|
||||||
C - Chorus
|
|
||||||
B - Bridge
|
|
||||||
|
|
||||||
Guitar chords can be provided 'above' the lyrics (the line is
|
C
|
||||||
preceeded by a'.') and _s can be used to signify long-drawn-out
|
Chorus
|
||||||
words:
|
|
||||||
|
B
|
||||||
|
Bridge
|
||||||
|
|
||||||
|
All verses are imported and tagged appropriately.
|
||||||
|
|
||||||
|
Guitar chords can be provided "above" the lyrics (the line is preceeded by
|
||||||
|
a period "."), and one or more "_" can be used to signify long-drawn-out
|
||||||
|
words. Chords and "_" are removed by this importer. For example::
|
||||||
|
|
||||||
. A7 Bm
|
. A7 Bm
|
||||||
1 Some____ Words
|
1 Some____ Words
|
||||||
|
|
||||||
Chords and _s are removed by this importer.
|
The <presentation> tag is used to populate the OpenLP verse display order
|
||||||
|
field. The Author and Copyright tags are also imported to the appropriate
|
||||||
The verses etc. are imported and tagged appropriately.
|
places.
|
||||||
|
|
||||||
The <presentation> tag is used to populate the OpenLP verse
|
|
||||||
display order field. The Author and Copyright tags are also
|
|
||||||
imported to the appropriate places.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, songmanager):
|
def __init__(self, manager, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialise the class. Requires a songmanager class which
|
Initialise the class.
|
||||||
is passed to SongImport for writing song to disk
|
|
||||||
"""
|
"""
|
||||||
self.songmanager = songmanager
|
SongImport.__init__(self, manager)
|
||||||
|
self.filenames = kwargs[u'filenames']
|
||||||
self.song = None
|
self.song = None
|
||||||
|
self.commit = True
|
||||||
|
|
||||||
def do_import(self, filename, commit=True):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Import either a single opensong file, or a zipfile
|
Import either each of the files in self.filenames - each element of
|
||||||
containing multiple opensong files If the commit parameter is
|
which can be either a single opensong file, or a zipfile containing
|
||||||
set False, the import will not be committed to the database
|
multiple opensong files. If `self.commit` is set False, the
|
||||||
(useful for test scripts)
|
import will not be committed to the database (useful for test scripts).
|
||||||
"""
|
"""
|
||||||
|
success = True
|
||||||
|
numfiles = 0
|
||||||
|
for filename in self.filenames:
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
if ext.lower() == ".zip":
|
if ext.lower() == u'.zip':
|
||||||
log.info('Zipfile found %s', filename)
|
z = ZipFile(filename, u'r')
|
||||||
|
numfiles += len(z.infolist())
|
||||||
|
else:
|
||||||
|
numfiles += 1
|
||||||
|
log.debug("Total number of files: %d", numfiles)
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(numfiles)
|
||||||
|
for filename in self.filenames:
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
ext = os.path.splitext(filename)[1]
|
||||||
|
if ext.lower() == u'.zip':
|
||||||
|
log.debug(u'Zipfile found %s', filename)
|
||||||
z = ZipFile(filename, u'r')
|
z = ZipFile(filename, u'r')
|
||||||
for song in z.infolist():
|
for song in z.infolist():
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
parts = os.path.split(song.filename)
|
parts = os.path.split(song.filename)
|
||||||
if parts[-1] == u'':
|
if parts[-1] == u'':
|
||||||
#No final part => directory
|
#No final part => directory
|
||||||
continue
|
continue
|
||||||
|
log.info(u'Zip importing %s', parts[-1])
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
unicode(translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Importing %s...')) % parts[-1])
|
||||||
songfile = z.open(song)
|
songfile = z.open(song)
|
||||||
self.do_import_file(songfile)
|
self.do_import_file(songfile)
|
||||||
if commit:
|
if self.commit:
|
||||||
self.finish()
|
self.finish()
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
|
# not a zipfile
|
||||||
log.info('Direct import %s', filename)
|
log.info('Direct import %s', filename)
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
unicode(translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Importing %s...')) % os.path.split(filename)[-1])
|
||||||
file = open(filename)
|
file = open(filename)
|
||||||
self.do_import_file(file)
|
self.do_import_file(file)
|
||||||
if commit:
|
if self.commit:
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
def do_import_file(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
Process the OpenSong file - pass in a file-like object,
|
Process the OpenSong file - pass in a file-like object,
|
||||||
not a filename
|
not a filename
|
||||||
"""
|
"""
|
||||||
self.song_import = SongImport(self.songmanager)
|
self.set_defaults()
|
||||||
|
try:
|
||||||
tree = objectify.parse(file)
|
tree = objectify.parse(file)
|
||||||
|
except (Error, LxmlError):
|
||||||
|
log.exception(u'Error parsing XML')
|
||||||
|
return
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
fields = dir(root)
|
fields = dir(root)
|
||||||
decode = {u'copyright':self.song_import.add_copyright,
|
decode = {
|
||||||
|
u'copyright': self.add_copyright,
|
||||||
u'ccli': u'ccli_number',
|
u'ccli': u'ccli_number',
|
||||||
u'author':self.song_import.parse_author,
|
u'author': self.parse_author,
|
||||||
u'title': u'title',
|
u'title': u'title',
|
||||||
u'aka': u'alternate_title',
|
u'aka': u'alternate_title',
|
||||||
u'hymn_number':u'song_number'}
|
u'hymn_number': u'song_number'
|
||||||
for (attr, fn_or_string) in decode.items():
|
}
|
||||||
|
for attr, fn_or_string in decode.items():
|
||||||
if attr in fields:
|
if attr in fields:
|
||||||
ustring = unicode(root.__getattr__(attr))
|
ustring = unicode(root.__getattr__(attr))
|
||||||
if type(fn_or_string) == type(u''):
|
if isinstance(fn_or_string, basestring):
|
||||||
self.song_import.__setattr__(fn_or_string, ustring)
|
setattr(self, fn_or_string, ustring)
|
||||||
else:
|
else:
|
||||||
fn_or_string(ustring)
|
fn_or_string(ustring)
|
||||||
if u'theme' in fields:
|
if u'theme' in fields and unicode(root.theme) not in self.topics:
|
||||||
self.song_import.topics.append(unicode(root.theme))
|
self.topics.append(unicode(root.theme))
|
||||||
if u'alttheme' in fields:
|
if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
|
||||||
self.song_import.topics.append(unicode(root.alttheme))
|
self.topics.append(unicode(root.alttheme))
|
||||||
# data storage while importing
|
# data storage while importing
|
||||||
verses = {}
|
verses = {}
|
||||||
lyrics = unicode(root.lyrics)
|
|
||||||
# keep track of a "default" verse order, in case none is specified
|
# keep track of a "default" verse order, in case none is specified
|
||||||
our_verse_order = []
|
our_verse_order = []
|
||||||
verses_seen = {}
|
verses_seen = {}
|
||||||
# in the absence of any other indication, verses are the default,
|
# in the absence of any other indication, verses are the default,
|
||||||
# erm, versetype!
|
# erm, versetype!
|
||||||
versetype = u'V'
|
versetype = u'V'
|
||||||
|
versenum = None
|
||||||
|
lyrics = unicode(root.lyrics)
|
||||||
for thisline in lyrics.split(u'\n'):
|
for thisline in lyrics.split(u'\n'):
|
||||||
# remove comments
|
# remove comments
|
||||||
semicolon = thisline.find(u';')
|
semicolon = thisline.find(u';')
|
||||||
|
@ -170,31 +224,31 @@ class OpenSongImport(object):
|
||||||
if thisline[0] == u'.' or thisline.startswith(u'---') \
|
if thisline[0] == u'.' or thisline.startswith(u'---') \
|
||||||
or thisline.startswith(u'-!!'):
|
or thisline.startswith(u'-!!'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# verse/chorus/etc. marker
|
# verse/chorus/etc. marker
|
||||||
if thisline[0] == u'[':
|
if thisline[0] == u'[':
|
||||||
versetype = thisline[1].upper()
|
# drop the square brackets
|
||||||
if versetype.isdigit():
|
|
||||||
versenum = versetype
|
|
||||||
versetype = u'V'
|
|
||||||
elif thisline[2] != u']':
|
|
||||||
# there's a number to go with it - extract that as well
|
|
||||||
right_bracket = thisline.find(u']')
|
right_bracket = thisline.find(u']')
|
||||||
versenum = thisline[2:right_bracket]
|
content = thisline[1:right_bracket].upper()
|
||||||
|
# have we got any digits? If so, versenumber is everything from the digits
|
||||||
|
# to the end (even if there are some alpha chars on the end)
|
||||||
|
match = re.match(u'(.*)(\d+.*)', content)
|
||||||
|
if match is not None:
|
||||||
|
versetype = match.group(1)
|
||||||
|
versenum = match.group(2)
|
||||||
else:
|
else:
|
||||||
# if there's no number, assume it's no.1
|
# otherwise we assume number 1 and take the whole prefix as versetype
|
||||||
|
versetype = content
|
||||||
versenum = u'1'
|
versenum = u'1'
|
||||||
continue
|
continue
|
||||||
words = None
|
words = None
|
||||||
|
|
||||||
# number at start of line.. it's verse number
|
# number at start of line.. it's verse number
|
||||||
if thisline[0].isdigit():
|
if thisline[0].isdigit():
|
||||||
versenum = thisline[0]
|
versenum = thisline[0]
|
||||||
words = thisline[1:].strip()
|
words = thisline[1:].strip()
|
||||||
if words is None and \
|
if words is None:
|
||||||
versenum is not None and \
|
|
||||||
versetype is not None:
|
|
||||||
words = thisline
|
words = thisline
|
||||||
|
if not versenum:
|
||||||
|
versenum = u'1'
|
||||||
if versenum is not None:
|
if versenum is not None:
|
||||||
versetag = u'%s%s' % (versetype, versenum)
|
versetag = u'%s%s' % (versetype, versenum)
|
||||||
if not verses.has_key(versetype):
|
if not verses.has_key(versetype):
|
||||||
|
@ -207,7 +261,7 @@ class OpenSongImport(object):
|
||||||
our_verse_order.append(versetag)
|
our_verse_order.append(versetag)
|
||||||
if words:
|
if words:
|
||||||
# Tidy text and remove the ____s from extended words
|
# Tidy text and remove the ____s from extended words
|
||||||
words = self.song_import.tidy_text(words)
|
words = self.tidy_text(words)
|
||||||
words = words.replace('_', '')
|
words = words.replace('_', '')
|
||||||
verses[versetype][versenum].append(words)
|
verses[versetype][versenum].append(words)
|
||||||
# done parsing
|
# done parsing
|
||||||
|
@ -215,29 +269,38 @@ class OpenSongImport(object):
|
||||||
versetypes.sort()
|
versetypes.sort()
|
||||||
versetags = {}
|
versetags = {}
|
||||||
for versetype in versetypes:
|
for versetype in versetypes:
|
||||||
|
our_verse_type = versetype
|
||||||
|
if our_verse_type == u'':
|
||||||
|
our_verse_type = u'V'
|
||||||
versenums = verses[versetype].keys()
|
versenums = verses[versetype].keys()
|
||||||
versenums.sort()
|
versenums.sort()
|
||||||
for num in versenums:
|
for num in versenums:
|
||||||
versetag = u'%s%s' % (versetype, num)
|
versetag = u'%s%s' % (our_verse_type, num)
|
||||||
lines = u'\n'.join(verses[versetype][num])
|
lines = u'\n'.join(verses[versetype][num])
|
||||||
self.song_import.verses.append([versetag, lines])
|
self.verses.append([versetag, lines])
|
||||||
# Keep track of what we have for error checking later
|
# Keep track of what we have for error checking later
|
||||||
versetags[versetag] = 1
|
versetags[versetag] = 1
|
||||||
# now figure out the presentation order
|
# now figure out the presentation order
|
||||||
|
order = []
|
||||||
if u'presentation' in fields and root.presentation != u'':
|
if u'presentation' in fields and root.presentation != u'':
|
||||||
order = unicode(root.presentation)
|
order = unicode(root.presentation)
|
||||||
order = order.split()
|
# We make all the tags in the lyrics upper case, so match that here
|
||||||
|
# and then split into a list on the whitespace
|
||||||
|
order = order.upper().split()
|
||||||
else:
|
else:
|
||||||
assert len(our_verse_order)>0
|
if len(our_verse_order) > 0:
|
||||||
order = our_verse_order
|
order = our_verse_order
|
||||||
for tag in order:
|
|
||||||
if len(tag) == 1:
|
|
||||||
tag = tag + u'1' # Assume it's no.1 if it's not there
|
|
||||||
if not versetags.has_key(tag):
|
|
||||||
log.warn(u'Got order %s but not in versetags, skipping', tag)
|
|
||||||
else:
|
else:
|
||||||
self.song_import.verse_order_list.append(tag)
|
log.warn(u'No verse order available for %s, skipping.',
|
||||||
|
self.title)
|
||||||
def finish(self):
|
for tag in order:
|
||||||
""" Separate function, allows test suite to not pollute database"""
|
if tag[0].isdigit():
|
||||||
self.song_import.finish()
|
# Assume it's a verse if it has no prefix
|
||||||
|
tag = u'V' + tag
|
||||||
|
elif not re.search('\d+', tag):
|
||||||
|
# Assume it's no.1 if there's no digits
|
||||||
|
tag = tag + u'1'
|
||||||
|
if not versetags.has_key(tag):
|
||||||
|
log.info(u'Got order %s but not in versetags, dropping this item from presentation order', tag)
|
||||||
|
else:
|
||||||
|
self.verse_order_list.append(tag)
|
||||||
|
|
|
@ -68,19 +68,30 @@ class SofImport(OooImport):
|
||||||
It attempts to detect italiced verses, and treats these as choruses in
|
It attempts to detect italiced verses, and treats these as choruses in
|
||||||
the verse ordering. Again not perfect, but a start.
|
the verse ordering. Again not perfect, but a start.
|
||||||
"""
|
"""
|
||||||
def __init__(self, songmanager):
|
def __init__(self, master_manager, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialise the class. Requires a songmanager class which is passed
|
Initialise the class. Requires a songmanager class which is passed
|
||||||
to SongImport for writing song to disk
|
to SongImport for writing song to disk
|
||||||
"""
|
"""
|
||||||
OooImport.__init__(self, songmanager)
|
OooImport.__init__(self, master_manager, **kwargs)
|
||||||
|
|
||||||
def import_sof(self, filename):
|
def do_import(self):
|
||||||
|
self.abort = False
|
||||||
self.start_ooo()
|
self.start_ooo()
|
||||||
|
for filename in self.filenames:
|
||||||
|
if self.abort:
|
||||||
|
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||||
|
return
|
||||||
|
filename = unicode(filename)
|
||||||
|
if os.path.isfile(filename):
|
||||||
self.open_ooo_file(filename)
|
self.open_ooo_file(filename)
|
||||||
|
if self.document:
|
||||||
self.process_sof_file()
|
self.process_sof_file()
|
||||||
self.close_ooo_file()
|
self.close_ooo_file()
|
||||||
self.close_ooo()
|
self.close_ooo()
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(1)
|
||||||
|
self.import_wizard.incrementProgressBar(u'', 1)
|
||||||
|
return True
|
||||||
|
|
||||||
def process_sof_file(self):
|
def process_sof_file(self):
|
||||||
"""
|
"""
|
||||||
|
@ -90,6 +101,9 @@ class SofImport(OooImport):
|
||||||
self.new_song()
|
self.new_song()
|
||||||
paragraphs = self.document.getText().createEnumeration()
|
paragraphs = self.document.getText().createEnumeration()
|
||||||
while paragraphs.hasMoreElements():
|
while paragraphs.hasMoreElements():
|
||||||
|
if self.abort:
|
||||||
|
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||||
|
return
|
||||||
paragraph = paragraphs.nextElement()
|
paragraph = paragraphs.nextElement()
|
||||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||||
self.process_paragraph(paragraph)
|
self.process_paragraph(paragraph)
|
||||||
|
@ -244,6 +258,7 @@ class SofImport(OooImport):
|
||||||
if title.endswith(u','):
|
if title.endswith(u','):
|
||||||
title = title[:-1]
|
title = title[:-1]
|
||||||
self.song.title = title
|
self.song.title = title
|
||||||
|
self.import_wizard.incrementProgressBar(u'Processing song ' + title, 0)
|
||||||
|
|
||||||
def add_author(self, text):
|
def add_author(self, text):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -30,7 +30,7 @@ from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.lib import Receiver, translate
|
from openlp.core.lib import Receiver, translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book
|
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
||||||
from openlp.plugins.songs.lib.xml import SongXMLBuilder
|
from openlp.plugins.songs.lib.xml import SongXMLBuilder
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -52,6 +52,15 @@ class SongImport(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self.stop_import_flag = False
|
self.stop_import_flag = False
|
||||||
|
self.set_defaults()
|
||||||
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
|
QtCore.SIGNAL(u'songs_stop_import'), self.stop_import)
|
||||||
|
def set_defaults(self):
|
||||||
|
"""
|
||||||
|
Create defaults for properties - call this before each song
|
||||||
|
if importing many songs at once to ensure a clean beginning
|
||||||
|
"""
|
||||||
|
self.authors = []
|
||||||
self.title = u''
|
self.title = u''
|
||||||
self.song_number = u''
|
self.song_number = u''
|
||||||
self.alternate_title = u''
|
self.alternate_title = u''
|
||||||
|
@ -61,18 +70,16 @@ class SongImport(QtCore.QObject):
|
||||||
self.ccli_number = u''
|
self.ccli_number = u''
|
||||||
self.authors = []
|
self.authors = []
|
||||||
self.topics = []
|
self.topics = []
|
||||||
|
self.media_files = []
|
||||||
self.song_book_name = u''
|
self.song_book_name = u''
|
||||||
self.song_book_pub = u''
|
self.song_book_pub = u''
|
||||||
self.verse_order_list = []
|
self.verse_order_list = []
|
||||||
self.verses = []
|
self.verses = []
|
||||||
self.versecount = 0
|
self.versecounts = {}
|
||||||
self.choruscount = 0
|
|
||||||
self.copyright_string = unicode(translate(
|
self.copyright_string = unicode(translate(
|
||||||
'SongsPlugin.SongImport', 'copyright'))
|
'SongsPlugin.SongImport', 'copyright'))
|
||||||
self.copyright_symbol = unicode(translate(
|
self.copyright_symbol = unicode(translate(
|
||||||
'SongsPlugin.SongImport', '\xa9'))
|
'SongsPlugin.SongImport', '\xa9'))
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'songs_stop_import'), self.stop_import)
|
|
||||||
|
|
||||||
def stop_import(self):
|
def stop_import(self):
|
||||||
"""
|
"""
|
||||||
|
@ -158,8 +165,7 @@ class SongImport(QtCore.QObject):
|
||||||
def parse_author(self, text):
|
def parse_author(self, text):
|
||||||
"""
|
"""
|
||||||
Add the author. OpenLP stores them individually so split by 'and', '&'
|
Add the author. OpenLP stores them individually so split by 'and', '&'
|
||||||
and comma.
|
and comma. However need to check for 'Mr and Mrs Smith' and turn it to
|
||||||
However need to check for 'Mr and Mrs Smith' and turn it to
|
|
||||||
'Mr Smith' and 'Mrs Smith'.
|
'Mr Smith' and 'Mrs Smith'.
|
||||||
"""
|
"""
|
||||||
for author in text.split(u','):
|
for author in text.split(u','):
|
||||||
|
@ -182,7 +188,15 @@ class SongImport(QtCore.QObject):
|
||||||
return
|
return
|
||||||
self.authors.append(author)
|
self.authors.append(author)
|
||||||
|
|
||||||
def add_verse(self, verse, versetag=None):
|
def add_media_file(self, filename):
|
||||||
|
"""
|
||||||
|
Add a media file to the list
|
||||||
|
"""
|
||||||
|
if filename in self.media_files:
|
||||||
|
return
|
||||||
|
self.media_files.append(filename)
|
||||||
|
|
||||||
|
def add_verse(self, verse, versetag=u'V'):
|
||||||
"""
|
"""
|
||||||
Add a verse. This is the whole verse, lines split by \n
|
Add a verse. This is the whole verse, lines split by \n
|
||||||
Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/
|
Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/
|
||||||
|
@ -194,13 +208,14 @@ class SongImport(QtCore.QObject):
|
||||||
if oldverse.strip() == verse.strip():
|
if oldverse.strip() == verse.strip():
|
||||||
self.verse_order_list.append(oldversetag)
|
self.verse_order_list.append(oldversetag)
|
||||||
return
|
return
|
||||||
if versetag == u'V' or not versetag:
|
if versetag[0] in self.versecounts:
|
||||||
self.versecount += 1
|
self.versecounts[versetag[0]] += 1
|
||||||
versetag = u'V' + unicode(self.versecount)
|
else:
|
||||||
if versetag.startswith(u'C'):
|
self.versecounts[versetag[0]] = 1
|
||||||
self.choruscount += 1
|
if len(versetag) == 1:
|
||||||
if versetag == u'C':
|
versetag += unicode(self.versecounts[versetag[0]])
|
||||||
versetag += unicode(self.choruscount)
|
elif int(versetag[1:]) > self.versecounts[versetag[0]]:
|
||||||
|
self.versecounts[versetag[0]] = int(versetag[1:])
|
||||||
self.verses.append([versetag, verse.rstrip()])
|
self.verses.append([versetag, verse.rstrip()])
|
||||||
self.verse_order_list.append(versetag)
|
self.verse_order_list.append(versetag)
|
||||||
if versetag.startswith(u'V') and self.contains_verse(u'C1'):
|
if versetag.startswith(u'V') and self.contains_verse(u'C1'):
|
||||||
|
@ -236,7 +251,7 @@ class SongImport(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
All fields have been set to this song. Write it away
|
All fields have been set to this song. Write it away
|
||||||
"""
|
"""
|
||||||
if len(self.authors) == 0:
|
if not self.authors:
|
||||||
self.authors.append(u'Author unknown')
|
self.authors.append(u'Author unknown')
|
||||||
self.commit_song()
|
self.commit_song()
|
||||||
|
|
||||||
|
@ -244,13 +259,16 @@ class SongImport(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
Write the song and its fields to disk
|
Write the song and its fields to disk
|
||||||
"""
|
"""
|
||||||
|
log.info(u'commiting song %s to database', self.title)
|
||||||
song = Song()
|
song = Song()
|
||||||
song.title = self.title
|
song.title = self.title
|
||||||
song.search_title = self.remove_punctuation(self.title) \
|
song.search_title = self.remove_punctuation(self.title) \
|
||||||
+ '@' + self.alternate_title
|
+ '@' + self.alternate_title
|
||||||
song.song_number = self.song_number
|
song.song_number = self.song_number
|
||||||
song.search_lyrics = u''
|
song.search_lyrics = u''
|
||||||
|
verses_changed_to_other = {}
|
||||||
sxml = SongXMLBuilder()
|
sxml = SongXMLBuilder()
|
||||||
|
other_count = 1
|
||||||
for (versetag, versetext) in self.verses:
|
for (versetag, versetext) in self.verses:
|
||||||
if versetag[0] == u'C':
|
if versetag[0] == u'C':
|
||||||
versetype = VerseType.to_string(VerseType.Chorus)
|
versetype = VerseType.to_string(VerseType.Chorus)
|
||||||
|
@ -265,10 +283,18 @@ class SongImport(QtCore.QObject):
|
||||||
elif versetag[0] == u'E':
|
elif versetag[0] == u'E':
|
||||||
versetype = VerseType.to_string(VerseType.Ending)
|
versetype = VerseType.to_string(VerseType.Ending)
|
||||||
else:
|
else:
|
||||||
|
newversetag = u'O%d' % other_count
|
||||||
|
verses_changed_to_other[versetag] = newversetag
|
||||||
|
other_count += 1
|
||||||
versetype = VerseType.to_string(VerseType.Other)
|
versetype = VerseType.to_string(VerseType.Other)
|
||||||
|
log.info(u'Versetype %s changing to %s' , versetag, newversetag)
|
||||||
|
versetag = newversetag
|
||||||
sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext)
|
sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext)
|
||||||
song.search_lyrics += u' ' + self.remove_punctuation(versetext)
|
song.search_lyrics += u' ' + self.remove_punctuation(versetext)
|
||||||
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
|
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
|
||||||
|
for i, current_verse_tag in enumerate(self.verse_order_list):
|
||||||
|
if verses_changed_to_other.has_key(current_verse_tag):
|
||||||
|
self.verse_order_list[i] = verses_changed_to_other[current_verse_tag]
|
||||||
song.verse_order = u' '.join(self.verse_order_list)
|
song.verse_order = u' '.join(self.verse_order_list)
|
||||||
song.copyright = self.copyright
|
song.copyright = self.copyright
|
||||||
song.comments = self.comments
|
song.comments = self.comments
|
||||||
|
@ -277,11 +303,16 @@ class SongImport(QtCore.QObject):
|
||||||
for authortext in self.authors:
|
for authortext in self.authors:
|
||||||
author = self.manager.get_object_filtered(Author,
|
author = self.manager.get_object_filtered(Author,
|
||||||
Author.display_name == authortext)
|
Author.display_name == authortext)
|
||||||
if author is None:
|
if not author:
|
||||||
author = Author.populate(display_name = authortext,
|
author = Author.populate(display_name = authortext,
|
||||||
last_name=authortext.split(u' ')[-1],
|
last_name=authortext.split(u' ')[-1],
|
||||||
first_name=u' '.join(authortext.split(u' ')[:-1]))
|
first_name=u' '.join(authortext.split(u' ')[:-1]))
|
||||||
song.authors.append(author)
|
song.authors.append(author)
|
||||||
|
for filename in self.media_files:
|
||||||
|
media_file = self.manager.get_object_filtered(MediaFile,
|
||||||
|
MediaFile.file_name == filename)
|
||||||
|
if not media_file:
|
||||||
|
song.media_files.append(MediaFile.populate(file_name=filename))
|
||||||
if self.song_book_name:
|
if self.song_book_name:
|
||||||
song_book = self.manager.get_object_filtered(Book,
|
song_book = self.manager.get_object_filtered(Book,
|
||||||
Book.name == self.song_book_name)
|
Book.name == self.song_book_name)
|
||||||
|
@ -298,6 +329,7 @@ class SongImport(QtCore.QObject):
|
||||||
topic = Topic.populate(name=topictext)
|
topic = Topic.populate(name=topictext)
|
||||||
song.topics.append(topic)
|
song.topics.append(topic)
|
||||||
self.manager.save_object(song)
|
self.manager.save_object(song)
|
||||||
|
self.set_defaults()
|
||||||
|
|
||||||
def print_song(self):
|
def print_song(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<song>
|
<song>
|
||||||
<title>Martins Test</title>
|
<title>Martins Test</title>
|
||||||
<author>MartiÑ Thómpson</author>
|
<author>MartiÑ & Martin2 Thómpson</author>
|
||||||
<copyright>2010 Martin Thompson</copyright>
|
<copyright>2010 Martin Thompson</copyright>
|
||||||
<hymn_number>1</hymn_number>
|
<hymn_number>1</hymn_number>
|
||||||
<presentation>V1 C V2 C2 V3 B1 V1</presentation>
|
<presentation>V1 C V2 C2 3a B1 V1 T U Rap1 Rap2 Rap3</presentation>
|
||||||
<ccli>Blah</ccli>
|
<ccli>Blah</ccli>
|
||||||
<capo print="false"></capo>
|
<capo print="false"></capo>
|
||||||
<key></key>
|
<key></key>
|
||||||
|
@ -17,7 +17,12 @@
|
||||||
<alttheme>TestAltTheme</alttheme>
|
<alttheme>TestAltTheme</alttheme>
|
||||||
<tempo></tempo>
|
<tempo></tempo>
|
||||||
<time_sig></time_sig>
|
<time_sig></time_sig>
|
||||||
<lyrics>;Comment
|
<lyrics>[3a]
|
||||||
|
. G A B
|
||||||
|
V3 Line 1
|
||||||
|
. G A B
|
||||||
|
V3 Line 2
|
||||||
|
|
||||||
. A B C
|
. A B C
|
||||||
1 v1 Line 1___
|
1 v1 Line 1___
|
||||||
2 v2 Line 1___
|
2 v2 Line 1___
|
||||||
|
@ -25,10 +30,6 @@
|
||||||
1 V1 Line 2
|
1 V1 Line 2
|
||||||
2 V2 Line 2
|
2 V2 Line 2
|
||||||
|
|
||||||
[3]
|
|
||||||
V3 Line 1
|
|
||||||
V3 Line 2
|
|
||||||
|
|
||||||
[b1]
|
[b1]
|
||||||
Bridge 1
|
Bridge 1
|
||||||
---
|
---
|
||||||
|
@ -42,6 +43,23 @@
|
||||||
[C2]
|
[C2]
|
||||||
. A B
|
. A B
|
||||||
Chorus 2
|
Chorus 2
|
||||||
|
|
||||||
|
[T]
|
||||||
|
T Line 1
|
||||||
|
|
||||||
|
[Rap]
|
||||||
|
1 Rap 1 Line 1
|
||||||
|
2 Rap 2 Line 1
|
||||||
|
1 Rap 1 Line 2
|
||||||
|
2 Rap 2 Line 2
|
||||||
|
|
||||||
|
[rap3]
|
||||||
|
Rap 3 Line 1
|
||||||
|
Rap 3 Line 2
|
||||||
|
|
||||||
|
|
||||||
|
[X]
|
||||||
|
Unreferenced verse line 1
|
||||||
</lyrics>
|
</lyrics>
|
||||||
<style index="default_style">
|
<style index="default_style">
|
||||||
<title enabled="true" valign="bottom" align="center" include_verse="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="26" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
|
<title enabled="true" valign="bottom" align="center" include_verse="false" margin-left="0" margin-right="0" margin-top="0" margin-bottom="0" font="Helvetica" size="26" bold="true" italic="true" underline="false" color="#FFFFFF" border="true" border_color="#000000" shadow="true" shadow_color="#000000" fill="false" fill_color="#000000"/>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<song>
|
||||||
|
<title>Test single verse</title>
|
||||||
|
<author>Martin Thompson</author>
|
||||||
|
<copyright>2010</copyright>
|
||||||
|
<ccli>123456</ccli>
|
||||||
|
<theme>Worship: Declaration</theme>
|
||||||
|
<lyrics> Line 1
|
||||||
|
Line 2
|
||||||
|
</lyrics></song>
|
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from openlp.plugins.songs.lib.opensongimport import OpenSongImport
|
||||||
|
from openlp.core.lib.db import Manager
|
||||||
|
from openlp.plugins.songs.lib.db import init_schema
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOG_FILENAME = 'test_import_file.log'
|
||||||
|
logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO)
|
||||||
|
|
||||||
|
from test_opensongimport import wizard_stub, progbar_stub
|
||||||
|
|
||||||
|
def test(filenames):
|
||||||
|
manager = Manager(u'songs', init_schema)
|
||||||
|
o = OpenSongImport(manager, filenames=filenames)
|
||||||
|
o.import_wizard = wizard_stub()
|
||||||
|
o.commit = False
|
||||||
|
o.do_import()
|
||||||
|
o.print_song()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test(sys.argv[1:])
|
|
@ -8,50 +8,29 @@ from traceback import print_exc
|
||||||
import sys
|
import sys
|
||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOG_FILENAME = 'import.log'
|
||||||
|
logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO)
|
||||||
|
|
||||||
|
from test_opensongimport import wizard_stub, progbar_stub
|
||||||
|
|
||||||
|
# Useful test function for importing a variety of different files
|
||||||
|
# Uncomment below depending on what problem trying to make occur!
|
||||||
|
|
||||||
def opensong_import_lots():
|
def opensong_import_lots():
|
||||||
ziploc = u'/home/mjt/openlp/OpenSong_Data/'
|
ziploc = u'/home/mjt/openlp/OpenSong_Data/'
|
||||||
files = []
|
files = []
|
||||||
#files = [u'test.opensong.zip', ziploc+u'ADond.zip']
|
files = [os.path.join(ziploc, u'RaoulSongs', u'Songs', u'Jesus Freak')]
|
||||||
files.extend(glob(ziploc+u'Songs.zip'))
|
# files.extend(glob(ziploc+u'Songs.zip'))
|
||||||
|
# files.extend(glob(ziploc+u'RaoulSongs.zip'))
|
||||||
# files.extend(glob(ziploc+u'SOF.zip'))
|
# files.extend(glob(ziploc+u'SOF.zip'))
|
||||||
# files.extend(glob(ziploc+u'spanish_songs_for_opensong.zip'))
|
# files.extend(glob(ziploc+u'spanish_songs_for_opensong.zip'))
|
||||||
# files.extend(glob(ziploc+u'opensong_*.zip'))
|
# files.extend(glob(ziploc+u'opensong_*.zip'))
|
||||||
errfile = codecs.open(u'import_lots_errors.txt', u'w', u'utf8')
|
errfile = codecs.open(u'import_lots_errors.txt', u'w', u'utf8')
|
||||||
manager = Manager(u'songs', init_schema)
|
manager = Manager(u'songs', init_schema)
|
||||||
for file in files:
|
o = OpenSongImport(manager, filenames=files)
|
||||||
print u'Importing', file
|
o.import_wizard=wizard_stub()
|
||||||
z = ZipFile(file, u'r')
|
o.do_import()
|
||||||
for song in z.infolist():
|
|
||||||
# need to handle unicode filenames (CP437 - Winzip does this)
|
|
||||||
filename = song.filename#.decode('cp852')
|
|
||||||
parts = os.path.split(filename)
|
|
||||||
if parts[-1] == u'':
|
|
||||||
#No final part => directory
|
|
||||||
continue
|
|
||||||
print " ", file, ":",filename,
|
|
||||||
songfile = z.open(song)
|
|
||||||
#z.extract(song)
|
|
||||||
#songfile=open(filename, u'r')
|
|
||||||
o = OpenSongImport(manager)
|
|
||||||
try:
|
|
||||||
o.do_import_file(songfile)
|
|
||||||
# o.song_import.print_song()
|
|
||||||
except:
|
|
||||||
print "Failure",
|
|
||||||
|
|
||||||
errfile.write(u'Failure: %s:%s\n' %(file, filename.decode('cp437')))
|
|
||||||
songfile = z.open(song)
|
|
||||||
for l in songfile.readlines():
|
|
||||||
l = l.decode('utf8')
|
|
||||||
print(u' |%s\n' % l.strip())
|
|
||||||
errfile.write(u' |%s\n'%l.strip())
|
|
||||||
print_exc(3, file = errfile)
|
|
||||||
print_exc(3)
|
|
||||||
sys.exit(1)
|
|
||||||
# continue
|
|
||||||
#o.finish()
|
|
||||||
print "OK"
|
|
||||||
#os.unlink(filename)
|
|
||||||
# o.song_import.print_song()
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
opensong_import_lots()
|
opensong_import_lots()
|
||||||
|
|
|
@ -28,62 +28,101 @@ from openlp.plugins.songs.lib.opensongimport import OpenSongImport
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.plugins.songs.lib.db import init_schema
|
from openlp.plugins.songs.lib.db import init_schema
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOG_FILENAME = 'test.log'
|
||||||
|
logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO)
|
||||||
|
|
||||||
|
# Stubs to replace the UI functions for raw testing
|
||||||
|
class wizard_stub:
|
||||||
|
def __init__(self):
|
||||||
|
self.importProgressBar=progbar_stub()
|
||||||
|
def incrementProgressBar(self, str):
|
||||||
|
pass
|
||||||
|
class progbar_stub:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
def setMaximum(self, arg):
|
||||||
|
pass
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
manager = Manager(u'songs', init_schema)
|
manager = Manager(u'songs', init_schema)
|
||||||
o = OpenSongImport(manager)
|
o = OpenSongImport(manager, filenames=[u'test.opensong'])
|
||||||
o.do_import(u'test.opensong', commit=False)
|
o.import_wizard = wizard_stub()
|
||||||
o.song_import.print_song()
|
o.commit = False
|
||||||
assert o.song_import.copyright == u'2010 Martin Thompson'
|
o.do_import()
|
||||||
assert o.song_import.authors == [u'MartiÑ Thómpson']
|
o.print_song()
|
||||||
assert o.song_import.title == u'Martins Test'
|
assert o.copyright == u'2010 Martin Thompson'
|
||||||
assert o.song_import.alternate_title == u''
|
assert o.authors == [u'MartiÑ Thómpson', u'Martin2 Thómpson']
|
||||||
assert o.song_import.song_number == u'1'
|
assert o.title == u'Martins Test'
|
||||||
assert [u'C1', u'Chorus 1'] in o.song_import.verses
|
assert o.alternate_title == u''
|
||||||
assert [u'C2', u'Chorus 2'] in o.song_import.verses
|
assert o.song_number == u'1'
|
||||||
assert not [u'C3', u'Chorus 3'] in o.song_import.verses
|
assert [u'C1', u'Chorus 1'] in o.verses
|
||||||
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses
|
assert [u'C2', u'Chorus 2'] in o.verses
|
||||||
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses
|
assert not [u'C3', u'Chorus 3'] in o.verses
|
||||||
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
|
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
|
||||||
assert o.song_import.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
|
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
|
||||||
assert o.song_import.ccli_number == u'Blah'
|
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
|
||||||
assert o.song_import.topics == [u'TestTheme', u'TestAltTheme']
|
assert [u'V3A', u'V3 Line 1\nV3 Line 2'] in o.verses
|
||||||
o.do_import(u'test.opensong.zip', commit=False)
|
assert [u'RAP1', u'Rap 1 Line 1\nRap 1 Line 2'] in o.verses
|
||||||
o.song_import.print_song()
|
assert [u'RAP2', u'Rap 2 Line 1\nRap 2 Line 2'] in o.verses
|
||||||
o.finish()
|
assert [u'RAP3', u'Rap 3 Line 1\nRap 3 Line 2'] in o.verses
|
||||||
assert o.song_import.copyright == u'2010 Martin Thompson'
|
assert [u'X1', u'Unreferenced verse line 1'] in o.verses
|
||||||
assert o.song_import.authors == [u'MartiÑ Thómpson']
|
assert o.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3A', u'B1', u'V1', u'T1', u'RAP1', u'RAP2', u'RAP3']
|
||||||
assert o.song_import.title == u'Martins Test'
|
assert o.ccli_number == u'Blah'
|
||||||
assert o.song_import.alternate_title == u''
|
assert o.topics == [u'TestTheme', u'TestAltTheme']
|
||||||
assert o.song_import.song_number == u'1'
|
|
||||||
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses
|
|
||||||
assert [u'C1', u'Chorus 1'] in o.song_import.verses
|
|
||||||
assert [u'C2', u'Chorus 2'] in o.song_import.verses
|
|
||||||
assert not [u'C3', u'Chorus 3'] in o.song_import.verses
|
|
||||||
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses
|
|
||||||
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
|
|
||||||
assert o.song_import.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
|
|
||||||
|
|
||||||
o = OpenSongImport(manager)
|
o.filenames = [u'test.opensong.zip']
|
||||||
o.do_import(u'test2.opensong', commit=False)
|
o.set_defaults()
|
||||||
# o.finish()
|
o.do_import()
|
||||||
o.song_import.print_song()
|
o.print_song()
|
||||||
assert o.song_import.copyright == u'2010 Martin Thompson'
|
assert o.copyright == u'2010 Martin Thompson'
|
||||||
assert o.song_import.authors == [u'Martin Thompson']
|
assert o.authors == [u'MartiÑ Thómpson']
|
||||||
assert o.song_import.title == u'Martins 2nd Test'
|
assert o.title == u'Martins Test'
|
||||||
assert o.song_import.alternate_title == u''
|
assert o.alternate_title == u''
|
||||||
assert o.song_import.song_number == u'2'
|
assert o.song_number == u'1'
|
||||||
print o.song_import.verses
|
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
|
||||||
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.song_import.verses
|
assert [u'C1', u'Chorus 1'] in o.verses
|
||||||
assert [u'C1', u'Chorus 1'] in o.song_import.verses
|
assert [u'C2', u'Chorus 2'] in o.verses
|
||||||
assert [u'C2', u'Chorus 2'] in o.song_import.verses
|
assert not [u'C3', u'Chorus 3'] in o.verses
|
||||||
assert not [u'C3', u'Chorus 3'] in o.song_import.verses
|
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
|
||||||
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.song_import.verses
|
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
|
||||||
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.song_import.verses
|
print o.verse_order_list
|
||||||
print o.song_import.verse_order_list
|
assert o.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
|
||||||
assert o.song_import.verse_order_list == [u'V1', u'V2', u'B1', u'C1', u'C2']
|
|
||||||
|
o.filenames = [u'test2.opensong']
|
||||||
|
o.set_defaults()
|
||||||
|
o.do_import()
|
||||||
|
o.print_song()
|
||||||
|
assert o.copyright == u'2010 Martin Thompson'
|
||||||
|
assert o.authors == [u'Martin Thompson']
|
||||||
|
assert o.title == u'Martins 2nd Test'
|
||||||
|
assert o.alternate_title == u''
|
||||||
|
assert o.song_number == u'2'
|
||||||
|
print o.verses
|
||||||
|
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
|
||||||
|
assert [u'C1', u'Chorus 1'] in o.verses
|
||||||
|
assert [u'C2', u'Chorus 2'] in o.verses
|
||||||
|
assert not [u'C3', u'Chorus 3'] in o.verses
|
||||||
|
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
|
||||||
|
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
|
||||||
|
print o.verse_order_list
|
||||||
|
assert o.verse_order_list == [u'V1', u'V2', u'B1', u'C1', u'C2']
|
||||||
|
|
||||||
|
o.filenames = [u'test3.opensong']
|
||||||
|
o.set_defaults()
|
||||||
|
o.do_import()
|
||||||
|
o.print_song()
|
||||||
|
assert o.copyright == u'2010'
|
||||||
|
assert o.authors == [u'Martin Thompson']
|
||||||
|
assert o.title == u'Test single verse'
|
||||||
|
assert o.alternate_title == u''
|
||||||
|
assert o.ccli_number == u'123456'
|
||||||
|
assert o.verse_order_list == [u'V1']
|
||||||
|
assert o.topics == [u'Worship: Declaration']
|
||||||
|
print o.verses[0]
|
||||||
|
assert [u'V1', u'Line 1\nLine 2'] in o.verses
|
||||||
|
|
||||||
print "Tests passed"
|
print "Tests passed"
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test()
|
test()
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
|
# Gorven, Scott Guerrieri, Meinert Jordan, 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`wowimport` module provides the functionality for importing Words of
|
||||||
|
Worship songs into the OpenLP database.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
|
BLOCK_TYPES = (u'V', u'C', u'B')
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class WowImport(SongImport):
|
||||||
|
"""
|
||||||
|
The :class:`WowImport` class provides the ability to import song files from
|
||||||
|
Words of Worship.
|
||||||
|
|
||||||
|
Words Of Worship Song File Format
|
||||||
|
`````````````````````````````````
|
||||||
|
|
||||||
|
The Words Of Worship song file format is as follows:
|
||||||
|
|
||||||
|
* The song title is the file name minus the extension.
|
||||||
|
* The song has a header, a number of blocks, followed by footer containing
|
||||||
|
the author and the copyright.
|
||||||
|
* A block can be a verse, chorus or bridge.
|
||||||
|
|
||||||
|
File Header:
|
||||||
|
Bytes are counted from one, i.e. the first byte is byte 1. These bytes,
|
||||||
|
up to the 56 byte, can change but no real meaning has been found. The
|
||||||
|
56th byte specifies how many blocks there are. The first block starts
|
||||||
|
with byte 83 after the "CSongDoc::CBlock" declaration.
|
||||||
|
|
||||||
|
Blocks:
|
||||||
|
Each block has a starting header, some lines of text, and an ending
|
||||||
|
footer. Each block starts with 4 bytes, the first byte specifies how
|
||||||
|
many lines are in that block, the next three bytes are null bytes.
|
||||||
|
|
||||||
|
Each block ends with 4 bytes, the first of which defines what type of
|
||||||
|
block it is, and the rest which are null bytes:
|
||||||
|
|
||||||
|
* ``NUL`` (\x00) - Verse
|
||||||
|
* ``SOH`` (\x01) - Chorus
|
||||||
|
* ``STX`` (\x02) - Bridge
|
||||||
|
|
||||||
|
Blocks are seperated by two bytes. The first byte is ``SOH`` (\x01),
|
||||||
|
and the second byte is ``€`` (\x80).
|
||||||
|
|
||||||
|
Lines:
|
||||||
|
Each line starts with a byte which specifies how long that line is,
|
||||||
|
the line text, and ends with a null byte.
|
||||||
|
|
||||||
|
|
||||||
|
Footer:
|
||||||
|
The footer follows on after the last block, the first byte specifies
|
||||||
|
the length of the author text, followed by the author text, if
|
||||||
|
this byte is null, then there is no author text. The byte after the
|
||||||
|
author text specifies the length of the copyright text, followed
|
||||||
|
by the copyright text.
|
||||||
|
|
||||||
|
The file is ended with four null bytes.
|
||||||
|
|
||||||
|
Valid extensions for a Words of Worship song file are:
|
||||||
|
|
||||||
|
* .wsg
|
||||||
|
* .wow-song
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, master_manager, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialise the import.
|
||||||
|
|
||||||
|
``master_manager``
|
||||||
|
The song manager for the running OpenLP installation.
|
||||||
|
"""
|
||||||
|
SongImport.__init__(self, master_manager)
|
||||||
|
self.master_manager = master_manager
|
||||||
|
if kwargs.has_key(u'filename'):
|
||||||
|
self.import_source = kwargs[u'filename']
|
||||||
|
if kwargs.has_key(u'filenames'):
|
||||||
|
self.import_source = kwargs[u'filenames']
|
||||||
|
log.debug(self.import_source)
|
||||||
|
|
||||||
|
def do_import(self):
|
||||||
|
"""
|
||||||
|
Recieve a single file, or a list of files to import.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(self.import_source, list):
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(
|
||||||
|
len(self.import_source))
|
||||||
|
for file in self.import_source:
|
||||||
|
# TODO: check that it is a valid words of worship file (could
|
||||||
|
# check header for WoW File Song Word)
|
||||||
|
self.author = u''
|
||||||
|
self.copyright = u''
|
||||||
|
# Get the song title
|
||||||
|
self.file_name = os.path.split(file)[1]
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
"Importing %s" % (self.file_name), 0)
|
||||||
|
self.title = self.file_name.rpartition(u'.')[0]
|
||||||
|
self.songData = open(file, 'rb')
|
||||||
|
# Seek to byte which stores number of blocks in the song
|
||||||
|
self.songData.seek(56)
|
||||||
|
self.no_of_blocks = ord(self.songData.read(1))
|
||||||
|
# Seek to the beging of the first block
|
||||||
|
self.songData.seek(82)
|
||||||
|
for block in range(self.no_of_blocks):
|
||||||
|
self.lines_to_read = ord(self.songData.read(1))
|
||||||
|
# Skip 3 nulls to the beginnig of the 1st line
|
||||||
|
self.songData.seek(3, os.SEEK_CUR)
|
||||||
|
self.block_text = u''
|
||||||
|
while self.lines_to_read:
|
||||||
|
self.length_of_line = ord(self.songData.read(1))
|
||||||
|
self.line_text = unicode(
|
||||||
|
self.songData.read(self.length_of_line), u'cp1252')
|
||||||
|
self.songData.seek(1, os.SEEK_CUR)
|
||||||
|
if self.block_text != u'':
|
||||||
|
self.block_text += u'\n'
|
||||||
|
self.block_text += self.line_text
|
||||||
|
self.lines_to_read -= 1
|
||||||
|
self.block_type = BLOCK_TYPES[ord(self.songData.read(1))]
|
||||||
|
# Skip 3 nulls at the end of the block
|
||||||
|
self.songData.seek(3, os.SEEK_CUR)
|
||||||
|
# Blocks are seperated by 2 bytes, skip them, but not if
|
||||||
|
# this is the last block!
|
||||||
|
if (block + 1) < self.no_of_blocks:
|
||||||
|
self.songData.seek(2, os.SEEK_CUR)
|
||||||
|
self.add_verse(self.block_text, self.block_type)
|
||||||
|
# Now to extact the author
|
||||||
|
self.author_length = ord(self.songData.read(1))
|
||||||
|
if self.author_length != 0:
|
||||||
|
self.author = unicode(
|
||||||
|
self.songData.read(self.author_length), u'cp1252')
|
||||||
|
# Finally the copyright
|
||||||
|
self.copyright_length = ord(self.songData.read(1))
|
||||||
|
if self.copyright_length != 0:
|
||||||
|
self.copyright = unicode(
|
||||||
|
self.songData.read(self.copyright_length), u'cp1252')
|
||||||
|
self.parse_author(self.author)
|
||||||
|
self.add_copyright(self.copyright)
|
||||||
|
self.songData.close()
|
||||||
|
self.finish()
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
"Importing %s" % (self.file_name))
|
||||||
|
return True
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ExceptionDialog</class>
|
||||||
|
<widget class="QDialog" name="ExceptionDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>580</width>
|
||||||
|
<height>407</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="exceptionLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="messageLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="bugLabel">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap">
|
||||||
|
<pixmap resource="../images/openlp-2.qrc">:/graphics/exception.png</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="messageLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Oops! OpenLP hit a problem, and couldn't recover. The text in the box below contains information that might be helpful to the OpenLP developers, so please e-mail it to bugs@openlp.org, along with a detailed description of what you were doing when the problem occurred.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="exceptionTextEdit">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="backgroundVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="exceptionButtonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../images/openlp-2.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>exceptionButtonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>ExceptionDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>exceptionButtonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>ExceptionDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -62,6 +62,7 @@
|
||||||
<file>openlp-logo-256x256.png</file>
|
<file>openlp-logo-256x256.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="graphics">
|
<qresource prefix="graphics">
|
||||||
|
<file>exception.png</file>
|
||||||
<file>openlp-about-logo.png</file>
|
<file>openlp-about-logo.png</file>
|
||||||
<file>openlp-splash-screen.png</file>
|
<file>openlp-splash-screen.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
--- openlp/core/resources.py.old Mon Jun 21 23:16:19 2010
|
--- openlp/core/resources.py.old Mon Jun 21 23:16:19 2010
|
||||||
+++ openlp/core/resources.py Mon Jun 21 23:27:48 2010
|
+++ openlp/core/resources.py Mon Jun 21 23:27:48 2010
|
||||||
@@ -1,10 +1,31 @@
|
@@ -1,10 +1,32 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
@ -14,8 +14,9 @@
|
||||||
+# --------------------------------------------------------------------------- #
|
+# --------------------------------------------------------------------------- #
|
||||||
+# Copyright (c) 2008-2010 Raoul Snyman #
|
+# Copyright (c) 2008-2010 Raoul Snyman #
|
||||||
+# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
+# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||||
+# Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin #
|
+# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian #
|
||||||
+# Thompson, Jon Tibble, Carsten Tinggaard #
|
+# 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 #
|
+# 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 #
|
+# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
|
|
@ -24,13 +24,31 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Short description
|
|
||||||
# Steps for creating languages:
|
|
||||||
# 1. make sure that the openlp_en.ts file exist
|
|
||||||
# 2. go to scripts folder and start:
|
|
||||||
# python translation_utils.py -a
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script is used to maintain the translation files in OpenLP. It downloads
|
||||||
|
the latest translation files from the Pootle translation server, updates the
|
||||||
|
local translation files from both the source code and the files from Pootle,
|
||||||
|
and can also generate the compiled translation files.
|
||||||
|
|
||||||
|
Create New Language
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To create a new language, simply run this script with the ``-a`` command line
|
||||||
|
option::
|
||||||
|
|
||||||
|
@:~$ ./translation_utils.py -a
|
||||||
|
|
||||||
|
Update Translation Files
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The best way to update the translations is to download the files from Pootle,
|
||||||
|
and then update the local files using both the downloaded files and the source.
|
||||||
|
This is done easily via the ``-d``, ``-p`` and ``-u`` options::
|
||||||
|
|
||||||
|
@:~$ ./translation_utils.py -dpu
|
||||||
|
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
import re
|
import re
|
||||||
|
@ -39,201 +57,277 @@ from optparse import OptionParser
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
from BeautifulSoup import BeautifulSoup
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
class TranslationUtils(object):
|
SERVER_URL = u'http://pootle.projecthq.biz/export/openlp/'
|
||||||
|
IGNORED_PATHS = [u'scripts']
|
||||||
|
IGNORED_FILES = [u'setup.py']
|
||||||
|
|
||||||
|
verbose_mode = False
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
"""
|
||||||
|
Provide an enumeration of commands.
|
||||||
|
"""
|
||||||
|
Download = 1
|
||||||
|
Create = 2
|
||||||
|
Prepare = 3
|
||||||
|
Update = 4
|
||||||
|
Generate = 5
|
||||||
|
|
||||||
|
class CommandStack(object):
|
||||||
|
"""
|
||||||
|
This class provides an iterable stack.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ignore_paths = [u'./scripts']
|
self.current_index = 0
|
||||||
self.ignore_files = [u'setup.py']
|
self.data = []
|
||||||
self.server_url = u'http://pootle.projecthq.biz/export/openlp/'
|
|
||||||
self.cmd_stack = []
|
|
||||||
self.stack_count = 0
|
|
||||||
self.verbose = False
|
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
def process_stack(self):
|
def __getitem__(self, index):
|
||||||
if len(self.cmd_stack) > 0:
|
if self.data[index].get(u'arguments'):
|
||||||
if len(self.cmd_stack) == self.stack_count:
|
return self.data[index][u'command'], self.data[index][u'arguments']
|
||||||
print u'Process %d commands' % self.stack_count
|
|
||||||
print u'%d. ' % (self.stack_count-len(self.cmd_stack)+1),
|
|
||||||
command = self.cmd_stack.pop(0)
|
|
||||||
if len(command) > 1:
|
|
||||||
command[0](command[1])
|
|
||||||
else:
|
else:
|
||||||
command[0]()
|
return self.data[index][u'command']
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self.current_index == len(self.data):
|
||||||
|
raise StopIteration
|
||||||
else:
|
else:
|
||||||
print "Finished all commands"
|
current_item = self.data[self.current_index][u'command']
|
||||||
|
self.current_index += 1
|
||||||
|
return current_item
|
||||||
|
|
||||||
|
def append(self, command, **kwargs):
|
||||||
|
data = {u'command': command}
|
||||||
|
if u'arguments' in kwargs:
|
||||||
|
data[u'arguments'] = kwargs[u'arguments']
|
||||||
|
self.data.append(data)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.current_index = 0
|
||||||
|
|
||||||
|
|
||||||
def downloadTranslations(self):
|
def print_verbose(text):
|
||||||
print 'Download Translation files from HQ-Server'
|
"""
|
||||||
page = urllib.urlopen(u'%s' % (self.server_url))
|
This method checks to see if we are in verbose mode, and if so prints
|
||||||
soup = BeautifulSoup(page)
|
``text`` out.
|
||||||
languages = soup.findAll(text=re.compile(".*\.ts"))
|
|
||||||
for language in languages:
|
|
||||||
filename = os.path.join(u'..', u'resources', u'i18n',
|
|
||||||
u'openlp_%s' % language)
|
|
||||||
self.printVerbose(u'Get Translation File: %s' % filename)
|
|
||||||
self.get_and_write_file(language, filename)
|
|
||||||
print u' done'
|
|
||||||
self.process_stack()
|
|
||||||
|
|
||||||
def get_and_write_file(self, language, filename):
|
``text``
|
||||||
page = urllib.urlopen(u'%s%s' % (self.server_url, language))
|
The text to print.
|
||||||
|
"""
|
||||||
|
global verbose_mode
|
||||||
|
if verbose_mode:
|
||||||
|
print u' %s' % text
|
||||||
|
|
||||||
|
def run(command):
|
||||||
|
"""
|
||||||
|
This method runs an external application.
|
||||||
|
|
||||||
|
``command``
|
||||||
|
The command to run.
|
||||||
|
"""
|
||||||
|
print_verbose(command)
|
||||||
|
process = QtCore.QProcess()
|
||||||
|
process.start(command)
|
||||||
|
while (process.waitForReadyRead()):
|
||||||
|
print_verbose(u'ReadyRead: %s' % QtCore.QString(process.readAll()))
|
||||||
|
print_verbose(u'Error(s):\n%s' % process.readAllStandardError())
|
||||||
|
print_verbose(u'Output:\n%s' % process.readAllStandardOutput())
|
||||||
|
print u' Done.'
|
||||||
|
|
||||||
|
def download_file(source_filename, dest_filename):
|
||||||
|
"""
|
||||||
|
Download a file and save it to disk.
|
||||||
|
|
||||||
|
``source_filename``
|
||||||
|
The file to download.
|
||||||
|
|
||||||
|
``dest_filename``
|
||||||
|
The new local file name.
|
||||||
|
"""
|
||||||
|
print_verbose(u'Downloading from: %s' % (SERVER_URL + source_filename))
|
||||||
|
page = urllib.urlopen(SERVER_URL + source_filename)
|
||||||
content = page.read().decode('utf8')
|
content = page.read().decode('utf8')
|
||||||
page.close()
|
page.close()
|
||||||
file = open(filename, u'w')
|
file = open(dest_filename, u'w')
|
||||||
file.write(content.encode('utf8'))
|
file.write(content.encode('utf8'))
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def creation(self, language):
|
def download_translations():
|
||||||
print "Create new Translation File"
|
|
||||||
"""
|
"""
|
||||||
Use this option to create a new translation file
|
This method downloads the translation files from the Pootle server.
|
||||||
this function:
|
|
||||||
* create the new *.ts file
|
|
||||||
"""
|
"""
|
||||||
filename = os.path.join(u'..', u'resources', u'i18n',
|
print 'Download translation files from Pootle'
|
||||||
u'openlp_%s.ts' % language)
|
page = urllib.urlopen(SERVER_URL)
|
||||||
self.get_and_write_file(u'en.ts', filename)
|
soup = BeautifulSoup(page)
|
||||||
self.printVerbose("""
|
languages = soup.findAll(text=re.compile(r'.*\.ts'))
|
||||||
Please remind: For permanent providing this language:
|
for language in languages:
|
||||||
this language name have to append to the global list
|
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n',
|
||||||
variable "translations" in this file
|
language)
|
||||||
and this file have to be uploaded to the Pootle Server
|
print_verbose(u'Get Translation File: %s' % filename)
|
||||||
Please contact the developers!
|
download_file(language, filename)
|
||||||
""")
|
print u' Done.'
|
||||||
print u' done'
|
|
||||||
self.process_stack()
|
|
||||||
|
|
||||||
|
def prepare_project():
|
||||||
def preparation(self):
|
"""
|
||||||
|
This method creates the project file needed to update the translation files
|
||||||
|
and compile them into .qm files.
|
||||||
|
"""
|
||||||
print u'Generating the openlp.pro file'
|
print u'Generating the openlp.pro file'
|
||||||
stringlist = []
|
lines = []
|
||||||
start_dir = os.path.join(u'..')
|
start_dir = os.path.abspath(u'..')
|
||||||
|
start_dir = start_dir + os.sep
|
||||||
|
print_verbose(u'Starting directory: %s' % start_dir)
|
||||||
for root, dirs, files in os.walk(start_dir):
|
for root, dirs, files in os.walk(start_dir):
|
||||||
for file in files:
|
for file in files:
|
||||||
path = u'%s' % root
|
path = root.replace(start_dir, u'').replace(u'\\', u'/') #.replace(u'..', u'.')
|
||||||
path = path.replace('\\','/')
|
|
||||||
path = path.replace('..','.')
|
|
||||||
|
|
||||||
if file.startswith(u'hook-') or file.startswith(u'test_'):
|
if file.startswith(u'hook-') or file.startswith(u'test_'):
|
||||||
continue
|
continue
|
||||||
|
ignore = False
|
||||||
cond = False
|
for ignored_path in IGNORED_PATHS:
|
||||||
for search in self.ignore_paths:
|
if path.startswith(ignored_path):
|
||||||
if path.startswith(search):
|
ignore = True
|
||||||
cond = True
|
break
|
||||||
if cond:
|
if ignore:
|
||||||
continue
|
continue
|
||||||
cond = False
|
ignore = False
|
||||||
for search in self.ignore_files:
|
for ignored_file in IGNORED_FILES:
|
||||||
if search == file:
|
if file == ignored_file:
|
||||||
cond = True
|
ignore = True
|
||||||
if cond:
|
break
|
||||||
|
if ignore:
|
||||||
continue
|
continue
|
||||||
|
if file.endswith(u'.py') or file.endswith(u'.pyw'):
|
||||||
if file.endswith(u'.py'):
|
if path:
|
||||||
line = u'%s/%s' % (path, file)
|
line = u'%s/%s' % (path, file)
|
||||||
self.printVerbose(u'Parsing "%s"' % line)
|
else:
|
||||||
stringlist.append(u'SOURCES += %s' % line)
|
line = file
|
||||||
elif file.endswith(u'.pyw'):
|
print_verbose(u'Parsing "%s"' % line)
|
||||||
line = u'%s/%s' % (path, file)
|
lines.append(u'SOURCES += %s' % line)
|
||||||
self.printVerbose(u'Parsing "%s"' % line)
|
|
||||||
stringlist.append(u'SOURCES += %s' % line)
|
|
||||||
elif file.endswith(u'.ts'):
|
elif file.endswith(u'.ts'):
|
||||||
line = u'%s/%s' % (path, file)
|
line = u'%s/%s' % (path, file)
|
||||||
self.printVerbose(u'Parsing "%s"' % line)
|
print_verbose(u'Parsing "%s"' % line)
|
||||||
stringlist.append(u'TRANSLATIONS += %s' % line)
|
lines.append(u'TRANSLATIONS += %s' % line)
|
||||||
|
lines.sort()
|
||||||
stringlist.sort()
|
file = open(os.path.join(start_dir, u'openlp.pro'), u'w')
|
||||||
self.write_file(os.path.join(start_dir, u'openlp.pro'), stringlist)
|
file.write(u'\n'.join(lines).encode('utf8'))
|
||||||
print u' done'
|
|
||||||
self.process_stack()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
print u'Update the translation files'
|
|
||||||
cmd = u'pylupdate4 -verbose -noobsolete ../openlp.pro'
|
|
||||||
self.start_cmd(cmd)
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
print u'Generate the related *.qm files'
|
|
||||||
cmd = u'lrelease ../openlp.pro'
|
|
||||||
self.start_cmd(cmd)
|
|
||||||
|
|
||||||
def write_file(self, filename, stringlist):
|
|
||||||
content = u''
|
|
||||||
for line in stringlist:
|
|
||||||
content = u'%s%s\n' % (content, line)
|
|
||||||
file = open(filename, u'w')
|
|
||||||
file.write(content.encode('utf8'))
|
|
||||||
file.close()
|
file.close()
|
||||||
|
print u' Done.'
|
||||||
|
|
||||||
def printVerbose(self, data):
|
def update_translations():
|
||||||
if self.verbose:
|
print u'Update the translation files'
|
||||||
print u' %s' % data
|
if not os.path.exists(os.path.join(os.path.abspath(u'..'), u'openlp.pro')):
|
||||||
|
print u'You have no generated a project file yet, please run this ' + \
|
||||||
|
u'script with the -p option.'
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
os.chdir(os.path.abspath(u'..'))
|
||||||
|
run(u'pylupdate4 -verbose -noobsolete openlp.pro')
|
||||||
|
|
||||||
def start_cmd(self, command):
|
def generate_binaries():
|
||||||
self.printVerbose(command)
|
print u'Generate the related *.qm files'
|
||||||
self.process = QtCore.QProcess()
|
if not os.path.exists(os.path.join(os.path.abspath(u'..'), u'openlp.pro')):
|
||||||
self.process.start(command)
|
print u'You have no generated a project file yet, please run this ' + \
|
||||||
while (self.process.waitForReadyRead()):
|
u'script with the -p option. It is also recommended that you ' + \
|
||||||
self.printVerbose(u'ReadyRead: %s' % QtCore.QString(self.process.readAll()))
|
u'this script with the -u option to update the translation ' + \
|
||||||
self.printVerbose(self.process.readAllStandardError())
|
u'files as well.'
|
||||||
self.printVerbose(self.process.readAllStandardOutput())
|
return
|
||||||
print u' done'
|
else:
|
||||||
self.process_stack()
|
os.chdir(os.path.abspath(u'..'))
|
||||||
|
run(u'lrelease openlp.pro')
|
||||||
|
|
||||||
|
def create_translation(language):
|
||||||
|
"""
|
||||||
|
This method creates a new translation file.
|
||||||
|
|
||||||
|
``language``
|
||||||
|
The language file to create.
|
||||||
|
"""
|
||||||
|
print "Create new Translation File"
|
||||||
|
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', language)
|
||||||
|
download_file(u'en.ts', filename)
|
||||||
|
print u'\n** Please Note **\n'
|
||||||
|
print u'In order to get this file into OpenLP and onto the Pootle ' + \
|
||||||
|
u'translation server you will need to subscribe to the OpenLP' + \
|
||||||
|
u'Translators mailing list, and request that your language file ' + \
|
||||||
|
u'be added to the project.\n'
|
||||||
|
print u' Done'
|
||||||
|
|
||||||
|
def process_stack(command_stack):
|
||||||
|
"""
|
||||||
|
This method looks at the commands in the command stack, and processes them
|
||||||
|
in the order they are in the stack.
|
||||||
|
|
||||||
|
``command_stack``
|
||||||
|
The command stack to process.
|
||||||
|
"""
|
||||||
|
if command_stack:
|
||||||
|
print u'Processing %d commands...' % len(command_stack)
|
||||||
|
for command in command_stack:
|
||||||
|
print u'%d.' % (command_stack.current_index),
|
||||||
|
if command == Command.Download:
|
||||||
|
download_translations()
|
||||||
|
elif command == Command.Prepare:
|
||||||
|
prepare_project()
|
||||||
|
elif command == Command.Update:
|
||||||
|
update_translations()
|
||||||
|
elif command == Command.Generate:
|
||||||
|
generate_binaries()
|
||||||
|
elif command == Command.Create:
|
||||||
|
command, arguments = command_stack[command_stack.current_index]
|
||||||
|
create_translation(*arguments)
|
||||||
|
print u'Finished processing commands.'
|
||||||
|
else:
|
||||||
|
print u'No commands to process.'
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# start Main Class
|
global verbose_mode
|
||||||
Util = TranslationUtils()
|
|
||||||
|
|
||||||
# Set up command line options.
|
# Set up command line options.
|
||||||
usage = u'''
|
usage = u'%prog [options]\nOptions are parsed in the order they are ' + \
|
||||||
This script handle the translation files for OpenLP.
|
u'listed below. If no options are given, "-dpug" will be used.\n\n' + \
|
||||||
Usage: %prog [options]
|
u'This script is used to manage OpenLP\'s translation files.'
|
||||||
If no option will be used, options "-d -p -u -g" will be set automatically
|
|
||||||
'''
|
|
||||||
parser = OptionParser(usage=usage)
|
parser = OptionParser(usage=usage)
|
||||||
parser.add_option('-d', '--download-ts', action='store_true',
|
parser.add_option('-d', '--download-ts', dest='download',
|
||||||
dest='download', help='Load languages from Pootle Server')
|
action='store_true', help='download language files from Pootle')
|
||||||
parser.add_option('-c', '--create', metavar='lang',
|
parser.add_option('-c', '--create', dest=u'create', metavar='LANG',
|
||||||
help='creation of new translation file, Parameter: language (e.g. "en_GB"')
|
help='create a new translation file for language LANG, e.g. "en_GB"')
|
||||||
parser.add_option('-p', '--prepare', action='store_true', dest='prepare',
|
parser.add_option('-p', '--prepare', dest='prepare', action='store_true',
|
||||||
help='preparation (generate pro file)')
|
help='generate a project file, used to update the translations')
|
||||||
parser.add_option('-u', '--update', action='store_true', dest='update',
|
parser.add_option('-u', '--update', action='store_true', dest='update',
|
||||||
help='update translation files')
|
help='update translation files (needs a project file)')
|
||||||
parser.add_option('-g', '--generate', action='store_true', dest='generate',
|
parser.add_option('-g', '--generate', dest='generate', action='store_true',
|
||||||
help='generate qm files')
|
help='compile .ts files into .qm files')
|
||||||
parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
|
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
|
||||||
help='Give more informations while processing')
|
help='show extra information while processing translations')
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
# Create and populate the command stack
|
||||||
|
command_stack = CommandStack()
|
||||||
if options.download:
|
if options.download:
|
||||||
Util.cmd_stack.append([Util.downloadTranslations])
|
command_stack.append(Command.Download)
|
||||||
if options.create:
|
if options.create:
|
||||||
Util.cmd_stack.append([Util.creation, u'%s' % options.create])
|
command_stack.append(Command.Create, arguments=[options.create])
|
||||||
if options.prepare:
|
if options.prepare:
|
||||||
Util.cmd_stack.append([Util.preparation])
|
command_stack.append(Command.Prepare)
|
||||||
if options.update:
|
if options.update:
|
||||||
Util.cmd_stack.append([Util.update])
|
command_stack.append(Command.Update)
|
||||||
if options.generate:
|
if options.generate:
|
||||||
Util.cmd_stack.append([Util.generate])
|
command_stack.append(Command.Generate)
|
||||||
if options.verbose:
|
verbose_mode = options.verbose
|
||||||
Util.verbose = True
|
if not command_stack:
|
||||||
|
command_stack.append(Command.Download)
|
||||||
if len(Util.cmd_stack) == 0:
|
command_stack.append(Command.Prepare)
|
||||||
Util.cmd_stack.append([Util.downloadTranslations])
|
command_stack.append(Command.Update)
|
||||||
Util.cmd_stack.append([Util.preparation])
|
command_stack.append(Command.Generate)
|
||||||
Util.cmd_stack.append([Util.update])
|
# Process the commands
|
||||||
Util.cmd_stack.append([Util.generate])
|
process_stack(command_stack)
|
||||||
|
|
||||||
Util.stack_count = len(Util.cmd_stack)
|
|
||||||
Util.process_stack()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == u'__main__':
|
if __name__ == u'__main__':
|
||||||
if os.path.split(os.path.abspath(u'.'))[1] != u'scripts':
|
if os.path.split(os.path.abspath(u'.'))[1] != u'scripts':
|
||||||
print u'You need to run this script from the scripts directory.'
|
print u'You need to run this script from the scripts directory.'
|
||||||
else:
|
else:
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,18 @@ Windows Build Script
|
||||||
This script is used to build the Windows binary and the accompanying installer.
|
This script is used to build the Windows binary and the accompanying installer.
|
||||||
For this script to work out of the box, it depends on a number of things:
|
For this script to work out of the box, it depends on a number of things:
|
||||||
|
|
||||||
|
Python 2.6
|
||||||
|
This build script only works with Python 2.6.
|
||||||
|
|
||||||
|
PyQt4
|
||||||
|
You should already have this installed, OpenLP doesn't work without it. The
|
||||||
|
version the script expects is the packaged one available from River Bank
|
||||||
|
Computing.
|
||||||
|
|
||||||
|
PyEnchant
|
||||||
|
This script expects the precompiled, installable version of PyEnchant to be
|
||||||
|
installed. You can find this on the PyEnchant site.
|
||||||
|
|
||||||
Inno Setup 5
|
Inno Setup 5
|
||||||
Inno Setup should be installed into "C:\%PROGRAMFILES%\Inno Setup 5"
|
Inno Setup should be installed into "C:\%PROGRAMFILES%\Inno Setup 5"
|
||||||
|
|
||||||
|
@ -41,9 +53,10 @@ UPX
|
||||||
add that directory to your PATH environment variable.
|
add that directory to your PATH environment variable.
|
||||||
|
|
||||||
PyInstaller
|
PyInstaller
|
||||||
PyInstaller should be a checkout of trunk, and in a directory called,
|
PyInstaller should be a checkout of revision 844 of trunk, and in a
|
||||||
"pyinstaller" on the same level as OpenLP's Bazaar shared repository
|
directory called, "pyinstaller" on the same level as OpenLP's Bazaar shared
|
||||||
directory.
|
repository directory. The revision is very important as there is currently
|
||||||
|
a major regression in HEAD.
|
||||||
|
|
||||||
To install PyInstaller, first checkout trunk from Subversion. The easiest
|
To install PyInstaller, first checkout trunk from Subversion. The easiest
|
||||||
way is to install TortoiseSVN and then checkout the following URL to a
|
way is to install TortoiseSVN and then checkout the following URL to a
|
||||||
|
@ -80,17 +93,32 @@ from subprocess import Popen, PIPE
|
||||||
script_path = os.path.split(os.path.abspath(__file__))[0]
|
script_path = os.path.split(os.path.abspath(__file__))[0]
|
||||||
branch_path = os.path.abspath(os.path.join(script_path, u'..'))
|
branch_path = os.path.abspath(os.path.join(script_path, u'..'))
|
||||||
source_path = os.path.join(branch_path, u'openlp')
|
source_path = os.path.join(branch_path, u'openlp')
|
||||||
|
i18n_path = os.path.join(branch_path, u'resources', u'i18n')
|
||||||
|
build_path = os.path.join(branch_path, u'build', u'pyi.win32', u'OpenLP')
|
||||||
dist_path = os.path.join(branch_path, u'dist', u'OpenLP')
|
dist_path = os.path.join(branch_path, u'dist', u'OpenLP')
|
||||||
pyinstaller_path = os.path.abspath(os.path.join(branch_path, u'..', u'..', u'pyinstaller'))
|
pyinstaller_path = os.path.abspath(os.path.join(branch_path, u'..', u'..', u'pyinstaller'))
|
||||||
innosetup_path = os.path.join(os.getenv(u'PROGRAMFILES'), 'Inno Setup 5')
|
innosetup_path = os.path.join(os.getenv(u'PROGRAMFILES'), 'Inno Setup 5')
|
||||||
iss_path = os.path.join(branch_path, u'resources', u'innosetup')
|
iss_path = os.path.join(branch_path, u'resources', u'innosetup')
|
||||||
|
lrelease_path = u'C:\\Python26\\Lib\\site-packages\\PyQt4\\bin\\lrelease.exe'
|
||||||
|
enchant_path = u'C:\\Python26\\Lib\\site-packages\\enchant'
|
||||||
|
|
||||||
|
def clean_build_directories():
|
||||||
|
#if not os.path.exists(build_path)
|
||||||
|
for root, dirs, files in os.walk(build_path, topdown=False):
|
||||||
|
print root
|
||||||
|
for file in files:
|
||||||
|
os.remove(os.path.join(root, file))
|
||||||
|
#os.removedirs(build_path)
|
||||||
|
for root, dirs, files in os.walk(dist_path, topdown=False):
|
||||||
|
for file in files:
|
||||||
|
os.remove(os.path.join(root, file))
|
||||||
|
#os.removedirs(dist_path)
|
||||||
|
|
||||||
def run_pyinstaller():
|
def run_pyinstaller():
|
||||||
print u'Running PyInstaller...'
|
print u'Running PyInstaller...'
|
||||||
os.chdir(branch_path)
|
os.chdir(branch_path)
|
||||||
pyinstaller = Popen((u'python', os.path.join(pyinstaller_path, u'Build.py'),
|
pyinstaller = Popen((u'python', os.path.join(pyinstaller_path, u'Build.py'),
|
||||||
u'OpenLP.spec'))
|
u'-y', u'OpenLP.spec'))
|
||||||
code = pyinstaller.wait()
|
code = pyinstaller.wait()
|
||||||
if code != 0:
|
if code != 0:
|
||||||
raise Exception(u'Error running PyInstaller Build.py')
|
raise Exception(u'Error running PyInstaller Build.py')
|
||||||
|
@ -120,6 +148,19 @@ def write_version_file():
|
||||||
f.write(versionstring)
|
f.write(versionstring)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
def copy_enchant():
|
||||||
|
print u'Copying enchant/pyenchant...'
|
||||||
|
source = enchant_path
|
||||||
|
dest = os.path.join(dist_path, u'enchant')
|
||||||
|
for root, dirs, files in os.walk(source):
|
||||||
|
for filename in files:
|
||||||
|
if not filename.endswith(u'.pyc') and not filename.endswith(u'.pyo'):
|
||||||
|
dest_path = os.path.join(dest, root[len(source)+1:])
|
||||||
|
if not os.path.exists(dest_path):
|
||||||
|
os.makedirs(dest_path)
|
||||||
|
copy(os.path.join(root, filename),
|
||||||
|
os.path.join(dest_path, filename))
|
||||||
|
|
||||||
def copy_plugins():
|
def copy_plugins():
|
||||||
print u'Copying plugins...'
|
print u'Copying plugins...'
|
||||||
source = os.path.join(source_path, u'plugins')
|
source = os.path.join(source_path, u'plugins')
|
||||||
|
@ -138,6 +179,21 @@ def copy_windows_files():
|
||||||
copy(os.path.join(iss_path, u'OpenLP.ico'), os.path.join(dist_path, u'OpenLP.ico'))
|
copy(os.path.join(iss_path, u'OpenLP.ico'), os.path.join(dist_path, u'OpenLP.ico'))
|
||||||
copy(os.path.join(iss_path, u'LICENSE.txt'), os.path.join(dist_path, u'LICENSE.txt'))
|
copy(os.path.join(iss_path, u'LICENSE.txt'), os.path.join(dist_path, u'LICENSE.txt'))
|
||||||
|
|
||||||
|
def compile_translations():
|
||||||
|
files = os.listdir(i18n_path)
|
||||||
|
if not os.path.exists(os.path.join(dist_path, u'i18n')):
|
||||||
|
os.makedirs(os.path.join(dist_path, u'i18n'))
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(u'.ts'):
|
||||||
|
source_path = os.path.join(i18n_path, file)
|
||||||
|
dest_path = os.path.join(dist_path, u'i18n',
|
||||||
|
file.replace(u'.ts', u'.qm'))
|
||||||
|
lconvert = Popen(u'"%s" "%s" -qm "%s"' % (lrelease_path, \
|
||||||
|
source_path, dest_path))
|
||||||
|
code = lconvert.wait()
|
||||||
|
if code != 0:
|
||||||
|
print 'Error running lconvert on %s' % source_path
|
||||||
|
|
||||||
def run_innosetup():
|
def run_innosetup():
|
||||||
print u'Running Inno Setup...'
|
print u'Running Inno Setup...'
|
||||||
os.chdir(iss_path)
|
os.chdir(iss_path)
|
||||||
|
@ -150,6 +206,8 @@ def run_innosetup():
|
||||||
raise Exception(u'Error running Inno Setup')
|
raise Exception(u'Error running Inno Setup')
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) > 1 and (sys.argv[1] == u'-v' or sys.argv[1] == u'--verbose'):
|
||||||
print "Script path:", script_path
|
print "Script path:", script_path
|
||||||
print "Branch path:", branch_path
|
print "Branch path:", branch_path
|
||||||
print "Source path:", source_path
|
print "Source path:", source_path
|
||||||
|
@ -157,10 +215,13 @@ def main():
|
||||||
print "PyInstaller path:", pyinstaller_path
|
print "PyInstaller path:", pyinstaller_path
|
||||||
print "Inno Setup path:", innosetup_path
|
print "Inno Setup path:", innosetup_path
|
||||||
print "ISS file path:", iss_path
|
print "ISS file path:", iss_path
|
||||||
|
#clean_build_directories()
|
||||||
run_pyinstaller()
|
run_pyinstaller()
|
||||||
write_version_file()
|
write_version_file()
|
||||||
|
copy_enchant()
|
||||||
copy_plugins()
|
copy_plugins()
|
||||||
copy_windows_files()
|
copy_windows_files()
|
||||||
|
compile_translations()
|
||||||
run_innosetup()
|
run_innosetup()
|
||||||
print "Done."
|
print "Done."
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue