forked from openlp/openlp
Head1025
This commit is contained in:
commit
515a12f7ea
20
openlp.pyw
20
openlp.pyw
|
@ -29,12 +29,15 @@ 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 import MainWindow, SplashScreen, ScreenList
|
from openlp.core.ui.mainwindow import MainWindow
|
||||||
|
from openlp.core.ui.exceptionform import ExceptionForm
|
||||||
|
from openlp.core.ui import SplashScreen, ScreenList
|
||||||
from openlp.core.utils import AppLocation, LanguageManager, VersionThread
|
from openlp.core.utils import AppLocation, LanguageManager, VersionThread
|
||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
@ -128,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()
|
||||||
|
@ -143,6 +146,13 @@ 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'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
|
||||||
|
@ -193,7 +203,7 @@ 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)
|
||||||
|
sys.excepthook = app.hookException
|
||||||
sys.exit(app.run())
|
sys.exit(app.run())
|
||||||
|
|
||||||
if __name__ == u'__main__':
|
if __name__ == u'__main__':
|
||||||
|
|
|
@ -35,6 +35,67 @@ from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# TODO make external and configurable in alpha 4 via a settings dialog
|
||||||
|
html_expands = []
|
||||||
|
|
||||||
|
html_expands.append({u'desc':u'Red', u'start tag':u'{r}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:red">', \
|
||||||
|
u'end tag':u'{/r}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Black', u'start tag':u'{b}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:black">', \
|
||||||
|
u'end tag':u'{/b}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:blue">', \
|
||||||
|
u'end tag':u'{/bl}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:yellow">', \
|
||||||
|
u'end tag':u'{/y}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Green', u'start tag':u'{g}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:green">', \
|
||||||
|
u'end tag':u'{/g}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:#CC33CC">', \
|
||||||
|
u'end tag':u'{/pk}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Orange', u'start tag':u'{o}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:#CC0033">', \
|
||||||
|
u'end tag':u'{/o}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:#9900FF">', \
|
||||||
|
u'end tag':u'{/pp}', u'end html':u'</span>', \
|
||||||
|
u'protected':False})
|
||||||
|
html_expands.append({u'desc':u'White', u'start tag':u'{w}', \
|
||||||
|
u'start html':u'<span style="-webkit-text-fill-color:white">', \
|
||||||
|
u'end tag':u'{/w}', u'end html':u'</span>', \
|
||||||
|
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})
|
||||||
|
html_expands.append({u'desc':u'Subscript', u'start tag':u'{sb}', \
|
||||||
|
u'start html':u'<sub>', \
|
||||||
|
u'end tag':u'{/sb}', u'end html':u'</sub>', \
|
||||||
|
u'protected':True})
|
||||||
|
html_expands.append({u'desc':u'Paragraph', u'start tag':u'{p}', \
|
||||||
|
u'start html':u'<p>', \
|
||||||
|
u'end tag':u'{/p}', u'end html':u'</p>', \
|
||||||
|
u'protected':True})
|
||||||
|
html_expands.append({u'desc':u'Bold', u'start tag':u'{st}', \
|
||||||
|
u'start html':u'<strong>', \
|
||||||
|
u'end tag':u'{/st}', \
|
||||||
|
u'end html':u'</strong>', \
|
||||||
|
u'protected':True})
|
||||||
|
html_expands.append({u'desc':u'Italics', u'start tag':u'{it}', \
|
||||||
|
u'start html':u'<em>', \
|
||||||
|
u'end tag':u'{/it}', u'end html':u'</em>', \
|
||||||
|
u'protected':True})
|
||||||
|
|
||||||
def translate(context, text, comment=None):
|
def translate(context, text, comment=None):
|
||||||
"""
|
"""
|
||||||
A special shortcut method to wrap around the Qt4 translation functions.
|
A special shortcut method to wrap around the Qt4 translation functions.
|
||||||
|
@ -166,15 +227,46 @@ def context_menu_separator(base):
|
||||||
action.setSeparator(True)
|
action.setSeparator(True)
|
||||||
return action
|
return action
|
||||||
|
|
||||||
def resize_image(image, width, height):
|
def image_to_byte(image):
|
||||||
|
"""
|
||||||
|
Resize an image to fit on the current screen for the web and returns
|
||||||
|
it as a byte stream.
|
||||||
|
|
||||||
|
``image``
|
||||||
|
The image to converted.
|
||||||
|
"""
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
# use buffer to store pixmap into byteArray
|
||||||
|
buffie = QtCore.QBuffer(byte_array)
|
||||||
|
buffie.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
if isinstance(image, QtGui.QImage):
|
||||||
|
pixmap = QtGui.QPixmap.fromImage(image)
|
||||||
|
else:
|
||||||
|
pixmap = QtGui.QPixmap(image)
|
||||||
|
pixmap.save(buffie, "PNG")
|
||||||
|
# convert to base64 encoding so does not get missed!
|
||||||
|
return byte_array.toBase64()
|
||||||
|
|
||||||
|
def resize_image(image, width, height, background=QtCore.Qt.black):
|
||||||
"""
|
"""
|
||||||
Resize an image to fit on the current screen.
|
Resize an image to fit on the current screen.
|
||||||
|
|
||||||
``image``
|
``image``
|
||||||
The image to resize.
|
The image to resize.
|
||||||
|
|
||||||
|
``width``
|
||||||
|
The new image width.
|
||||||
|
|
||||||
|
``height``
|
||||||
|
The new image height.
|
||||||
|
|
||||||
|
``background``
|
||||||
|
The background colour defaults to black.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
preview = QtGui.QImage(image)
|
preview = QtGui.QImage(image)
|
||||||
if not preview.isNull():
|
if not preview.isNull():
|
||||||
|
# Only resize if different size
|
||||||
if preview.width() == width and preview.height == height:
|
if preview.width() == width and preview.height == height:
|
||||||
return preview
|
return preview
|
||||||
preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio,
|
preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio,
|
||||||
|
@ -184,7 +276,7 @@ def resize_image(image, width, height):
|
||||||
# and move it to the centre of the preview space
|
# and move it to the centre of the preview space
|
||||||
new_image = QtGui.QImage(width, height,
|
new_image = QtGui.QImage(width, height,
|
||||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
new_image.fill(QtCore.Qt.black)
|
new_image.fill(background)
|
||||||
painter = QtGui.QPainter(new_image)
|
painter = QtGui.QPainter(new_image)
|
||||||
painter.drawImage((width - realw) / 2, (height - realh) / 2, preview)
|
painter.drawImage((width - realw) / 2, (height - realh) / 2, preview)
|
||||||
return new_image
|
return new_image
|
||||||
|
@ -205,6 +297,26 @@ def check_item_selected(list_widget, message):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def clean_tags(text):
|
||||||
|
"""
|
||||||
|
Remove Tags from text for display
|
||||||
|
"""
|
||||||
|
text = text.replace(u'<br>', u'\n')
|
||||||
|
for tag in html_expands:
|
||||||
|
text = text.replace(tag[u'start tag'], u'')
|
||||||
|
text = text.replace(tag[u'end tag'], u'')
|
||||||
|
return text
|
||||||
|
|
||||||
|
def expand_tags(text):
|
||||||
|
"""
|
||||||
|
Expand tags HTML for display
|
||||||
|
"""
|
||||||
|
for tag in html_expands:
|
||||||
|
text = text.replace(tag[u'start tag'], tag[u'start html'])
|
||||||
|
text = text.replace(tag[u'end tag'], tag[u'end html'])
|
||||||
|
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
|
||||||
|
@ -213,6 +325,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, 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
|
||||||
|
|
|
@ -0,0 +1,558 @@
|
||||||
|
# -*- 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 logging
|
||||||
|
from PyQt4 import QtWebKit
|
||||||
|
|
||||||
|
from openlp.core.lib import image_to_byte
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
HTMLSRC = u"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OpenLP Display</title>
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
%s;
|
||||||
|
}
|
||||||
|
.size {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
width: %spx;
|
||||||
|
height: %spx;
|
||||||
|
}
|
||||||
|
#black {
|
||||||
|
z-index:8;
|
||||||
|
background-color: black;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#image {
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
|
#video {
|
||||||
|
z-index:2;
|
||||||
|
}
|
||||||
|
#alert {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
z-index:10;
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
#footer {
|
||||||
|
position: absolute;
|
||||||
|
z-index:5;
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
/* lyric css */
|
||||||
|
%s
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script language="javascript">
|
||||||
|
var timer = null;
|
||||||
|
var transition = %s;
|
||||||
|
|
||||||
|
function show_video(state, path, volume, loop){
|
||||||
|
var vid = document.getElementById('video');
|
||||||
|
if(path != null)
|
||||||
|
vid.src = path;
|
||||||
|
if(loop != null){
|
||||||
|
if(loop)
|
||||||
|
vid.loop = 'loop';
|
||||||
|
else
|
||||||
|
vid.loop = '';
|
||||||
|
}
|
||||||
|
if(volume != null){
|
||||||
|
vid.volume = volume;
|
||||||
|
}
|
||||||
|
switch(state){
|
||||||
|
case 'play':
|
||||||
|
vid.play();
|
||||||
|
vid.style.display = 'block';
|
||||||
|
break;
|
||||||
|
case 'pause':
|
||||||
|
vid.pause();
|
||||||
|
vid.style.display = 'block';
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
vid.pause();
|
||||||
|
vid.style.display = 'none';
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
vid.pause();
|
||||||
|
vid.style.display = 'none';
|
||||||
|
vid.src = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_image(src){
|
||||||
|
var img = document.getElementById('image');
|
||||||
|
img.src = src;
|
||||||
|
if(src == '')
|
||||||
|
img.style.display = 'none';
|
||||||
|
else
|
||||||
|
img.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_blank(state){
|
||||||
|
var black = 'none';
|
||||||
|
var lyrics = '';
|
||||||
|
var pause = false;
|
||||||
|
switch(state){
|
||||||
|
case 'theme':
|
||||||
|
lyrics = 'hidden';
|
||||||
|
pause = true;
|
||||||
|
break;
|
||||||
|
case 'black':
|
||||||
|
black = 'block';
|
||||||
|
pause = true;
|
||||||
|
break;
|
||||||
|
case 'desktop':
|
||||||
|
pause = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
document.getElementById('black').style.display = black;
|
||||||
|
document.getElementById('lyricsmain').style.visibility = lyrics;
|
||||||
|
document.getElementById('image').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;
|
||||||
|
var vid = document.getElementById('video');
|
||||||
|
if(vid.src != ''){
|
||||||
|
if(pause)
|
||||||
|
vid.pause();
|
||||||
|
else
|
||||||
|
vid.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_alert(alerttext, position){
|
||||||
|
var text = document.getElementById('alert');
|
||||||
|
text.innerHTML = alerttext;
|
||||||
|
if(alerttext == '') {
|
||||||
|
text.style.visibility = 'hidden';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(position == ''){
|
||||||
|
position = getComputedStyle(text, '').verticalAlign;
|
||||||
|
}
|
||||||
|
switch(position)
|
||||||
|
{
|
||||||
|
case 'top':
|
||||||
|
text.style.top = '0px';
|
||||||
|
break;
|
||||||
|
case 'middle':
|
||||||
|
text.style.top = ((window.innerHeight - text.clientHeight) / 2)
|
||||||
|
+ 'px';
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
text.style.top = (window.innerHeight - text.clientHeight)
|
||||||
|
+ 'px';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
text.style.visibility = 'visible';
|
||||||
|
return text.clientHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_footer(footertext){
|
||||||
|
document.getElementById('footer').innerHTML = footertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_text(newtext){
|
||||||
|
if(timer != null)
|
||||||
|
clearTimeout(timer);
|
||||||
|
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(id, newtext){
|
||||||
|
/*
|
||||||
|
Using -webkit-transition: opacity 1s linear; would have been preferred
|
||||||
|
but it isn't currently quick enough when animating multiple layers of
|
||||||
|
large areas of large text. Therefore do it manually as best we can.
|
||||||
|
Hopefully in the future we can revisit and do more interesting
|
||||||
|
transitions using -webkit-transition and -webkit-transform.
|
||||||
|
However we need to ensure interrupted transitions (quickly change 2
|
||||||
|
slides) still looks pretty and is zippy.
|
||||||
|
*/
|
||||||
|
var text = document.getElementById(id);
|
||||||
|
if(text==null) return;
|
||||||
|
if(!transition){
|
||||||
|
text.innerHTML = newtext;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(newtext==text.innerHTML){
|
||||||
|
text.style.opacity = parseFloat(text.style.opacity) + 0.3;
|
||||||
|
if(text.style.opacity>0.7)
|
||||||
|
text.style.opacity = 1;
|
||||||
|
} else {
|
||||||
|
text.style.opacity = parseFloat(text.style.opacity) - 0.3;
|
||||||
|
if(text.style.opacity<=0.1){
|
||||||
|
text.innerHTML = newtext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function text_opacity(){
|
||||||
|
var text = document.getElementById('lyricsmain');
|
||||||
|
return getComputedStyle(text, '').opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_text_complete(){
|
||||||
|
return (text_opacity()==1);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img id="image" class="size" src="%s" />
|
||||||
|
<video id="video" class="size"></video>
|
||||||
|
%s
|
||||||
|
<div id="footer" class="footer"></div>
|
||||||
|
<div id="black" class="size"></div>
|
||||||
|
<div id="alert" style="visibility:hidden;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def build_html(item, screen, alert, islive):
|
||||||
|
"""
|
||||||
|
Build the full web paged structure for display
|
||||||
|
|
||||||
|
`item`
|
||||||
|
Service Item to be displayed
|
||||||
|
`screen`
|
||||||
|
Current display information
|
||||||
|
`alert`
|
||||||
|
Alert display display information
|
||||||
|
`islive`
|
||||||
|
Item is going live, rather than preview/theme building
|
||||||
|
"""
|
||||||
|
width = screen[u'size'].width()
|
||||||
|
height = screen[u'size'].height()
|
||||||
|
theme = item.themedata
|
||||||
|
webkitvers = webkit_version()
|
||||||
|
if item.bg_frame:
|
||||||
|
image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame)
|
||||||
|
else:
|
||||||
|
image = u''
|
||||||
|
html = HTMLSRC % (build_background_css(item, width, height),
|
||||||
|
width, height,
|
||||||
|
build_alert_css(alert, width),
|
||||||
|
build_footer_css(item, height),
|
||||||
|
build_lyrics_css(item, webkitvers),
|
||||||
|
u'true' if theme and theme.display_slideTransition and islive \
|
||||||
|
else u'false',
|
||||||
|
image,
|
||||||
|
build_lyrics_html(item, webkitvers))
|
||||||
|
return html
|
||||||
|
|
||||||
|
def webkit_version():
|
||||||
|
"""
|
||||||
|
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`
|
||||||
|
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 = """
|
||||||
|
.lyricstable {
|
||||||
|
z-index:4;
|
||||||
|
position: absolute;
|
||||||
|
display: table;
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricscell {
|
||||||
|
display:table-cell;
|
||||||
|
word-wrap: break-word;
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsmain {
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsoutline {
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
.lyricsshadow {
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
theme = item.themedata
|
||||||
|
lyricstable = u''
|
||||||
|
lyrics = u''
|
||||||
|
lyricsmain = u''
|
||||||
|
outline = u''
|
||||||
|
shadow = u''
|
||||||
|
if theme and item.main:
|
||||||
|
lyricstable = u'left: %spx; top: %spx;' % \
|
||||||
|
(item.main.x(), item.main.y())
|
||||||
|
lyrics = build_lyrics_format_css(theme, item.main.width(),
|
||||||
|
item.main.height())
|
||||||
|
# For performance reasons we want to show as few DIV's as possible,
|
||||||
|
# especially when animating/transitions.
|
||||||
|
# However some bugs in older versions of qtwebkit mean we need to
|
||||||
|
# perform workarounds and add extra divs. Only do these when needed.
|
||||||
|
#
|
||||||
|
# Before 533.3 the webkit-text-fill colour wasn't displayed, only the
|
||||||
|
# stroke (outline) color. So put stroke layer underneath the main text.
|
||||||
|
#
|
||||||
|
# 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:
|
||||||
|
outline = build_lyrics_outline_css(theme)
|
||||||
|
if theme.display_shadow:
|
||||||
|
if theme.display_outline and webkitvers < 534.3:
|
||||||
|
shadow = u'padding-left: %spx; padding-top: %spx ' % \
|
||||||
|
(theme.display_shadow_size, theme.display_shadow_size)
|
||||||
|
shadow += build_lyrics_outline_css(theme, True)
|
||||||
|
else:
|
||||||
|
lyricsmain += u' text-shadow: %s %spx %spx;' % \
|
||||||
|
(theme.display_shadow_color, theme.display_shadow_size,
|
||||||
|
theme.display_shadow_size)
|
||||||
|
lyrics_css = style % (lyricstable, lyrics, lyricsmain, outline, shadow)
|
||||||
|
return lyrics_css
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
`item`
|
||||||
|
Service Item to be processed.
|
||||||
|
"""
|
||||||
|
style = """
|
||||||
|
left: %spx;
|
||||||
|
bottom: %spx;
|
||||||
|
width: %spx;
|
||||||
|
font-family: %s;
|
||||||
|
font-size: %spt;
|
||||||
|
color: %s;
|
||||||
|
text-align: left;
|
||||||
|
white-space:nowrap;
|
||||||
|
"""
|
||||||
|
theme = item.themedata
|
||||||
|
if not theme or not item.footer:
|
||||||
|
return u''
|
||||||
|
bottom = height - int(item.footer.y()) - int(item.footer.height())
|
||||||
|
lyrics_html = style % (item.footer.x(), bottom,
|
||||||
|
item.footer.width(), theme.font_footer_name,
|
||||||
|
theme.font_footer_proportion, theme.font_footer_color)
|
||||||
|
return lyrics_html
|
||||||
|
|
||||||
|
def build_alert_css(alertTab, width):
|
||||||
|
"""
|
||||||
|
Build the display of the footer
|
||||||
|
|
||||||
|
`alertTab`
|
||||||
|
Details from the Alert tab for fonts etc
|
||||||
|
"""
|
||||||
|
style = """
|
||||||
|
width: %spx;
|
||||||
|
vertical-align: %s;
|
||||||
|
font-family: %s;
|
||||||
|
font-size: %spt;
|
||||||
|
color: %s;
|
||||||
|
background-color: %s;
|
||||||
|
"""
|
||||||
|
if not alertTab:
|
||||||
|
return u''
|
||||||
|
align = u''
|
||||||
|
if alertTab.location == 2:
|
||||||
|
align = u'bottom'
|
||||||
|
elif alertTab.location == 1:
|
||||||
|
align = u'middle'
|
||||||
|
else:
|
||||||
|
align = u'top'
|
||||||
|
alert = style % (width, align, alertTab.font_face, alertTab.font_size,
|
||||||
|
alertTab.font_color, alertTab.bg_color)
|
||||||
|
return alert
|
|
@ -131,7 +131,6 @@ class Plugin(QtCore.QObject):
|
||||||
self.serviceManager = plugin_helpers[u'service']
|
self.serviceManager = plugin_helpers[u'service']
|
||||||
self.settingsForm = plugin_helpers[u'settings form']
|
self.settingsForm = plugin_helpers[u'settings form']
|
||||||
self.mediadock = plugin_helpers[u'toolbox']
|
self.mediadock = plugin_helpers[u'toolbox']
|
||||||
self.displayManager = plugin_helpers[u'displaymanager']
|
|
||||||
self.pluginManager = plugin_helpers[u'pluginmanager']
|
self.pluginManager = plugin_helpers[u'pluginmanager']
|
||||||
self.formparent = plugin_helpers[u'formparent']
|
self.formparent = plugin_helpers[u'formparent']
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
|
|
|
@ -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
|
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.
|
||||||
|
@ -80,145 +68,13 @@ class Renderer(object):
|
||||||
self.bg_image = None
|
self.bg_image = None
|
||||||
self._bg_image_filename = None
|
self._bg_image_filename = None
|
||||||
self.theme_name = theme.theme_name
|
self.theme_name = theme.theme_name
|
||||||
self._set_theme_font()
|
|
||||||
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)
|
||||||
|
if self.frame:
|
||||||
def set_bg_image(self, filename):
|
self.bg_image = resize_image(self._bg_image_filename,
|
||||||
"""
|
self.frame.width(),
|
||||||
Set a background image.
|
self.frame.height())
|
||||||
|
|
||||||
``filename``
|
|
||||||
The name of the image file.
|
|
||||||
"""
|
|
||||||
log.debug(u'set bg image %s', filename)
|
|
||||||
self._bg_image_filename = unicode(filename)
|
|
||||||
if self.frame:
|
|
||||||
self.bg_image = resize_image(self._bg_image_filename,
|
|
||||||
self.frame.width(),
|
|
||||||
self.frame.height())
|
|
||||||
|
|
||||||
def set_frame_dest(self, frame_width, frame_height, preview=False):
|
|
||||||
"""
|
|
||||||
Set the size of the slide.
|
|
||||||
|
|
||||||
``frame_width``
|
|
||||||
The width of the slide.
|
|
||||||
|
|
||||||
``frame_height``
|
|
||||||
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,
|
|
||||||
frame_height)
|
|
||||||
self.frame = QtGui.QImage(frame_width, frame_height,
|
|
||||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
|
||||||
self.frame_opaque = QtGui.QImage(frame_width, frame_height,
|
|
||||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
|
||||||
if self._bg_image_filename and not self.bg_image:
|
|
||||||
self.bg_image = resize_image(self._bg_image_filename,
|
|
||||||
self.frame.width(), self.frame.height())
|
|
||||||
if self.bg_frame is None:
|
|
||||||
self._generate_background_frame()
|
|
||||||
|
|
||||||
def format_slide(self, words, footer):
|
|
||||||
"""
|
|
||||||
Figure out how much text can appear on a slide, using the current
|
|
||||||
theme settings.
|
|
||||||
|
|
||||||
``words``
|
|
||||||
The words to be fitted on the slide.
|
|
||||||
|
|
||||||
``footer``
|
|
||||||
The footer of the slide.
|
|
||||||
"""
|
|
||||||
log.debug(u'format_slide - Start')
|
|
||||||
words = words.replace(u'\r\n', u'\n')
|
|
||||||
verses_text = words.split(u'\n')
|
|
||||||
text = []
|
|
||||||
for verse in verses_text:
|
|
||||||
lines = verse.split(u'\n')
|
|
||||||
for line in lines:
|
|
||||||
text.append(line)
|
|
||||||
split_text = self.pre_render_text(text)
|
|
||||||
log.debug(u'format_slide - End')
|
|
||||||
return split_text
|
|
||||||
|
|
||||||
def pre_render_text(self, text):
|
|
||||||
metrics = QtGui.QFontMetrics(self.main_font)
|
|
||||||
#work out line width
|
|
||||||
line_width = self._rect.width()
|
|
||||||
#number of lines on a page - adjust for rounding up.
|
|
||||||
line_height = metrics.height()
|
|
||||||
if self._theme.display_shadow:
|
|
||||||
line_height += int(self._theme.display_shadow_size)
|
|
||||||
if self._theme.display_outline:
|
|
||||||
# pixels top/bottom
|
|
||||||
line_height += 2 * int(self._theme.display_outline_size)
|
|
||||||
page_length = int(self._rect.height() / line_height )
|
|
||||||
#Average number of characters in line
|
|
||||||
ave_line_width = line_width / metrics.averageCharWidth()
|
|
||||||
#Maximum size of a character
|
|
||||||
max_char_width = metrics.maxWidth()
|
|
||||||
#Max characters pre line based on min size of a character
|
|
||||||
char_per_line = line_width / metrics.width(u'i')
|
|
||||||
log.debug(u'Page Length area height %s , metrics %s , lines %s' %
|
|
||||||
(int(self._rect.height()), metrics.height(), page_length ))
|
|
||||||
split_pages = []
|
|
||||||
page = []
|
|
||||||
split_lines = []
|
|
||||||
count = 0
|
|
||||||
for line in text:
|
|
||||||
#Must be a blank line so keep it.
|
|
||||||
if len(line) == 0:
|
|
||||||
line = u' '
|
|
||||||
while line:
|
|
||||||
pos = char_per_line
|
|
||||||
split_text = line[:pos]
|
|
||||||
#line needs splitting
|
|
||||||
if metrics.width(split_text, -1) > line_width:
|
|
||||||
#We have no spaces
|
|
||||||
if split_text.find(u' ') == -1:
|
|
||||||
#Move back 1 char at a time till it fits
|
|
||||||
while metrics.width(split_text, -1) > line_width:
|
|
||||||
split_text = split_text[:-1]
|
|
||||||
pos = len(split_text)
|
|
||||||
else:
|
|
||||||
#We have spaces so split at previous one
|
|
||||||
while metrics.width(split_text, -1) > line_width:
|
|
||||||
pos = split_text.rfind(u' ')
|
|
||||||
#no more spaces and we are still too long
|
|
||||||
if pos == -1:
|
|
||||||
while \
|
|
||||||
metrics.width(split_text, -1) > line_width:
|
|
||||||
split_text = split_text[:-1]
|
|
||||||
pos = len(split_text)
|
|
||||||
else:
|
|
||||||
split_text = line[:pos]
|
|
||||||
split_lines.append(split_text)
|
|
||||||
line = line[pos:].lstrip()
|
|
||||||
#if we have more text add up to 10 spaces on the front.
|
|
||||||
if line and self._theme.font_main_indentation > 0:
|
|
||||||
line = u'%s%s' % \
|
|
||||||
(u' '[:int(self._theme.font_main_indentation)],
|
|
||||||
line)
|
|
||||||
#Text fits in a line now
|
|
||||||
for count, line in enumerate(split_lines):
|
|
||||||
page.append(line)
|
|
||||||
#last but one line and only 2 lines to go or end of page
|
|
||||||
if (len(page) == page_length - 1 and
|
|
||||||
len(split_lines) - 3 == count) or \
|
|
||||||
len(page) == page_length:
|
|
||||||
split_pages.append(page)
|
|
||||||
page = []
|
|
||||||
if page and page != u' ':
|
|
||||||
split_pages.append(page)
|
|
||||||
return split_pages
|
|
||||||
|
|
||||||
def set_text_rectangle(self, rect_main, rect_footer):
|
def set_text_rectangle(self, rect_main, rect_footer):
|
||||||
"""
|
"""
|
||||||
|
@ -234,363 +90,82 @@ class Renderer(object):
|
||||||
self._rect = rect_main
|
self._rect = rect_main
|
||||||
self._rect_footer = rect_footer
|
self._rect_footer = rect_footer
|
||||||
|
|
||||||
def generate_frame_from_lines(self, lines, footer_lines=None):
|
def set_frame_dest(self, frame_width, frame_height):
|
||||||
"""
|
"""
|
||||||
Render a set of lines according to the theme, and return the block
|
Set the size of the slide.
|
||||||
dimensions.
|
|
||||||
|
|
||||||
``lines``
|
``frame_width``
|
||||||
The lines to be rendered.
|
The width of the slide.
|
||||||
|
|
||||||
``footer_lines``
|
``frame_height``
|
||||||
Defaults to *None*. The footer to render.
|
The height of the slide.
|
||||||
"""
|
|
||||||
log.debug(u'generate_frame_from_lines - Start')
|
|
||||||
bbox = self._render_lines_unaligned(lines, False)
|
|
||||||
if footer_lines:
|
|
||||||
bbox1 = self._render_lines_unaligned(footer_lines, True)
|
|
||||||
# reset the frame. first time do not worry about what you paint on.
|
|
||||||
self.frame = QtGui.QImage(self.bg_frame)
|
|
||||||
if self._theme.display_slideTransition:
|
|
||||||
self.frame_opaque = QtGui.QImage(self.bg_frame)
|
|
||||||
x, y = self._correct_alignment(self._rect, bbox)
|
|
||||||
bbox = self._render_lines_unaligned(lines, False, (x, y), True)
|
|
||||||
if footer_lines:
|
|
||||||
bbox = self._render_lines_unaligned(footer_lines, True,
|
|
||||||
(self._rect_footer.left(), self._rect_footer.top()), True)
|
|
||||||
log.debug(u'generate_frame_from_lines - Finish')
|
|
||||||
if self._theme.display_slideTransition:
|
|
||||||
return {u'main':self.frame, u'trans':self.frame_opaque}
|
|
||||||
else:
|
|
||||||
return {u'main':self.frame, u'trans':None}
|
|
||||||
|
|
||||||
def _generate_background_frame(self):
|
|
||||||
"""
|
"""
|
||||||
Generate a background frame to the same size as the frame to be used.
|
log.debug(u'set frame dest (frame) w %d h %d', frame_width,
|
||||||
Results are cached for performance reasons.
|
frame_height)
|
||||||
"""
|
self.frame = QtGui.QImage(frame_width, frame_height,
|
||||||
assert(self._theme)
|
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
if self._theme.background_mode == u'transparent':
|
if self._bg_image_filename and not self.bg_image:
|
||||||
self.bg_frame = \
|
self.bg_image = resize_image(self._bg_image_filename,
|
||||||
QtGui.QPixmap(self.frame.width(), self.frame.height())
|
self.frame.width(), self.frame.height())
|
||||||
self.bg_frame.fill(QtCore.Qt.transparent)
|
if self._theme.background_type == u'image':
|
||||||
else:
|
|
||||||
self.bg_frame = QtGui.QImage(self.frame.width(),
|
self.bg_frame = QtGui.QImage(self.frame.width(),
|
||||||
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
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_mode == u'transparent':
|
|
||||||
painter.fillRect(self.frame.rect(), QtCore.Qt.transparent)
|
|
||||||
else:
|
|
||||||
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()
|
|
||||||
log.debug(u'render background End')
|
|
||||||
|
|
||||||
def _correct_alignment(self, rect, bbox):
|
|
||||||
"""
|
|
||||||
Corrects the vertical alignment of text.
|
|
||||||
|
|
||||||
``rect``
|
|
||||||
The block dimentions.
|
|
||||||
|
|
||||||
``bbox``
|
|
||||||
Footer dimensions?
|
|
||||||
"""
|
|
||||||
x = rect.left()
|
|
||||||
if self._theme.display_verticalAlign == 0:
|
|
||||||
# top align
|
|
||||||
y = rect.top()
|
|
||||||
elif self._theme.display_verticalAlign == 2:
|
|
||||||
# bottom align
|
|
||||||
y = rect.bottom() - bbox.height()
|
|
||||||
elif self._theme.display_verticalAlign == 1:
|
|
||||||
# centre align
|
|
||||||
y = rect.top() + (rect.height() - bbox.height()) / 2
|
|
||||||
else:
|
|
||||||
log.error(u'Invalid value for theme.VerticalAlign:%s',
|
|
||||||
self._theme.display_verticalAlign)
|
|
||||||
return x, y
|
|
||||||
|
|
||||||
def _render_lines_unaligned(self, lines, footer, tlcorner=(0, 0),
|
|
||||||
live=False):
|
|
||||||
"""
|
|
||||||
Given a list of lines to render, render each one in turn (using the
|
|
||||||
``_render_single_line`` fn - which may result in going off the bottom).
|
|
||||||
They are expected to be pre-arranged to less than a screenful (eg. by
|
|
||||||
using split_set_of_lines).
|
|
||||||
|
|
||||||
Returns the bounding box of the text as QRect.
|
|
||||||
|
|
||||||
``lines``
|
|
||||||
The lines of text to render.
|
|
||||||
|
|
||||||
``footer``
|
|
||||||
The slide footer.
|
|
||||||
|
|
||||||
``tlcorner``
|
|
||||||
Defaults to *``(0, 0)``*. Co-ordinates of the top left corner.
|
|
||||||
|
|
||||||
``live``
|
|
||||||
Defaults to *False*. Whether or not this is a live screen.
|
|
||||||
"""
|
|
||||||
x, y = tlcorner
|
|
||||||
brx = x
|
|
||||||
bry = y
|
|
||||||
for line in lines:
|
|
||||||
# render after current bottom, but at original left edge
|
|
||||||
# keep track of right edge to see which is biggest
|
|
||||||
(thisx, bry) = self._render_and_wrap_single_line(line, footer,
|
|
||||||
(x, bry), live)
|
|
||||||
if (thisx > brx):
|
|
||||||
brx = thisx
|
|
||||||
retval = QtCore.QRect(x, y, brx - x, bry - y)
|
|
||||||
if self._debug:
|
|
||||||
painter = QtGui.QPainter()
|
painter = QtGui.QPainter()
|
||||||
painter.begin(self.frame)
|
painter.begin(self.bg_frame)
|
||||||
painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 255)))
|
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
||||||
painter.drawRect(retval)
|
if self.bg_image:
|
||||||
|
painter.drawImage(0, 0, self.bg_image)
|
||||||
painter.end()
|
painter.end()
|
||||||
return retval
|
|
||||||
|
|
||||||
def _render_and_wrap_single_line(self, line, footer, tlcorner=(0, 0),
|
|
||||||
live=False):
|
|
||||||
"""
|
|
||||||
Render a single line of words onto the DC, top left corner specified.
|
|
||||||
If the line is too wide for the context, it wraps, but right-aligns
|
|
||||||
the surplus words in the manner of song lyrics.
|
|
||||||
|
|
||||||
Returns the bottom-right corner (of what was rendered) as a tuple(x, y).
|
|
||||||
|
|
||||||
``line``
|
|
||||||
Line of text to be rendered.
|
|
||||||
|
|
||||||
``footer``
|
|
||||||
The footer of the slide.
|
|
||||||
|
|
||||||
``tlcorner``
|
|
||||||
Defaults to *``(0, 0)``*. The top left corner.
|
|
||||||
|
|
||||||
``live``
|
|
||||||
Defaults to *False*. Whether or not this is a live screen.
|
|
||||||
"""
|
|
||||||
x, y = tlcorner
|
|
||||||
maxx = self._rect.width()
|
|
||||||
maxy = self._rect.height()
|
|
||||||
lines = []
|
|
||||||
lines.append(line)
|
|
||||||
startx = x
|
|
||||||
starty = y
|
|
||||||
rightextent = None
|
|
||||||
self.painter = QtGui.QPainter()
|
|
||||||
self.painter.begin(self.frame)
|
|
||||||
self.painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
||||||
if self._theme.display_slideTransition:
|
|
||||||
self.painter2 = QtGui.QPainter()
|
|
||||||
self.painter2.begin(self.frame_opaque)
|
|
||||||
self.painter2.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
||||||
self.painter2.setOpacity(0.7)
|
|
||||||
# dont allow alignment messing with footers
|
|
||||||
if footer:
|
|
||||||
align = 0
|
|
||||||
display_shadow_size = self._display_shadow_size_footer
|
|
||||||
display_outline_size = self._display_outline_size_footer
|
|
||||||
else:
|
else:
|
||||||
align = self._theme.display_horizontalAlign
|
self.bg_frame = None
|
||||||
display_shadow_size = int(self._theme.display_shadow_size)
|
|
||||||
display_outline_size = int(self._theme.display_outline_size)
|
|
||||||
for linenum in range(len(lines)):
|
|
||||||
line = lines[linenum]
|
|
||||||
#find out how wide line is
|
|
||||||
w, h = self._get_extent_and_render(line, footer, tlcorner=(x, y),
|
|
||||||
draw=False)
|
|
||||||
if self._theme.display_shadow:
|
|
||||||
w += display_shadow_size
|
|
||||||
h += display_shadow_size
|
|
||||||
if self._theme.display_outline:
|
|
||||||
# pixels either side
|
|
||||||
w += 2 * display_outline_size
|
|
||||||
# pixels top/bottom
|
|
||||||
h += 2 * display_outline_size
|
|
||||||
if align == 0: # left align
|
|
||||||
rightextent = x + w
|
|
||||||
# shift right from last line's rh edge
|
|
||||||
if self._theme.display_wrapStyle == 1 and linenum != 0:
|
|
||||||
rightextent = self._first_line_right_extent
|
|
||||||
if rightextent > maxx:
|
|
||||||
rightextent = maxx
|
|
||||||
x = rightextent - w
|
|
||||||
# right align
|
|
||||||
elif align == 1:
|
|
||||||
rightextent = maxx
|
|
||||||
x = maxx - w
|
|
||||||
# centre
|
|
||||||
elif align == 2:
|
|
||||||
x = (maxx - w) / 2
|
|
||||||
rightextent = x + w
|
|
||||||
if live:
|
|
||||||
# now draw the text, and any outlines/shadows
|
|
||||||
if self._theme.display_shadow:
|
|
||||||
self._get_extent_and_render(line, footer,
|
|
||||||
tlcorner=(x + display_shadow_size,
|
|
||||||
y + display_shadow_size),
|
|
||||||
draw=True, color=self._theme.display_shadow_color)
|
|
||||||
self._get_extent_and_render(line, footer, tlcorner=(x, y),
|
|
||||||
draw=True, outline_size=display_outline_size)
|
|
||||||
y += h
|
|
||||||
if linenum == 0:
|
|
||||||
self._first_line_right_extent = rightextent
|
|
||||||
# draw a box around the text - debug only
|
|
||||||
|
|
||||||
if self._debug:
|
def format_slide(self, words, line_break):
|
||||||
self.painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0)))
|
|
||||||
self.painter.drawRect(startx, starty, rightextent-startx, y-starty)
|
|
||||||
brcorner = (rightextent, y)
|
|
||||||
self.painter.end()
|
|
||||||
if self._theme.display_slideTransition:
|
|
||||||
self.painter2.end()
|
|
||||||
return brcorner
|
|
||||||
|
|
||||||
def _set_theme_font(self):
|
|
||||||
"""
|
"""
|
||||||
Set the fonts from the current theme settings.
|
Figure out how much text can appear on a slide, using the current
|
||||||
|
theme settings.
|
||||||
|
|
||||||
|
``words``
|
||||||
|
The words to be fitted on the slide.
|
||||||
"""
|
"""
|
||||||
footer_weight = 50
|
log.debug(u'format_slide - Start')
|
||||||
if self._theme.font_footer_weight == u'Bold':
|
line_end = u''
|
||||||
footer_weight = 75
|
if line_break:
|
||||||
#TODO Add myfont.setPixelSize((screen_height / 100) * font_size)
|
line_end = u'<br>'
|
||||||
self.footer_font = QtGui.QFont(self._theme.font_footer_name,
|
words = words.replace(u'\r\n', u'\n')
|
||||||
self._theme.font_footer_proportion, # size
|
verses_text = words.split(u'\n')
|
||||||
footer_weight, # weight
|
text = []
|
||||||
self._theme.font_footer_italics) # italic
|
for verse in verses_text:
|
||||||
self.footer_font.setPixelSize(self._theme.font_footer_proportion)
|
lines = verse.split(u'\n')
|
||||||
main_weight = 50
|
for line in lines:
|
||||||
if self._theme.font_main_weight == u'Bold':
|
text.append(line)
|
||||||
main_weight = 75
|
web = QtWebKit.QWebView()
|
||||||
self.main_font = QtGui.QFont(self._theme.font_main_name,
|
web.resize(self._rect.width(), self._rect.height())
|
||||||
self._theme.font_main_proportion, # size
|
web.setVisible(False)
|
||||||
main_weight, # weight
|
frame = web.page().mainFrame()
|
||||||
self._theme.font_main_italics)# italic
|
# Adjust width and height to account for shadow. outline done in css
|
||||||
self.main_font.setPixelSize(self._theme.font_main_proportion)
|
width = self._rect.width() - int(self._theme.display_shadow_size)
|
||||||
|
height = self._rect.height() - int(self._theme.display_shadow_size)
|
||||||
def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False,
|
shell = u'<html><head><style>#main {%s %s}</style><body>' \
|
||||||
color=None, outline_size=0):
|
u'<div id="main">' % \
|
||||||
"""
|
(build_lyrics_format_css(self._theme, width, height),
|
||||||
Find bounding box of text - as render_single_line. If draw is set,
|
build_lyrics_outline_css(self._theme))
|
||||||
actually draw the text to the current DC as well return width and
|
formatted = []
|
||||||
height of text as a tuple (w, h).
|
html_text = u''
|
||||||
|
styled_text = u''
|
||||||
``line``
|
js_height = 'document.getElementById("main").scrollHeight'
|
||||||
The line of text to render.
|
for line in text:
|
||||||
|
styled_line = expand_tags(line) + line_end
|
||||||
``footer``
|
styled_text += styled_line
|
||||||
The footer text.
|
html = shell + styled_text + u'</div></body></html>'
|
||||||
|
web.setHtml(html)
|
||||||
``tlcorner``
|
# Text too long so go to next page
|
||||||
Defaults to *``(0, 0)``*. The top left corner co-ordinates.
|
text_height = int(frame.evaluateJavaScript(js_height).toString())
|
||||||
|
if text_height > height:
|
||||||
``draw``
|
formatted.append(html_text)
|
||||||
Defaults to *False*. Draw the text to the current surface.
|
html_text = u''
|
||||||
|
styled_text = styled_line
|
||||||
``color``
|
html_text += line + line_end
|
||||||
Defaults to *None*. The colour to draw with.
|
formatted.append(html_text)
|
||||||
"""
|
log.debug(u'format_slide - End')
|
||||||
# setup defaults
|
return formatted
|
||||||
if footer:
|
|
||||||
font = self.footer_font
|
|
||||||
else:
|
|
||||||
font = self.main_font
|
|
||||||
metrics = QtGui.QFontMetrics(font)
|
|
||||||
w = metrics.width(line)
|
|
||||||
if footer:
|
|
||||||
h = metrics.height()
|
|
||||||
else:
|
|
||||||
h = metrics.height() + int(self._theme.font_main_line_adjustment)
|
|
||||||
if draw:
|
|
||||||
self.painter.setFont(font)
|
|
||||||
if color is None:
|
|
||||||
if footer:
|
|
||||||
pen = QtGui.QColor(self._theme.font_footer_color)
|
|
||||||
else:
|
|
||||||
pen = QtGui.QColor(self._theme.font_main_color)
|
|
||||||
else:
|
|
||||||
pen = QtGui.QColor(color)
|
|
||||||
x, y = tlcorner
|
|
||||||
rowpos = y + metrics.ascent()
|
|
||||||
if self._theme.display_outline and outline_size != 0 and not footer:
|
|
||||||
path = QtGui.QPainterPath()
|
|
||||||
path.addText(QtCore.QPointF(x, rowpos), font, line)
|
|
||||||
self.painter.setBrush(self.painter.pen().brush())
|
|
||||||
self.painter.setPen(QtGui.QPen(QtGui.QColor(
|
|
||||||
self._theme.display_outline_color), outline_size))
|
|
||||||
self.painter.drawPath(path)
|
|
||||||
self.painter.setPen(pen)
|
|
||||||
self.painter.drawText(x, rowpos, line)
|
|
||||||
if self._theme.display_slideTransition:
|
|
||||||
# Print 2nd image with 70% weight
|
|
||||||
if self._theme.display_outline and outline_size != 0 and \
|
|
||||||
not footer:
|
|
||||||
path = QtGui.QPainterPath()
|
|
||||||
path.addText(QtCore.QPointF(x, rowpos), font, line)
|
|
||||||
self.painter2.setBrush(self.painter2.pen().brush())
|
|
||||||
self.painter2.setPen(QtGui.QPen(
|
|
||||||
QtGui.QColor(self._theme.display_outline_color),
|
|
||||||
outline_size))
|
|
||||||
self.painter2.drawPath(path)
|
|
||||||
self.painter2.setFont(font)
|
|
||||||
self.painter2.setPen(pen)
|
|
||||||
self.painter2.drawText(x, rowpos, line)
|
|
||||||
return (w, h)
|
|
||||||
|
|
||||||
def snoop_image(self, image, image2=None):
|
|
||||||
"""
|
|
||||||
Debugging method to allow images to be viewed.
|
|
||||||
|
|
||||||
``image``
|
|
||||||
An image to save to disk.
|
|
||||||
|
|
||||||
``image2``
|
|
||||||
Defaults to *None*. Another image to save to disk.
|
|
||||||
"""
|
|
||||||
image.save(u'renderer.png', u'png')
|
|
||||||
if image2:
|
|
||||||
image2.save(u'renderer2.png', u'png')
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ import logging
|
||||||
|
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.lib import Renderer, ThemeLevel
|
from openlp.core.lib import Renderer, ThemeLevel, ServiceItem
|
||||||
|
from openlp.core.ui import MainDisplay
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -55,6 +56,8 @@ class RenderManager(object):
|
||||||
"""
|
"""
|
||||||
log.debug(u'Initilisation started')
|
log.debug(u'Initilisation started')
|
||||||
self.screens = screens
|
self.screens = screens
|
||||||
|
self.display = MainDisplay(self, screens, False)
|
||||||
|
self.display.setup()
|
||||||
self.theme_manager = theme_manager
|
self.theme_manager = theme_manager
|
||||||
self.renderer = Renderer()
|
self.renderer = Renderer()
|
||||||
self.calculate_default(self.screens.current[u'size'])
|
self.calculate_default(self.screens.current[u'size'])
|
||||||
|
@ -63,6 +66,7 @@ class RenderManager(object):
|
||||||
self.theme_level = u''
|
self.theme_level = u''
|
||||||
self.override_background = None
|
self.override_background = None
|
||||||
self.themedata = None
|
self.themedata = None
|
||||||
|
self.alertTab = None
|
||||||
|
|
||||||
def update_display(self):
|
def update_display(self):
|
||||||
"""
|
"""
|
||||||
|
@ -70,7 +74,10 @@ class RenderManager(object):
|
||||||
"""
|
"""
|
||||||
log.debug(u'Update Display')
|
log.debug(u'Update Display')
|
||||||
self.calculate_default(self.screens.current[u'size'])
|
self.calculate_default(self.screens.current[u'size'])
|
||||||
|
self.display = MainDisplay(self, self.screens, False)
|
||||||
|
self.display.setup()
|
||||||
self.renderer.bg_frame = None
|
self.renderer.bg_frame = None
|
||||||
|
self.themedata = None
|
||||||
|
|
||||||
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
|
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
|
||||||
"""
|
"""
|
||||||
|
@ -86,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):
|
||||||
"""
|
"""
|
||||||
|
@ -95,18 +105,28 @@ 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):
|
def set_override_theme(self, theme, overrideLevels=False):
|
||||||
"""
|
"""
|
||||||
Set the appropriate theme depending on the theme level.
|
Set the appropriate theme depending on the theme level.
|
||||||
|
Called by the service item when building a display frame
|
||||||
|
|
||||||
``theme``
|
``theme``
|
||||||
The name of the song-level theme.
|
The name of the song-level theme. None means the service
|
||||||
|
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)
|
||||||
if self.theme_level == ThemeLevel.Global:
|
theme_level = self.theme_level
|
||||||
|
if overrideLevels:
|
||||||
|
theme_level = ThemeLevel.Song
|
||||||
|
if theme_level == ThemeLevel.Global:
|
||||||
self.theme = self.global_theme
|
self.theme = self.global_theme
|
||||||
elif self.theme_level == ThemeLevel.Service:
|
elif theme_level == ThemeLevel.Service:
|
||||||
if self.service_theme == u'':
|
if self.service_theme == u'':
|
||||||
self.theme = self.global_theme
|
self.theme = self.global_theme
|
||||||
else:
|
else:
|
||||||
|
@ -114,20 +134,27 @@ class RenderManager(object):
|
||||||
else:
|
else:
|
||||||
if theme:
|
if theme:
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
elif self.theme_level == ThemeLevel.Song or \
|
elif theme_level == ThemeLevel.Song or \
|
||||||
self.theme_level == ThemeLevel.Service:
|
theme_level == ThemeLevel.Service:
|
||||||
if self.service_theme == u'':
|
if self.service_theme == u'':
|
||||||
self.theme = self.global_theme
|
self.theme = self.global_theme
|
||||||
else:
|
else:
|
||||||
self.theme = self.service_theme
|
self.theme = self.service_theme
|
||||||
else:
|
else:
|
||||||
self.theme = self.global_theme
|
self.theme = self.global_theme
|
||||||
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:
|
||||||
log.debug(u'theme is now %s', self.theme)
|
log.debug(u'theme is now %s', self.theme)
|
||||||
self.themedata = self.theme_manager.getThemeData(self.theme)
|
# Force the theme to be the one passed in.
|
||||||
|
if overrideLevels:
|
||||||
|
self.themedata = theme
|
||||||
|
else:
|
||||||
|
self.themedata = self.theme_manager.getThemeData(self.theme)
|
||||||
self.calculate_default(self.screens.current[u'size'])
|
self.calculate_default(self.screens.current[u'size'])
|
||||||
self.renderer.set_theme(self.themedata)
|
self.renderer.set_theme(self.themedata)
|
||||||
self.build_text_rectangle(self.themedata)
|
self.build_text_rectangle(self.themedata)
|
||||||
|
self.renderer.set_frame_dest(self.width, self.height)
|
||||||
|
return self.renderer._rect, self.renderer._rect_footer
|
||||||
|
|
||||||
def build_text_rectangle(self, theme):
|
def build_text_rectangle(self, theme):
|
||||||
"""
|
"""
|
||||||
|
@ -163,13 +190,8 @@ class RenderManager(object):
|
||||||
The theme to generated a preview for.
|
The theme to generated a preview for.
|
||||||
"""
|
"""
|
||||||
log.debug(u'generate preview')
|
log.debug(u'generate preview')
|
||||||
#set the default image size for previews
|
# set the default image size for previews
|
||||||
self.calculate_default(self.screens.preview[u'size'])
|
self.calculate_default(self.screens.preview[u'size'])
|
||||||
self.renderer.set_theme(themedata)
|
|
||||||
self.build_text_rectangle(themedata)
|
|
||||||
self.renderer.set_frame_dest(self.width, self.height, True)
|
|
||||||
#Reset the real screen size for subsequent render requests
|
|
||||||
self.calculate_default(self.screens.current[u'size'])
|
|
||||||
verse = u'Amazing Grace!\n'\
|
verse = u'Amazing Grace!\n'\
|
||||||
'How sweet the sound\n'\
|
'How sweet the sound\n'\
|
||||||
'To save a wretch like me;\n'\
|
'To save a wretch like me;\n'\
|
||||||
|
@ -179,12 +201,21 @@ class RenderManager(object):
|
||||||
footer.append(u'Amazing Grace (John Newton)' )
|
footer.append(u'Amazing Grace (John Newton)' )
|
||||||
footer.append(u'Public Domain')
|
footer.append(u'Public Domain')
|
||||||
footer.append(u'CCLI 123456')
|
footer.append(u'CCLI 123456')
|
||||||
formatted = self.renderer.format_slide(verse, False)
|
# build a service item to generate preview
|
||||||
#Only Render the first slide page returned
|
serviceItem = ServiceItem()
|
||||||
return self.renderer.generate_frame_from_lines(formatted[0],
|
serviceItem.theme = themedata
|
||||||
footer)[u'main']
|
serviceItem.add_from_text(u'', verse, footer)
|
||||||
|
serviceItem.render_manager = self
|
||||||
|
serviceItem.raw_footer = footer
|
||||||
|
serviceItem.render(True)
|
||||||
|
self.display.buildHtml(serviceItem)
|
||||||
|
raw_html = serviceItem.get_rendered_frame(0)[1]
|
||||||
|
preview = self.display.text(raw_html)
|
||||||
|
# Reset the real screen size for subsequent render requests
|
||||||
|
self.calculate_default(self.screens.current[u'size'])
|
||||||
|
return preview
|
||||||
|
|
||||||
def format_slide(self, words):
|
def format_slide(self, words, line_break):
|
||||||
"""
|
"""
|
||||||
Calculate how much text can fit on a slide.
|
Calculate how much text can fit on a slide.
|
||||||
|
|
||||||
|
@ -193,22 +224,7 @@ class RenderManager(object):
|
||||||
"""
|
"""
|
||||||
log.debug(u'format slide')
|
log.debug(u'format slide')
|
||||||
self.build_text_rectangle(self.themedata)
|
self.build_text_rectangle(self.themedata)
|
||||||
return self.renderer.format_slide(words, False)
|
return self.renderer.format_slide(words, line_break)
|
||||||
|
|
||||||
def generate_slide(self, main_text, footer_text):
|
|
||||||
"""
|
|
||||||
Generate the actual slide image.
|
|
||||||
|
|
||||||
``main_text``
|
|
||||||
The text for the main area of the slide.
|
|
||||||
|
|
||||||
``footer_text``
|
|
||||||
The text for the slide footer.
|
|
||||||
"""
|
|
||||||
log.debug(u'generate slide')
|
|
||||||
self.build_text_rectangle(self.themedata)
|
|
||||||
self.renderer.set_frame_dest(self.width, self.height)
|
|
||||||
return self.renderer.generate_frame_from_lines(main_text, footer_text)
|
|
||||||
|
|
||||||
def calculate_default(self, screen):
|
def calculate_default(self, screen):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -35,7 +35,7 @@ import uuid
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.lib import build_icon, resize_image
|
from openlp.core.lib import build_icon, resize_image, clean_tags, expand_tags
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ class ItemCapabilities(object):
|
||||||
RequiresMedia = 4
|
RequiresMedia = 4
|
||||||
AllowsLoop = 5
|
AllowsLoop = 5
|
||||||
AllowsAdditions = 6
|
AllowsAdditions = 6
|
||||||
|
NoLineBreaks = 7
|
||||||
|
|
||||||
class ServiceItem(object):
|
class ServiceItem(object):
|
||||||
"""
|
"""
|
||||||
|
@ -82,6 +83,7 @@ class ServiceItem(object):
|
||||||
self.items = []
|
self.items = []
|
||||||
self.iconic_representation = None
|
self.iconic_representation = None
|
||||||
self.raw_footer = None
|
self.raw_footer = None
|
||||||
|
self.foot_text = None
|
||||||
self.theme = None
|
self.theme = None
|
||||||
self.service_item_type = None
|
self.service_item_type = None
|
||||||
self._raw_frames = []
|
self._raw_frames = []
|
||||||
|
@ -91,10 +93,18 @@ class ServiceItem(object):
|
||||||
self.from_plugin = False
|
self.from_plugin = False
|
||||||
self.capabilities = []
|
self.capabilities = []
|
||||||
self.is_valid = True
|
self.is_valid = True
|
||||||
self.cache = {}
|
|
||||||
self.icon = None
|
self.icon = None
|
||||||
|
self.themedata = None
|
||||||
|
self.main = None
|
||||||
|
self.footer = None
|
||||||
|
self.bg_frame = None
|
||||||
|
|
||||||
def _new_item(self):
|
def _new_item(self):
|
||||||
|
"""
|
||||||
|
Method to set the internal id of the item
|
||||||
|
This is used to compare service items to see if they are
|
||||||
|
the same
|
||||||
|
"""
|
||||||
self._uuid = unicode(uuid.uuid1())
|
self._uuid = unicode(uuid.uuid1())
|
||||||
|
|
||||||
def add_capability(self, capability):
|
def add_capability(self, capability):
|
||||||
|
@ -126,36 +136,41 @@ class ServiceItem(object):
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.iconic_representation = build_icon(icon)
|
self.iconic_representation = build_icon(icon)
|
||||||
|
|
||||||
def render(self):
|
def render(self, useOverride=False):
|
||||||
"""
|
"""
|
||||||
The render method is what generates the frames for the screen.
|
The render method is what generates the frames for the screen and
|
||||||
|
obtains the display information from the renderemanager.
|
||||||
|
At this point all the slides are build for the given
|
||||||
|
display size.
|
||||||
"""
|
"""
|
||||||
log.debug(u'Render called')
|
log.debug(u'Render called')
|
||||||
self._display_frames = []
|
self._display_frames = []
|
||||||
self.clear_cache()
|
self.bg_frame = None
|
||||||
|
line_break = True
|
||||||
|
if self.is_capable(ItemCapabilities.NoLineBreaks):
|
||||||
|
line_break = False
|
||||||
if self.service_item_type == ServiceItemType.Text:
|
if self.service_item_type == ServiceItemType.Text:
|
||||||
log.debug(u'Formatting slides')
|
log.debug(u'Formatting slides')
|
||||||
if self.theme is None:
|
theme = None
|
||||||
self.render_manager.set_override_theme(None)
|
if self.theme:
|
||||||
else:
|
theme = self.theme
|
||||||
self.render_manager.set_override_theme(self.theme)
|
self.main, self.footer = \
|
||||||
|
self.render_manager.set_override_theme(theme, useOverride)
|
||||||
|
self.bg_frame = self.render_manager.renderer.bg_frame
|
||||||
|
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.format_slide(slide[u'raw_slide'])
|
formatted = self.render_manager \
|
||||||
for format in formated:
|
.format_slide(slide[u'raw_slide'], line_break)
|
||||||
lines = u''
|
for page in formatted:
|
||||||
title = u''
|
self._display_frames.append(
|
||||||
for line in format:
|
{u'title': clean_tags(page),
|
||||||
if title == u'':
|
u'text': clean_tags(page.rstrip()),
|
||||||
title = line
|
u'html': expand_tags(page.rstrip()),
|
||||||
lines += line + u'\n'
|
|
||||||
self._display_frames.append({u'title': title,
|
|
||||||
u'text': lines.rstrip(),
|
|
||||||
u'verseTag': slide[u'verseTag'] })
|
u'verseTag': slide[u'verseTag'] })
|
||||||
if len(self._display_frames) in self.cache.keys():
|
|
||||||
del self.cache[len(self._display_frames)]
|
|
||||||
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)
|
||||||
|
@ -163,29 +178,14 @@ class ServiceItem(object):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
log.error(u'Invalid value renderer :%s' % self.service_item_type)
|
log.error(u'Invalid value renderer :%s' % self.service_item_type)
|
||||||
|
self.title = clean_tags(self.title)
|
||||||
def render_individual(self, row):
|
self.foot_text = None
|
||||||
"""
|
if self.raw_footer:
|
||||||
Takes an array of text and generates an Image from the
|
for foot in self.raw_footer:
|
||||||
theme. It assumes the text will fit on the screen as it
|
if not self.foot_text:
|
||||||
has generated by the render method above.
|
self.foot_text = foot
|
||||||
"""
|
else:
|
||||||
log.debug(u'render individual')
|
self.foot_text = u'%s<br>%s' % (self.foot_text, foot)
|
||||||
if self.theme is None:
|
|
||||||
self.render_manager.set_override_theme(None)
|
|
||||||
else:
|
|
||||||
self.render_manager.set_override_theme(self.theme)
|
|
||||||
format = self._display_frames[row][u'text'].split(u'\n')
|
|
||||||
if self.cache.get(row):
|
|
||||||
frame = self.cache[row]
|
|
||||||
else:
|
|
||||||
if format[0]:
|
|
||||||
frame = self.render_manager.generate_slide(format,
|
|
||||||
self.raw_footer)
|
|
||||||
else:
|
|
||||||
frame = self.render_manager.generate_slide(format, u'')
|
|
||||||
self.cache[row] = frame
|
|
||||||
return frame
|
|
||||||
|
|
||||||
def add_from_image(self, path, title, image):
|
def add_from_image(self, path, title, image):
|
||||||
"""
|
"""
|
||||||
|
@ -375,9 +375,9 @@ class ServiceItem(object):
|
||||||
renders it if required.
|
renders it if required.
|
||||||
"""
|
"""
|
||||||
if self.service_item_type == ServiceItemType.Text:
|
if self.service_item_type == ServiceItemType.Text:
|
||||||
return self.render_individual(row)
|
return None, self._display_frames[row][u'html'].split(u'\n')[0]
|
||||||
else:
|
else:
|
||||||
return {u'main':self._raw_frames[row][u'image'], u'trans':None}
|
return self._raw_frames[row][u'image'], u''
|
||||||
|
|
||||||
def get_frame_title(self, row=0):
|
def get_frame_title(self, row=0):
|
||||||
"""
|
"""
|
||||||
|
@ -390,9 +390,3 @@ class ServiceItem(object):
|
||||||
Returns the title of the raw frame
|
Returns the title of the raw frame
|
||||||
"""
|
"""
|
||||||
return self._raw_frames[row][u'path']
|
return self._raw_frames[row][u'path']
|
||||||
|
|
||||||
def clear_cache(self):
|
|
||||||
"""
|
|
||||||
Clear's the service item's cache.
|
|
||||||
"""
|
|
||||||
self.cache = {}
|
|
||||||
|
|
|
@ -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())))
|
|
@ -55,9 +55,8 @@ BLANK_THEME_XML = \
|
||||||
<proportion>30</proportion>
|
<proportion>30</proportion>
|
||||||
<weight>Normal</weight>
|
<weight>Normal</weight>
|
||||||
<italics>False</italics>
|
<italics>False</italics>
|
||||||
<indentation>0</indentation>
|
|
||||||
<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,9 +64,8 @@ BLANK_THEME_XML = \
|
||||||
<proportion>12</proportion>
|
<proportion>12</proportion>
|
||||||
<weight>Normal</weight>
|
<weight>Normal</weight>
|
||||||
<italics>False</italics>
|
<italics>False</italics>
|
||||||
<indentation>0</indentation>
|
|
||||||
<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>
|
||||||
|
@ -184,7 +182,7 @@ class ThemeXML(object):
|
||||||
self.child_element(background, u'filename', filename)
|
self.child_element(background, u'filename', filename)
|
||||||
|
|
||||||
def add_font(self, name, color, proportion, override, fonttype=u'main',
|
def add_font(self, name, color, proportion, override, fonttype=u'main',
|
||||||
weight=u'Normal', italics=u'False', indentation=0, line_adjustment=0,
|
weight=u'Normal', italics=u'False', line_adjustment=0,
|
||||||
xpos=0, ypos=0, width=0, height=0):
|
xpos=0, ypos=0, width=0, height=0):
|
||||||
"""
|
"""
|
||||||
Add a Font.
|
Add a Font.
|
||||||
|
@ -210,9 +208,6 @@ class ThemeXML(object):
|
||||||
``italics``
|
``italics``
|
||||||
Does the font render to italics Defaults to 0 Normal
|
Does the font render to italics Defaults to 0 Normal
|
||||||
|
|
||||||
``indentation``
|
|
||||||
Number of characters the wrap line is indented
|
|
||||||
|
|
||||||
``xpos``
|
``xpos``
|
||||||
The X position of the text block.
|
The X position of the text block.
|
||||||
|
|
||||||
|
@ -239,8 +234,6 @@ class ThemeXML(object):
|
||||||
#Create italics name element
|
#Create italics name element
|
||||||
self.child_element(background, u'italics', italics)
|
self.child_element(background, u'italics', italics)
|
||||||
#Create indentation name element
|
#Create indentation name element
|
||||||
self.child_element(background, u'indentation', unicode(indentation))
|
|
||||||
#Create indentation name element
|
|
||||||
self.child_element(
|
self.child_element(
|
||||||
background, u'line_adjustment', unicode(line_adjustment))
|
background, u'line_adjustment', unicode(line_adjustment))
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,11 @@ class HideMode(object):
|
||||||
Theme = 2
|
Theme = 2
|
||||||
Screen = 3
|
Screen = 3
|
||||||
|
|
||||||
|
from maindisplay import MainDisplay
|
||||||
from slidecontroller import HideMode
|
from slidecontroller import HideMode
|
||||||
from servicenoteform import ServiceNoteForm
|
from servicenoteform import ServiceNoteForm
|
||||||
from serviceitemeditform import ServiceItemEditForm
|
from serviceitemeditform import ServiceItemEditForm
|
||||||
from screen import ScreenList
|
from screen import ScreenList
|
||||||
from maindisplay import MainDisplay
|
|
||||||
from maindisplay import VideoDisplay
|
|
||||||
from maindisplay import DisplayManager
|
|
||||||
from amendthemeform import AmendThemeForm
|
from amendthemeform import AmendThemeForm
|
||||||
from slidecontroller import SlideController
|
from slidecontroller import SlideController
|
||||||
from splashscreen import SplashScreen
|
from splashscreen import SplashScreen
|
||||||
|
@ -56,8 +54,7 @@ from settingsform import SettingsForm
|
||||||
from mediadockmanager import MediaDockManager
|
from mediadockmanager import MediaDockManager
|
||||||
from servicemanager import ServiceManager
|
from servicemanager import ServiceManager
|
||||||
from thememanager import ThemeManager
|
from thememanager import ThemeManager
|
||||||
from mainwindow import MainWindow
|
|
||||||
|
|
||||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow',
|
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm',
|
||||||
'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager',
|
'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager',
|
||||||
'AmendThemeForm', 'MediaDockManager', 'ServiceItemEditForm']
|
'AmendThemeForm', 'MediaDockManager', 'ServiceItemEditForm']
|
||||||
|
|
|
@ -68,17 +68,6 @@ class Ui_AmendThemeDialog(object):
|
||||||
self.backgroundLayout.setMargin(8)
|
self.backgroundLayout.setMargin(8)
|
||||||
self.backgroundLayout.setSpacing(8)
|
self.backgroundLayout.setSpacing(8)
|
||||||
self.backgroundLayout.setObjectName(u'backgroundLayout')
|
self.backgroundLayout.setObjectName(u'backgroundLayout')
|
||||||
self.backgroundLabel = QtGui.QLabel(self.backgroundTab)
|
|
||||||
self.backgroundLabel.setObjectName(u'backgroundLabel')
|
|
||||||
self.backgroundLayout.setWidget(0, QtGui.QFormLayout.LabelRole,
|
|
||||||
self.backgroundLabel)
|
|
||||||
self.backgroundComboBox = QtGui.QComboBox(self.backgroundTab)
|
|
||||||
self.backgroundComboBox.setObjectName(u'backgroundComboBox')
|
|
||||||
self.backgroundLabel.setBuddy(self.backgroundComboBox)
|
|
||||||
self.backgroundComboBox.addItem(QtCore.QString())
|
|
||||||
self.backgroundComboBox.addItem(QtCore.QString())
|
|
||||||
self.backgroundLayout.setWidget(0, QtGui.QFormLayout.FieldRole,
|
|
||||||
self.backgroundComboBox)
|
|
||||||
self.backgroundTypeLabel = QtGui.QLabel(self.backgroundTab)
|
self.backgroundTypeLabel = QtGui.QLabel(self.backgroundTab)
|
||||||
self.backgroundTypeLabel.setObjectName(u'backgroundTypeLabel')
|
self.backgroundTypeLabel.setObjectName(u'backgroundTypeLabel')
|
||||||
self.backgroundLayout.setWidget(1, QtGui.QFormLayout.LabelRole,
|
self.backgroundLayout.setWidget(1, QtGui.QFormLayout.LabelRole,
|
||||||
|
@ -216,17 +205,6 @@ class Ui_AmendThemeDialog(object):
|
||||||
self.fontMainLineAdjustmentSpinBox.setMinimum(-99)
|
self.fontMainLineAdjustmentSpinBox.setMinimum(-99)
|
||||||
self.mainFontLayout.setWidget(4, QtGui.QFormLayout.FieldRole,
|
self.mainFontLayout.setWidget(4, QtGui.QFormLayout.FieldRole,
|
||||||
self.fontMainLineAdjustmentSpinBox)
|
self.fontMainLineAdjustmentSpinBox)
|
||||||
self.fontMainWrapIndentationLabel = QtGui.QLabel(self.fontMainGroupBox)
|
|
||||||
self.fontMainWrapIndentationLabel.setObjectName(
|
|
||||||
u'fontMainWrapIndentationLabel')
|
|
||||||
self.mainFontLayout.setWidget(5, QtGui.QFormLayout.LabelRole,
|
|
||||||
self.fontMainWrapIndentationLabel)
|
|
||||||
self.fontMainLineSpacingSpinBox = QtGui.QSpinBox(self.fontMainGroupBox)
|
|
||||||
self.fontMainLineSpacingSpinBox.setObjectName(
|
|
||||||
u'fontMainLineSpacingSpinBox')
|
|
||||||
self.fontMainLineSpacingSpinBox.setMaximum(10)
|
|
||||||
self.mainFontLayout.setWidget(5, QtGui.QFormLayout.FieldRole,
|
|
||||||
self.fontMainLineSpacingSpinBox)
|
|
||||||
self.fontMainLinesPageLabel = QtGui.QLabel(self.fontMainGroupBox)
|
self.fontMainLinesPageLabel = QtGui.QLabel(self.fontMainGroupBox)
|
||||||
self.fontMainLinesPageLabel.setObjectName(u'fontMainLinesPageLabel')
|
self.fontMainLinesPageLabel.setObjectName(u'fontMainLinesPageLabel')
|
||||||
self.mainFontLayout.addRow(self.fontMainLinesPageLabel)
|
self.mainFontLayout.addRow(self.fontMainLinesPageLabel)
|
||||||
|
@ -661,12 +639,6 @@ class Ui_AmendThemeDialog(object):
|
||||||
translate('OpenLP.AmendThemeForm', 'Theme Maintenance'))
|
translate('OpenLP.AmendThemeForm', 'Theme Maintenance'))
|
||||||
self.themeNameLabel.setText(
|
self.themeNameLabel.setText(
|
||||||
translate('OpenLP.AmendThemeForm', 'Theme &name:'))
|
translate('OpenLP.AmendThemeForm', 'Theme &name:'))
|
||||||
self.backgroundLabel.setText(
|
|
||||||
translate('OpenLP.AmendThemeForm', '&Visibility:'))
|
|
||||||
self.backgroundComboBox.setItemText(0,
|
|
||||||
translate('OpenLP.AmendThemeForm', 'Opaque'))
|
|
||||||
self.backgroundComboBox.setItemText(1,
|
|
||||||
translate('OpenLP.AmendThemeForm', 'Transparent'))
|
|
||||||
self.backgroundTypeLabel.setText(
|
self.backgroundTypeLabel.setText(
|
||||||
translate('OpenLP.AmendThemeForm', 'Type:'))
|
translate('OpenLP.AmendThemeForm', 'Type:'))
|
||||||
self.backgroundTypeComboBox.setItemText(0,
|
self.backgroundTypeComboBox.setItemText(0,
|
||||||
|
@ -700,8 +672,6 @@ class Ui_AmendThemeDialog(object):
|
||||||
translate('OpenLP.AmendThemeForm', 'Size:'))
|
translate('OpenLP.AmendThemeForm', 'Size:'))
|
||||||
self.fontMainSizeSpinBox.setSuffix(
|
self.fontMainSizeSpinBox.setSuffix(
|
||||||
translate('OpenLP.AmendThemeForm', 'pt'))
|
translate('OpenLP.AmendThemeForm', 'pt'))
|
||||||
self.fontMainWrapIndentationLabel.setText(
|
|
||||||
translate('OpenLP.AmendThemeForm', 'Wrap indentation:'))
|
|
||||||
self.fontMainWrapLineAdjustmentLabel.setText(
|
self.fontMainWrapLineAdjustmentLabel.setText(
|
||||||
translate('OpenLP.AmendThemeForm', 'Adjust line spacing:'))
|
translate('OpenLP.AmendThemeForm', 'Adjust line spacing:'))
|
||||||
self.fontMainWeightComboBox.setItemText(0,
|
self.fontMainWeightComboBox.setItemText(0,
|
||||||
|
|
|
@ -50,7 +50,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.path = None
|
self.path = None
|
||||||
self.theme = ThemeXML()
|
self.theme = ThemeXML()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
# define signals
|
|
||||||
# Buttons
|
# Buttons
|
||||||
QtCore.QObject.connect(self.color1PushButton,
|
QtCore.QObject.connect(self.color1PushButton,
|
||||||
QtCore.SIGNAL(u'pressed()'), self.onColor1PushButtonClicked)
|
QtCore.SIGNAL(u'pressed()'), self.onColor1PushButtonClicked)
|
||||||
|
@ -68,8 +67,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
QtCore.QObject.connect(self.imageToolButton,
|
QtCore.QObject.connect(self.imageToolButton,
|
||||||
QtCore.SIGNAL(u'clicked()'), self.onImageToolButtonClicked)
|
QtCore.SIGNAL(u'clicked()'), self.onImageToolButtonClicked)
|
||||||
# Combo boxes
|
# Combo boxes
|
||||||
QtCore.QObject.connect(self.backgroundComboBox,
|
|
||||||
QtCore.SIGNAL(u'activated(int)'), self.onBackgroundComboBoxSelected)
|
|
||||||
QtCore.QObject.connect(self.backgroundTypeComboBox,
|
QtCore.QObject.connect(self.backgroundTypeComboBox,
|
||||||
QtCore.SIGNAL(u'activated(int)'),
|
QtCore.SIGNAL(u'activated(int)'),
|
||||||
self.onBackgroundTypeComboBoxSelected)
|
self.onBackgroundTypeComboBoxSelected)
|
||||||
|
@ -109,9 +106,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
QtCore.QObject.connect(self.fontMainLineAdjustmentSpinBox,
|
QtCore.QObject.connect(self.fontMainLineAdjustmentSpinBox,
|
||||||
QtCore.SIGNAL(u'editingFinished()'),
|
QtCore.SIGNAL(u'editingFinished()'),
|
||||||
self.onFontMainLineAdjustmentSpinBoxChanged)
|
self.onFontMainLineAdjustmentSpinBoxChanged)
|
||||||
QtCore.QObject.connect(self.fontMainLineSpacingSpinBox,
|
|
||||||
QtCore.SIGNAL(u'editingFinished()'),
|
|
||||||
self.onFontMainLineSpacingSpinBoxChanged)
|
|
||||||
QtCore.QObject.connect(self.fontFooterXSpinBox,
|
QtCore.QObject.connect(self.fontFooterXSpinBox,
|
||||||
QtCore.SIGNAL(u'editingFinished()'),
|
QtCore.SIGNAL(u'editingFinished()'),
|
||||||
self.onFontFooterXSpinBoxChanged)
|
self.onFontFooterXSpinBoxChanged)
|
||||||
|
@ -151,30 +145,26 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
new_theme.new_document(theme_name)
|
new_theme.new_document(theme_name)
|
||||||
save_from = None
|
save_from = None
|
||||||
save_to = None
|
save_to = None
|
||||||
if self.theme.background_mode == u'transparent':
|
if self.theme.background_type == u'solid':
|
||||||
new_theme.add_background_transparent()
|
new_theme.add_background_solid(
|
||||||
|
unicode(self.theme.background_color))
|
||||||
|
elif self.theme.background_type == u'gradient':
|
||||||
|
new_theme.add_background_gradient(
|
||||||
|
unicode(self.theme.background_startColor),
|
||||||
|
unicode(self.theme.background_endColor),
|
||||||
|
self.theme.background_direction)
|
||||||
else:
|
else:
|
||||||
if self.theme.background_type == u'solid':
|
filename = \
|
||||||
new_theme.add_background_solid(
|
os.path.split(unicode(self.theme.background_filename))[1]
|
||||||
unicode(self.theme.background_color))
|
new_theme.add_background_image(filename)
|
||||||
elif self.theme.background_type == u'gradient':
|
save_to = os.path.join(self.path, theme_name, filename)
|
||||||
new_theme.add_background_gradient(
|
save_from = self.theme.background_filename
|
||||||
unicode(self.theme.background_startColor),
|
|
||||||
unicode(self.theme.background_endColor),
|
|
||||||
self.theme.background_direction)
|
|
||||||
else:
|
|
||||||
filename = \
|
|
||||||
os.path.split(unicode(self.theme.background_filename))[1]
|
|
||||||
new_theme.add_background_image(filename)
|
|
||||||
save_to = os.path.join(self.path, theme_name, filename)
|
|
||||||
save_from = self.theme.background_filename
|
|
||||||
new_theme.add_font(unicode(self.theme.font_main_name),
|
new_theme.add_font(unicode(self.theme.font_main_name),
|
||||||
unicode(self.theme.font_main_color),
|
unicode(self.theme.font_main_color),
|
||||||
unicode(self.theme.font_main_proportion),
|
unicode(self.theme.font_main_proportion),
|
||||||
unicode(self.theme.font_main_override), u'main',
|
unicode(self.theme.font_main_override), u'main',
|
||||||
unicode(self.theme.font_main_weight),
|
unicode(self.theme.font_main_weight),
|
||||||
unicode(self.theme.font_main_italics),
|
unicode(self.theme.font_main_italics),
|
||||||
unicode(self.theme.font_main_indentation),
|
|
||||||
unicode(self.theme.font_main_line_adjustment),
|
unicode(self.theme.font_main_line_adjustment),
|
||||||
unicode(self.theme.font_main_x),
|
unicode(self.theme.font_main_x),
|
||||||
unicode(self.theme.font_main_y),
|
unicode(self.theme.font_main_y),
|
||||||
|
@ -186,7 +176,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
unicode(self.theme.font_footer_override), u'footer',
|
unicode(self.theme.font_footer_override), u'footer',
|
||||||
unicode(self.theme.font_footer_weight),
|
unicode(self.theme.font_footer_weight),
|
||||||
unicode(self.theme.font_footer_italics),
|
unicode(self.theme.font_footer_italics),
|
||||||
0, # indentation
|
|
||||||
0, # line adjustment
|
0, # line adjustment
|
||||||
unicode(self.theme.font_footer_x),
|
unicode(self.theme.font_footer_x),
|
||||||
unicode(self.theme.font_footer_y),
|
unicode(self.theme.font_footer_y),
|
||||||
|
@ -230,7 +219,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
#
|
#
|
||||||
#Main Font Tab
|
# Main Font Tab
|
||||||
#
|
#
|
||||||
def onFontMainComboBoxSelected(self):
|
def onFontMainComboBoxSelected(self):
|
||||||
self.theme.font_main_name = self.fontMainComboBox.currentFont().family()
|
self.theme.font_main_name = self.fontMainComboBox.currentFont().family()
|
||||||
|
@ -283,8 +272,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.fontMainHeightSpinBox.setValue(self.theme.font_main_height)
|
self.fontMainHeightSpinBox.setValue(self.theme.font_main_height)
|
||||||
self.fontMainLineAdjustmentSpinBox.setValue(
|
self.fontMainLineAdjustmentSpinBox.setValue(
|
||||||
self.theme.font_main_line_adjustment)
|
self.theme.font_main_line_adjustment)
|
||||||
self.fontMainLineSpacingSpinBox.setValue(
|
|
||||||
self.theme.font_main_indentation)
|
|
||||||
self.stateChanging(self.theme)
|
self.stateChanging(self.theme)
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
|
@ -310,20 +297,13 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.fontMainLineAdjustmentSpinBox.value()
|
self.fontMainLineAdjustmentSpinBox.value()
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
def onFontMainLineSpacingSpinBoxChanged(self):
|
|
||||||
if self.theme.font_main_indentation != \
|
|
||||||
self.fontMainLineSpacingSpinBox.value():
|
|
||||||
self.theme.font_main_indentation = \
|
|
||||||
self.fontMainLineSpacingSpinBox.value()
|
|
||||||
self.previewTheme()
|
|
||||||
|
|
||||||
def onFontMainHeightSpinBoxChanged(self):
|
def onFontMainHeightSpinBoxChanged(self):
|
||||||
if self.theme.font_main_height != self.fontMainHeightSpinBox.value():
|
if self.theme.font_main_height != self.fontMainHeightSpinBox.value():
|
||||||
self.theme.font_main_height = self.fontMainHeightSpinBox.value()
|
self.theme.font_main_height = self.fontMainHeightSpinBox.value()
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
#
|
#
|
||||||
#Footer Font Tab
|
# Footer Font Tab
|
||||||
#
|
#
|
||||||
def onFontFooterComboBoxSelected(self):
|
def onFontFooterComboBoxSelected(self):
|
||||||
self.theme.font_footer_name = \
|
self.theme.font_footer_name = \
|
||||||
|
@ -404,20 +384,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
#
|
#
|
||||||
#Background Tab
|
# Background Tab
|
||||||
#
|
#
|
||||||
def onGradientComboBoxSelected(self, currentIndex):
|
def onGradientComboBoxSelected(self, currentIndex):
|
||||||
self.setBackground(self.backgroundTypeComboBox.currentIndex(),
|
self.setBackground(self.backgroundTypeComboBox.currentIndex(),
|
||||||
currentIndex)
|
currentIndex)
|
||||||
|
|
||||||
def onBackgroundComboBoxSelected(self, currentIndex):
|
|
||||||
if currentIndex == 0: # Opaque
|
|
||||||
self.theme.background_mode = u'opaque'
|
|
||||||
else:
|
|
||||||
self.theme.background_mode = u'transparent'
|
|
||||||
self.stateChanging(self.theme)
|
|
||||||
self.previewTheme()
|
|
||||||
|
|
||||||
def onBackgroundTypeComboBoxSelected(self, currentIndex):
|
def onBackgroundTypeComboBoxSelected(self, currentIndex):
|
||||||
self.setBackground(currentIndex, self.gradientComboBox.currentIndex())
|
self.setBackground(currentIndex, self.gradientComboBox.currentIndex())
|
||||||
|
|
||||||
|
@ -472,7 +444,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
#
|
#
|
||||||
#Other Tab
|
# Other Tab
|
||||||
#
|
#
|
||||||
def onOutlineCheckBoxChanged(self, value):
|
def onOutlineCheckBoxChanged(self, value):
|
||||||
if value == 2: # checked
|
if value == 2: # checked
|
||||||
|
@ -537,16 +509,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.previewTheme()
|
self.previewTheme()
|
||||||
|
|
||||||
#
|
#
|
||||||
#Local Methods
|
# Local Methods
|
||||||
#
|
#
|
||||||
def paintUi(self, theme):
|
def paintUi(self, theme):
|
||||||
self.stateChanging(theme)
|
self.stateChanging(theme)
|
||||||
self.themeNameEdit.setText(self.theme.theme_name)
|
self.themeNameEdit.setText(self.theme.theme_name)
|
||||||
# Background Tab
|
# Background Tab
|
||||||
if self.theme.background_mode == u'opaque':
|
|
||||||
self.backgroundComboBox.setCurrentIndex(0)
|
|
||||||
else:
|
|
||||||
self.backgroundComboBox.setCurrentIndex(1)
|
|
||||||
self.imageLineEdit.setText(u'')
|
self.imageLineEdit.setText(u'')
|
||||||
if theme.background_type == u'solid':
|
if theme.background_type == u'solid':
|
||||||
self.backgroundTypeComboBox.setCurrentIndex(0)
|
self.backgroundTypeComboBox.setCurrentIndex(0)
|
||||||
|
@ -576,8 +544,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.fontMainWeightComboBox.setCurrentIndex(2)
|
self.fontMainWeightComboBox.setCurrentIndex(2)
|
||||||
else:
|
else:
|
||||||
self.fontMainWeightComboBox.setCurrentIndex(3)
|
self.fontMainWeightComboBox.setCurrentIndex(3)
|
||||||
self.fontMainLineSpacingSpinBox.setValue(
|
|
||||||
self.theme.font_main_indentation)
|
|
||||||
self.fontMainXSpinBox.setValue(self.theme.font_main_x)
|
self.fontMainXSpinBox.setValue(self.theme.font_main_x)
|
||||||
self.fontMainYSpinBox.setValue(self.theme.font_main_y)
|
self.fontMainYSpinBox.setValue(self.theme.font_main_y)
|
||||||
self.fontMainWidthSpinBox.setValue(self.theme.font_main_width)
|
self.fontMainWidthSpinBox.setValue(self.theme.font_main_width)
|
||||||
|
@ -641,9 +607,15 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.verticalComboBox.setCurrentIndex(self.theme.display_verticalAlign)
|
self.verticalComboBox.setCurrentIndex(self.theme.display_verticalAlign)
|
||||||
|
|
||||||
def stateChanging(self, theme):
|
def stateChanging(self, theme):
|
||||||
if theme.background_mode == u'transparent':
|
self.backgroundTypeComboBox.setVisible(True)
|
||||||
self.color1Label.setVisible(False)
|
self.backgroundTypeLabel.setVisible(True)
|
||||||
self.color1PushButton.setVisible(False)
|
if theme.background_type == u'solid':
|
||||||
|
self.color1PushButton.setStyleSheet(
|
||||||
|
u'background-color: %s' % unicode(theme.background_color))
|
||||||
|
self.color1Label.setText(
|
||||||
|
translate('OpenLP.AmendThemeForm', 'Color:'))
|
||||||
|
self.color1Label.setVisible(True)
|
||||||
|
self.color1PushButton.setVisible(True)
|
||||||
self.color2Label.setVisible(False)
|
self.color2Label.setVisible(False)
|
||||||
self.color2PushButton.setVisible(False)
|
self.color2PushButton.setVisible(False)
|
||||||
self.imageLabel.setVisible(False)
|
self.imageLabel.setVisible(False)
|
||||||
|
@ -651,53 +623,34 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
||||||
self.imageFilenameWidget.setVisible(False)
|
self.imageFilenameWidget.setVisible(False)
|
||||||
self.gradientLabel.setVisible(False)
|
self.gradientLabel.setVisible(False)
|
||||||
self.gradientComboBox.setVisible(False)
|
self.gradientComboBox.setVisible(False)
|
||||||
self.backgroundTypeComboBox.setVisible(False)
|
elif theme.background_type == u'gradient':
|
||||||
self.backgroundTypeLabel.setVisible(False)
|
self.color1PushButton.setStyleSheet(u'background-color: %s' \
|
||||||
else:
|
% unicode(theme.background_startColor))
|
||||||
self.backgroundTypeComboBox.setVisible(True)
|
self.color2PushButton.setStyleSheet(u'background-color: %s' \
|
||||||
self.backgroundTypeLabel.setVisible(True)
|
% unicode(theme.background_endColor))
|
||||||
if theme.background_type == u'solid':
|
self.color1Label.setText(
|
||||||
self.color1PushButton.setStyleSheet(
|
translate('OpenLP.AmendThemeForm', 'First color:'))
|
||||||
u'background-color: %s' % unicode(theme.background_color))
|
self.color2Label.setText(
|
||||||
self.color1Label.setText(
|
translate('OpenLP.AmendThemeForm', 'Second color:'))
|
||||||
translate('OpenLP.AmendThemeForm', 'Color:'))
|
self.color1Label.setVisible(True)
|
||||||
self.color1Label.setVisible(True)
|
self.color1PushButton.setVisible(True)
|
||||||
self.color1PushButton.setVisible(True)
|
self.color2Label.setVisible(True)
|
||||||
self.color2Label.setVisible(False)
|
self.color2PushButton.setVisible(True)
|
||||||
self.color2PushButton.setVisible(False)
|
self.imageLabel.setVisible(False)
|
||||||
self.imageLabel.setVisible(False)
|
self.imageLineEdit.setVisible(False)
|
||||||
self.imageLineEdit.setVisible(False)
|
self.imageFilenameWidget.setVisible(False)
|
||||||
self.imageFilenameWidget.setVisible(False)
|
self.gradientLabel.setVisible(True)
|
||||||
self.gradientLabel.setVisible(False)
|
self.gradientComboBox.setVisible(True)
|
||||||
self.gradientComboBox.setVisible(False)
|
else: # must be image
|
||||||
elif theme.background_type == u'gradient':
|
self.color1Label.setVisible(False)
|
||||||
self.color1PushButton.setStyleSheet(u'background-color: %s' \
|
self.color1PushButton.setVisible(False)
|
||||||
% unicode(theme.background_startColor))
|
self.color2Label.setVisible(False)
|
||||||
self.color2PushButton.setStyleSheet(u'background-color: %s' \
|
self.color2PushButton.setVisible(False)
|
||||||
% unicode(theme.background_endColor))
|
self.imageLabel.setVisible(True)
|
||||||
self.color1Label.setText(
|
self.imageLineEdit.setVisible(True)
|
||||||
translate('OpenLP.AmendThemeForm', 'First color:'))
|
self.imageFilenameWidget.setVisible(True)
|
||||||
self.color2Label.setText(
|
self.gradientLabel.setVisible(False)
|
||||||
translate('OpenLP.AmendThemeForm', 'Second color:'))
|
self.gradientComboBox.setVisible(False)
|
||||||
self.color1Label.setVisible(True)
|
|
||||||
self.color1PushButton.setVisible(True)
|
|
||||||
self.color2Label.setVisible(True)
|
|
||||||
self.color2PushButton.setVisible(True)
|
|
||||||
self.imageLabel.setVisible(False)
|
|
||||||
self.imageLineEdit.setVisible(False)
|
|
||||||
self.imageFilenameWidget.setVisible(False)
|
|
||||||
self.gradientLabel.setVisible(True)
|
|
||||||
self.gradientComboBox.setVisible(True)
|
|
||||||
else: # must be image
|
|
||||||
self.color1Label.setVisible(False)
|
|
||||||
self.color1PushButton.setVisible(False)
|
|
||||||
self.color2Label.setVisible(False)
|
|
||||||
self.color2PushButton.setVisible(False)
|
|
||||||
self.imageLabel.setVisible(True)
|
|
||||||
self.imageLineEdit.setVisible(True)
|
|
||||||
self.imageFilenameWidget.setVisible(True)
|
|
||||||
self.gradientLabel.setVisible(False)
|
|
||||||
self.gradientComboBox.setVisible(False)
|
|
||||||
if not theme.font_main_override:
|
if not theme.font_main_override:
|
||||||
self.fontMainXSpinBox.setEnabled(False)
|
self.fontMainXSpinBox.setEnabled(False)
|
||||||
self.fontMainYSpinBox.setEnabled(False)
|
self.fontMainYSpinBox.setEnabled(False)
|
||||||
|
|
|
@ -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 Occured'))
|
||||||
|
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,
|
||||||
|
@ -390,26 +390,16 @@ class GeneralTab(SettingsTab):
|
||||||
unicode(self.screens.current[u'size'].width()))
|
unicode(self.screens.current[u'size'].width()))
|
||||||
self.overrideCheckBox.setChecked(settings.value(u'override position',
|
self.overrideCheckBox.setChecked(settings.value(u'override position',
|
||||||
QtCore.QVariant(False)).toBool())
|
QtCore.QVariant(False)).toBool())
|
||||||
if self.overrideCheckBox.isChecked():
|
self.customXValueEdit.setText(settings.value(u'x position',
|
||||||
self.customXValueEdit.setText(settings.value(u'x position',
|
QtCore.QVariant(self.screens.current[u'size'].x())).toString())
|
||||||
QtCore.QVariant(self.screens.current[u'size'].x())).toString())
|
self.customYValueEdit.setText(settings.value(u'y position',
|
||||||
self.customYValueEdit.setText(settings.value(u'y position',
|
QtCore.QVariant(self.screens.current[u'size'].y())).toString())
|
||||||
QtCore.QVariant(self.screens.current[u'size'].y())).toString())
|
self.customHeightValueEdit.setText(
|
||||||
self.customHeightValueEdit.setText(
|
settings.value(u'height', QtCore.QVariant(
|
||||||
settings.value(u'height', QtCore.QVariant(
|
self.screens.current[u'size'].height())).toString())
|
||||||
self.screens.current[u'size'].height())).toString())
|
self.customWidthValueEdit.setText(
|
||||||
self.customWidthValueEdit.setText(
|
settings.value(u'width', QtCore.QVariant(
|
||||||
settings.value(u'width', QtCore.QVariant(
|
self.screens.current[u'size'].width())).toString())
|
||||||
self.screens.current[u'size'].width())).toString())
|
|
||||||
else:
|
|
||||||
self.customXValueEdit.setText(
|
|
||||||
unicode(self.screens.current[u'size'].x()))
|
|
||||||
self.customYValueEdit.setText(
|
|
||||||
unicode(self.screens.current[u'size'].y()))
|
|
||||||
self.customHeightValueEdit.setText(
|
|
||||||
unicode(self.screens.current[u'size'].height()))
|
|
||||||
self.customWidthValueEdit.setText(
|
|
||||||
unicode(self.screens.current[u'size'].width()))
|
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
||||||
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
||||||
|
@ -436,10 +426,8 @@ class GeneralTab(SettingsTab):
|
||||||
QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked()))
|
QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked()))
|
||||||
settings.setValue(u'auto preview',
|
settings.setValue(u'auto preview',
|
||||||
QtCore.QVariant(self.autoPreviewCheckBox.isChecked()))
|
QtCore.QVariant(self.autoPreviewCheckBox.isChecked()))
|
||||||
settings.setValue(u'loop delay',
|
settings.setValue(u'loop delay',
|
||||||
QtCore.QVariant(self.timeoutSpinBox.value()))
|
QtCore.QVariant(self.timeoutSpinBox.value()))
|
||||||
Receiver.send_message(u'slidecontroller_live_spin_delay',
|
|
||||||
self.timeoutSpinBox.value())
|
|
||||||
settings.setValue(u'ccli number',
|
settings.setValue(u'ccli number',
|
||||||
QtCore.QVariant(self.numberEdit.displayText()))
|
QtCore.QVariant(self.numberEdit.displayText()))
|
||||||
settings.setValue(u'songselect username',
|
settings.setValue(u'songselect username',
|
||||||
|
@ -459,17 +447,18 @@ class GeneralTab(SettingsTab):
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
self.screens.display = self.displayOnMonitorCheck.isChecked()
|
self.screens.display = self.displayOnMonitorCheck.isChecked()
|
||||||
# Monitor Number has changed.
|
# Monitor Number has changed.
|
||||||
|
postUpdate = False
|
||||||
if self.screens.monitor_number != self.monitorNumber:
|
if self.screens.monitor_number != self.monitorNumber:
|
||||||
self.screens.monitor_number = self.monitorNumber
|
self.screens.monitor_number = self.monitorNumber
|
||||||
self.screens.set_current_display(self.monitorNumber)
|
self.screens.set_current_display(self.monitorNumber)
|
||||||
Receiver.send_message(u'config_screen_changed')
|
postUpdate = True
|
||||||
Receiver.send_message(u'config_updated')
|
|
||||||
# On save update the screens as well
|
# On save update the screens as well
|
||||||
self.postSetUp()
|
self.postSetUp(postUpdate)
|
||||||
|
|
||||||
def postSetUp(self):
|
def postSetUp(self, postUpdate=False):
|
||||||
"""
|
"""
|
||||||
Apply settings after settings tab has loaded
|
Apply settings after settings tab has loaded and most of the
|
||||||
|
system so must be delayed
|
||||||
"""
|
"""
|
||||||
Receiver.send_message(u'slidecontroller_live_spin_delay',
|
Receiver.send_message(u'slidecontroller_live_spin_delay',
|
||||||
self.timeoutSpinBox.value())
|
self.timeoutSpinBox.value())
|
||||||
|
@ -480,12 +469,14 @@ class GeneralTab(SettingsTab):
|
||||||
int(self.customYValueEdit.text()),
|
int(self.customYValueEdit.text()),
|
||||||
int(self.customWidthValueEdit.text()),
|
int(self.customWidthValueEdit.text()),
|
||||||
int(self.customHeightValueEdit.text()))
|
int(self.customHeightValueEdit.text()))
|
||||||
if self.overrideCheckBox.isChecked():
|
if self.overrideCheckBox.isChecked():
|
||||||
self.screens.set_override_display()
|
self.screens.set_override_display()
|
||||||
Receiver.send_message(u'config_screen_changed')
|
else:
|
||||||
else:
|
self.screens.reset_current_display()
|
||||||
self.screens.reset_current_display()
|
# Order is important so be careful if you change
|
||||||
Receiver.send_message(u'config_screen_changed')
|
if self.overrideChanged or postUpdate:
|
||||||
|
Receiver.send_message(u'config_screen_changed')
|
||||||
|
self.overrideChanged = False
|
||||||
|
|
||||||
def onOverrideCheckBoxToggled(self, checked):
|
def onOverrideCheckBoxToggled(self, checked):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -26,149 +26,46 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui, QtWebKit
|
from PyQt4 import QtCore, QtGui, QtWebKit
|
||||||
from PyQt4.phonon import Phonon
|
from PyQt4.phonon import Phonon
|
||||||
|
|
||||||
from openlp.core.lib import Receiver, resize_image
|
from openlp.core.lib import Receiver, resize_image, build_html, ServiceItem, \
|
||||||
|
image_to_byte
|
||||||
from openlp.core.ui import HideMode
|
from openlp.core.ui import HideMode
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
#http://www.steveheffernan.com/html5-video-player/demo-video-player.html
|
#http://www.steveheffernan.com/html5-video-player/demo-video-player.html
|
||||||
HTMLVIDEO = u"""<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
*{
|
|
||||||
margin: 0;
|
|
||||||
padding:0
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript" charset="utf-8">
|
|
||||||
var video;
|
|
||||||
var bodyLoaded = function(){
|
|
||||||
video = document.getElementById("video");
|
|
||||||
video.volume = 0;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body id="body" onload="bodyLoaded();">
|
|
||||||
<video id="video" src="%s" autoplay="autoplay" loop="loop"
|
|
||||||
width="%s" height="%s" autobuffer="autobuffer" preload="preload" />
|
|
||||||
</body></html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
class DisplayManager(QtGui.QWidget):
|
|
||||||
"""
|
|
||||||
Wrapper class to hold the display widgets.
|
|
||||||
I will provide API's in future to access the screens allow for
|
|
||||||
extra displays to be added.
|
|
||||||
RenderManager is poked in by MainWindow
|
|
||||||
"""
|
|
||||||
def __init__(self, screens):
|
|
||||||
QtGui.QWidget.__init__(self)
|
|
||||||
self.screens = screens
|
|
||||||
self.videoDisplay = VideoDisplay(self, screens)
|
|
||||||
self.audioPlayer = AudioPlayer(self)
|
|
||||||
self.mainDisplay = MainDisplay(self, screens)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'maindisplay_hide'), self.hideDisplay)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'maindisplay_show'), self.showDisplay)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'videodisplay_start'), self.onStartVideo)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'videodisplay_stop'), self.onStopVideo)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
self.videoDisplay.setup()
|
|
||||||
self.mainDisplay.setup()
|
|
||||||
|
|
||||||
def hideDisplay(self, message):
|
|
||||||
"""
|
|
||||||
Hide the output displays
|
|
||||||
"""
|
|
||||||
self.videoDisplay.mediaHide(message)
|
|
||||||
self.mainDisplay.hideDisplay(message)
|
|
||||||
|
|
||||||
def showDisplay(self):
|
|
||||||
"""
|
|
||||||
Hide the output displays
|
|
||||||
"""
|
|
||||||
self.videoDisplay.mediaShow()
|
|
||||||
self.mainDisplay.showDisplay()
|
|
||||||
|
|
||||||
def addAlert(self, alertMessage, location):
|
|
||||||
"""
|
|
||||||
Handles the addition of an Alert Message to the Displays
|
|
||||||
"""
|
|
||||||
self.mainDisplay.addAlert(alertMessage, location)
|
|
||||||
|
|
||||||
def displayImageWithText(self, frame):
|
|
||||||
"""
|
|
||||||
Handles the addition of a background Image to the displays
|
|
||||||
"""
|
|
||||||
self.mainDisplay.addImageWithText(frame)
|
|
||||||
|
|
||||||
def displayImage(self, frame):
|
|
||||||
"""
|
|
||||||
Handles the addition of a background Image to the displays
|
|
||||||
"""
|
|
||||||
self.mainDisplay.displayImage(frame)
|
|
||||||
|
|
||||||
def displayVideo(self, path):
|
|
||||||
"""
|
|
||||||
Handles the addition of a background Video to the displays
|
|
||||||
"""
|
|
||||||
self.mainDisplay.displayVideo(path)
|
|
||||||
|
|
||||||
def onStartVideo(self, item):
|
|
||||||
"""
|
|
||||||
Handles the Starting of a Video and Display Management
|
|
||||||
"""
|
|
||||||
self.videoDisplay.setVisible(True)
|
|
||||||
self.mainDisplay.setVisible(False)
|
|
||||||
self.videoDisplay.onMediaQueue(item)
|
|
||||||
|
|
||||||
def onStopVideo(self):
|
|
||||||
"""
|
|
||||||
Handles the Stopping of a Video and Display Management
|
|
||||||
"""
|
|
||||||
self.mainDisplay.setVisible(True)
|
|
||||||
self.videoDisplay.setVisible(False)
|
|
||||||
self.videoDisplay.onMediaStop()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Handles the closure of the displays
|
|
||||||
"""
|
|
||||||
self.videoDisplay.close()
|
|
||||||
self.audioPlayer.close()
|
|
||||||
self.mainDisplay.close()
|
|
||||||
|
|
||||||
|
|
||||||
class DisplayWidget(QtGui.QGraphicsView):
|
class DisplayWidget(QtGui.QGraphicsView):
|
||||||
"""
|
"""
|
||||||
Customised version of QTableWidget which can respond to keyboard
|
Customised version of QTableWidget which can respond to keyboard
|
||||||
events.
|
events.
|
||||||
"""
|
"""
|
||||||
log.info(u'MainDisplay loaded')
|
log.info(u'Display Widget loaded')
|
||||||
|
|
||||||
def __init__(self, parent=None, name=None, primary=False):
|
def __init__(self, live, parent=None):
|
||||||
QtGui.QWidget.__init__(self, None)
|
QtGui.QGraphicsView.__init__(self)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.primary = primary
|
self.live = live
|
||||||
self.hotkey_map = {
|
self.hotkey_map = {
|
||||||
QtCore.Qt.Key_Return: 'servicemanager_next_item',
|
QtCore.Qt.Key_Return: 'servicemanager_next_item',
|
||||||
QtCore.Qt.Key_Space: 'slidecontroller_live_next_noloop',
|
QtCore.Qt.Key_Space: 'slidecontroller_live_next_noloop',
|
||||||
QtCore.Qt.Key_Enter: 'slidecontroller_live_next_noloop',
|
QtCore.Qt.Key_Enter: 'slidecontroller_live_next_noloop',
|
||||||
QtCore.Qt.Key_0: 'servicemanager_next_item',
|
QtCore.Qt.Key_0: 'servicemanager_next_item',
|
||||||
QtCore.Qt.Key_Backspace: 'slidecontroller_live_previous_noloop'}
|
QtCore.Qt.Key_Backspace: 'slidecontroller_live_previous_noloop'}
|
||||||
|
self.setStyleSheet(u'border: none;')
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
|
"""
|
||||||
|
Handle key events from display screen
|
||||||
|
"""
|
||||||
|
# Key events only needed for live
|
||||||
|
if not self.live:
|
||||||
|
return
|
||||||
if isinstance(event, QtGui.QKeyEvent):
|
if isinstance(event, QtGui.QKeyEvent):
|
||||||
#here accept the event and do something
|
# Here accept the event and do something
|
||||||
if event.key() == QtCore.Qt.Key_Up:
|
if event.key() == QtCore.Qt.Key_Up:
|
||||||
Receiver.send_message(u'slidecontroller_live_previous')
|
Receiver.send_message(u'slidecontroller_live_previous')
|
||||||
event.accept()
|
event.accept()
|
||||||
|
@ -185,157 +82,277 @@ class DisplayWidget(QtGui.QGraphicsView):
|
||||||
Receiver.send_message(self.hotkey_map[event.key()])
|
Receiver.send_message(self.hotkey_map[event.key()])
|
||||||
event.accept()
|
event.accept()
|
||||||
elif event.key() == QtCore.Qt.Key_Escape:
|
elif event.key() == QtCore.Qt.Key_Escape:
|
||||||
self.resetDisplay()
|
self.setVisible(False)
|
||||||
|
self.videoStop()
|
||||||
event.accept()
|
event.accept()
|
||||||
event.ignore()
|
event.ignore()
|
||||||
else:
|
else:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
|
|
||||||
def resetDisplay(self):
|
|
||||||
log.debug(u'resetDisplay')
|
|
||||||
Receiver.send_message(u'slidecontroller_live_stop_loop')
|
|
||||||
if self.primary:
|
|
||||||
self.setVisible(False)
|
|
||||||
else:
|
|
||||||
self.setVisible(True)
|
|
||||||
|
|
||||||
class MainDisplay(DisplayWidget):
|
class MainDisplay(DisplayWidget):
|
||||||
"""
|
|
||||||
This is the form that is used to display things on the projector.
|
|
||||||
"""
|
|
||||||
log.info(u'MainDisplay Loaded')
|
|
||||||
|
|
||||||
def __init__(self, parent, screens):
|
def __init__(self, parent, screens, live):
|
||||||
"""
|
DisplayWidget.__init__(self, live, parent=None)
|
||||||
The constructor for the display form.
|
self.parent = parent
|
||||||
|
|
||||||
``parent``
|
|
||||||
The parent widget.
|
|
||||||
|
|
||||||
``screens``
|
|
||||||
The list of screens.
|
|
||||||
"""
|
|
||||||
log.debug(u'Initialisation started')
|
|
||||||
DisplayWidget.__init__(self, parent, primary=True)
|
|
||||||
self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)
|
|
||||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
# WA_TranslucentBackground is not available in QT4.4
|
|
||||||
try:
|
|
||||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.screens = screens
|
self.screens = screens
|
||||||
self.setupScene()
|
self.isLive = live
|
||||||
self.setupVideo()
|
self.alertTab = None
|
||||||
self.setupImage()
|
self.hide_mode = None
|
||||||
self.setupText()
|
self.setWindowTitle(u'OpenLP Display')
|
||||||
self.setupAlert()
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
||||||
self.setupBlank()
|
QtCore.Qt.WindowStaysOnTopHint)
|
||||||
self.blankFrame = None
|
if self.isLive:
|
||||||
self.frame = None
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
#Hide desktop for now until we know where to put it
|
QtCore.SIGNAL(u'maindisplay_hide'), self.hideDisplay)
|
||||||
#and what size it should be.
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
self.setVisible(False)
|
QtCore.SIGNAL(u'maindisplay_show'), self.showDisplay)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""
|
"""
|
||||||
Sets up the screen on a particular screen.
|
Set up and build the output screen
|
||||||
"""
|
"""
|
||||||
log.debug(u'Setup %s for %s ' % (
|
log.debug(u'Setup live = %s for %s ' % (self.isLive,
|
||||||
self.screens, self.screens.monitor_number))
|
self.screens.monitor_number))
|
||||||
self.setVisible(False)
|
|
||||||
self.screen = self.screens.current
|
self.screen = self.screens.current
|
||||||
#Sort out screen locations and sizes
|
self.setVisible(False)
|
||||||
self.setGeometry(self.screen[u'size'])
|
self.setGeometry(self.screen[u'size'])
|
||||||
self.scene.setSceneRect(0, 0, self.size().width(),
|
self.scene = QtGui.QGraphicsScene()
|
||||||
self.size().height())
|
|
||||||
self.webView.setGeometry(0, 0, self.size().width(),
|
|
||||||
self.size().height())
|
|
||||||
self.alertText.setTextWidth(self.size().width())
|
|
||||||
#Build a custom splash screen
|
|
||||||
self.initialFrame = QtGui.QImage(
|
|
||||||
self.screen[u'size'].width(),
|
|
||||||
self.screen[u'size'].height(),
|
|
||||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
|
||||||
splash_image = QtGui.QImage(u':/graphics/openlp-splash-screen.png')
|
|
||||||
painter_image = QtGui.QPainter()
|
|
||||||
painter_image.begin(self.initialFrame)
|
|
||||||
painter_image.fillRect(self.initialFrame.rect(), QtCore.Qt.white)
|
|
||||||
painter_image.drawImage(
|
|
||||||
(self.screen[u'size'].width() - splash_image.width()) / 2,
|
|
||||||
(self.screen[u'size'].height() - splash_image.height()) / 2,
|
|
||||||
splash_image)
|
|
||||||
#build a blank transparent image
|
|
||||||
self.transparent = QtGui.QPixmap(
|
|
||||||
self.screen[u'size'].width(), self.screen[u'size'].height())
|
|
||||||
self.transparent.fill(QtCore.Qt.transparent)
|
|
||||||
self.displayImage(self.initialFrame)
|
|
||||||
self.repaint()
|
|
||||||
#Build a Black screen
|
|
||||||
painter = QtGui.QPainter()
|
|
||||||
self.blankFrame = QtGui.QImage(
|
|
||||||
self.screen[u'size'].width(),
|
|
||||||
self.screen[u'size'].height(),
|
|
||||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
|
||||||
painter.begin(self.blankFrame)
|
|
||||||
painter.fillRect(self.blankFrame.rect(), QtCore.Qt.black)
|
|
||||||
# To display or not to display?
|
|
||||||
if not self.screen[u'primary']:
|
|
||||||
self.setVisible(True)
|
|
||||||
self.primary = False
|
|
||||||
else:
|
|
||||||
self.setVisible(False)
|
|
||||||
self.primary = True
|
|
||||||
|
|
||||||
def setupScene(self):
|
|
||||||
self.scene = QtGui.QGraphicsScene(self)
|
|
||||||
self.scene.setSceneRect(0, 0, self.size().width(), self.size().height())
|
|
||||||
self.setScene(self.scene)
|
self.setScene(self.scene)
|
||||||
|
self.webView = QtWebKit.QGraphicsWebView()
|
||||||
def setupVideo(self):
|
self.scene.addItem(self.webView)
|
||||||
self.webView = QtWebKit.QWebView()
|
self.webView.resize(self.screen[u'size'].width(), \
|
||||||
|
self.screen[u'size'].height())
|
||||||
self.page = self.webView.page()
|
self.page = self.webView.page()
|
||||||
self.videoDisplay = self.page.mainFrame()
|
self.frame = self.page.mainFrame()
|
||||||
self.videoDisplay.setScrollBarPolicy(QtCore.Qt.Vertical,
|
QtCore.QObject.connect(self.webView,
|
||||||
|
QtCore.SIGNAL(u'loadFinished(bool)'), self.isLoaded)
|
||||||
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
|
||||||
QtCore.Qt.ScrollBarAlwaysOff)
|
QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.videoDisplay.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||||
QtCore.Qt.ScrollBarAlwaysOff)
|
QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.proxy = QtGui.QGraphicsProxyWidget()
|
if self.isLive:
|
||||||
self.proxy.setWidget(self.webView)
|
# Build the initial frame.
|
||||||
self.proxy.setWindowFlags(QtCore.Qt.Window |
|
self.black = QtGui.QImage(
|
||||||
QtCore.Qt.FramelessWindowHint)
|
self.screens.current[u'size'].width(),
|
||||||
self.proxy.setZValue(1)
|
self.screens.current[u'size'].height(),
|
||||||
self.scene.addItem(self.proxy)
|
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
|
painter_image = QtGui.QPainter()
|
||||||
|
painter_image.begin(self.black)
|
||||||
|
painter_image.fillRect(self.black.rect(), QtCore.Qt.black)
|
||||||
|
# Build the initial frame.
|
||||||
|
initialFrame = QtGui.QImage(
|
||||||
|
self.screens.current[u'size'].width(),
|
||||||
|
self.screens.current[u'size'].height(),
|
||||||
|
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
|
splash_image = QtGui.QImage(u':/graphics/openlp-splash-screen.png')
|
||||||
|
painter_image = QtGui.QPainter()
|
||||||
|
painter_image.begin(initialFrame)
|
||||||
|
painter_image.fillRect(initialFrame.rect(), QtCore.Qt.white)
|
||||||
|
painter_image.drawImage(
|
||||||
|
(self.screens.current[u'size'].width() \
|
||||||
|
- splash_image.width()) / 2,
|
||||||
|
(self.screens.current[u'size'].height() \
|
||||||
|
- splash_image.height()) / 2,
|
||||||
|
splash_image)
|
||||||
|
serviceItem = ServiceItem()
|
||||||
|
serviceItem.bg_frame = initialFrame
|
||||||
|
self.webView.setHtml(build_html(serviceItem, self.screen,
|
||||||
|
self.parent.alertTab, self.isLive))
|
||||||
|
self.initialFrame = True
|
||||||
|
# To display or not to display?
|
||||||
|
if not self.screen[u'primary']:
|
||||||
|
self.show()
|
||||||
|
self.primary = False
|
||||||
|
else:
|
||||||
|
self.primary = True
|
||||||
|
|
||||||
def setupImage(self):
|
def text(self, slide):
|
||||||
self.imageDisplay = QtGui.QGraphicsPixmapItem()
|
"""
|
||||||
self.imageDisplay.setZValue(2)
|
Add the slide text from slideController
|
||||||
self.scene.addItem(self.imageDisplay)
|
|
||||||
|
|
||||||
def setupText(self):
|
`slide`
|
||||||
#self.displayText = QtGui.QGraphicsTextItem()
|
The slide text to be displayed
|
||||||
self.displayText = QtGui.QGraphicsPixmapItem()
|
"""
|
||||||
#self.displayText.setPos(0,0)
|
log.debug(u'text')
|
||||||
#self.displayText.setTextWidth(self.size().width())
|
self.frame.evaluateJavaScript(u'show_text("%s")' % \
|
||||||
self.displayText.setZValue(4)
|
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
|
||||||
self.scene.addItem(self.displayText)
|
return self.preview()
|
||||||
|
|
||||||
def setupAlert(self):
|
def alert(self, text):
|
||||||
self.alertText = QtGui.QGraphicsTextItem()
|
"""
|
||||||
self.alertText.setZValue(8)
|
Add the alert text
|
||||||
self.scene.addItem(self.alertText)
|
|
||||||
|
|
||||||
def setupBlank(self):
|
`slide`
|
||||||
self.displayBlank = QtGui.QGraphicsPixmapItem()
|
The slide text to be displayed
|
||||||
self.displayBlank.setZValue(10)
|
"""
|
||||||
self.scene.addItem(self.displayBlank)
|
log.debug(u'alert')
|
||||||
|
if self.height() != self.screen[u'size'].height() \
|
||||||
|
or not self.isVisible():
|
||||||
|
shrink = True
|
||||||
|
else:
|
||||||
|
shrink = False
|
||||||
|
js = u'show_alert("%s", "%s")' % (
|
||||||
|
text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'),
|
||||||
|
u'top' if shrink else u'')
|
||||||
|
height = self.frame.evaluateJavaScript(js)
|
||||||
|
if shrink:
|
||||||
|
if text:
|
||||||
|
self.resize(self.width(), int(height.toString()))
|
||||||
|
self.setVisible(True)
|
||||||
|
else:
|
||||||
|
self.setGeometry(self.screen[u'size'])
|
||||||
|
self.setVisible(False)
|
||||||
|
|
||||||
# def hideDisplayForVideo(self):
|
def image(self, image):
|
||||||
# """
|
"""
|
||||||
# Hides the main display if for the video to be played
|
Add an image as the background. The image is converted to a
|
||||||
# """
|
bytestream on route.
|
||||||
# self.hideDisplay(HideMode.Screen)
|
|
||||||
|
`Image`
|
||||||
|
The Image to be displayed can be QImage or QPixmap
|
||||||
|
"""
|
||||||
|
log.debug(u'image')
|
||||||
|
image = resize_image(image, self.screen[u'size'].width(),
|
||||||
|
self.screen[u'size'].height())
|
||||||
|
self.resetVideo()
|
||||||
|
self.displayImage(image)
|
||||||
|
# show screen
|
||||||
|
if self.isLive:
|
||||||
|
self.setVisible(True)
|
||||||
|
|
||||||
|
def displayImage(self, image):
|
||||||
|
"""
|
||||||
|
Display an image, as is.
|
||||||
|
"""
|
||||||
|
if image:
|
||||||
|
js = u'show_image("data:image/png;base64,%s");' % \
|
||||||
|
image_to_byte(image)
|
||||||
|
else:
|
||||||
|
js = u'show_image("");'
|
||||||
|
self.frame.evaluateJavaScript(js)
|
||||||
|
|
||||||
|
def resetImage(self):
|
||||||
|
"""
|
||||||
|
Reset the backgound image to the service item image.
|
||||||
|
Used after Image plugin has changed the background
|
||||||
|
"""
|
||||||
|
log.debug(u'resetImage')
|
||||||
|
self.displayImage(self.serviceItem.bg_frame)
|
||||||
|
|
||||||
|
def resetVideo(self):
|
||||||
|
"""
|
||||||
|
Used after Video plugin has changed the background
|
||||||
|
"""
|
||||||
|
log.debug(u'resetVideo')
|
||||||
|
self.frame.evaluateJavaScript(u'show_video("close");')
|
||||||
|
|
||||||
|
def videoPlay(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to play a loaded video
|
||||||
|
"""
|
||||||
|
log.debug(u'videoPlay')
|
||||||
|
self.frame.evaluateJavaScript(u'show_video("play");')
|
||||||
|
# show screen
|
||||||
|
if self.isLive:
|
||||||
|
self.setVisible(True)
|
||||||
|
|
||||||
|
def videoPause(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to pause a loaded video
|
||||||
|
"""
|
||||||
|
log.debug(u'videoPause')
|
||||||
|
self.frame.evaluateJavaScript(u'show_video("pause");')
|
||||||
|
|
||||||
|
def videoStop(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to stop a loaded video
|
||||||
|
"""
|
||||||
|
log.debug(u'videoStop')
|
||||||
|
self.frame.evaluateJavaScript(u'show_video("stop");')
|
||||||
|
|
||||||
|
def videoVolume(self, volume):
|
||||||
|
"""
|
||||||
|
Changes the volume of a running video
|
||||||
|
"""
|
||||||
|
log.debug(u'videoVolume %d' % volume)
|
||||||
|
self.frame.evaluateJavaScript(u'show_video(null, null, %s);' %
|
||||||
|
str(float(volume)/float(10)))
|
||||||
|
|
||||||
|
def video(self, videoPath, volume):
|
||||||
|
"""
|
||||||
|
Loads and starts a video to run with the option of sound
|
||||||
|
"""
|
||||||
|
log.debug(u'video')
|
||||||
|
self.loaded = True
|
||||||
|
js = u'show_video("play", "%s", %s, true);' % \
|
||||||
|
(videoPath.replace(u'\\', u'\\\\'), str(float(volume)/float(10)))
|
||||||
|
self.frame.evaluateJavaScript(js)
|
||||||
|
return self.preview()
|
||||||
|
|
||||||
|
def isLoaded(self):
|
||||||
|
"""
|
||||||
|
Called by webView event to show display is fully loaded
|
||||||
|
"""
|
||||||
|
log.debug(u'loaded')
|
||||||
|
self.loaded = True
|
||||||
|
|
||||||
|
def preview(self):
|
||||||
|
"""
|
||||||
|
Generates a preview of the image displayed.
|
||||||
|
"""
|
||||||
|
log.debug(u'preview for %s', self.isLive)
|
||||||
|
if self.isLive:
|
||||||
|
# Wait for the fade to finish before geting the preview.
|
||||||
|
# Important otherwise preview will have incorrect text if at all !
|
||||||
|
if self.serviceItem.themedata and \
|
||||||
|
self.serviceItem.themedata.display_slideTransition:
|
||||||
|
while self.frame.evaluateJavaScript(u'show_text_complete()') \
|
||||||
|
.toString() == u'false':
|
||||||
|
Receiver.send_message(u'openlp_process_events')
|
||||||
|
# Wait for the webview to update before geting the preview.
|
||||||
|
# Important otherwise first preview will miss the background !
|
||||||
|
while not self.loaded:
|
||||||
|
Receiver.send_message(u'openlp_process_events')
|
||||||
|
preview = QtGui.QImage(self.screen[u'size'].width(),
|
||||||
|
self.screen[u'size'].height(),
|
||||||
|
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
|
painter = QtGui.QPainter(preview)
|
||||||
|
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||||
|
self.frame.render(painter)
|
||||||
|
painter.end()
|
||||||
|
# Make display show up if in single screen mode
|
||||||
|
if self.isLive:
|
||||||
|
self.setVisible(True)
|
||||||
|
return preview
|
||||||
|
|
||||||
|
def buildHtml(self, serviceItem):
|
||||||
|
"""
|
||||||
|
Store the serviceItem and build the new HTML from it. Add the
|
||||||
|
HTML to the display
|
||||||
|
"""
|
||||||
|
log.debug(u'buildHtml')
|
||||||
|
self.loaded = False
|
||||||
|
self.initialFrame = False
|
||||||
|
self.serviceItem = serviceItem
|
||||||
|
html = build_html(self.serviceItem, self.screen, self.parent.alertTab,\
|
||||||
|
self.isLive)
|
||||||
|
self.webView.setHtml(html)
|
||||||
|
if serviceItem.foot_text and 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):
|
||||||
|
"""
|
||||||
|
Display the Footer
|
||||||
|
"""
|
||||||
|
log.debug(u'footer')
|
||||||
|
js = "show_footer('" + \
|
||||||
|
text.replace("\\", "\\\\").replace("\'", "\\\'") + "')"
|
||||||
|
self.frame.evaluateJavaScript(js)
|
||||||
|
|
||||||
def hideDisplay(self, mode=HideMode.Screen):
|
def hideDisplay(self, mode=HideMode.Screen):
|
||||||
"""
|
"""
|
||||||
|
@ -343,22 +360,16 @@ class MainDisplay(DisplayWidget):
|
||||||
Store the images so they can be replaced when required
|
Store the images so they can be replaced when required
|
||||||
"""
|
"""
|
||||||
log.debug(u'hideDisplay mode = %d', mode)
|
log.debug(u'hideDisplay mode = %d', mode)
|
||||||
#self.displayText.setPixmap(self.transparent)
|
|
||||||
if mode == HideMode.Screen:
|
if mode == HideMode.Screen:
|
||||||
#self.display_image.setPixmap(self.transparent)
|
self.frame.evaluateJavaScript(u'show_blank("desktop");')
|
||||||
self.setVisible(False)
|
self.setVisible(False)
|
||||||
elif mode == HideMode.Blank:
|
elif mode == HideMode.Blank or self.initialFrame:
|
||||||
self.displayBlank.setPixmap(
|
self.frame.evaluateJavaScript(u'show_blank("black");')
|
||||||
QtGui.QPixmap.fromImage(self.blankFrame))
|
|
||||||
else:
|
else:
|
||||||
if self.parent.renderManager.renderer.bg_frame:
|
self.frame.evaluateJavaScript(u'show_blank("theme");')
|
||||||
self.displayBlank.setPixmap(QtGui.QPixmap.fromImage(
|
|
||||||
self.parent.renderManager.renderer.bg_frame))
|
|
||||||
else:
|
|
||||||
self.displayBlank.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(self.blankFrame))
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
|
@ -367,275 +378,17 @@ class MainDisplay(DisplayWidget):
|
||||||
Make the stored images None to release memory.
|
Make the stored images None to release memory.
|
||||||
"""
|
"""
|
||||||
log.debug(u'showDisplay')
|
log.debug(u'showDisplay')
|
||||||
self.displayBlank.setPixmap(self.transparent)
|
self.frame.evaluateJavaScript('show_blank("show");')
|
||||||
if self.isHidden():
|
if self.isHidden():
|
||||||
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
|
||||||
def addImageWithText(self, frame):
|
|
||||||
log.debug(u'addImageWithText')
|
|
||||||
frame = resize_image(
|
|
||||||
frame, self.screen[u'size'].width(), self.screen[u'size'].height())
|
|
||||||
self.imageDisplay.setPixmap(QtGui.QPixmap.fromImage(frame))
|
|
||||||
self.videoDisplay.setHtml(u'<html></html>')
|
|
||||||
|
|
||||||
def addAlert(self, message, location):
|
|
||||||
"""
|
|
||||||
Places the Alert text on the display at the correct location
|
|
||||||
``message``
|
|
||||||
Text to be displayed
|
|
||||||
``location``
|
|
||||||
Where on the screen the text should be. From the AlertTab
|
|
||||||
Combo box.
|
|
||||||
"""
|
|
||||||
log.debug(u'addAlertImage')
|
|
||||||
if location == 0:
|
|
||||||
self.alertText.setPos(0, 0)
|
|
||||||
elif location == 1:
|
|
||||||
self.alertText.setPos(0, self.size().height() / 2)
|
|
||||||
else:
|
|
||||||
self.alertText.setPos(0, self.size().height() - 76)
|
|
||||||
self.alertText.setHtml(message)
|
|
||||||
|
|
||||||
def displayImage(self, frame):
|
|
||||||
"""
|
|
||||||
Places the Image passed on the display screen
|
|
||||||
``frame``
|
|
||||||
The image to be displayed
|
|
||||||
"""
|
|
||||||
log.debug(u'adddisplayImage')
|
|
||||||
if isinstance(frame, QtGui.QImage):
|
|
||||||
self.imageDisplay.setPixmap(QtGui.QPixmap.fromImage(frame))
|
|
||||||
else:
|
|
||||||
self.imageDisplay.setPixmap(frame)
|
|
||||||
self.frameView(self.transparent)
|
|
||||||
self.videoDisplay.setHtml(u'<html></html>')
|
|
||||||
|
|
||||||
def displayVideo(self, path):
|
|
||||||
"""
|
|
||||||
Places the Video passed on the display screen
|
|
||||||
``path``
|
|
||||||
The path to the image to be displayed
|
|
||||||
"""
|
|
||||||
log.debug(u'adddisplayVideo')
|
|
||||||
self.displayImage(self.transparent)
|
|
||||||
self.videoDisplay.setHtml(HTMLVIDEO %
|
|
||||||
(path, self.screen[u'size'].width(),
|
|
||||||
self.screen[u'size'].height()))
|
|
||||||
|
|
||||||
def frameView(self, frame, transition=False):
|
|
||||||
"""
|
|
||||||
Called from a slide controller to display a frame
|
|
||||||
if the alert is in progress the alert is added on top
|
|
||||||
``frame``
|
|
||||||
Image frame to be rendered
|
|
||||||
``transition``
|
|
||||||
Are transitions required.
|
|
||||||
"""
|
|
||||||
log.debug(u'frameView')
|
|
||||||
if transition:
|
|
||||||
if self.frame is not None:
|
|
||||||
self.displayText.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(self.frame))
|
|
||||||
self.repaint()
|
|
||||||
Receiver.send_message(u'openlp_process_events')
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.frame = None
|
|
||||||
if frame[u'trans'] is not None:
|
|
||||||
self.displayText.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(frame[u'trans']))
|
|
||||||
self.repaint()
|
|
||||||
Receiver.send_message(u'openlp_process_events')
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.frame = frame[u'trans']
|
|
||||||
self.displayText.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(frame[u'main']))
|
|
||||||
else:
|
|
||||||
if isinstance(frame, QtGui.QPixmap):
|
|
||||||
self.displayText.setPixmap(frame)
|
|
||||||
else:
|
|
||||||
self.displayText.setPixmap(QtGui.QPixmap.fromImage(frame))
|
|
||||||
if not self.isVisible() and self.screens.current['primary']:
|
|
||||||
self.setVisible(True)
|
|
||||||
|
|
||||||
class VideoDisplay(Phonon.VideoWidget):
|
|
||||||
"""
|
|
||||||
This is the form that is used to display videos on the projector.
|
|
||||||
"""
|
|
||||||
log.info(u'VideoDisplay Loaded')
|
|
||||||
|
|
||||||
def __init__(self, parent, screens,
|
|
||||||
aspect=Phonon.VideoWidget.AspectRatioWidget):
|
|
||||||
"""
|
|
||||||
The constructor for the display form.
|
|
||||||
|
|
||||||
``parent``
|
|
||||||
The parent widget.
|
|
||||||
|
|
||||||
``screens``
|
|
||||||
The list of screens.
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Initialisation started')
|
|
||||||
Phonon.VideoWidget.__init__(self)
|
|
||||||
self.setWindowTitle(u'OpenLP Video Display')
|
|
||||||
self.parent = parent
|
|
||||||
self.screens = screens
|
|
||||||
self.hidden = False
|
|
||||||
self.message = None
|
|
||||||
self.mediaActive = False
|
|
||||||
self.mediaObject = Phonon.MediaObject()
|
|
||||||
self.setAspectRatio(aspect)
|
|
||||||
self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory)
|
|
||||||
Phonon.createPath(self.mediaObject, self)
|
|
||||||
Phonon.createPath(self.mediaObject, self.audioObject)
|
|
||||||
flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Dialog
|
|
||||||
## # WindowsStaysOnBottomHint is not available in QT4.4
|
|
||||||
# try:
|
|
||||||
# flags = flags | QtCore.Qt.WindowStaysOnBottomHint
|
|
||||||
# except AttributeError:
|
|
||||||
# pass
|
|
||||||
self.setWindowFlags(flags)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'videodisplay_play'), self.onMediaPlay)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'videodisplay_pause'), self.onMediaPause)
|
|
||||||
# QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
# QtCore.SIGNAL(u'videodisplay_background'), self.onMediaBackground)
|
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
|
||||||
QtCore.SIGNAL(u'config_updated'), self.setup)
|
|
||||||
QtCore.QObject.connect(self.mediaObject,
|
|
||||||
QtCore.SIGNAL(u'finished()'), self.onMediaStop)
|
|
||||||
self.setVisible(False)
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if isinstance(event, QtGui.QKeyEvent):
|
|
||||||
#here accept the event and do something
|
|
||||||
if event.key() == QtCore.Qt.Key_Escape:
|
|
||||||
self.onMediaStop()
|
|
||||||
event.accept()
|
|
||||||
event.ignore()
|
|
||||||
else:
|
|
||||||
event.ignore()
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
"""
|
|
||||||
Sets up the screen on a particular screen.
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Setup %s for %s ' % (self.screens,
|
|
||||||
self.screens.monitor_number))
|
|
||||||
self.screen = self.screens.current
|
|
||||||
#Sort out screen locations and sizes
|
|
||||||
self.setGeometry(self.screen[u'size'])
|
|
||||||
# To display or not to display?
|
|
||||||
if not self.screen[u'primary']: # and self.isVisible():
|
|
||||||
#self.showFullScreen()
|
|
||||||
self.setVisible(False)
|
|
||||||
self.primary = False
|
|
||||||
else:
|
|
||||||
self.setVisible(False)
|
|
||||||
self.primary = True
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
"""
|
|
||||||
Shutting down so clean up connections
|
|
||||||
"""
|
|
||||||
self.onMediaStop()
|
|
||||||
for path in self.outputPaths():
|
|
||||||
path.disconnect()
|
|
||||||
|
|
||||||
# def onMediaBackground(self, message=None):
|
|
||||||
# """
|
|
||||||
# Play a video triggered from the video plugin with the
|
|
||||||
# file name passed in on the event.
|
|
||||||
# Also triggered from the Finish event so the video will loop
|
|
||||||
# if it is triggered from the plugin
|
|
||||||
# """
|
|
||||||
# log.debug(u'VideoDisplay Queue new media message %s' % message)
|
|
||||||
# #If not file take the stored one
|
|
||||||
# if not message:
|
|
||||||
# message = self.message
|
|
||||||
# # still no file name then stop as it was a normal video stopping
|
|
||||||
# if message:
|
|
||||||
# self.mediaObject.setCurrentSource(Phonon.MediaSource(message))
|
|
||||||
# self.message = message
|
|
||||||
# self._play()
|
|
||||||
|
|
||||||
def onMediaQueue(self, message):
|
|
||||||
"""
|
|
||||||
Set up a video to play from the serviceitem.
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Queue new media message %s' % message)
|
|
||||||
file = os.path.join(message.get_frame_path(),
|
|
||||||
message.get_frame_title())
|
|
||||||
self.mediaObject.setCurrentSource(Phonon.MediaSource(file))
|
|
||||||
self.mediaActive = True
|
|
||||||
self._play()
|
|
||||||
|
|
||||||
def onMediaPlay(self):
|
|
||||||
"""
|
|
||||||
Respond to the Play button on the slide controller unless the display
|
|
||||||
has been hidden by the slidecontroller
|
|
||||||
"""
|
|
||||||
if not self.hidden:
|
|
||||||
log.debug(u'VideoDisplay Play the new media, Live ')
|
|
||||||
self._play()
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
"""
|
|
||||||
We want to play the video so start it and display the screen
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay _play called')
|
|
||||||
self.mediaObject.play()
|
|
||||||
self.setVisible(True)
|
|
||||||
|
|
||||||
def onMediaPause(self):
|
|
||||||
"""
|
|
||||||
Pause the video and refresh the screen
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Media paused by user')
|
|
||||||
self.mediaObject.pause()
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
def onMediaStop(self):
|
|
||||||
"""
|
|
||||||
Stop the video and clean up
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Media stopped by user')
|
|
||||||
self.message = None
|
|
||||||
self.mediaActive = False
|
|
||||||
self.mediaObject.stop()
|
|
||||||
self.onMediaFinish()
|
|
||||||
|
|
||||||
def onMediaFinish(self):
|
|
||||||
"""
|
|
||||||
Clean up the Object queue
|
|
||||||
"""
|
|
||||||
log.debug(u'VideoDisplay Reached end of media playlist')
|
|
||||||
self.mediaObject.clearQueue()
|
|
||||||
self.setVisible(False)
|
|
||||||
|
|
||||||
def mediaHide(self, message=u''):
|
|
||||||
"""
|
|
||||||
Hide the video display
|
|
||||||
"""
|
|
||||||
self.mediaObject.pause()
|
|
||||||
self.hidden = True
|
|
||||||
self.setVisible(False)
|
|
||||||
|
|
||||||
def mediaShow(self):
|
|
||||||
"""
|
|
||||||
Show the video display if it was already hidden
|
|
||||||
"""
|
|
||||||
if self.hidden:
|
|
||||||
self.hidden = False
|
|
||||||
if self.mediaActive:
|
|
||||||
self._play()
|
|
||||||
|
|
||||||
class AudioPlayer(QtCore.QObject):
|
class AudioPlayer(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
This Class will play audio only allowing components to work with a
|
This Class will play audio only allowing components to work with a
|
||||||
soundtrack which does not take over the user interface.
|
soundtrack independent of the user interface.
|
||||||
"""
|
"""
|
||||||
log.info(u'AudioPlayer Loaded')
|
log.info(u'AudioPlayer Loaded')
|
||||||
|
|
||||||
|
@ -675,9 +428,9 @@ class AudioPlayer(QtCore.QObject):
|
||||||
Set up a video to play from the serviceitem.
|
Set up a video to play from the serviceitem.
|
||||||
"""
|
"""
|
||||||
log.debug(u'AudioPlayer Queue new media message %s' % message)
|
log.debug(u'AudioPlayer Queue new media message %s' % message)
|
||||||
file = os.path.join(message[0].get_frame_path(),
|
mfile = os.path.join(message[0].get_frame_path(),
|
||||||
message[0].get_frame_title())
|
message[0].get_frame_title())
|
||||||
self.mediaObject.setCurrentSource(Phonon.MediaSource(file))
|
self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile))
|
||||||
self.onMediaPlay()
|
self.onMediaPlay()
|
||||||
|
|
||||||
def onMediaPlay(self):
|
def onMediaPlay(self):
|
||||||
|
|
|
@ -29,7 +29,7 @@ import logging
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
|
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
|
||||||
ThemeManager, SlideController, PluginForm, MediaDockManager, DisplayManager
|
ThemeManager, SlideController, PluginForm, MediaDockManager
|
||||||
from openlp.core.lib import RenderManager, build_icon, OpenLPDockWidget, \
|
from openlp.core.lib import RenderManager, build_icon, OpenLPDockWidget, \
|
||||||
SettingsManager, PluginManager, Receiver, translate
|
SettingsManager, PluginManager, Receiver, translate
|
||||||
from openlp.core.utils import AppLocation, add_actions, LanguageManager
|
from openlp.core.utils import AppLocation, add_actions, LanguageManager
|
||||||
|
@ -94,8 +94,10 @@ class Ui_MainWindow(object):
|
||||||
self.ControlSplitter.setObjectName(u'ControlSplitter')
|
self.ControlSplitter.setObjectName(u'ControlSplitter')
|
||||||
self.MainContentLayout.addWidget(self.ControlSplitter)
|
self.MainContentLayout.addWidget(self.ControlSplitter)
|
||||||
# Create slide controllers
|
# Create slide controllers
|
||||||
self.PreviewController = SlideController(self, self.settingsmanager)
|
self.PreviewController = SlideController(self, self.settingsmanager,
|
||||||
self.LiveController = SlideController(self, self.settingsmanager, True)
|
self.screens)
|
||||||
|
self.LiveController = SlideController(self, self.settingsmanager,
|
||||||
|
self.screens, True)
|
||||||
# Create menu
|
# Create menu
|
||||||
self.MenuBar = QtGui.QMenuBar(MainWindow)
|
self.MenuBar = QtGui.QMenuBar(MainWindow)
|
||||||
self.MenuBar.setGeometry(QtCore.QRect(0, 0, 1087, 27))
|
self.MenuBar.setGeometry(QtCore.QRect(0, 0, 1087, 27))
|
||||||
|
@ -509,7 +511,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
self.songsSettingsSection = u'songs'
|
self.songsSettingsSection = u'songs'
|
||||||
self.serviceNotSaved = False
|
self.serviceNotSaved = False
|
||||||
self.settingsmanager = SettingsManager(screens)
|
self.settingsmanager = SettingsManager(screens)
|
||||||
self.displayManager = DisplayManager(screens)
|
|
||||||
self.aboutForm = AboutForm(self, applicationVersion)
|
self.aboutForm = AboutForm(self, applicationVersion)
|
||||||
self.settingsForm = SettingsForm(self.screens, self, self)
|
self.settingsForm = SettingsForm(self.screens, self, self)
|
||||||
self.recentFiles = QtCore.QStringList()
|
self.recentFiles = QtCore.QStringList()
|
||||||
|
@ -594,7 +595,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
#ThemeManager needs to call RenderManager
|
#ThemeManager needs to call RenderManager
|
||||||
self.RenderManager = RenderManager(
|
self.RenderManager = RenderManager(
|
||||||
self.ThemeManagerContents, self.screens)
|
self.ThemeManagerContents, self.screens)
|
||||||
self.displayManager.renderManager = self.RenderManager
|
|
||||||
#Define the media Dock Manager
|
#Define the media Dock Manager
|
||||||
self.mediaDockManager = MediaDockManager(self.MediaToolBox)
|
self.mediaDockManager = MediaDockManager(self.MediaToolBox)
|
||||||
log.info(u'Load Plugins')
|
log.info(u'Load Plugins')
|
||||||
|
@ -605,7 +605,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
self.plugin_helpers[u'service'] = self.ServiceManagerContents
|
self.plugin_helpers[u'service'] = self.ServiceManagerContents
|
||||||
self.plugin_helpers[u'settings form'] = self.settingsForm
|
self.plugin_helpers[u'settings form'] = self.settingsForm
|
||||||
self.plugin_helpers[u'toolbox'] = self.mediaDockManager
|
self.plugin_helpers[u'toolbox'] = self.mediaDockManager
|
||||||
self.plugin_helpers[u'displaymanager'] = self.displayManager
|
|
||||||
self.plugin_helpers[u'pluginmanager'] = self.plugin_manager
|
self.plugin_helpers[u'pluginmanager'] = self.plugin_manager
|
||||||
self.plugin_helpers[u'formparent'] = self
|
self.plugin_helpers[u'formparent'] = self
|
||||||
self.plugin_manager.find_plugins(pluginpath, self.plugin_helpers)
|
self.plugin_manager.find_plugins(pluginpath, self.plugin_helpers)
|
||||||
|
@ -662,9 +661,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
Show the main form, as well as the display form
|
Show the main form, as well as the display form
|
||||||
"""
|
"""
|
||||||
QtGui.QWidget.show(self)
|
QtGui.QWidget.show(self)
|
||||||
self.displayManager.setup()
|
self.LiveController.display.setup()
|
||||||
if self.displayManager.mainDisplay.isVisible():
|
self.PreviewController.display.setup()
|
||||||
self.displayManager.mainDisplay.setFocus()
|
if self.LiveController.display.isVisible():
|
||||||
|
self.LiveController.display.setFocus()
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
if QtCore.QSettings().value(
|
if QtCore.QSettings().value(
|
||||||
self.generalSettingsSection + u'/auto open',
|
self.generalSettingsSection + u'/auto open',
|
||||||
|
@ -744,8 +744,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
The screen has changed to so tell the displays to update_display
|
The screen has changed to so tell the displays to update_display
|
||||||
their locations
|
their locations
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'screenChanged')
|
||||||
self.RenderManager.update_display()
|
self.RenderManager.update_display()
|
||||||
self.displayManager.setup()
|
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
|
|
||||||
|
@ -791,8 +791,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
self.plugin_manager.finalise_plugins()
|
self.plugin_manager.finalise_plugins()
|
||||||
# Save settings
|
# Save settings
|
||||||
self.saveSettings()
|
self.saveSettings()
|
||||||
#Close down the displays
|
#Close down the display
|
||||||
self.displayManager.close()
|
self.LiveController.display.close()
|
||||||
|
|
||||||
def serviceChanged(self, reset=False, serviceName=None):
|
def serviceChanged(self, reset=False, serviceName=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -93,7 +93,6 @@ 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.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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -44,9 +44,9 @@ class ScreenList(object):
|
||||||
self.override = None
|
self.override = None
|
||||||
self.screen_list = []
|
self.screen_list = []
|
||||||
self.display_count = 0
|
self.display_count = 0
|
||||||
#actual display number
|
# actual display number
|
||||||
self.current_display = 0
|
self.current_display = 0
|
||||||
#save config display number
|
# save config display number
|
||||||
self.monitor_number = 0
|
self.monitor_number = 0
|
||||||
|
|
||||||
def add_screen(self, screen):
|
def add_screen(self, screen):
|
||||||
|
|
|
@ -317,9 +317,8 @@ class ServiceManager(QtGui.QWidget):
|
||||||
self.serviceItemEditForm.setServiceItem(
|
self.serviceItemEditForm.setServiceItem(
|
||||||
self.serviceItems[item][u'service_item'])
|
self.serviceItems[item][u'service_item'])
|
||||||
if self.serviceItemEditForm.exec_():
|
if self.serviceItemEditForm.exec_():
|
||||||
self.serviceItems[item][u'service_item'] = \
|
self.addServiceItem(self.serviceItemEditForm.getServiceItem(),
|
||||||
self.serviceItemEditForm.getServiceItem()
|
replace=True)
|
||||||
self.repaintServiceList(item, 0)
|
|
||||||
|
|
||||||
def nextItem(self):
|
def nextItem(self):
|
||||||
"""
|
"""
|
||||||
|
@ -575,7 +574,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'),
|
||||||
|
@ -633,6 +632,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(
|
||||||
|
@ -756,6 +757,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(
|
||||||
|
@ -768,6 +770,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)
|
||||||
|
@ -780,7 +783,8 @@ 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.
|
||||||
"""
|
"""
|
||||||
#force reset of renderer as theme data has changed
|
log.debug(u'regenerateServiceItems')
|
||||||
|
# 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:
|
||||||
tempServiceItems = self.serviceItems
|
tempServiceItems = self.serviceItems
|
||||||
|
@ -790,8 +794,8 @@ class ServiceManager(QtGui.QWidget):
|
||||||
for item in tempServiceItems:
|
for item in tempServiceItems:
|
||||||
self.addServiceItem(
|
self.addServiceItem(
|
||||||
item[u'service_item'], False, item[u'expanded'])
|
item[u'service_item'], False, item[u'expanded'])
|
||||||
#Set to False as items may have changed rendering
|
# Set to False as items may have changed rendering
|
||||||
#does not impact the saved song so True may also be valid
|
# does not impact the saved song so True may also be valid
|
||||||
self.parent.serviceChanged(False, self.serviceName)
|
self.parent.serviceChanged(False, self.serviceName)
|
||||||
|
|
||||||
def addServiceItem(self, item, rebuild=False, expand=True, replace=False):
|
def addServiceItem(self, item, rebuild=False, expand=True, replace=False):
|
||||||
|
@ -801,6 +805,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:
|
||||||
|
@ -873,6 +878,7 @@ class ServiceManager(QtGui.QWidget):
|
||||||
ItemCapabilities.AllowsPreview):
|
ItemCapabilities.AllowsPreview):
|
||||||
self.parent.PreviewController.addServiceManagerItem(
|
self.parent.PreviewController.addServiceManagerItem(
|
||||||
self.serviceItems[item][u'service_item'], 0)
|
self.serviceItems[item][u'service_item'], 0)
|
||||||
|
self.parent.LiveController.PreviewListWidget.setFocus()
|
||||||
else:
|
else:
|
||||||
QtGui.QMessageBox.critical(self,
|
QtGui.QMessageBox.critical(self,
|
||||||
translate('OpenLP.ServiceManager', 'Missing Display Handler'),
|
translate('OpenLP.ServiceManager', 'Missing Display Handler'),
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -25,36 +25,17 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
from PyQt4.phonon import Phonon
|
from PyQt4.phonon import Phonon
|
||||||
|
|
||||||
from openlp.core.ui import HideMode
|
from openlp.core.ui import HideMode, MainDisplay
|
||||||
from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \
|
from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \
|
||||||
ItemCapabilities, translate
|
ItemCapabilities, translate
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class SlideThread(QtCore.QThread):
|
|
||||||
"""
|
|
||||||
A special Qt thread class to speed up the display of text based frames.
|
|
||||||
This is threaded so it loads the frames in background
|
|
||||||
"""
|
|
||||||
def __init__(self, parent, prefix, count):
|
|
||||||
QtCore.QThread.__init__(self, parent)
|
|
||||||
self.prefix = prefix
|
|
||||||
self.count = count
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Run the thread.
|
|
||||||
"""
|
|
||||||
time.sleep(1)
|
|
||||||
for i in range(0, self.count):
|
|
||||||
Receiver.send_message(u'%s_slide_cache' % self.prefix, i)
|
|
||||||
|
|
||||||
class SlideList(QtGui.QTableWidget):
|
class SlideList(QtGui.QTableWidget):
|
||||||
"""
|
"""
|
||||||
Customised version of QTableWidget which can respond to keyboard
|
Customised version of QTableWidget which can respond to keyboard
|
||||||
|
@ -97,7 +78,7 @@ class SlideController(QtGui.QWidget):
|
||||||
SlideController is the slide controller widget. This widget is what the
|
SlideController is the slide controller widget. This widget is what the
|
||||||
user uses to control the displaying of verses/slides/etc on the screen.
|
user uses to control the displaying of verses/slides/etc on the screen.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, settingsmanager, isLive=False):
|
def __init__(self, parent, settingsmanager, screens, isLive=False):
|
||||||
"""
|
"""
|
||||||
Set up the Slide Controller.
|
Set up the Slide Controller.
|
||||||
"""
|
"""
|
||||||
|
@ -105,8 +86,10 @@ class SlideController(QtGui.QWidget):
|
||||||
self.settingsmanager = settingsmanager
|
self.settingsmanager = settingsmanager
|
||||||
self.isLive = isLive
|
self.isLive = isLive
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.mainDisplay = self.parent.displayManager.mainDisplay
|
self.screens = screens
|
||||||
self.displayManager = self.parent.displayManager
|
self.ratio = float(self.screens.current[u'size'].width()) / \
|
||||||
|
float(self.screens.current[u'size'].height())
|
||||||
|
self.display = MainDisplay(self, screens, isLive)
|
||||||
self.loopList = [
|
self.loopList = [
|
||||||
u'Start Loop',
|
u'Start Loop',
|
||||||
u'Loop Separator',
|
u'Loop Separator',
|
||||||
|
@ -115,13 +98,14 @@ class SlideController(QtGui.QWidget):
|
||||||
self.songEditList = [
|
self.songEditList = [
|
||||||
u'Edit Song',
|
u'Edit Song',
|
||||||
]
|
]
|
||||||
|
self.volume = 10
|
||||||
self.timer_id = 0
|
self.timer_id = 0
|
||||||
self.songEdit = False
|
self.songEdit = False
|
||||||
self.selectedRow = 0
|
self.selectedRow = 0
|
||||||
self.serviceItem = None
|
self.serviceItem = None
|
||||||
|
self.alertTab = None
|
||||||
self.Panel = QtGui.QWidget(parent.ControlSplitter)
|
self.Panel = QtGui.QWidget(parent.ControlSplitter)
|
||||||
self.slideList = {}
|
self.slideList = {}
|
||||||
self.canDisplay = True
|
|
||||||
# Layout for holding panel
|
# Layout for holding panel
|
||||||
self.PanelLayout = QtGui.QVBoxLayout(self.Panel)
|
self.PanelLayout = QtGui.QVBoxLayout(self.Panel)
|
||||||
self.PanelLayout.setSpacing(0)
|
self.PanelLayout.setSpacing(0)
|
||||||
|
@ -133,7 +117,8 @@ class SlideController(QtGui.QWidget):
|
||||||
self.split = 1
|
self.split = 1
|
||||||
self.typePrefix = u'live'
|
self.typePrefix = u'live'
|
||||||
else:
|
else:
|
||||||
self.TypeLabel.setText(translate('OpenLP.SlideController', 'Preview'))
|
self.TypeLabel.setText(translate('OpenLP.SlideController',
|
||||||
|
'Preview'))
|
||||||
self.split = 0
|
self.split = 0
|
||||||
self.typePrefix = u'preview'
|
self.typePrefix = u'preview'
|
||||||
self.TypeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;')
|
self.TypeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;')
|
||||||
|
@ -177,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'),
|
||||||
|
@ -190,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)
|
||||||
|
@ -213,20 +188,24 @@ class SlideController(QtGui.QWidget):
|
||||||
self.ThemeScreen.setCheckable(True)
|
self.ThemeScreen.setCheckable(True)
|
||||||
QtCore.QObject.connect(self.ThemeScreen,
|
QtCore.QObject.connect(self.ThemeScreen,
|
||||||
QtCore.SIGNAL("triggered(bool)"), self.onThemeDisplay)
|
QtCore.SIGNAL("triggered(bool)"), self.onThemeDisplay)
|
||||||
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(
|
if self.screens.display_count > 1:
|
||||||
u':/slides/slide_desktop.png'), u'Show Desktop', self.HideMenu)
|
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(
|
||||||
self.DesktopScreen.setCheckable(True)
|
u':/slides/slide_desktop.png'), u'Show Desktop',
|
||||||
QtCore.QObject.connect(self.DesktopScreen,
|
self.HideMenu)
|
||||||
QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
|
self.DesktopScreen.setCheckable(True)
|
||||||
|
QtCore.QObject.connect(self.DesktopScreen,
|
||||||
|
QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
|
||||||
self.HideMenu.setDefaultAction(self.BlankScreen)
|
self.HideMenu.setDefaultAction(self.BlankScreen)
|
||||||
self.HideMenu.menu().addAction(self.BlankScreen)
|
self.HideMenu.menu().addAction(self.BlankScreen)
|
||||||
self.HideMenu.menu().addAction(self.ThemeScreen)
|
self.HideMenu.menu().addAction(self.ThemeScreen)
|
||||||
self.HideMenu.menu().addAction(self.DesktopScreen)
|
if self.screens.display_count > 1:
|
||||||
|
self.HideMenu.menu().addAction(self.DesktopScreen)
|
||||||
if not self.isLive:
|
if not self.isLive:
|
||||||
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
||||||
self.Toolbar.addToolbarButton(
|
self.Toolbar.addToolbarButton(
|
||||||
u'Go Live', u':/general/general_live.png',
|
u'Go Live', u':/general/general_live.png',
|
||||||
translate('OpenLP.SlideController', 'Move to live'), self.onGoLive)
|
translate('OpenLP.SlideController', 'Move to live'),
|
||||||
|
self.onGoLive)
|
||||||
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',
|
||||||
|
@ -247,11 +226,12 @@ class SlideController(QtGui.QWidget):
|
||||||
self.DelaySpinBox.setMaximum(180)
|
self.DelaySpinBox.setMaximum(180)
|
||||||
self.Toolbar.addToolbarWidget(
|
self.Toolbar.addToolbarWidget(
|
||||||
u'Image SpinBox', self.DelaySpinBox)
|
u'Image SpinBox', self.DelaySpinBox)
|
||||||
self.DelaySpinBox.setSuffix(translate('OpenLP.SlideController', 's'))
|
self.DelaySpinBox.setSuffix(translate('OpenLP.SlideController',
|
||||||
|
's'))
|
||||||
self.DelaySpinBox.setToolTip(translate('OpenLP.SlideController',
|
self.DelaySpinBox.setToolTip(translate('OpenLP.SlideController',
|
||||||
'Delay between slides in seconds'))
|
'Delay between slides in seconds'))
|
||||||
self.ControllerLayout.addWidget(self.Toolbar)
|
self.ControllerLayout.addWidget(self.Toolbar)
|
||||||
#Build a Media ToolBar
|
# Build a Media ToolBar
|
||||||
self.Mediabar = OpenLPToolbar(self)
|
self.Mediabar = OpenLPToolbar(self)
|
||||||
self.Mediabar.addToolbarButton(
|
self.Mediabar.addToolbarButton(
|
||||||
u'Media Start', u':/slides/media_playback_start.png',
|
u'Media Start', u':/slides/media_playback_start.png',
|
||||||
|
@ -271,19 +251,30 @@ class SlideController(QtGui.QWidget):
|
||||||
self.seekSlider.setObjectName(u'seekSlider')
|
self.seekSlider.setObjectName(u'seekSlider')
|
||||||
self.Mediabar.addToolbarWidget(
|
self.Mediabar.addToolbarWidget(
|
||||||
u'Seek Slider', self.seekSlider)
|
u'Seek Slider', self.seekSlider)
|
||||||
self.volumeSlider = Phonon.VolumeSlider()
|
self.volumeSlider = Phonon.VolumeSlider()
|
||||||
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
|
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
|
||||||
self.volumeSlider.setObjectName(u'volumeSlider')
|
self.volumeSlider.setObjectName(u'volumeSlider')
|
||||||
self.Mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
|
self.Mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
|
||||||
|
else:
|
||||||
|
self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
|
||||||
|
self.volumeSlider.setTickInterval(1)
|
||||||
|
self.volumeSlider.setTickPosition(QtGui.QSlider.TicksAbove)
|
||||||
|
self.volumeSlider.setMinimum(0)
|
||||||
|
self.volumeSlider.setMaximum(10)
|
||||||
|
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
|
||||||
|
self.volumeSlider.setObjectName(u'volumeSlider')
|
||||||
|
self.Mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
|
||||||
self.ControllerLayout.addWidget(self.Mediabar)
|
self.ControllerLayout.addWidget(self.Mediabar)
|
||||||
# Build the Song Toolbar
|
# Build the Song Toolbar
|
||||||
if isLive:
|
if isLive:
|
||||||
self.SongMenu = QtGui.QToolButton(self.Toolbar)
|
self.SongMenu = QtGui.QToolButton(self.Toolbar)
|
||||||
self.SongMenu.setText(translate('OpenLP.SlideController', 'Go to Verse'))
|
self.SongMenu.setText(translate('OpenLP.SlideController',
|
||||||
|
'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'), self.Toolbar))
|
translate('OpenLP.SlideController', 'Go to'),
|
||||||
|
self.Toolbar))
|
||||||
self.Toolbar.makeWidgetsInvisible([u'Song Menu'])
|
self.Toolbar.makeWidgetsInvisible([u'Song Menu'])
|
||||||
# Screen preview area
|
# Screen preview area
|
||||||
self.PreviewFrame = QtGui.QFrame(self.Splitter)
|
self.PreviewFrame = QtGui.QFrame(self.Splitter)
|
||||||
|
@ -322,7 +313,7 @@ class SlideController(QtGui.QWidget):
|
||||||
self.SlidePreview.setSizePolicy(sizePolicy)
|
self.SlidePreview.setSizePolicy(sizePolicy)
|
||||||
self.SlidePreview.setFixedSize(
|
self.SlidePreview.setFixedSize(
|
||||||
QtCore.QSize(self.settingsmanager.slidecontroller_image,
|
QtCore.QSize(self.settingsmanager.slidecontroller_image,
|
||||||
self.settingsmanager.slidecontroller_image / 1.3 ))
|
self.settingsmanager.slidecontroller_image / self.ratio))
|
||||||
self.SlidePreview.setFrameShape(QtGui.QFrame.Box)
|
self.SlidePreview.setFrameShape(QtGui.QFrame.Box)
|
||||||
self.SlidePreview.setFrameShadow(QtGui.QFrame.Plain)
|
self.SlidePreview.setFrameShadow(QtGui.QFrame.Plain)
|
||||||
self.SlidePreview.setLineWidth(1)
|
self.SlidePreview.setLineWidth(1)
|
||||||
|
@ -390,17 +381,37 @@ class SlideController(QtGui.QWidget):
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'config_updated'), self.refreshServiceItem)
|
QtCore.SIGNAL(u'config_updated'), self.refreshServiceItem)
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'%s_slide_cache' % self.typePrefix), self.slideCache)
|
QtCore.SIGNAL(u'config_screen_changed'), self.screenSizeChanged)
|
||||||
|
if self.isLive:
|
||||||
|
QtCore.QObject.connect(self.volumeSlider,
|
||||||
|
QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume)
|
||||||
|
|
||||||
|
def screenSizeChanged(self):
|
||||||
|
"""
|
||||||
|
Settings dialog has changed the screen size of adjust output and
|
||||||
|
screen previews
|
||||||
|
"""
|
||||||
|
log.debug(u'screenSizeChanged live = %s' % self.isLive)
|
||||||
|
# rebuild display as screen size changed
|
||||||
|
self.display = MainDisplay(self, self.screens, self.isLive)
|
||||||
|
self.display.alertTab = self.alertTab
|
||||||
|
self.ratio = float(self.screens.current[u'size'].width()) / \
|
||||||
|
float(self.screens.current[u'size'].height())
|
||||||
|
self.display.setup()
|
||||||
|
self.SlidePreview.setFixedSize(
|
||||||
|
QtCore.QSize(self.settingsmanager.slidecontroller_image,
|
||||||
|
self.settingsmanager.slidecontroller_image / self.ratio))
|
||||||
|
|
||||||
def widthChanged(self):
|
def widthChanged(self):
|
||||||
"""
|
"""
|
||||||
Handle changes of width from the splitter between the live and preview
|
Handle changes of width from the splitter between the live and preview
|
||||||
controller. Event only issues when changes have finished
|
controller. Event only issues when changes have finished
|
||||||
"""
|
"""
|
||||||
|
log.debug(u'widthChanged live = %s' % self.isLive)
|
||||||
width = self.parent.ControlSplitter.sizes()[self.split]
|
width = self.parent.ControlSplitter.sizes()[self.split]
|
||||||
height = width * self.parent.RenderManager.screen_ratio
|
height = width * self.parent.RenderManager.screen_ratio
|
||||||
self.PreviewListWidget.setColumnWidth(0, width)
|
self.PreviewListWidget.setColumnWidth(0, width)
|
||||||
#Sort out image heights (Songs, bibles excluded)
|
# Sort out image heights (Songs, bibles excluded)
|
||||||
if self.serviceItem and not self.serviceItem.is_text():
|
if self.serviceItem and not self.serviceItem.is_text():
|
||||||
for framenumber in range(len(self.serviceItem.get_frames())):
|
for framenumber in range(len(self.serviceItem.get_frames())):
|
||||||
self.PreviewListWidget.setRowHeight(framenumber, height)
|
self.PreviewListWidget.setRowHeight(framenumber, height)
|
||||||
|
@ -453,8 +464,6 @@ class SlideController(QtGui.QWidget):
|
||||||
if item.is_media():
|
if item.is_media():
|
||||||
self.Toolbar.setVisible(False)
|
self.Toolbar.setVisible(False)
|
||||||
self.Mediabar.setVisible(True)
|
self.Mediabar.setVisible(True)
|
||||||
#self.volumeSlider.setAudioOutput(
|
|
||||||
# self.mainDisplay.videoDisplay.audio)
|
|
||||||
|
|
||||||
def enablePreviewToolBar(self, item):
|
def enablePreviewToolBar(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -474,22 +483,20 @@ class SlideController(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Method to update the service item if the screen has changed
|
Method to update the service item if the screen has changed
|
||||||
"""
|
"""
|
||||||
log.debug(u'refreshServiceItem')
|
log.debug(u'refreshServiceItem live = %s' % self.isLive)
|
||||||
if self.serviceItem:
|
if self.serviceItem:
|
||||||
if self.serviceItem.is_text() or self.serviceItem.is_image():
|
if self.serviceItem.is_text() or self.serviceItem.is_image():
|
||||||
item = self.serviceItem
|
item = self.serviceItem
|
||||||
item.render()
|
item.render()
|
||||||
self.addServiceManagerItem(item, self.selectedRow)
|
self._processItem(item, self.selectedRow)
|
||||||
|
|
||||||
def addServiceItem(self, item):
|
def addServiceItem(self, item):
|
||||||
"""
|
"""
|
||||||
Method to install the service item into the controller
|
Method to install the service item into the controller
|
||||||
Called by plugins
|
Called by plugins
|
||||||
"""
|
"""
|
||||||
log.debug(u'addServiceItem')
|
log.debug(u'addServiceItem live = %s' % self.isLive)
|
||||||
before = time.time()
|
|
||||||
item.render()
|
item.render()
|
||||||
log.log(15, u'Rendering took %4s' % (time.time() - before))
|
|
||||||
slideno = 0
|
slideno = 0
|
||||||
if self.songEdit:
|
if self.songEdit:
|
||||||
slideno = self.selectedRow
|
slideno = self.selectedRow
|
||||||
|
@ -509,8 +516,8 @@ class SlideController(QtGui.QWidget):
|
||||||
request the correct toolbar for the plugin.
|
request the correct toolbar for the plugin.
|
||||||
Called by ServiceManager
|
Called by ServiceManager
|
||||||
"""
|
"""
|
||||||
log.debug(u'addServiceManagerItem')
|
log.debug(u'addServiceManagerItem live = %s' % self.isLive)
|
||||||
#If service item is the same as the current on only change slide
|
# If service item is the same as the current on only change slide
|
||||||
if item.__eq__(self.serviceItem):
|
if item.__eq__(self.serviceItem):
|
||||||
self.PreviewListWidget.selectRow(slideno)
|
self.PreviewListWidget.selectRow(slideno)
|
||||||
self.onSlideSelected()
|
self.onSlideSelected()
|
||||||
|
@ -522,17 +529,15 @@ class SlideController(QtGui.QWidget):
|
||||||
Loads a ServiceItem into the system from ServiceManager
|
Loads a ServiceItem into the system from ServiceManager
|
||||||
Display the slide number passed
|
Display the slide number passed
|
||||||
"""
|
"""
|
||||||
log.debug(u'processManagerItem')
|
log.debug(u'processManagerItem live = %s' % self.isLive)
|
||||||
self.onStopLoop()
|
self.onStopLoop()
|
||||||
#If old item was a command tell it to stop
|
# If old item was a command tell it to stop
|
||||||
if self.serviceItem:
|
if self.serviceItem:
|
||||||
if self.serviceItem.is_command():
|
if self.serviceItem.is_command():
|
||||||
Receiver.send_message(u'%s_stop' %
|
Receiver.send_message(u'%s_stop' %
|
||||||
self.serviceItem.name.lower(), [serviceItem, self.isLive])
|
self.serviceItem.name.lower(), [serviceItem, self.isLive])
|
||||||
if self.serviceItem.is_media():
|
if self.serviceItem.is_media():
|
||||||
self.onMediaStop()
|
self.onMediaStop()
|
||||||
if serviceItem.is_media():
|
|
||||||
self.onMediaStart(serviceItem)
|
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
blanked = self.BlankScreen.isChecked()
|
blanked = self.BlankScreen.isChecked()
|
||||||
else:
|
else:
|
||||||
|
@ -541,12 +546,8 @@ class SlideController(QtGui.QWidget):
|
||||||
[serviceItem, self.isLive, blanked, slideno])
|
[serviceItem, self.isLive, blanked, slideno])
|
||||||
self.slideList = {}
|
self.slideList = {}
|
||||||
width = self.parent.ControlSplitter.sizes()[self.split]
|
width = self.parent.ControlSplitter.sizes()[self.split]
|
||||||
#Set pointing cursor when we have somthing to point at
|
# Set pointing cursor when we have somthing to point at
|
||||||
self.PreviewListWidget.setCursor(QtCore.Qt.PointingHandCursor)
|
self.PreviewListWidget.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
before = time.time()
|
|
||||||
#Clear the old serviceItem cache to release memory
|
|
||||||
if self.serviceItem and self.serviceItem is not serviceItem:
|
|
||||||
self.serviceItem.clear_cache()
|
|
||||||
self.serviceItem = serviceItem
|
self.serviceItem = serviceItem
|
||||||
self.PreviewListWidget.clear()
|
self.PreviewListWidget.clear()
|
||||||
self.PreviewListWidget.setRowCount(0)
|
self.PreviewListWidget.setRowCount(0)
|
||||||
|
@ -560,20 +561,19 @@ class SlideController(QtGui.QWidget):
|
||||||
self.PreviewListWidget.rowCount() + 1)
|
self.PreviewListWidget.rowCount() + 1)
|
||||||
item = QtGui.QTableWidgetItem()
|
item = QtGui.QTableWidgetItem()
|
||||||
slideHeight = 0
|
slideHeight = 0
|
||||||
#It is a based Text Render
|
|
||||||
if self.serviceItem.is_text():
|
if self.serviceItem.is_text():
|
||||||
if frame[u'verseTag']:
|
if frame[u'verseTag']:
|
||||||
bits = frame[u'verseTag'].split(u':')
|
bits = frame[u'verseTag'].split(u':')
|
||||||
tag = u'%s\n%s' % (bits[0][0], bits[1][0:] )
|
tag = u'%s\n%s' % (bits[0][0], bits[1][0:] )
|
||||||
tag1 = u'%s%s' % (bits[0][0], bits[1][0:] )
|
tag1 = u'%s%s' % (bits[0][0], bits[1][0:] )
|
||||||
row = tag
|
row = tag
|
||||||
|
if self.isLive:
|
||||||
|
if tag1 not in self.slideList:
|
||||||
|
self.slideList[tag1] = framenumber
|
||||||
|
self.SongMenu.menu().addAction(tag1,
|
||||||
|
self.onSongBarHandler)
|
||||||
else:
|
else:
|
||||||
row += 1
|
row += 1
|
||||||
if self.isLive and frame[u'verseTag'] is not None:
|
|
||||||
if tag1 not in self.slideList:
|
|
||||||
self.slideList[tag1] = framenumber
|
|
||||||
self.SongMenu.menu().addAction(tag1,
|
|
||||||
self.onSongBarHandler)
|
|
||||||
item.setText(frame[u'text'])
|
item.setText(frame[u'text'])
|
||||||
else:
|
else:
|
||||||
label = QtGui.QLabel()
|
label = QtGui.QLabel()
|
||||||
|
@ -600,15 +600,14 @@ class SlideController(QtGui.QWidget):
|
||||||
else:
|
else:
|
||||||
self.PreviewListWidget.selectRow(slideno)
|
self.PreviewListWidget.selectRow(slideno)
|
||||||
self.enableToolBar(serviceItem)
|
self.enableToolBar(serviceItem)
|
||||||
|
# Pass to display for viewing
|
||||||
|
self.display.buildHtml(self.serviceItem)
|
||||||
|
if serviceItem.is_media():
|
||||||
|
self.onMediaStart(serviceItem)
|
||||||
self.onSlideSelected()
|
self.onSlideSelected()
|
||||||
self.PreviewListWidget.setFocus()
|
self.PreviewListWidget.setFocus()
|
||||||
Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix,
|
Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix,
|
||||||
[serviceItem])
|
[serviceItem])
|
||||||
if self.serviceItem.is_text():
|
|
||||||
st = SlideThread(
|
|
||||||
self, self.typePrefix, len(self.serviceItem.get_frames()))
|
|
||||||
st.start()
|
|
||||||
log.log(15, u'Display Rendering took %4s' % (time.time() - before))
|
|
||||||
|
|
||||||
def onTextRequest(self):
|
def onTextRequest(self):
|
||||||
"""
|
"""
|
||||||
|
@ -620,7 +619,7 @@ class SlideController(QtGui.QWidget):
|
||||||
dataItem = {}
|
dataItem = {}
|
||||||
if self.serviceItem.is_text():
|
if self.serviceItem.is_text():
|
||||||
dataItem[u'tag'] = unicode(frame[u'verseTag'])
|
dataItem[u'tag'] = unicode(frame[u'verseTag'])
|
||||||
dataItem[u'text'] = unicode(frame[u'text'])
|
dataItem[u'text'] = unicode(frame[u'html'])
|
||||||
else:
|
else:
|
||||||
dataItem[u'tag'] = unicode(framenumber)
|
dataItem[u'tag'] = unicode(framenumber)
|
||||||
dataItem[u'text'] = u''
|
dataItem[u'text'] = u''
|
||||||
|
@ -630,7 +629,7 @@ class SlideController(QtGui.QWidget):
|
||||||
Receiver.send_message(u'slidecontroller_%s_text_response'
|
Receiver.send_message(u'slidecontroller_%s_text_response'
|
||||||
% self.typePrefix, data)
|
% self.typePrefix, data)
|
||||||
|
|
||||||
#Screen event methods
|
# Screen event methods
|
||||||
def onSlideSelectedFirst(self):
|
def onSlideSelectedFirst(self):
|
||||||
"""
|
"""
|
||||||
Go to the first slide.
|
Go to the first slide.
|
||||||
|
@ -664,9 +663,9 @@ class SlideController(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Allow the main display to blank the main display at startup time
|
Allow the main display to blank the main display at startup time
|
||||||
"""
|
"""
|
||||||
log.debug(u'mainDisplaySetBackground')
|
log.debug(u'mainDisplaySetBackground live = %s' % self.isLive)
|
||||||
if not self.mainDisplay.primary:
|
if not self.display.primary:
|
||||||
self.onBlankDisplay(True)
|
self.onHideDisplay(True)
|
||||||
|
|
||||||
def onSlideBlank(self):
|
def onSlideBlank(self):
|
||||||
"""
|
"""
|
||||||
|
@ -688,7 +687,8 @@ class SlideController(QtGui.QWidget):
|
||||||
self.HideMenu.setDefaultAction(self.BlankScreen)
|
self.HideMenu.setDefaultAction(self.BlankScreen)
|
||||||
self.BlankScreen.setChecked(checked)
|
self.BlankScreen.setChecked(checked)
|
||||||
self.ThemeScreen.setChecked(False)
|
self.ThemeScreen.setChecked(False)
|
||||||
self.DesktopScreen.setChecked(False)
|
if self.screens.display_count > 1:
|
||||||
|
self.DesktopScreen.setChecked(False)
|
||||||
QtCore.QSettings().setValue(
|
QtCore.QSettings().setValue(
|
||||||
self.parent.generalSettingsSection + u'/screen blank',
|
self.parent.generalSettingsSection + u'/screen blank',
|
||||||
QtCore.QVariant(checked))
|
QtCore.QVariant(checked))
|
||||||
|
@ -706,7 +706,8 @@ class SlideController(QtGui.QWidget):
|
||||||
self.HideMenu.setDefaultAction(self.ThemeScreen)
|
self.HideMenu.setDefaultAction(self.ThemeScreen)
|
||||||
self.BlankScreen.setChecked(False)
|
self.BlankScreen.setChecked(False)
|
||||||
self.ThemeScreen.setChecked(checked)
|
self.ThemeScreen.setChecked(checked)
|
||||||
self.DesktopScreen.setChecked(False)
|
if self.screens.display_count > 1:
|
||||||
|
self.DesktopScreen.setChecked(False)
|
||||||
if checked:
|
if checked:
|
||||||
Receiver.send_message(u'maindisplay_hide', HideMode.Theme)
|
Receiver.send_message(u'maindisplay_hide', HideMode.Theme)
|
||||||
else:
|
else:
|
||||||
|
@ -721,7 +722,8 @@ class SlideController(QtGui.QWidget):
|
||||||
self.HideMenu.setDefaultAction(self.DesktopScreen)
|
self.HideMenu.setDefaultAction(self.DesktopScreen)
|
||||||
self.BlankScreen.setChecked(False)
|
self.BlankScreen.setChecked(False)
|
||||||
self.ThemeScreen.setChecked(False)
|
self.ThemeScreen.setChecked(False)
|
||||||
self.DesktopScreen.setChecked(checked)
|
if self.screens.display_count > 1:
|
||||||
|
self.DesktopScreen.setChecked(checked)
|
||||||
if checked:
|
if checked:
|
||||||
Receiver.send_message(u'maindisplay_hide', HideMode.Screen)
|
Receiver.send_message(u'maindisplay_hide', HideMode.Screen)
|
||||||
else:
|
else:
|
||||||
|
@ -758,13 +760,6 @@ class SlideController(QtGui.QWidget):
|
||||||
% self.serviceItem.name.lower(),
|
% self.serviceItem.name.lower(),
|
||||||
[self.serviceItem, self.isLive])
|
[self.serviceItem, self.isLive])
|
||||||
|
|
||||||
def slideCache(self, slide):
|
|
||||||
"""
|
|
||||||
Generate a slide cache item rendered and ready for use
|
|
||||||
in the background.
|
|
||||||
"""
|
|
||||||
self.serviceItem.get_rendered_frame(int(slide))
|
|
||||||
|
|
||||||
def onSlideSelected(self):
|
def onSlideSelected(self):
|
||||||
"""
|
"""
|
||||||
Generate the preview when you click on a slide.
|
Generate the preview when you click on a slide.
|
||||||
|
@ -778,24 +773,15 @@ class SlideController(QtGui.QWidget):
|
||||||
if self.serviceItem.is_command() and self.isLive:
|
if self.serviceItem.is_command() and self.isLive:
|
||||||
self.updatePreview()
|
self.updatePreview()
|
||||||
else:
|
else:
|
||||||
before = time.time()
|
frame, raw_html = self.serviceItem.get_rendered_frame(row)
|
||||||
frame = self.serviceItem.get_rendered_frame(row)
|
if self.serviceItem.is_text():
|
||||||
|
frame = self.display.text(raw_html)
|
||||||
|
else:
|
||||||
|
self.display.image(frame)
|
||||||
if isinstance(frame, QtGui.QImage):
|
if isinstance(frame, QtGui.QImage):
|
||||||
self.SlidePreview.setPixmap(QtGui.QPixmap.fromImage(frame))
|
self.SlidePreview.setPixmap(QtGui.QPixmap.fromImage(frame))
|
||||||
else:
|
else:
|
||||||
if isinstance(frame[u'main'], basestring):
|
self.SlidePreview.setPixmap(QtGui.QPixmap(frame))
|
||||||
self.SlidePreview.setPixmap(
|
|
||||||
QtGui.QPixmap(frame[u'main']))
|
|
||||||
else:
|
|
||||||
self.SlidePreview.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(frame[u'main']))
|
|
||||||
log.log(
|
|
||||||
15, u'Slide Rendering took %4s' % (time.time() - before))
|
|
||||||
if self.isLive:
|
|
||||||
if self.serviceItem.is_text():
|
|
||||||
self.mainDisplay.frameView(frame, True)
|
|
||||||
else:
|
|
||||||
self.displayManager.displayImage(frame[u'main'])
|
|
||||||
self.selectedRow = row
|
self.selectedRow = row
|
||||||
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix,
|
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix,
|
||||||
row)
|
row)
|
||||||
|
@ -810,8 +796,7 @@ class SlideController(QtGui.QWidget):
|
||||||
row)
|
row)
|
||||||
|
|
||||||
def updatePreview(self):
|
def updatePreview(self):
|
||||||
rm = self.parent.RenderManager
|
if not self.screens.current[u'primary']:
|
||||||
if not rm.screens.current[u'primary']:
|
|
||||||
# Grab now, but try again in a couple of seconds if slide change
|
# Grab now, but try again in a couple of seconds if slide change
|
||||||
# is slow
|
# is slow
|
||||||
QtCore.QTimer.singleShot(0.5, self.grabMainDisplay)
|
QtCore.QTimer.singleShot(0.5, self.grabMainDisplay)
|
||||||
|
@ -819,12 +804,12 @@ class SlideController(QtGui.QWidget):
|
||||||
else:
|
else:
|
||||||
label = self.PreviewListWidget.cellWidget(
|
label = self.PreviewListWidget.cellWidget(
|
||||||
self.PreviewListWidget.currentRow(), 1)
|
self.PreviewListWidget.currentRow(), 1)
|
||||||
self.SlidePreview.setPixmap(label.pixmap())
|
if label:
|
||||||
|
self.SlidePreview.setPixmap(label.pixmap())
|
||||||
|
|
||||||
def grabMainDisplay(self):
|
def grabMainDisplay(self):
|
||||||
rm = self.parent.RenderManager
|
|
||||||
winid = QtGui.QApplication.desktop().winId()
|
winid = QtGui.QApplication.desktop().winId()
|
||||||
rect = rm.screens.current[u'size']
|
rect = self.screens.current[u'size']
|
||||||
winimg = QtGui.QPixmap.grabWindow(winid, rect.x(),
|
winimg = QtGui.QPixmap.grabWindow(winid, rect.x(),
|
||||||
rect.y(), rect.width(), rect.height())
|
rect.y(), rect.width(), rect.height())
|
||||||
self.SlidePreview.setPixmap(winimg)
|
self.SlidePreview.setPixmap(winimg)
|
||||||
|
@ -941,7 +926,9 @@ class SlideController(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
log.debug(u'SlideController onMediaStart')
|
log.debug(u'SlideController onMediaStart')
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
Receiver.send_message(u'videodisplay_start', item)
|
file = os.path.join(item.get_frame_path(), item.get_frame_title())
|
||||||
|
self.display.video(file, self.volume)
|
||||||
|
self.volumeSlider.setValue(self.volume)
|
||||||
else:
|
else:
|
||||||
self.mediaObject.stop()
|
self.mediaObject.stop()
|
||||||
self.mediaObject.clearQueue()
|
self.mediaObject.clearQueue()
|
||||||
|
@ -951,13 +938,21 @@ class SlideController(QtGui.QWidget):
|
||||||
self.seekSlider.show()
|
self.seekSlider.show()
|
||||||
self.onMediaPlay()
|
self.onMediaPlay()
|
||||||
|
|
||||||
|
def mediaVolume(self):
|
||||||
|
"""
|
||||||
|
Respond to the release of Volume Slider
|
||||||
|
"""
|
||||||
|
log.debug(u'SlideController mediaVolume')
|
||||||
|
self.volume = self.volumeSlider.value()
|
||||||
|
self.display.videoVolume(self.volume)
|
||||||
|
|
||||||
def onMediaPause(self):
|
def onMediaPause(self):
|
||||||
"""
|
"""
|
||||||
Respond to the Pause from the media Toolbar
|
Respond to the Pause from the media Toolbar
|
||||||
"""
|
"""
|
||||||
log.debug(u'SlideController onMediaPause')
|
log.debug(u'SlideController onMediaPause')
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
Receiver.send_message(u'videodisplay_pause')
|
self.display.videoPause()
|
||||||
else:
|
else:
|
||||||
self.mediaObject.pause()
|
self.mediaObject.pause()
|
||||||
|
|
||||||
|
@ -967,7 +962,7 @@ class SlideController(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
log.debug(u'SlideController onMediaPlay')
|
log.debug(u'SlideController onMediaPlay')
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
Receiver.send_message(u'videodisplay_play')
|
self.display.videoPlay()
|
||||||
else:
|
else:
|
||||||
self.SlidePreview.hide()
|
self.SlidePreview.hide()
|
||||||
self.video.show()
|
self.video.show()
|
||||||
|
@ -979,7 +974,7 @@ class SlideController(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
log.debug(u'SlideController onMediaStop')
|
log.debug(u'SlideController onMediaStop')
|
||||||
if self.isLive:
|
if self.isLive:
|
||||||
Receiver.send_message(u'videodisplay_stop')
|
self.display.videoStop()
|
||||||
else:
|
else:
|
||||||
self.mediaObject.stop()
|
self.mediaObject.stop()
|
||||||
self.video.hide()
|
self.video.hide()
|
||||||
|
|
|
@ -139,13 +139,13 @@ class ThemeManager(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
log.debug(u'changeGlobalFromTab %s', themeName)
|
log.debug(u'changeGlobalFromTab %s', themeName)
|
||||||
for count in range (0, self.themeListWidget.count()):
|
for count in range (0, self.themeListWidget.count()):
|
||||||
#reset the old name
|
# reset the old name
|
||||||
item = self.themeListWidget.item(count)
|
item = self.themeListWidget.item(count)
|
||||||
oldName = item.text()
|
oldName = item.text()
|
||||||
newName = unicode(item.data(QtCore.Qt.UserRole).toString())
|
newName = unicode(item.data(QtCore.Qt.UserRole).toString())
|
||||||
if oldName != newName:
|
if oldName != newName:
|
||||||
self.themeListWidget.item(count).setText(newName)
|
self.themeListWidget.item(count).setText(newName)
|
||||||
#Set the new name
|
# Set the new name
|
||||||
if themeName == newName:
|
if themeName == newName:
|
||||||
name = unicode(translate('OpenLP.ThemeManager',
|
name = unicode(translate('OpenLP.ThemeManager',
|
||||||
'%s (default)')) % newName
|
'%s (default)')) % newName
|
||||||
|
@ -161,11 +161,11 @@ class ThemeManager(QtGui.QWidget):
|
||||||
for count in range (0, self.themeListWidget.count()):
|
for count in range (0, self.themeListWidget.count()):
|
||||||
item = self.themeListWidget.item(count)
|
item = self.themeListWidget.item(count)
|
||||||
oldName = item.text()
|
oldName = item.text()
|
||||||
#reset the old name
|
# reset the old name
|
||||||
if oldName != unicode(item.data(QtCore.Qt.UserRole).toString()):
|
if oldName != unicode(item.data(QtCore.Qt.UserRole).toString()):
|
||||||
self.themeListWidget.item(count).setText(
|
self.themeListWidget.item(count).setText(
|
||||||
unicode(item.data(QtCore.Qt.UserRole).toString()))
|
unicode(item.data(QtCore.Qt.UserRole).toString()))
|
||||||
#Set the new name
|
# Set the new name
|
||||||
if count == selected_row:
|
if count == selected_row:
|
||||||
self.global_theme = unicode(
|
self.global_theme = unicode(
|
||||||
self.themeListWidget.item(count).text())
|
self.themeListWidget.item(count).text())
|
||||||
|
@ -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'.theme')
|
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')
|
||||||
|
@ -346,11 +346,10 @@ class ThemeManager(QtGui.QWidget):
|
||||||
log.debug(u'Load themes from dir')
|
log.debug(u'Load themes from dir')
|
||||||
self.themelist = []
|
self.themelist = []
|
||||||
self.themeListWidget.clear()
|
self.themeListWidget.clear()
|
||||||
#root, dirs, files = os.walk(self.path)
|
|
||||||
dirList = os.listdir(self.path)
|
dirList = os.listdir(self.path)
|
||||||
for name in dirList:
|
for name in dirList:
|
||||||
if name.endswith(u'.png'):
|
if name.endswith(u'.png'):
|
||||||
#check to see file is in theme root directory
|
# check to see file is in theme root directory
|
||||||
theme = os.path.join(self.path, name)
|
theme = os.path.join(self.path, name)
|
||||||
if os.path.exists(theme):
|
if os.path.exists(theme):
|
||||||
textName = os.path.splitext(name)[0]
|
textName = os.path.splitext(name)[0]
|
||||||
|
@ -660,9 +659,8 @@ class ThemeManager(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Call the RenderManager to build a Sample Image
|
Call the RenderManager to build a Sample Image
|
||||||
"""
|
"""
|
||||||
log.debug(u'generateImage %s ', themedata)
|
log.debug(u'generateImage \n%s ', themedata)
|
||||||
frame = self.parent.RenderManager.generate_preview(themedata)
|
return self.parent.RenderManager.generate_preview(themedata)
|
||||||
return frame
|
|
||||||
|
|
||||||
def getPreviewImage(self, theme):
|
def getPreviewImage(self, theme):
|
||||||
"""
|
"""
|
||||||
|
@ -732,8 +730,6 @@ class ThemeManager(QtGui.QWidget):
|
||||||
theme.display_slideTransition = theme.display_slideTransition
|
theme.display_slideTransition = theme.display_slideTransition
|
||||||
theme.font_footer_color = theme.font_footer_color.strip()
|
theme.font_footer_color = theme.font_footer_color.strip()
|
||||||
theme.font_footer_height = int(theme.font_footer_height.strip())
|
theme.font_footer_height = int(theme.font_footer_height.strip())
|
||||||
theme.font_footer_indentation = \
|
|
||||||
int(theme.font_footer_indentation.strip())
|
|
||||||
theme.font_footer_italics = str_to_bool(theme.font_footer_italics)
|
theme.font_footer_italics = str_to_bool(theme.font_footer_italics)
|
||||||
theme.font_footer_name = theme.font_footer_name.strip()
|
theme.font_footer_name = theme.font_footer_name.strip()
|
||||||
#theme.font_footer_override
|
#theme.font_footer_override
|
||||||
|
@ -746,7 +742,6 @@ class ThemeManager(QtGui.QWidget):
|
||||||
theme.font_main_color = theme.font_main_color.strip()
|
theme.font_main_color = theme.font_main_color.strip()
|
||||||
theme.font_main_height = int(theme.font_main_height.strip())
|
theme.font_main_height = int(theme.font_main_height.strip())
|
||||||
theme.font_main_italics = str_to_bool(theme.font_main_italics)
|
theme.font_main_italics = str_to_bool(theme.font_main_italics)
|
||||||
theme.font_main_indentation = int(theme.font_main_indentation)
|
|
||||||
theme.font_main_name = theme.font_main_name.strip()
|
theme.font_main_name = theme.font_main_name.strip()
|
||||||
#theme.font_main_override
|
#theme.font_main_override
|
||||||
theme.font_main_proportion = int(theme.font_main_proportion.strip())
|
theme.font_main_proportion = int(theme.font_main_proportion.strip())
|
||||||
|
@ -757,3 +752,8 @@ class ThemeManager(QtGui.QWidget):
|
||||||
#theme.theme_mode
|
#theme.theme_mode
|
||||||
theme.theme_name = theme.theme_name.strip()
|
theme.theme_name = theme.theme_name.strip()
|
||||||
#theme.theme_version
|
#theme.theme_version
|
||||||
|
# Remove the Transparent settings as they are not relevent
|
||||||
|
if theme.background_mode == u'transparent':
|
||||||
|
theme.background_mode = u'opaque'
|
||||||
|
theme.background_type = u'solid'
|
||||||
|
theme.background_startColor = u'#000000'
|
||||||
|
|
|
@ -80,9 +80,10 @@ class AlertsPlugin(Plugin):
|
||||||
log.info(u'Alerts Initialising')
|
log.info(u'Alerts Initialising')
|
||||||
Plugin.initialise(self)
|
Plugin.initialise(self)
|
||||||
self.toolsAlertItem.setVisible(True)
|
self.toolsAlertItem.setVisible(True)
|
||||||
|
self.liveController.alertTab = self.alertsTab
|
||||||
|
|
||||||
def finalise(self):
|
def finalise(self):
|
||||||
log.info(u'Plugin Finalise')
|
log.info(u'Alerts Finalising')
|
||||||
Plugin.finalise(self)
|
Plugin.finalise(self)
|
||||||
self.toolsAlertItem.setVisible(False)
|
self.toolsAlertItem.setVisible(False)
|
||||||
|
|
||||||
|
|
|
@ -32,18 +32,9 @@ from openlp.core.lib import Receiver, translate
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
HTMLCODE = u"""
|
|
||||||
<p style=\"color:%s;
|
|
||||||
background-color:%s;
|
|
||||||
font-family:%s;
|
|
||||||
font-size: %spt; \">
|
|
||||||
%s
|
|
||||||
</p>
|
|
||||||
"""
|
|
||||||
|
|
||||||
class AlertsManager(QtCore.QObject):
|
class AlertsManager(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
AlertsTab is the Alerts settings tab in the settings dialog.
|
AlertsManager manages the settings of Alerts.
|
||||||
"""
|
"""
|
||||||
log.info(u'Alert Manager loaded')
|
log.info(u'Alert Manager loaded')
|
||||||
|
|
||||||
|
@ -94,10 +85,7 @@ class AlertsManager(QtCore.QObject):
|
||||||
return
|
return
|
||||||
text = self.alertList.pop(0)
|
text = self.alertList.pop(0)
|
||||||
alertTab = self.parent.alertsTab
|
alertTab = self.parent.alertsTab
|
||||||
text = HTMLCODE % (alertTab.font_color, alertTab.bg_color,
|
self.parent.liveController.display.alert(text)
|
||||||
alertTab.font_face, alertTab.font_size, text)
|
|
||||||
self.parent.previewController.parent.displayManager.addAlert(text,
|
|
||||||
alertTab.location)
|
|
||||||
# check to see if we have a timer running
|
# check to see if we have a timer running
|
||||||
if self.timer_id == 0:
|
if self.timer_id == 0:
|
||||||
self.timer_id = self.startTimer(int(alertTab.timeout) * 1000)
|
self.timer_id = self.startTimer(int(alertTab.timeout) * 1000)
|
||||||
|
@ -111,10 +99,8 @@ class AlertsManager(QtCore.QObject):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
log.debug(u'timer event')
|
log.debug(u'timer event')
|
||||||
alertTab = self.parent.alertsTab
|
|
||||||
if event.timerId() == self.timer_id:
|
if event.timerId() == self.timer_id:
|
||||||
self.parent.previewController.parent.displayManager.addAlert(u'',
|
self.parent.liveController.display.alert(u'')
|
||||||
alertTab.location)
|
|
||||||
self.killTimer(self.timer_id)
|
self.killTimer(self.timer_id)
|
||||||
self.timer_id = 0
|
self.timer_id = 0
|
||||||
self.generateAlert()
|
self.generateAlert()
|
||||||
|
|
|
@ -261,7 +261,7 @@ class AlertsTab(SettingsTab):
|
||||||
self.font_face = unicode(settings.value(
|
self.font_face = unicode(settings.value(
|
||||||
u'font face', QtCore.QVariant(QtGui.QFont().family())).toString())
|
u'font face', QtCore.QVariant(QtGui.QFont().family())).toString())
|
||||||
self.location = settings.value(
|
self.location = settings.value(
|
||||||
u'location', QtCore.QVariant(0)).toInt()[0]
|
u'location', QtCore.QVariant(1)).toInt()[0]
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
self.FontSizeSpinBox.setValue(self.font_size)
|
self.FontSizeSpinBox.setValue(self.font_size)
|
||||||
self.TimeoutSpinBox.setValue(self.timeout)
|
self.TimeoutSpinBox.setValue(self.timeout)
|
||||||
|
@ -296,3 +296,4 @@ class AlertsTab(SettingsTab):
|
||||||
self.FontPreview.setFont(font)
|
self.FontPreview.setFont(font)
|
||||||
self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' %
|
self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' %
|
||||||
(self.bg_color, self.font_color))
|
(self.bg_color, self.font_color))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,6 @@ class BibleListView(BaseListWithDnD):
|
||||||
self.parent().onListViewResize(event.size().width(),
|
self.parent().onListViewResize(event.size().width(),
|
||||||
event.size().width())
|
event.size().width())
|
||||||
|
|
||||||
|
|
||||||
class BibleMediaItem(MediaManagerItem):
|
class BibleMediaItem(MediaManagerItem):
|
||||||
"""
|
"""
|
||||||
This is the custom media manager item for Bibles.
|
This is the custom media manager item for Bibles.
|
||||||
|
@ -276,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)
|
||||||
|
@ -465,20 +465,28 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
self.displayResults(bible, dual_bible)
|
self.displayResults(bible, dual_bible)
|
||||||
|
|
||||||
def generateSlideData(self, service_item, item=None):
|
def generateSlideData(self, service_item, item=None):
|
||||||
'''
|
"""
|
||||||
Generates and formats the slides for the service item.
|
Generates and formats the slides for the service item as well as the
|
||||||
'''
|
service item's title.
|
||||||
|
"""
|
||||||
log.debug(u'generating slide data')
|
log.debug(u'generating slide data')
|
||||||
items = self.listView.selectedIndexes()
|
items = self.listView.selectedIndexes()
|
||||||
if len(items) == 0:
|
if len(items) == 0:
|
||||||
return False
|
return False
|
||||||
|
has_dual_bible = False
|
||||||
bible_text = u''
|
bible_text = u''
|
||||||
old_chapter = u''
|
old_chapter = u''
|
||||||
raw_footer = []
|
raw_footer = []
|
||||||
raw_slides = []
|
raw_slides = []
|
||||||
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
for item in items:
|
||||||
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
bitem = self.listView.item(item.row())
|
||||||
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
reference = bitem.data(QtCore.Qt.UserRole)
|
||||||
|
if isinstance(reference, QtCore.QVariant):
|
||||||
|
reference = reference.toPyObject()
|
||||||
|
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
||||||
|
if dual_bible:
|
||||||
|
has_dual_bible = True
|
||||||
|
break
|
||||||
# Let's loop through the main lot, and assemble our verses.
|
# Let's loop through the main lot, and assemble our verses.
|
||||||
for item in items:
|
for item in items:
|
||||||
bitem = self.listView.item(item.row())
|
bitem = self.listView.item(item.row())
|
||||||
|
@ -491,7 +499,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
bible = self._decodeQtObject(reference, 'bible')
|
bible = self._decodeQtObject(reference, 'bible')
|
||||||
version = self._decodeQtObject(reference, 'version')
|
version = self._decodeQtObject(reference, 'version')
|
||||||
copyright = self._decodeQtObject(reference, 'copyright')
|
copyright = self._decodeQtObject(reference, 'copyright')
|
||||||
#permission = self._decodeQtObject(reference, 'permission')
|
permission = self._decodeQtObject(reference, 'permission')
|
||||||
text = self._decodeQtObject(reference, 'text')
|
text = self._decodeQtObject(reference, 'text')
|
||||||
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
||||||
if dual_bible:
|
if dual_bible:
|
||||||
|
@ -499,65 +507,57 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
'dual_version')
|
'dual_version')
|
||||||
dual_copyright = self._decodeQtObject(reference,
|
dual_copyright = self._decodeQtObject(reference,
|
||||||
'dual_copyright')
|
'dual_copyright')
|
||||||
#dual_permission = self._decodeQtObject(reference,
|
dual_permission = self._decodeQtObject(reference,
|
||||||
# 'dual_permission')
|
'dual_permission')
|
||||||
dual_text = self._decodeQtObject(reference, 'dual_text')
|
dual_text = self._decodeQtObject(reference, 'dual_text')
|
||||||
if self.parent.settings_tab.display_style == 1:
|
verse_text = self.formatVerse(old_chapter, chapter, verse)
|
||||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
footer = u'%s (%s %s %s)' % (book, version, copyright, permission)
|
||||||
u'(', u')')
|
|
||||||
elif self.parent.settings_tab.display_style == 2:
|
|
||||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
|
||||||
u'{', u'}')
|
|
||||||
elif self.parent.settings_tab.display_style == 3:
|
|
||||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
|
||||||
u'[', u']')
|
|
||||||
else:
|
|
||||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
|
||||||
u'', u'')
|
|
||||||
old_chapter = chapter
|
|
||||||
footer = u'%s (%s %s)' % (book, version, copyright)
|
|
||||||
# If not found add to footer
|
|
||||||
if footer not in raw_footer:
|
if footer not in raw_footer:
|
||||||
raw_footer.append(footer)
|
raw_footer.append(footer)
|
||||||
if dual_bible:
|
if has_dual_bible:
|
||||||
footer = u'%s (%s %s)' % (book, dual_version,
|
if dual_bible:
|
||||||
dual_copyright)
|
footer = u'%s (%s %s %s)' % (book, dual_version,
|
||||||
# If not found add second version and copyright to footer.
|
dual_copyright, dual_permission)
|
||||||
if footer not in raw_footer:
|
if footer not in raw_footer:
|
||||||
raw_footer.append(footer)
|
raw_footer.append(footer)
|
||||||
bible_text = u'%s %s \n\n %s %s' % (verse_text, text,
|
# If there is an old bible_text we have to add it.
|
||||||
verse_text, dual_text)
|
if bible_text:
|
||||||
raw_slides.append(bible_text)
|
|
||||||
bible_text = u''
|
|
||||||
else:
|
|
||||||
# If we are 'Verse Per Line' then force a new line.
|
|
||||||
if self.parent.settings_tab.layout_style == 1:
|
|
||||||
text = text + u'\n\n'
|
|
||||||
bible_text = u'%s %s %s' % (bible_text, verse_text, text)
|
|
||||||
# If we are 'Verse Per Slide' then create a new slide.
|
|
||||||
if self.parent.settings_tab.layout_style == 0:
|
|
||||||
raw_slides.append(bible_text)
|
|
||||||
bible_text = u''
|
|
||||||
# If we are not 'Verse Per Slide' we have to make sure, that we
|
|
||||||
# add more verses.
|
|
||||||
else:
|
|
||||||
if item.row() < len(items) - 1:
|
|
||||||
bitem = items[item.row() + 1]
|
|
||||||
reference = bitem.data(QtCore.Qt.UserRole)
|
|
||||||
if isinstance(reference, QtCore.QVariant):
|
|
||||||
reference = reference.toPyObject()
|
|
||||||
bible_new = self._decodeQtObject(reference, 'bible')
|
|
||||||
dual_bible_new = self._decodeQtObject(reference, 'dual_bible')
|
|
||||||
if dual_bible_new:
|
|
||||||
raw_slides.append(bible_text)
|
|
||||||
bible_text = u''
|
|
||||||
elif bible != bible_new:
|
|
||||||
raw_slides.append(bible_text)
|
|
||||||
bible_text = u''
|
|
||||||
else:
|
|
||||||
raw_slides.append(bible_text)
|
raw_slides.append(bible_text)
|
||||||
bible_text = u''
|
bible_text = u''
|
||||||
# service item title
|
bible_text = u'%s %s\n\n%s %s' % (verse_text, text,
|
||||||
|
verse_text, dual_text)
|
||||||
|
raw_slides.append(bible_text)
|
||||||
|
bible_text = u''
|
||||||
|
elif self.parent.settings_tab.layout_style == 0:
|
||||||
|
bible_text = u'%s %s' % (verse_text, text)
|
||||||
|
raw_slides.append(bible_text)
|
||||||
|
bible_text = u''
|
||||||
|
else:
|
||||||
|
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||||
|
# If we are 'Verse Per Slide' then create a new slide.
|
||||||
|
elif self.parent.settings_tab.layout_style == 0:
|
||||||
|
bible_text = u'%s %s' % (verse_text, text)
|
||||||
|
raw_slides.append(bible_text)
|
||||||
|
bible_text = u''
|
||||||
|
# If we are 'Verse Per Line' then force a new line.
|
||||||
|
elif self.parent.settings_tab.layout_style == 1:
|
||||||
|
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||||
|
# We have to be 'Continuous'.
|
||||||
|
else:
|
||||||
|
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||||
|
old_chapter = chapter
|
||||||
|
# If there are no more items we check whether we have to add bible_text.
|
||||||
|
if bible_text:
|
||||||
|
raw_slides.append(bible_text)
|
||||||
|
bible_text = u''
|
||||||
|
# Service Item: Capabilities
|
||||||
|
if self.parent.settings_tab.layout_style == 2 and not has_dual_bible:
|
||||||
|
# split the line but do not replace line breaks in renderer
|
||||||
|
service_item.add_capability(ItemCapabilities.NoLineBreaks)
|
||||||
|
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
||||||
|
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
||||||
|
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
||||||
|
# Service Item: Title
|
||||||
if not service_item.title:
|
if not service_item.title:
|
||||||
if dual_bible:
|
if dual_bible:
|
||||||
service_item.title = u'%s (%s, %s) %s' % (book, version,
|
service_item.title = u'%s (%s, %s) %s' % (book, version,
|
||||||
|
@ -568,7 +568,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
translate('BiblesPlugin.MediaItem', 'etc')) == -1:
|
translate('BiblesPlugin.MediaItem', 'etc')) == -1:
|
||||||
service_item.title = u'%s, %s' % (service_item.title,
|
service_item.title = u'%s, %s' % (service_item.title,
|
||||||
translate('BiblesPlugin.MediaItem', 'etc'))
|
translate('BiblesPlugin.MediaItem', 'etc'))
|
||||||
# item theme
|
# Service Item: Theme
|
||||||
if len(self.parent.settings_tab.bible_theme) == 0:
|
if len(self.parent.settings_tab.bible_theme) == 0:
|
||||||
service_item.theme = None
|
service_item.theme = None
|
||||||
else:
|
else:
|
||||||
|
@ -582,14 +582,20 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
service_item.raw_footer = raw_footer
|
service_item.raw_footer = raw_footer
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def formatVerse(self, old_chapter, chapter, verse, opening, closing):
|
def formatVerse(self, old_chapter, chapter, verse):
|
||||||
verse_text = opening
|
if not self.parent.settings_tab.show_new_chapters or \
|
||||||
if old_chapter != chapter:
|
old_chapter != chapter:
|
||||||
verse_text += chapter + u':'
|
verse_text = chapter + u':' + verse
|
||||||
elif not self.parent.settings_tab.show_new_chapters:
|
else:
|
||||||
verse_text += chapter + u':'
|
verse_text = verse
|
||||||
verse_text += verse
|
if self.parent.settings_tab.display_style == 1:
|
||||||
verse_text += closing
|
verse_text = u'{su}(' + verse_text + u'){/su}'
|
||||||
|
elif self.parent.settings_tab.display_style == 2:
|
||||||
|
verse_text = u'{su}{' + verse_text + u'}{/su}'
|
||||||
|
elif self.parent.settings_tab.display_style == 3:
|
||||||
|
verse_text = u'{su}[' + verse_text + u']{/su}'
|
||||||
|
else:
|
||||||
|
verse_text = u'{su}' + verse_text + u'{/su}'
|
||||||
return verse_text
|
return verse_text
|
||||||
|
|
||||||
def reloadBibles(self):
|
def reloadBibles(self):
|
||||||
|
@ -634,14 +640,14 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
for i in range(int(range_from), int(range_to) + 1):
|
for i in range(int(range_from), int(range_to) + 1):
|
||||||
combo.addItem(unicode(i))
|
combo.addItem(unicode(i))
|
||||||
|
|
||||||
def displayResults(self, bible, dual_bible=None):
|
def displayResults(self, bible, dual_bible=u''):
|
||||||
'''
|
"""
|
||||||
Displays the search results in the media manager. All data needed for further
|
Displays the search results in the media manager. All data needed for
|
||||||
action is saved for/in each row.
|
further action is saved for/in each row.
|
||||||
'''
|
"""
|
||||||
version = self.parent.manager.get_meta_data(bible, u'Version')
|
version = self.parent.manager.get_meta_data(bible, u'Version')
|
||||||
copyright = self.parent.manager.get_meta_data(bible, u'Copyright')
|
copyright = self.parent.manager.get_meta_data(bible, u'Copyright')
|
||||||
#permission = self.parent.manager.get_meta_data(bible, u'Permissions')
|
permission = self.parent.manager.get_meta_data(bible, u'Permissions')
|
||||||
if dual_bible:
|
if dual_bible:
|
||||||
dual_version = self.parent.manager.get_meta_data(dual_bible,
|
dual_version = self.parent.manager.get_meta_data(dual_bible,
|
||||||
u'Version')
|
u'Version')
|
||||||
|
@ -649,43 +655,41 @@ class BibleMediaItem(MediaManagerItem):
|
||||||
u'Copyright')
|
u'Copyright')
|
||||||
dual_permission = self.parent.manager.get_meta_data(dual_bible,
|
dual_permission = self.parent.manager.get_meta_data(dual_bible,
|
||||||
u'Permissions')
|
u'Permissions')
|
||||||
if dual_permission:
|
if not dual_permission:
|
||||||
dual_permission = dual_permission.value
|
|
||||||
else:
|
|
||||||
dual_permission = u''
|
dual_permission = u''
|
||||||
# We count the number of rows which are maybe already present.
|
# We count the number of rows which are maybe already present.
|
||||||
start_count = self.listView.count()
|
start_count = self.listView.count()
|
||||||
for count, verse in enumerate(self.search_results):
|
for count, verse in enumerate(self.search_results):
|
||||||
if dual_bible:
|
if dual_bible:
|
||||||
vdict = {
|
vdict = {
|
||||||
'book':QtCore.QVariant(verse.book.name),
|
'book': QtCore.QVariant(verse.book.name),
|
||||||
'chapter':QtCore.QVariant(verse.chapter),
|
'chapter': QtCore.QVariant(verse.chapter),
|
||||||
'verse':QtCore.QVariant(verse.verse),
|
'verse': QtCore.QVariant(verse.verse),
|
||||||
'bible':QtCore.QVariant(bible),
|
'bible': QtCore.QVariant(bible),
|
||||||
'version':QtCore.QVariant(version.value),
|
'version': QtCore.QVariant(version.value),
|
||||||
'copyright':QtCore.QVariant(copyright.value),
|
'copyright': QtCore.QVariant(copyright.value),
|
||||||
#'permission':QtCore.QVariant(permission.value),
|
'permission': QtCore.QVariant(permission.value),
|
||||||
'text':QtCore.QVariant(verse.text),
|
'text': QtCore.QVariant(verse.text),
|
||||||
'dual_bible':QtCore.QVariant(dual_bible),
|
'dual_bible': QtCore.QVariant(dual_bible),
|
||||||
'dual_version':QtCore.QVariant(dual_version.value),
|
'dual_version': QtCore.QVariant(dual_version.value),
|
||||||
'dual_copyright':QtCore.QVariant(dual_copyright.value),
|
'dual_copyright': QtCore.QVariant(dual_copyright.value),
|
||||||
#'dual_permission':QtCore.QVariant(dual_permission),
|
'dual_permission': QtCore.QVariant(dual_permission.value),
|
||||||
'dual_text':QtCore.QVariant(
|
'dual_text': QtCore.QVariant(
|
||||||
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),
|
||||||
'chapter':QtCore.QVariant(verse.chapter),
|
'chapter': QtCore.QVariant(verse.chapter),
|
||||||
'verse':QtCore.QVariant(verse.verse),
|
'verse': QtCore.QVariant(verse.verse),
|
||||||
'bible':QtCore.QVariant(bible),
|
'bible': QtCore.QVariant(bible),
|
||||||
'version':QtCore.QVariant(version.value),
|
'version': QtCore.QVariant(version.value),
|
||||||
'copyright':QtCore.QVariant(copyright.value),
|
'copyright': QtCore.QVariant(copyright.value),
|
||||||
#'permission':QtCore.QVariant(permission.value),
|
'permission': QtCore.QVariant(permission.value),
|
||||||
'text':QtCore.QVariant(verse.text),
|
'text': QtCore.QVariant(verse.text),
|
||||||
'dual_bible':QtCore.QVariant(dual_bible)
|
'dual_bible': QtCore.QVariant(dual_bible)
|
||||||
}
|
}
|
||||||
bible_text = u' %s %d:%d (%s)' % (verse.book.name,
|
bible_text = u' %s %d:%d (%s)' % (verse.book.name,
|
||||||
verse.chapter, verse.verse, version.value)
|
verse.chapter, verse.verse, version.value)
|
||||||
|
|
|
@ -26,7 +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
|
||||||
|
|
||||||
class Ui_CustomEditDialog(object):
|
class Ui_CustomEditDialog(object):
|
||||||
def setupUi(self, customEditDialog):
|
def setupUi(self, customEditDialog):
|
||||||
|
@ -73,7 +73,7 @@ class Ui_CustomEditDialog(object):
|
||||||
self.editLayout3.setSpacing(8)
|
self.editLayout3.setSpacing(8)
|
||||||
self.editLayout3.setMargin(0)
|
self.editLayout3.setMargin(0)
|
||||||
self.editLayout3.setObjectName(u'editLayout3')
|
self.editLayout3.setObjectName(u'editLayout3')
|
||||||
self.verseTextEdit = QtGui.QTextEdit(self.editWidget)
|
self.verseTextEdit = SpellTextEdit(self)
|
||||||
self.verseTextEdit.setObjectName(u'verseTextEdit')
|
self.verseTextEdit.setObjectName(u'verseTextEdit')
|
||||||
self.editLayout3.addWidget(self.verseTextEdit)
|
self.editLayout3.addWidget(self.verseTextEdit)
|
||||||
self.buttonWidget = QtGui.QWidget(self.editWidget)
|
self.buttonWidget = QtGui.QWidget(self.editWidget)
|
||||||
|
|
|
@ -110,8 +110,14 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
u':/slides/slide_blank.png',
|
u':/slides/slide_blank.png',
|
||||||
translate('ImagePlugin.MediaItem', 'Replace Live Background'),
|
translate('ImagePlugin.MediaItem', 'Replace Live Background'),
|
||||||
self.onReplaceClick, False)
|
self.onReplaceClick, False)
|
||||||
|
self.resetButton = self.toolbar.addToolbarButton(
|
||||||
|
translate('ImagePlugin.MediaItem', u'Reset Background'),
|
||||||
|
u':/system/system_close.png',
|
||||||
|
translate('ImagePlugin.MediaItem', 'Reset Live Background'),
|
||||||
|
self.onResetClick, False)
|
||||||
# Add the song widget to the page layout
|
# Add the song widget to the page layout
|
||||||
self.pageLayout.addWidget(self.ImageWidget)
|
self.pageLayout.addWidget(self.ImageWidget)
|
||||||
|
self.resetButton.setVisible(False)
|
||||||
|
|
||||||
def onDeleteClick(self):
|
def onDeleteClick(self):
|
||||||
"""
|
"""
|
||||||
|
@ -169,6 +175,10 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def onResetClick(self):
|
||||||
|
self.resetButton.setVisible(False)
|
||||||
|
self.parent.liveController.display.resetImage()
|
||||||
|
|
||||||
def onReplaceClick(self):
|
def onReplaceClick(self):
|
||||||
if check_item_selected(self.listView,
|
if check_item_selected(self.listView,
|
||||||
translate('ImagePlugin.MediaItem',
|
translate('ImagePlugin.MediaItem',
|
||||||
|
@ -178,7 +188,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
bitem = self.listView.item(item.row())
|
bitem = self.listView.item(item.row())
|
||||||
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
|
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
|
||||||
frame = QtGui.QImage(unicode(filename))
|
frame = QtGui.QImage(unicode(filename))
|
||||||
self.parent.displayManager.displayImageWithText(frame)
|
self.parent.liveController.display.image(frame)
|
||||||
|
self.resetButton.setVisible(True)
|
||||||
|
|
||||||
def onPreviewClick(self):
|
def onPreviewClick(self):
|
||||||
MediaManagerItem.onPreviewClick(self)
|
MediaManagerItem.onPreviewClick(self)
|
||||||
|
|
|
@ -97,8 +97,17 @@ class MediaMediaItem(MediaManagerItem):
|
||||||
u':/slides/slide_blank.png',
|
u':/slides/slide_blank.png',
|
||||||
translate('MediaPlugin.MediaItem', 'Replace Live Background'),
|
translate('MediaPlugin.MediaItem', 'Replace Live Background'),
|
||||||
self.onReplaceClick, False)
|
self.onReplaceClick, False)
|
||||||
|
self.resetButton = self.toolbar.addToolbarButton(
|
||||||
|
u'Reset Background', u':/system/system_close.png',
|
||||||
|
translate('ImagePlugin.MediaItem', 'Reset Live Background'),
|
||||||
|
self.onResetClick, False)
|
||||||
# Add the song widget to the page layout
|
# Add the song widget to the page layout
|
||||||
self.pageLayout.addWidget(self.ImageWidget)
|
self.pageLayout.addWidget(self.ImageWidget)
|
||||||
|
self.resetButton.setVisible(False)
|
||||||
|
|
||||||
|
def onResetClick(self):
|
||||||
|
self.resetButton.setVisible(False)
|
||||||
|
self.parent.liveController.display.resetVideo()
|
||||||
|
|
||||||
def onReplaceClick(self):
|
def onReplaceClick(self):
|
||||||
if check_item_selected(self.listView,
|
if check_item_selected(self.listView,
|
||||||
|
@ -106,7 +115,8 @@ class MediaMediaItem(MediaManagerItem):
|
||||||
'You must select a media file to replace the background with.')):
|
'You must select a media file to replace the background with.')):
|
||||||
item = self.listView.currentItem()
|
item = self.listView.currentItem()
|
||||||
filename = unicode(item.data(QtCore.Qt.UserRole).toString())
|
filename = unicode(item.data(QtCore.Qt.UserRole).toString())
|
||||||
self.parent.displayManager.displayVideo(filename)
|
self.parent.liveController.display.video(filename, 0)
|
||||||
|
self.resetButton.setVisible(True)
|
||||||
|
|
||||||
def generateSlideData(self, service_item, item=None):
|
def generateSlideData(self, service_item, item=None):
|
||||||
if item is None:
|
if item is None:
|
||||||
|
|
|
@ -1,119 +1,57 @@
|
||||||
<html>
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
<head>
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<title>OpenLP Controller</title>
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||||
<script type='text/javascript'>
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
function send_event(eventname, data){
|
<title>OpenLP Remote Controller</title>
|
||||||
var req = new XMLHttpRequest();
|
<script type="text/javascript" src="/files/jquery.js"></script>
|
||||||
req.onreadystatechange = function() {
|
<script type='text/javascript' src="/files/openlp.js"></script>
|
||||||
if(req.readyState==4)
|
<script type='text/javascript' src="/files/init.js"></script>
|
||||||
response(eventname, req);
|
<link rel="stylesheet" href="/files/style.css" type="text/css" />
|
||||||
}
|
|
||||||
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
|
||||||
|
@ -37,7 +41,7 @@ from openlp.core.utils import AppLocation
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class HttpServer(object):
|
class HttpServer(object):
|
||||||
"""
|
"""
|
||||||
Ability to control OpenLP via a webbrowser
|
Ability to control OpenLP via a webbrowser
|
||||||
e.g. http://localhost:4316/send/slidecontroller_live_next
|
e.g. http://localhost:4316/send/slidecontroller_live_next
|
||||||
http://localhost:4316/send/alerts_text?q=your%20alert%20text
|
http://localhost:4316/send/alerts_text?q=your%20alert%20text
|
||||||
|
@ -59,7 +63,7 @@ class HttpServer(object):
|
||||||
def start_tcp(self):
|
def start_tcp(self):
|
||||||
"""
|
"""
|
||||||
Start the http server, use the port in the settings default to 4316
|
Start the http server, use the port in the settings default to 4316
|
||||||
Listen out for slide and song changes so they can be broadcast to
|
Listen out for slide and song changes so they can be broadcast to
|
||||||
clients. Listen out for socket connections
|
clients. Listen out for socket connections
|
||||||
"""
|
"""
|
||||||
log.debug(u'Start TCP server')
|
log.debug(u'Start TCP server')
|
||||||
|
@ -67,13 +71,13 @@ class HttpServer(object):
|
||||||
self.parent.settingsSection + u'/remote port',
|
self.parent.settingsSection + u'/remote port',
|
||||||
QtCore.QVariant(4316)).toInt()[0]
|
QtCore.QVariant(4316)).toInt()[0]
|
||||||
self.server = QtNetwork.QTcpServer()
|
self.server = QtNetwork.QTcpServer()
|
||||||
self.server.listen(QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any),
|
self.server.listen(QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any),
|
||||||
port)
|
port)
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'slidecontroller_live_changed'),
|
QtCore.SIGNAL(u'slidecontroller_live_changed'),
|
||||||
self.slide_change)
|
self.slide_change)
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'slidecontroller_live_started'),
|
QtCore.SIGNAL(u'slidecontroller_live_started'),
|
||||||
self.item_change)
|
self.item_change)
|
||||||
QtCore.QObject.connect(self.server,
|
QtCore.QObject.connect(self.server,
|
||||||
QtCore.SIGNAL(u'newConnection()'), self.new_connection)
|
QtCore.SIGNAL(u'newConnection()'), self.new_connection)
|
||||||
|
@ -92,7 +96,7 @@ class HttpServer(object):
|
||||||
"""
|
"""
|
||||||
self.current_item = items[0].title
|
self.current_item = items[0].title
|
||||||
self.send_poll()
|
self.send_poll()
|
||||||
|
|
||||||
def send_poll(self):
|
def send_poll(self):
|
||||||
"""
|
"""
|
||||||
Tell the clients something has changed
|
Tell the clients something has changed
|
||||||
|
@ -100,7 +104,7 @@ class HttpServer(object):
|
||||||
Receiver.send_message(u'remotes_poll_response',
|
Receiver.send_message(u'remotes_poll_response',
|
||||||
{'slide': self.current_slide,
|
{'slide': self.current_slide,
|
||||||
'item': self.current_item})
|
'item': self.current_item})
|
||||||
|
|
||||||
def new_connection(self):
|
def new_connection(self):
|
||||||
"""
|
"""
|
||||||
A new http connection has been made. Create a client object to handle
|
A new http connection has been made. Create a client object to handle
|
||||||
|
@ -110,7 +114,7 @@ class HttpServer(object):
|
||||||
socket = self.server.nextPendingConnection()
|
socket = self.server.nextPendingConnection()
|
||||||
if socket:
|
if socket:
|
||||||
self.connections.append(HttpConnection(self, socket))
|
self.connections.append(HttpConnection(self, socket))
|
||||||
|
|
||||||
def close_connection(self, connection):
|
def close_connection(self, connection):
|
||||||
"""
|
"""
|
||||||
The connection has been closed. Clean up
|
The connection has been closed. Clean up
|
||||||
|
@ -124,9 +128,9 @@ class HttpServer(object):
|
||||||
"""
|
"""
|
||||||
log.debug(u'close http server')
|
log.debug(u'close http server')
|
||||||
self.server.close()
|
self.server.close()
|
||||||
|
|
||||||
class HttpConnection(object):
|
class HttpConnection(object):
|
||||||
"""
|
"""
|
||||||
A single connection, this handles communication between the server
|
A single connection, this handles communication between the server
|
||||||
and the client
|
and the client
|
||||||
"""
|
"""
|
||||||
|
@ -134,7 +138,7 @@ class HttpConnection(object):
|
||||||
"""
|
"""
|
||||||
Initialise the http connection. Listen out for socket signals
|
Initialise the http connection. Listen out for socket signals
|
||||||
"""
|
"""
|
||||||
log.debug(u'Initialise HttpConnection: %s' %
|
log.debug(u'Initialise HttpConnection: %s' %
|
||||||
socket.peerAddress().toString())
|
socket.peerAddress().toString())
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
@ -180,13 +184,13 @@ class HttpConnection(object):
|
||||||
def serve_file(self, filename):
|
def serve_file(self, filename):
|
||||||
"""
|
"""
|
||||||
Send a file to the socket. For now, just a subset of file types
|
Send a file to the socket. For now, just a subset of file types
|
||||||
and must be top level inside the html folder.
|
and must be top level inside the html folder.
|
||||||
If subfolders requested return 404, easier for security for the present.
|
If subfolders requested return 404, easier for security for the present.
|
||||||
|
|
||||||
Ultimately for i18n, this could first look for xx/file.html before
|
Ultimately for i18n, this could first look for xx/file.html before
|
||||||
falling back to file.html... where xx is the language, e.g. 'en'
|
falling back to file.html... where xx is the language, e.g. 'en'
|
||||||
"""
|
"""
|
||||||
log.debug(u'serve file request %s' % filename)
|
log.debug(u'serve file request %s' % filename)
|
||||||
if not filename:
|
if not filename:
|
||||||
filename = u'index.html'
|
filename = u'index.html'
|
||||||
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
|
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
|
||||||
|
@ -229,8 +233,8 @@ class HttpConnection(object):
|
||||||
if not params:
|
if not params:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return params['q']
|
return params['q']
|
||||||
|
|
||||||
def process_event(self, event, params):
|
def process_event(self, event, params):
|
||||||
"""
|
"""
|
||||||
Send a signal to openlp to perform an action.
|
Send a signal to openlp to perform an action.
|
||||||
|
@ -239,21 +243,27 @@ class HttpConnection(object):
|
||||||
"""
|
"""
|
||||||
log.debug(u'Processing event %s' % event)
|
log.debug(u'Processing event %s' % event)
|
||||||
if params:
|
if params:
|
||||||
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'):
|
||||||
|
@ -271,14 +281,14 @@ class HttpConnection(object):
|
||||||
else:
|
else:
|
||||||
self.timer.start(10000)
|
self.timer.start(10000)
|
||||||
if params:
|
if params:
|
||||||
Receiver.send_message(event, params)
|
Receiver.send_message(event, params)
|
||||||
else:
|
else:
|
||||||
Receiver.send_message(event)
|
Receiver.send_message(event)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def process_response(self, data):
|
def process_response(self, data):
|
||||||
"""
|
"""
|
||||||
The recipient of a _request signal has sent data. Convert this to
|
The recipient of a _request signal has sent data. Convert this to
|
||||||
json and return it to client
|
json and return it to client
|
||||||
"""
|
"""
|
||||||
log.debug(u'Processing response for %s' % self.event)
|
log.debug(u'Processing response for %s' % self.event)
|
||||||
|
@ -292,7 +302,7 @@ class HttpConnection(object):
|
||||||
|
|
||||||
def send_200_ok(self, mimetype='text/html; charset="utf-8"'):
|
def send_200_ok(self, mimetype='text/html; charset="utf-8"'):
|
||||||
"""
|
"""
|
||||||
Successful request. Send OK headers. Assume html for now.
|
Successful request. Send OK headers. Assume html for now.
|
||||||
"""
|
"""
|
||||||
self.socket.write(u'HTTP/1.1 200 OK\r\n' + \
|
self.socket.write(u'HTTP/1.1 200 OK\r\n' + \
|
||||||
u'Content-Type: %s\r\n\r\n' % mimetype)
|
u'Content-Type: %s\r\n\r\n' % mimetype)
|
||||||
|
@ -307,11 +317,11 @@ class HttpConnection(object):
|
||||||
|
|
||||||
def send_408_timeout(self):
|
def send_408_timeout(self):
|
||||||
"""
|
"""
|
||||||
A _request hasn't returned anything in the timeout period.
|
A _request hasn't returned anything in the timeout period.
|
||||||
Return timeout
|
Return timeout
|
||||||
"""
|
"""
|
||||||
self.socket.write(u'HTTP/1.1 408 Request Timeout\r\n')
|
self.socket.write(u'HTTP/1.1 408 Request Timeout\r\n')
|
||||||
|
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
"""
|
"""
|
||||||
Listener for timeout signal
|
Listener for timeout signal
|
||||||
|
@ -320,14 +330,14 @@ class HttpConnection(object):
|
||||||
return
|
return
|
||||||
self.send_408_timeout()
|
self.send_408_timeout()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def disconnected(self):
|
def disconnected(self):
|
||||||
"""
|
"""
|
||||||
The client has disconnected. Tidy up
|
The client has disconnected. Tidy up
|
||||||
"""
|
"""
|
||||||
log.debug(u'socket disconnected')
|
log.debug(u'socket disconnected')
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
The server has closed the connection. Tidy up
|
The server has closed the connection. Tidy up
|
||||||
|
|
|
@ -263,8 +263,8 @@ class Ui_EditSongDialog(object):
|
||||||
self.SongbookLayout.setSpacing(8)
|
self.SongbookLayout.setSpacing(8)
|
||||||
self.SongbookLayout.setObjectName(u'SongbookLayout')
|
self.SongbookLayout.setObjectName(u'SongbookLayout')
|
||||||
self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup)
|
self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup)
|
||||||
sizePolicy = QtGui.QSizePolicy(
|
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
|
||||||
QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
|
QtGui.QSizePolicy.Fixed)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
sizePolicy.setHeightForWidth(
|
sizePolicy.setHeightForWidth(
|
||||||
|
@ -273,6 +273,12 @@ class Ui_EditSongDialog(object):
|
||||||
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.addWidget(self.SongbookCombo, 0, 0, 1, 1)
|
||||||
|
self.songBookNumberLabel = QtGui.QLabel(self.SongBookGroup)
|
||||||
|
self.SongbookLayout.addWidget(self.songBookNumberLabel, 0, 1, 1, 1)
|
||||||
|
self.songBookNumberEdit = QtGui.QLineEdit(self.SongBookGroup)
|
||||||
|
self.songBookNumberLabel.setBuddy(self.songBookNumberEdit)
|
||||||
|
self.songBookNumberEdit.setMaximumWidth(35)
|
||||||
|
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'')
|
||||||
|
@ -440,6 +446,8 @@ 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.songBookNumberLabel.setText(translate('SongsPlugin.EditSongForm',
|
||||||
|
'Song No.:'))
|
||||||
self.SongTabWidget.setTabText(
|
self.SongTabWidget.setTabText(
|
||||||
self.SongTabWidget.indexOf(self.AuthorsTab),
|
self.SongTabWidget.indexOf(self.AuthorsTab),
|
||||||
translate('SongsPlugin.EditSongForm',
|
translate('SongsPlugin.EditSongForm',
|
||||||
|
|
|
@ -49,6 +49,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
"""
|
"""
|
||||||
QtGui.QDialog.__init__(self, parent)
|
QtGui.QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.song = None
|
||||||
# can this be automated?
|
# can this be automated?
|
||||||
self.width = 400
|
self.width = 400
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
@ -83,10 +84,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
QtCore.QObject.connect(self.VerseListWidget,
|
QtCore.QObject.connect(self.VerseListWidget,
|
||||||
QtCore.SIGNAL(u'itemClicked(QTableWidgetItem*)'),
|
QtCore.SIGNAL(u'itemClicked(QTableWidgetItem*)'),
|
||||||
self.onVerseListViewPressed)
|
self.onVerseListViewPressed)
|
||||||
QtCore.QObject.connect(self.SongbookCombo,
|
|
||||||
QtCore.SIGNAL(u'activated(int)'), self.onSongBookComboChanged)
|
|
||||||
QtCore.QObject.connect(self.ThemeSelectionComboItem,
|
|
||||||
QtCore.SIGNAL(u'activated(int)'), self.onThemeComboChanged)
|
|
||||||
QtCore.QObject.connect(self.ThemeAddButton,
|
QtCore.QObject.connect(self.ThemeAddButton,
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
QtCore.SIGNAL(u'clicked()'),
|
||||||
self.parent.parent.renderManager.theme_manager.onAddTheme)
|
self.parent.parent.renderManager.theme_manager.onAddTheme)
|
||||||
|
@ -157,7 +154,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
def newSong(self):
|
def newSong(self):
|
||||||
log.debug(u'New Song')
|
log.debug(u'New Song')
|
||||||
self.SongTabWidget.setCurrentIndex(0)
|
self.SongTabWidget.setCurrentIndex(0)
|
||||||
self.song = Song()
|
|
||||||
self.TitleEditItem.setText(u'')
|
self.TitleEditItem.setText(u'')
|
||||||
self.AlternativeEdit.setText(u'')
|
self.AlternativeEdit.setText(u'')
|
||||||
self.CopyrightEditItem.setText(u'')
|
self.CopyrightEditItem.setText(u'')
|
||||||
|
@ -278,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)
|
||||||
|
|
||||||
|
@ -298,8 +294,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
else:
|
else:
|
||||||
author = Author.populate(first_name=text.rsplit(u' ', 1)[0],
|
author = Author.populate(first_name=text.rsplit(u' ', 1)[0],
|
||||||
last_name=text.rsplit(u' ', 1)[1], display_name=text)
|
last_name=text.rsplit(u' ', 1)[1], display_name=text)
|
||||||
self.songmanager.save_object(author, False)
|
self.songmanager.save_object(author)
|
||||||
self.song.authors.append(author)
|
|
||||||
author_item = QtGui.QListWidgetItem(
|
author_item = QtGui.QListWidgetItem(
|
||||||
unicode(author.display_name))
|
unicode(author.display_name))
|
||||||
author_item.setData(QtCore.Qt.UserRole,
|
author_item.setData(QtCore.Qt.UserRole,
|
||||||
|
@ -312,13 +307,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
elif item > 0:
|
elif item > 0:
|
||||||
item_id = (self.AuthorsSelectionComboItem.itemData(item)).toInt()[0]
|
item_id = (self.AuthorsSelectionComboItem.itemData(item)).toInt()[0]
|
||||||
author = self.songmanager.get_object(Author, item_id)
|
author = self.songmanager.get_object(Author, item_id)
|
||||||
if author in self.song.authors:
|
if self.AuthorsListView.findItems(unicode(author.display_name),
|
||||||
|
QtCore.Qt.MatchExactly):
|
||||||
QtGui.QMessageBox.warning(self,
|
QtGui.QMessageBox.warning(self,
|
||||||
translate('SongsPlugin.EditSongForm', 'Error'),
|
translate('SongsPlugin.EditSongForm', 'Error'),
|
||||||
translate('SongsPlugin.EditSongForm', 'This author is '
|
translate('SongsPlugin.EditSongForm', 'This author is '
|
||||||
'already in the list.'))
|
'already in the list.'))
|
||||||
else:
|
else:
|
||||||
self.song.authors.append(author)
|
|
||||||
author_item = QtGui.QListWidgetItem(unicode(
|
author_item = QtGui.QListWidgetItem(unicode(
|
||||||
author.display_name))
|
author.display_name))
|
||||||
author_item.setData(QtCore.Qt.UserRole,
|
author_item.setData(QtCore.Qt.UserRole,
|
||||||
|
@ -340,9 +335,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
def onAuthorRemoveButtonClicked(self):
|
def onAuthorRemoveButtonClicked(self):
|
||||||
self.AuthorRemoveButton.setEnabled(False)
|
self.AuthorRemoveButton.setEnabled(False)
|
||||||
item = self.AuthorsListView.currentItem()
|
item = self.AuthorsListView.currentItem()
|
||||||
author_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
|
|
||||||
author = self.songmanager.get_object(Author, author_id)
|
|
||||||
self.song.authors.remove(author)
|
|
||||||
row = self.AuthorsListView.row(item)
|
row = self.AuthorsListView.row(item)
|
||||||
self.AuthorsListView.takeItem(row)
|
self.AuthorsListView.takeItem(row)
|
||||||
|
|
||||||
|
@ -357,8 +349,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
|
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
|
||||||
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
|
QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
|
||||||
topic = Topic.populate(name=text)
|
topic = Topic.populate(name=text)
|
||||||
self.songmanager.save_object(topic, False)
|
self.songmanager.save_object(topic)
|
||||||
self.song.topics.append(topic)
|
|
||||||
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
||||||
topic_item.setData(QtCore.Qt.UserRole,
|
topic_item.setData(QtCore.Qt.UserRole,
|
||||||
QtCore.QVariant(topic.id))
|
QtCore.QVariant(topic.id))
|
||||||
|
@ -370,13 +361,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
elif item > 0:
|
elif item > 0:
|
||||||
item_id = (self.SongTopicCombo.itemData(item)).toInt()[0]
|
item_id = (self.SongTopicCombo.itemData(item)).toInt()[0]
|
||||||
topic = self.songmanager.get_object(Topic, item_id)
|
topic = self.songmanager.get_object(Topic, item_id)
|
||||||
if topic in self.song.topics:
|
if self.TopicsListView.findItems(unicode(topic.name),
|
||||||
|
QtCore.Qt.MatchExactly):
|
||||||
QtGui.QMessageBox.warning(self,
|
QtGui.QMessageBox.warning(self,
|
||||||
translate('SongsPlugin.EditSongForm', 'Error'),
|
translate('SongsPlugin.EditSongForm', 'Error'),
|
||||||
translate('SongsPlugin.EditSongForm', 'This topic is '
|
translate('SongsPlugin.EditSongForm', 'This topic is '
|
||||||
'already in the list.'))
|
'already in the list.'))
|
||||||
else:
|
else:
|
||||||
self.song.topics.append(topic)
|
|
||||||
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
||||||
topic_item.setData(QtCore.Qt.UserRole,
|
topic_item.setData(QtCore.Qt.UserRole,
|
||||||
QtCore.QVariant(topic.id))
|
QtCore.QVariant(topic.id))
|
||||||
|
@ -396,27 +387,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
def onTopicRemoveButtonClicked(self):
|
def onTopicRemoveButtonClicked(self):
|
||||||
self.TopicRemoveButton.setEnabled(False)
|
self.TopicRemoveButton.setEnabled(False)
|
||||||
item = self.TopicsListView.currentItem()
|
item = self.TopicsListView.currentItem()
|
||||||
topic_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
|
|
||||||
topic = self.songmanager.get_object(Topic, topic_id)
|
|
||||||
self.song.topics.remove(topic)
|
|
||||||
row = self.TopicsListView.row(item)
|
row = self.TopicsListView.row(item)
|
||||||
self.TopicsListView.takeItem(row)
|
self.TopicsListView.takeItem(row)
|
||||||
|
|
||||||
def onSongBookComboChanged(self, item):
|
|
||||||
if item >= 1:
|
|
||||||
self.song.song_book_id = \
|
|
||||||
(self.SongbookCombo.itemData(item)).toInt()[0]
|
|
||||||
else:
|
|
||||||
self.song.song_book_id = 0
|
|
||||||
|
|
||||||
def onThemeComboChanged(self, item):
|
|
||||||
if item == 0:
|
|
||||||
# None means no Theme
|
|
||||||
self.song.theme_name = None
|
|
||||||
else:
|
|
||||||
them_name = unicode(self.ThemeSelectionComboItem.itemText(item))
|
|
||||||
self.song.theme_name = them_name
|
|
||||||
|
|
||||||
def onVerseListViewPressed(self):
|
def onVerseListViewPressed(self):
|
||||||
self.VerseEditButton.setEnabled(True)
|
self.VerseEditButton.setEnabled(True)
|
||||||
self.VerseDeleteButton.setEnabled(True)
|
self.VerseDeleteButton.setEnabled(True)
|
||||||
|
@ -600,10 +573,17 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
self.CopyrightEditItem.setCursorPosition(pos + 1)
|
self.CopyrightEditItem.setCursorPosition(pos + 1)
|
||||||
|
|
||||||
def onMaintenanceButtonClicked(self):
|
def onMaintenanceButtonClicked(self):
|
||||||
|
temp_song_book = None
|
||||||
|
item = int(self.SongbookCombo.currentIndex())
|
||||||
|
text = unicode(self.SongbookCombo.currentText())
|
||||||
|
if item == 0 and text:
|
||||||
|
temp_song_book = text
|
||||||
self.parent.song_maintenance_form.exec_()
|
self.parent.song_maintenance_form.exec_()
|
||||||
self.loadAuthors()
|
self.loadAuthors()
|
||||||
self.loadBooks()
|
self.loadBooks()
|
||||||
self.loadTopics()
|
self.loadTopics()
|
||||||
|
if temp_song_book:
|
||||||
|
self.SongbookCombo.setEditText(temp_song_book)
|
||||||
|
|
||||||
def onPreview(self, button):
|
def onPreview(self, button):
|
||||||
"""
|
"""
|
||||||
|
@ -620,6 +600,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
log.debug(u'accept')
|
log.debug(u'accept')
|
||||||
|
if not self.song:
|
||||||
|
self.song = Song()
|
||||||
item = int(self.SongbookCombo.currentIndex())
|
item = int(self.SongbookCombo.currentIndex())
|
||||||
text = unicode(self.SongbookCombo.currentText())
|
text = unicode(self.SongbookCombo.currentText())
|
||||||
if item == 0 and text:
|
if item == 0 and text:
|
||||||
|
@ -632,7 +614,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
book = Book.populate(name=text, publisher=u'')
|
book = Book.populate(name=text, publisher=u'')
|
||||||
self.songmanager.save_object(book)
|
self.songmanager.save_object(book)
|
||||||
self.song.book = book
|
self.song.book = book
|
||||||
self.loadBooks()
|
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
if self.saveSong():
|
if self.saveSong():
|
||||||
|
@ -648,9 +629,22 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
self.song.comments = unicode(self.CommentsEdit.toPlainText())
|
self.song.comments = unicode(self.CommentsEdit.toPlainText())
|
||||||
self.song.verse_order = unicode(self.VerseOrderEdit.text())
|
self.song.verse_order = unicode(self.VerseOrderEdit.text())
|
||||||
self.song.ccli_number = unicode(self.CCLNumberEdit.text())
|
self.song.ccli_number = unicode(self.CCLNumberEdit.text())
|
||||||
|
self.song.song_number = unicode(self.songBookNumberEdit.text())
|
||||||
if self._validate_song():
|
if self._validate_song():
|
||||||
self.processLyrics()
|
self.processLyrics()
|
||||||
self.processTitle()
|
self.processTitle()
|
||||||
|
self.song.authors = []
|
||||||
|
for row in range(self.AuthorsListView.count()):
|
||||||
|
item = self.AuthorsListView.item(row)
|
||||||
|
authorId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
|
||||||
|
self.song.authors.append(self.songmanager.get_object(Author,
|
||||||
|
authorId))
|
||||||
|
self.song.topics = []
|
||||||
|
for row in range(self.TopicsListView.count()):
|
||||||
|
item = self.TopicsListView.item(row)
|
||||||
|
topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0]
|
||||||
|
self.song.topics.append(self.songmanager.get_object(Topic,
|
||||||
|
topicId))
|
||||||
self.songmanager.save_object(self.song)
|
self.songmanager.save_object(self.song)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -682,5 +676,5 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||||
|
|
||||||
def processTitle(self):
|
def processTitle(self):
|
||||||
log.debug(u'processTitle')
|
log.debug(u'processTitle')
|
||||||
self.song.search_title = \
|
self.song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
|
||||||
re.sub(r'[\'"`,;:(){}?]+', u'', unicode(self.song.search_title))
|
unicode(self.song.search_title))
|
||||||
|
|
|
@ -26,81 +26,82 @@
|
||||||
|
|
||||||
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.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
|
|
||||||
class Ui_EditVerseDialog(object):
|
class Ui_EditVerseDialog(object):
|
||||||
def setupUi(self, EditVerseDialog):
|
def setupUi(self, editVerseDialog):
|
||||||
EditVerseDialog.setObjectName(u'EditVerseDialog')
|
editVerseDialog.setObjectName(u'editVerseDialog')
|
||||||
EditVerseDialog.resize(474, 442)
|
editVerseDialog.resize(474, 442)
|
||||||
EditVerseDialog.setModal(True)
|
editVerseDialog.setModal(True)
|
||||||
self.EditVerseLayout = QtGui.QVBoxLayout(EditVerseDialog)
|
self.editVerseLayout = QtGui.QVBoxLayout(editVerseDialog)
|
||||||
self.EditVerseLayout.setSpacing(8)
|
self.editVerseLayout.setSpacing(8)
|
||||||
self.EditVerseLayout.setMargin(8)
|
self.editVerseLayout.setMargin(8)
|
||||||
self.EditVerseLayout.setObjectName(u'EditVerseLayout')
|
self.editVerseLayout.setObjectName(u'editVerseLayout')
|
||||||
self.VerseTextEdit = QtGui.QPlainTextEdit(EditVerseDialog)
|
self.verseTextEdit = SpellTextEdit(editVerseDialog)
|
||||||
self.VerseTextEdit.setObjectName(u'VerseTextEdit')
|
self.verseTextEdit.setObjectName(u'verseTextEdit')
|
||||||
self.EditVerseLayout.addWidget(self.VerseTextEdit)
|
self.editVerseLayout.addWidget(self.verseTextEdit)
|
||||||
self.VerseTypeLayout = QtGui.QHBoxLayout()
|
self.verseTypeLayout = QtGui.QHBoxLayout()
|
||||||
self.VerseTypeLayout.setSpacing(8)
|
self.verseTypeLayout.setSpacing(8)
|
||||||
self.VerseTypeLayout.setObjectName(u'VerseTypeLayout')
|
self.verseTypeLayout.setObjectName(u'verseTypeLayout')
|
||||||
self.VerseTypeLabel = QtGui.QLabel(EditVerseDialog)
|
self.verseTypeLabel = QtGui.QLabel(editVerseDialog)
|
||||||
self.VerseTypeLabel.setObjectName(u'VerseTypeLabel')
|
self.verseTypeLabel.setObjectName(u'verseTypeLabel')
|
||||||
self.VerseTypeLayout.addWidget(self.VerseTypeLabel)
|
self.verseTypeLayout.addWidget(self.verseTypeLabel)
|
||||||
self.VerseTypeComboBox = QtGui.QComboBox(EditVerseDialog)
|
self.verseTypeComboBox = QtGui.QComboBox(editVerseDialog)
|
||||||
self.VerseTypeComboBox.setObjectName(u'VerseTypeComboBox')
|
self.verseTypeComboBox.setObjectName(u'verseTypeComboBox')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeLabel.setBuddy(self.verseTypeComboBox)
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeComboBox.addItem(u'')
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseTypeLayout.addWidget(self.VerseTypeComboBox)
|
self.verseTypeComboBox.addItem(u'')
|
||||||
self.VerseNumberBox = QtGui.QSpinBox(EditVerseDialog)
|
self.verseTypeLayout.addWidget(self.verseTypeComboBox)
|
||||||
self.VerseNumberBox.setMinimum(1)
|
self.verseNumberBox = QtGui.QSpinBox(editVerseDialog)
|
||||||
self.VerseNumberBox.setObjectName(u'VerseNumberBox')
|
self.verseNumberBox.setMinimum(1)
|
||||||
self.VerseTypeLayout.addWidget(self.VerseNumberBox)
|
self.verseNumberBox.setObjectName(u'verseNumberBox')
|
||||||
self.InsertButton = QtGui.QPushButton(EditVerseDialog)
|
self.verseTypeLayout.addWidget(self.verseNumberBox)
|
||||||
self.InsertButton.setIcon(build_icon(u':/general/general_add.png'))
|
self.insertButton = QtGui.QPushButton(editVerseDialog)
|
||||||
self.InsertButton.setObjectName(u'InsertButton')
|
self.insertButton.setIcon(build_icon(u':/general/general_add.png'))
|
||||||
self.VerseTypeLayout.addWidget(self.InsertButton)
|
self.insertButton.setObjectName(u'insertButton')
|
||||||
self.VerseTypeSpacer = QtGui.QSpacerItem(40, 20,
|
self.verseTypeLayout.addWidget(self.insertButton)
|
||||||
|
self.verseTypeSpacer = QtGui.QSpacerItem(40, 20,
|
||||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
self.VerseTypeLayout.addItem(self.VerseTypeSpacer)
|
self.verseTypeLayout.addItem(self.verseTypeSpacer)
|
||||||
self.EditVerseLayout.addLayout(self.VerseTypeLayout)
|
self.editVerseLayout.addLayout(self.verseTypeLayout)
|
||||||
self.EditButtonBox = QtGui.QDialogButtonBox(EditVerseDialog)
|
self.editButtonBox = QtGui.QDialogButtonBox(editVerseDialog)
|
||||||
self.EditButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
self.editButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||||
self.EditButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
|
self.editButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
|
||||||
QtGui.QDialogButtonBox.Save)
|
QtGui.QDialogButtonBox.Save)
|
||||||
self.EditButtonBox.setObjectName(u'EditButtonBox')
|
self.editButtonBox.setObjectName(u'editButtonBox')
|
||||||
self.EditVerseLayout.addWidget(self.EditButtonBox)
|
self.editVerseLayout.addWidget(self.editButtonBox)
|
||||||
|
|
||||||
self.retranslateUi(EditVerseDialog)
|
self.retranslateUi(editVerseDialog)
|
||||||
QtCore.QObject.connect(self.EditButtonBox, QtCore.SIGNAL(u'accepted()'),
|
QtCore.QObject.connect(self.editButtonBox, QtCore.SIGNAL(u'accepted()'),
|
||||||
EditVerseDialog.accept)
|
editVerseDialog.accept)
|
||||||
QtCore.QObject.connect(self.EditButtonBox, QtCore.SIGNAL(u'rejected()'),
|
QtCore.QObject.connect(self.editButtonBox, QtCore.SIGNAL(u'rejected()'),
|
||||||
EditVerseDialog.reject)
|
editVerseDialog.reject)
|
||||||
QtCore.QMetaObject.connectSlotsByName(EditVerseDialog)
|
QtCore.QMetaObject.connectSlotsByName(editVerseDialog)
|
||||||
|
|
||||||
def retranslateUi(self, EditVerseDialog):
|
def retranslateUi(self, editVerseDialog):
|
||||||
EditVerseDialog.setWindowTitle(
|
editVerseDialog.setWindowTitle(
|
||||||
translate('SongsPlugin.EditVerseForm', 'Edit Verse'))
|
translate('SongsPlugin.EditVerseForm', 'Edit Verse'))
|
||||||
self.VerseTypeLabel.setText(
|
self.verseTypeLabel.setText(
|
||||||
translate('SongsPlugin.EditVerseForm', '&Verse type:'))
|
translate('SongsPlugin.EditVerseForm', '&Verse type:'))
|
||||||
self.VerseTypeComboBox.setItemText(0,
|
self.verseTypeComboBox.setItemText(0,
|
||||||
VerseType.to_string(VerseType.Verse))
|
VerseType.to_string(VerseType.Verse))
|
||||||
self.VerseTypeComboBox.setItemText(1,
|
self.verseTypeComboBox.setItemText(1,
|
||||||
VerseType.to_string(VerseType.Chorus))
|
VerseType.to_string(VerseType.Chorus))
|
||||||
self.VerseTypeComboBox.setItemText(2,
|
self.verseTypeComboBox.setItemText(2,
|
||||||
VerseType.to_string(VerseType.Bridge))
|
VerseType.to_string(VerseType.Bridge))
|
||||||
self.VerseTypeComboBox.setItemText(3,
|
self.verseTypeComboBox.setItemText(3,
|
||||||
VerseType.to_string(VerseType.PreChorus))
|
VerseType.to_string(VerseType.PreChorus))
|
||||||
self.VerseTypeComboBox.setItemText(4,
|
self.verseTypeComboBox.setItemText(4,
|
||||||
VerseType.to_string(VerseType.Intro))
|
VerseType.to_string(VerseType.Intro))
|
||||||
self.VerseTypeComboBox.setItemText(5,
|
self.verseTypeComboBox.setItemText(5,
|
||||||
VerseType.to_string(VerseType.Ending))
|
VerseType.to_string(VerseType.Ending))
|
||||||
self.VerseTypeComboBox.setItemText(6,
|
self.verseTypeComboBox.setItemText(6,
|
||||||
VerseType.to_string(VerseType.Other))
|
VerseType.to_string(VerseType.Other))
|
||||||
self.InsertButton.setText(
|
self.insertButton.setText(
|
||||||
translate('SongsPlugin.EditVerseForm', '&Insert'))
|
translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||||
|
|
|
@ -45,34 +45,36 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||||
"""
|
"""
|
||||||
QtGui.QDialog.__init__(self, parent)
|
QtGui.QDialog.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
QtCore.QObject.connect(
|
QtCore.QObject.connect(self.verseTextEdit,
|
||||||
self.InsertButton,
|
QtCore.SIGNAL('customContextMenuRequested(QPoint)'),
|
||||||
QtCore.SIGNAL(u'clicked()'),
|
self.contextMenu)
|
||||||
self.onInsertButtonClicked
|
QtCore.QObject.connect(self.insertButton, QtCore.SIGNAL(u'clicked()'),
|
||||||
)
|
self.onInsertButtonClicked)
|
||||||
QtCore.QObject.connect(
|
QtCore.QObject.connect(self.verseTextEdit,
|
||||||
self.VerseTextEdit,
|
|
||||||
QtCore.SIGNAL(u'cursorPositionChanged()'),
|
QtCore.SIGNAL(u'cursorPositionChanged()'),
|
||||||
self.onCursorPositionChanged
|
self.onCursorPositionChanged)
|
||||||
)
|
|
||||||
self.verse_regex = re.compile(r'---\[([-\w]+):([\d]+)\]---')
|
self.verse_regex = re.compile(r'---\[([-\w]+):([\d]+)\]---')
|
||||||
|
|
||||||
|
def contextMenu(self, point):
|
||||||
|
item = self.serviceManagerList.itemAt(point)
|
||||||
|
print item
|
||||||
|
|
||||||
def insertVerse(self, title, num=1):
|
def insertVerse(self, title, num=1):
|
||||||
if self.VerseTextEdit.textCursor().columnNumber() != 0:
|
if self.verseTextEdit.textCursor().columnNumber() != 0:
|
||||||
self.VerseTextEdit.insertPlainText(u'\n')
|
self.verseTextEdit.insertPlainText(u'\n')
|
||||||
self.VerseTextEdit.insertPlainText(u'---[%s:%s]---\n' % (title, num))
|
self.verseTextEdit.insertPlainText(u'---[%s:%s]---\n' % (title, num))
|
||||||
self.VerseTextEdit.setFocus()
|
self.verseTextEdit.setFocus()
|
||||||
|
|
||||||
def onInsertButtonClicked(self):
|
def onInsertButtonClicked(self):
|
||||||
if self.VerseTextEdit.textCursor().columnNumber() != 0:
|
if self.verseTextEdit.textCursor().columnNumber() != 0:
|
||||||
self.VerseTextEdit.insertPlainText(u'\n')
|
self.verseTextEdit.insertPlainText(u'\n')
|
||||||
verse_type = self.VerseTypeComboBox.currentIndex()
|
verse_type = self.verseTypeComboBox.currentIndex()
|
||||||
if verse_type == VerseType.Verse:
|
if verse_type == VerseType.Verse:
|
||||||
self.insertVerse(VerseType.to_string(VerseType.Verse),
|
self.insertVerse(VerseType.to_string(VerseType.Verse),
|
||||||
self.VerseNumberBox.value())
|
self.verseNumberBox.value())
|
||||||
elif verse_type == VerseType.Chorus:
|
elif verse_type == VerseType.Chorus:
|
||||||
self.insertVerse(VerseType.to_string(VerseType.Chorus),
|
self.insertVerse(VerseType.to_string(VerseType.Chorus),
|
||||||
self.VerseNumberBox.value())
|
self.verseNumberBox.value())
|
||||||
elif verse_type == VerseType.Bridge:
|
elif verse_type == VerseType.Bridge:
|
||||||
self.insertVerse(VerseType.to_string(VerseType.Bridge))
|
self.insertVerse(VerseType.to_string(VerseType.Bridge))
|
||||||
elif verse_type == VerseType.PreChorus:
|
elif verse_type == VerseType.PreChorus:
|
||||||
|
@ -85,12 +87,12 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||||
self.insertVerse(VerseType.to_string(VerseType.Other))
|
self.insertVerse(VerseType.to_string(VerseType.Other))
|
||||||
|
|
||||||
def onCursorPositionChanged(self):
|
def onCursorPositionChanged(self):
|
||||||
position = self.VerseTextEdit.textCursor().position()
|
position = self.verseTextEdit.textCursor().position()
|
||||||
text = unicode(self.VerseTextEdit.toPlainText())
|
text = unicode(self.verseTextEdit.toPlainText())
|
||||||
if not text:
|
if not text:
|
||||||
return
|
return
|
||||||
if text.rfind(u'[', 0, position) > text.rfind(u']', 0, position) and \
|
if text.rfind(u'[', 0, position) > text.rfind(u']', 0, position) and \
|
||||||
text.find(u']', position) < text.find(u'[', position):
|
text.find(u']', position) < text.find(u'[', position):
|
||||||
return
|
return
|
||||||
position = text.rfind(u'---[', 0, position)
|
position = text.rfind(u'---[', 0, position)
|
||||||
if position == -1:
|
if position == -1:
|
||||||
|
@ -106,8 +108,8 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||||
verse_number = int(match.group(2))
|
verse_number = int(match.group(2))
|
||||||
verse_type_index = VerseType.from_string(verse_type)
|
verse_type_index = VerseType.from_string(verse_type)
|
||||||
if verse_type_index is not None:
|
if verse_type_index is not None:
|
||||||
self.VerseTypeComboBox.setCurrentIndex(verse_type_index)
|
self.verseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||||
self.VerseNumberBox.setValue(verse_number)
|
self.verseNumberBox.setValue(verse_number)
|
||||||
|
|
||||||
def setVerse(self, text, single=False,
|
def setVerse(self, text, single=False,
|
||||||
tag=u'%s:1' % VerseType.to_string(VerseType.Verse)):
|
tag=u'%s:1' % VerseType.to_string(VerseType.Verse)):
|
||||||
|
@ -115,26 +117,26 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||||
verse_type, verse_number = tag.split(u':')
|
verse_type, verse_number = tag.split(u':')
|
||||||
verse_type_index = VerseType.from_string(verse_type)
|
verse_type_index = VerseType.from_string(verse_type)
|
||||||
if verse_type_index is not None:
|
if verse_type_index is not None:
|
||||||
self.VerseTypeComboBox.setCurrentIndex(verse_type_index)
|
self.verseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||||
self.VerseNumberBox.setValue(int(verse_number))
|
self.verseNumberBox.setValue(int(verse_number))
|
||||||
self.InsertButton.setVisible(False)
|
self.insertButton.setVisible(False)
|
||||||
else:
|
else:
|
||||||
if not text:
|
if not text:
|
||||||
text = u'---[%s:1]---\n' % VerseType.to_string(VerseType.Verse)
|
text = u'---[%s:1]---\n' % VerseType.to_string(VerseType.Verse)
|
||||||
self.VerseTypeComboBox.setCurrentIndex(0)
|
self.verseTypeComboBox.setCurrentIndex(0)
|
||||||
self.VerseNumberBox.setValue(1)
|
self.verseNumberBox.setValue(1)
|
||||||
self.InsertButton.setVisible(True)
|
self.insertButton.setVisible(True)
|
||||||
self.VerseTextEdit.setPlainText(text)
|
self.verseTextEdit.setPlainText(text)
|
||||||
self.VerseTextEdit.setFocus(QtCore.Qt.OtherFocusReason)
|
self.verseTextEdit.setFocus(QtCore.Qt.OtherFocusReason)
|
||||||
self.VerseTextEdit.moveCursor(QtGui.QTextCursor.End)
|
self.verseTextEdit.moveCursor(QtGui.QTextCursor.End)
|
||||||
|
|
||||||
def getVerse(self):
|
def getVerse(self):
|
||||||
return self.VerseTextEdit.toPlainText(), \
|
return self.verseTextEdit.toPlainText(), \
|
||||||
VerseType.to_string(self.VerseTypeComboBox.currentIndex()), \
|
VerseType.to_string(self.verseTypeComboBox.currentIndex()), \
|
||||||
unicode(self.VerseNumberBox.value())
|
unicode(self.verseNumberBox.value())
|
||||||
|
|
||||||
def getVerseAll(self):
|
def getVerseAll(self):
|
||||||
text = self.VerseTextEdit.toPlainText()
|
text = self.verseTextEdit.toPlainText()
|
||||||
if not text.startsWith(u'---['):
|
if not text.startsWith(u'---['):
|
||||||
text = u'---[%s:1]---\n%s' % (VerseType.to_string(VerseType.Verse),
|
text = u'---[%s:1]---\n%s' % (VerseType.to_string(VerseType.Verse),
|
||||||
text)
|
text)
|
||||||
|
|
|
@ -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__)
|
||||||
|
@ -83,6 +82,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 +135,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'),
|
||||||
|
@ -160,7 +165,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'),
|
||||||
|
@ -277,6 +282,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',
|
||||||
|
@ -302,8 +317,8 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||||
Stop the import on pressing the cancel button.
|
Stop the import on pressing the cancel button.
|
||||||
"""
|
"""
|
||||||
log.debug('Cancel button pressed!')
|
log.debug('Cancel button pressed!')
|
||||||
if self.currentId() == 3:
|
if self.currentId() == 2:
|
||||||
Receiver.send_message(u'song_stop_import')
|
Receiver.send_message(u'songs_stop_import')
|
||||||
|
|
||||||
def onCurrentIdChanged(self, id):
|
def onCurrentIdChanged(self, id):
|
||||||
if id == 2:
|
if id == 2:
|
||||||
|
@ -315,6 +330,7 @@ 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'')
|
||||||
|
@ -357,11 +373,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,
|
||||||
|
|
|
@ -215,25 +215,32 @@ class Ui_SongImportWizard(object):
|
||||||
self.wordsOfWorshipLayout.setSpacing(8)
|
self.wordsOfWorshipLayout.setSpacing(8)
|
||||||
self.wordsOfWorshipLayout.setMargin(0)
|
self.wordsOfWorshipLayout.setMargin(0)
|
||||||
self.wordsOfWorshipLayout.setObjectName(u'wordsOfWorshipLayout')
|
self.wordsOfWorshipLayout.setObjectName(u'wordsOfWorshipLayout')
|
||||||
self.wordsOfWorshipFileListWidget = QtGui.QListWidget(self.wordsOfWorshipPage)
|
self.wordsOfWorshipFileListWidget = QtGui.QListWidget(
|
||||||
|
self.wordsOfWorshipPage)
|
||||||
self.wordsOfWorshipFileListWidget.setSelectionMode(
|
self.wordsOfWorshipFileListWidget.setSelectionMode(
|
||||||
QtGui.QAbstractItemView.ExtendedSelection)
|
QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
self.wordsOfWorshipFileListWidget.setObjectName(u'wordsOfWorshipFileListWidget')
|
self.wordsOfWorshipFileListWidget.setObjectName(
|
||||||
|
u'wordsOfWorshipFileListWidget')
|
||||||
self.wordsOfWorshipLayout.addWidget(self.wordsOfWorshipFileListWidget)
|
self.wordsOfWorshipLayout.addWidget(self.wordsOfWorshipFileListWidget)
|
||||||
self.wordsOfWorshipButtonLayout = QtGui.QHBoxLayout()
|
self.wordsOfWorshipButtonLayout = QtGui.QHBoxLayout()
|
||||||
self.wordsOfWorshipButtonLayout.setSpacing(8)
|
self.wordsOfWorshipButtonLayout.setSpacing(8)
|
||||||
self.wordsOfWorshipButtonLayout.setObjectName(u'wordsOfWorshipButtonLayout')
|
self.wordsOfWorshipButtonLayout.setObjectName(
|
||||||
self.wordsOfWorshipAddButton = QtGui.QPushButton(self.wordsOfWorshipPage)
|
u'wordsOfWorshipButtonLayout')
|
||||||
|
self.wordsOfWorshipAddButton = QtGui.QPushButton(
|
||||||
|
self.wordsOfWorshipPage)
|
||||||
self.wordsOfWorshipAddButton.setIcon(openIcon)
|
self.wordsOfWorshipAddButton.setIcon(openIcon)
|
||||||
self.wordsOfWorshipAddButton.setObjectName(u'wordsOfWorshipAddButton')
|
self.wordsOfWorshipAddButton.setObjectName(u'wordsOfWorshipAddButton')
|
||||||
self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipAddButton)
|
self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipAddButton)
|
||||||
self.wordsOfWorshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
self.wordsOfWorshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
self.wordsOfWorshipButtonLayout.addItem(self.wordsOfWorshipButtonSpacer)
|
self.wordsOfWorshipButtonLayout.addItem(self.wordsOfWorshipButtonSpacer)
|
||||||
self.wordsOfWorshipRemoveButton = QtGui.QPushButton(self.wordsOfWorshipPage)
|
self.wordsOfWorshipRemoveButton = QtGui.QPushButton(
|
||||||
|
self.wordsOfWorshipPage)
|
||||||
self.wordsOfWorshipRemoveButton.setIcon(deleteIcon)
|
self.wordsOfWorshipRemoveButton.setIcon(deleteIcon)
|
||||||
self.wordsOfWorshipRemoveButton.setObjectName(u'wordsOfWorshipRemoveButton')
|
self.wordsOfWorshipRemoveButton.setObjectName(
|
||||||
self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipRemoveButton)
|
u'wordsOfWorshipRemoveButton')
|
||||||
|
self.wordsOfWorshipButtonLayout.addWidget(
|
||||||
|
self.wordsOfWorshipRemoveButton)
|
||||||
self.wordsOfWorshipLayout.addLayout(self.wordsOfWorshipButtonLayout)
|
self.wordsOfWorshipLayout.addLayout(self.wordsOfWorshipButtonLayout)
|
||||||
self.formatStackedWidget.addWidget(self.wordsOfWorshipPage)
|
self.formatStackedWidget.addWidget(self.wordsOfWorshipPage)
|
||||||
# CCLI File import
|
# CCLI File import
|
||||||
|
@ -267,30 +274,43 @@ class Ui_SongImportWizard(object):
|
||||||
# Songs of Fellowship
|
# Songs of Fellowship
|
||||||
self.songsOfFellowshipPage = QtGui.QWidget()
|
self.songsOfFellowshipPage = QtGui.QWidget()
|
||||||
self.songsOfFellowshipPage.setObjectName(u'songsOfFellowshipPage')
|
self.songsOfFellowshipPage.setObjectName(u'songsOfFellowshipPage')
|
||||||
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(self.songsOfFellowshipPage)
|
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(
|
||||||
|
self.songsOfFellowshipPage)
|
||||||
self.songsOfFellowshipLayout.setMargin(0)
|
self.songsOfFellowshipLayout.setMargin(0)
|
||||||
self.songsOfFellowshipLayout.setSpacing(8)
|
self.songsOfFellowshipLayout.setSpacing(8)
|
||||||
self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout')
|
self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout')
|
||||||
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(self.songsOfFellowshipPage)
|
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(
|
||||||
|
self.songsOfFellowshipPage)
|
||||||
self.songsOfFellowshipFileListWidget.setSelectionMode(
|
self.songsOfFellowshipFileListWidget.setSelectionMode(
|
||||||
QtGui.QAbstractItemView.ExtendedSelection)
|
QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
self.songsOfFellowshipFileListWidget.setObjectName(u'songsOfFellowshipFileListWidget')
|
self.songsOfFellowshipFileListWidget.setObjectName(
|
||||||
self.songsOfFellowshipLayout.addWidget(self.songsOfFellowshipFileListWidget)
|
u'songsOfFellowshipFileListWidget')
|
||||||
|
self.songsOfFellowshipLayout.addWidget(
|
||||||
|
self.songsOfFellowshipFileListWidget)
|
||||||
self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout()
|
self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout()
|
||||||
self.songsOfFellowshipButtonLayout.setSpacing(8)
|
self.songsOfFellowshipButtonLayout.setSpacing(8)
|
||||||
self.songsOfFellowshipButtonLayout.setObjectName(u'songsOfFellowshipButtonLayout')
|
self.songsOfFellowshipButtonLayout.setObjectName(
|
||||||
self.songsOfFellowshipAddButton = QtGui.QPushButton(self.songsOfFellowshipPage)
|
u'songsOfFellowshipButtonLayout')
|
||||||
|
self.songsOfFellowshipAddButton = QtGui.QPushButton(
|
||||||
|
self.songsOfFellowshipPage)
|
||||||
self.songsOfFellowshipAddButton.setIcon(openIcon)
|
self.songsOfFellowshipAddButton.setIcon(openIcon)
|
||||||
self.songsOfFellowshipAddButton.setObjectName(u'songsOfFellowshipAddButton')
|
self.songsOfFellowshipAddButton.setObjectName(
|
||||||
self.songsOfFellowshipButtonLayout.addWidget(self.songsOfFellowshipAddButton)
|
u'songsOfFellowshipAddButton')
|
||||||
|
self.songsOfFellowshipButtonLayout.addWidget(
|
||||||
|
self.songsOfFellowshipAddButton)
|
||||||
self.songsOfFellowshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
self.songsOfFellowshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
self.songsOfFellowshipButtonLayout.addItem(self.songsOfFellowshipButtonSpacer)
|
self.songsOfFellowshipButtonLayout.addItem(
|
||||||
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(self.songsOfFellowshipPage)
|
self.songsOfFellowshipButtonSpacer)
|
||||||
|
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(
|
||||||
|
self.songsOfFellowshipPage)
|
||||||
self.songsOfFellowshipRemoveButton.setIcon(deleteIcon)
|
self.songsOfFellowshipRemoveButton.setIcon(deleteIcon)
|
||||||
self.songsOfFellowshipRemoveButton.setObjectName(u'songsOfFellowshipRemoveButton')
|
self.songsOfFellowshipRemoveButton.setObjectName(
|
||||||
self.songsOfFellowshipButtonLayout.addWidget(self.songsOfFellowshipRemoveButton)
|
u'songsOfFellowshipRemoveButton')
|
||||||
self.songsOfFellowshipLayout.addLayout(self.songsOfFellowshipButtonLayout)
|
self.songsOfFellowshipButtonLayout.addWidget(
|
||||||
|
self.songsOfFellowshipRemoveButton)
|
||||||
|
self.songsOfFellowshipLayout.addLayout(
|
||||||
|
self.songsOfFellowshipButtonLayout)
|
||||||
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()
|
||||||
|
|
|
@ -404,12 +404,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||||
book.publisher = temp_publisher
|
book.publisher = temp_publisher
|
||||||
|
|
||||||
def mergeAuthors(self, old_author):
|
def mergeAuthors(self, old_author):
|
||||||
'''
|
"""
|
||||||
Merges two authors into one author.
|
Merges two authors into one author.
|
||||||
|
|
||||||
``old_author``
|
``old_author``
|
||||||
The author which will be deleted afterwards.
|
The author which will be deleted afterwards.
|
||||||
'''
|
"""
|
||||||
existing_author = self.songmanager.get_object_filtered(Author,
|
existing_author = self.songmanager.get_object_filtered(Author,
|
||||||
and_(Author.first_name == old_author.first_name,
|
and_(Author.first_name == old_author.first_name,
|
||||||
Author.last_name == old_author.last_name,
|
Author.last_name == old_author.last_name,
|
||||||
|
@ -426,12 +426,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||||
self.songmanager.delete_object(Author, old_author.id)
|
self.songmanager.delete_object(Author, old_author.id)
|
||||||
|
|
||||||
def mergeTopics(self, old_topic):
|
def mergeTopics(self, old_topic):
|
||||||
'''
|
"""
|
||||||
Merges two topics into one topic.
|
Merges two topics into one topic.
|
||||||
|
|
||||||
``old_topic``
|
``old_topic``
|
||||||
The topic which will be deleted afterwards.
|
The topic which will be deleted afterwards.
|
||||||
'''
|
"""
|
||||||
existing_topic = self.songmanager.get_object_filtered(Topic,
|
existing_topic = self.songmanager.get_object_filtered(Topic,
|
||||||
Topic.name == old_topic.name)
|
Topic.name == old_topic.name)
|
||||||
songs = self.songmanager.get_all_objects(Song,
|
songs = self.songmanager.get_all_objects(Song,
|
||||||
|
@ -446,12 +446,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||||
self.songmanager.delete_object(Topic, old_topic.id)
|
self.songmanager.delete_object(Topic, old_topic.id)
|
||||||
|
|
||||||
def mergeBooks(self, old_book):
|
def mergeBooks(self, old_book):
|
||||||
'''
|
"""
|
||||||
Merges two books into one book.
|
Merges two books into one book.
|
||||||
|
|
||||||
``old_book``
|
``old_book``
|
||||||
The book which will be deleted afterwards.
|
The book which will be deleted afterwards.
|
||||||
'''
|
"""
|
||||||
existing_book = self.songmanager.get_object_filtered(Book,
|
existing_book = self.songmanager.get_object_filtered(Book,
|
||||||
and_(Book.name == old_book.name,
|
and_(Book.name == old_book.name,
|
||||||
Book.publisher == old_book.publisher))
|
Book.publisher == old_book.publisher))
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
# -*- 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)
|
||||||
|
pass
|
||||||
|
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()
|
||||||
|
|
|
@ -72,7 +72,7 @@ def init_schema(url):
|
||||||
``url``
|
``url``
|
||||||
The database to setup
|
The database to setup
|
||||||
"""
|
"""
|
||||||
session, metadata = init_db(url, auto_flush=False)
|
session, metadata = init_db(url)
|
||||||
|
|
||||||
# Definition of the "authors" table
|
# Definition of the "authors" table
|
||||||
authors_table = Table(u'authors', metadata,
|
authors_table = Table(u'authors', metadata,
|
||||||
|
|
|
@ -26,9 +26,12 @@
|
||||||
|
|
||||||
from opensongimport import OpenSongImport
|
from opensongimport import OpenSongImport
|
||||||
from olpimport import OpenLPSongImport
|
from olpimport import OpenLPSongImport
|
||||||
|
from olp1import import OpenLP1SongImport
|
||||||
try:
|
try:
|
||||||
from sofimport import SofImport
|
from sofimport import SofImport
|
||||||
from oooimport import OooImport
|
from oooimport import OooImport
|
||||||
|
from cclifileimport import CCLIFileImport
|
||||||
|
from wowimport import WowImport
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -59,12 +62,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
|
||||||
|
|
||||||
|
|
|
@ -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,129 @@
|
||||||
|
# -*- 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
|
||||||
|
try:
|
||||||
|
import sqlite
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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 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()
|
||||||
|
# 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()
|
||||||
|
# "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]
|
||||||
|
title = unicode(song[1], u'cp1252')
|
||||||
|
lyrics = unicode(song[2], u'cp1252').replace(u'\r', u'')
|
||||||
|
copyright = unicode(song[3], u'cp1252')
|
||||||
|
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(unicode(author[1], u'cp1252'))
|
||||||
|
break
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
cursor.execute(u'SELECT name FROM sqlite_master '
|
||||||
|
u'WHERE type = \'table\' AND name = \'tracks\'')
|
||||||
|
table_list = cursor.fetchall()
|
||||||
|
if len(table_list) > 0:
|
||||||
|
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(unicode(track[1], u'cp1252'))
|
||||||
|
break
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
self.finish()
|
||||||
|
return success
|
|
@ -75,18 +75,17 @@ class OpenLPSongImport(SongImport):
|
||||||
The :class:`OpenLPSongImport` class provides OpenLP with the ability to
|
The :class:`OpenLPSongImport` class provides OpenLP with the ability to
|
||||||
import song databases from other installations of OpenLP.
|
import song databases from other installations of OpenLP.
|
||||||
"""
|
"""
|
||||||
def __init__(self, master_manager, **kwargs):
|
def __init__(self, manager, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialise the import.
|
Initialise the import.
|
||||||
|
|
||||||
``master_manager``
|
``manager``
|
||||||
The song manager for the running OpenLP installation.
|
The song manager for the running OpenLP installation.
|
||||||
|
|
||||||
``source_db``
|
``source_db``
|
||||||
The database providing the data to import.
|
The database providing the data to import.
|
||||||
"""
|
"""
|
||||||
SongImport.__init__(self, master_manager)
|
SongImport.__init__(self, manager)
|
||||||
self.master_manager = master_manager
|
|
||||||
self.import_source = u'sqlite:///%s' % kwargs[u'filename']
|
self.import_source = u'sqlite:///%s' % kwargs[u'filename']
|
||||||
log.debug(self.import_source)
|
log.debug(self.import_source)
|
||||||
self.source_session = None
|
self.source_session = None
|
||||||
|
@ -145,7 +144,12 @@ class OpenLPSongImport(SongImport):
|
||||||
mapper(OldTopic, source_topics_table)
|
mapper(OldTopic, source_topics_table)
|
||||||
|
|
||||||
source_songs = self.source_session.query(OldSong).all()
|
source_songs = self.source_session.query(OldSong).all()
|
||||||
|
song_total = len(source_songs)
|
||||||
|
self.import_wizard.importProgressBar.setMaximum(song_total)
|
||||||
|
song_count = 1
|
||||||
for song in source_songs:
|
for song in source_songs:
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
u'Importing song %s of %s' % (song_count, song_total))
|
||||||
new_song = Song()
|
new_song = Song()
|
||||||
new_song.title = song.title
|
new_song.title = song.title
|
||||||
if has_media_files:
|
if has_media_files:
|
||||||
|
@ -167,7 +171,7 @@ class OpenLPSongImport(SongImport):
|
||||||
new_song.ccli_number = song.ccli_number
|
new_song.ccli_number = song.ccli_number
|
||||||
if song.authors:
|
if song.authors:
|
||||||
for author in song.authors:
|
for author in song.authors:
|
||||||
existing_author = self.master_manager.get_object_filtered(
|
existing_author = self.manager.get_object_filtered(
|
||||||
Author, Author.display_name == author.display_name)
|
Author, Author.display_name == author.display_name)
|
||||||
if existing_author:
|
if existing_author:
|
||||||
new_song.authors.append(existing_author)
|
new_song.authors.append(existing_author)
|
||||||
|
@ -177,7 +181,7 @@ class OpenLPSongImport(SongImport):
|
||||||
last_name=author.last_name,
|
last_name=author.last_name,
|
||||||
display_name=author.display_name))
|
display_name=author.display_name))
|
||||||
else:
|
else:
|
||||||
au = self.master_manager.get_object_filtered(Author,
|
au = self.manager.get_object_filtered(Author,
|
||||||
Author.display_name == u'Author Unknown')
|
Author.display_name == u'Author Unknown')
|
||||||
if au:
|
if au:
|
||||||
new_song.authors.append(au)
|
new_song.authors.append(au)
|
||||||
|
@ -185,7 +189,7 @@ class OpenLPSongImport(SongImport):
|
||||||
new_song.authors.append(Author.populate(
|
new_song.authors.append(Author.populate(
|
||||||
display_name=u'Author Unknown'))
|
display_name=u'Author Unknown'))
|
||||||
if song.book:
|
if song.book:
|
||||||
existing_song_book = self.master_manager.get_object_filtered(
|
existing_song_book = self.manager.get_object_filtered(
|
||||||
Book, Book.name == song.book.name)
|
Book, Book.name == song.book.name)
|
||||||
if existing_song_book:
|
if existing_song_book:
|
||||||
new_song.book = existing_song_book
|
new_song.book = existing_song_book
|
||||||
|
@ -194,7 +198,7 @@ class OpenLPSongImport(SongImport):
|
||||||
publisher=song.book.publisher)
|
publisher=song.book.publisher)
|
||||||
if song.topics:
|
if song.topics:
|
||||||
for topic in song.topics:
|
for topic in song.topics:
|
||||||
existing_topic = self.master_manager.get_object_filtered(
|
existing_topic = self.manager.get_object_filtered(
|
||||||
Topic, Topic.name == topic.name)
|
Topic, Topic.name == topic.name)
|
||||||
if existing_topic:
|
if existing_topic:
|
||||||
new_song.topics.append(existing_topic)
|
new_song.topics.append(existing_topic)
|
||||||
|
@ -204,12 +208,16 @@ class OpenLPSongImport(SongImport):
|
||||||
# if song.media_files:
|
# if song.media_files:
|
||||||
# for media_file in song.media_files:
|
# for media_file in song.media_files:
|
||||||
# existing_media_file = \
|
# existing_media_file = \
|
||||||
# self.master_manager.get_object_filtered(MediaFile,
|
# self.manager.get_object_filtered(MediaFile,
|
||||||
# MediaFile.file_name == media_file.file_name)
|
# MediaFile.file_name == media_file.file_name)
|
||||||
# if existing_media_file:
|
# if existing_media_file:
|
||||||
# new_song.media_files.append(existing_media_file)
|
# new_song.media_files.append(existing_media_file)
|
||||||
# else:
|
# else:
|
||||||
# new_song.media_files.append(MediaFile.populate(
|
# new_song.media_files.append(MediaFile.populate(
|
||||||
# file_name=media_file.file_name))
|
# file_name=media_file.file_name))
|
||||||
self.master_manager.save_object(new_song)
|
self.manager.save_object(new_song)
|
||||||
|
song_count += 1
|
||||||
|
if self.stop_import_flag:
|
||||||
|
return False
|
||||||
engine.dispose()
|
engine.dispose()
|
||||||
|
return True
|
||||||
|
|
|
@ -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,6 +28,7 @@ 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
|
||||||
|
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
|
@ -36,119 +37,163 @@ 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:
|
Verses can be expressed in one of 2 ways, either in complete verses, or by
|
||||||
<lyrics>
|
line grouping, i.e. grouping all line 1's of a verse together, all line 2's
|
||||||
[v1]List of words
|
of a verse together, and so on.
|
||||||
Another Line
|
|
||||||
|
|
||||||
[v2]Some words for the 2nd verse
|
An example of complete verses::
|
||||||
etc...
|
|
||||||
</lyrics>
|
|
||||||
|
|
||||||
The 'v' can be left out - it is implied
|
<lyrics>
|
||||||
or:
|
[v1]
|
||||||
<lyrics>
|
List of words
|
||||||
[V]
|
Another Line
|
||||||
1List of words
|
|
||||||
2Some words for the 2nd Verse
|
|
||||||
|
|
||||||
1Another Line
|
[v2]
|
||||||
2etc...
|
Some words for the 2nd verse
|
||||||
</lyrics>
|
etc...
|
||||||
|
</lyrics>
|
||||||
|
|
||||||
Either or both forms can be used in one song. The Number does not
|
The 'v' in the verse specifiers above can be left out, it is implied.
|
||||||
necessarily appear at the start of the line
|
|
||||||
|
An example of line grouping::
|
||||||
|
|
||||||
|
<lyrics>
|
||||||
|
[V]
|
||||||
|
1List of words
|
||||||
|
2Some words for the 2nd Verse
|
||||||
|
|
||||||
|
1Another Line
|
||||||
|
2etc...
|
||||||
|
</lyrics>
|
||||||
|
|
||||||
|
Either or both forms can be used in one song. The number does not
|
||||||
|
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:
|
|
||||||
|
|
||||||
. A7 Bm
|
B
|
||||||
1 Some____ Words
|
Bridge
|
||||||
|
|
||||||
Chords and _s are removed by this importer.
|
All verses are imported and tagged appropriately.
|
||||||
|
|
||||||
The verses etc. 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::
|
||||||
|
|
||||||
The <presentation> tag is used to populate the OpenLP verse
|
. A7 Bm
|
||||||
display order field. The Author and Copyright tags are also
|
1 Some____ Words
|
||||||
imported to the appropriate 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 a single opensong file, or a zipfile containing multiple
|
||||||
containing multiple opensong files If the commit parameter is
|
opensong files. If `self.commit` is set False, the import will not be
|
||||||
set False, the import will not be committed to the database
|
committed to the database (useful for test scripts).
|
||||||
(useful for test scripts)
|
|
||||||
"""
|
"""
|
||||||
ext = os.path.splitext(filename)[1]
|
success = True
|
||||||
if ext.lower() == ".zip":
|
self.import_wizard.importProgressBar.setMaximum(len(self.filenames))
|
||||||
log.info('Zipfile found %s', filename)
|
for filename in self.filenames:
|
||||||
z = ZipFile(filename, u'r')
|
if self.stop_import_flag:
|
||||||
for song in z.infolist():
|
success = False
|
||||||
parts = os.path.split(song.filename)
|
break
|
||||||
if parts[-1] == u'':
|
ext = os.path.splitext(filename)[1]
|
||||||
#No final part => directory
|
if ext.lower() == u'.zip':
|
||||||
continue
|
log.debug(u'Zipfile found %s', filename)
|
||||||
songfile = z.open(song)
|
z = ZipFile(filename, u'r')
|
||||||
self.do_import_file(songfile)
|
self.import_wizard.importProgressBar.setMaximum(
|
||||||
if commit:
|
self.import_wizard.importProgressBar.maximum() +
|
||||||
|
len(z.infolist()))
|
||||||
|
for song in z.infolist():
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
parts = os.path.split(song.filename)
|
||||||
|
if parts[-1] == u'':
|
||||||
|
#No final part => directory
|
||||||
|
continue
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
unicode(translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Importing %s...')) % parts[-1])
|
||||||
|
songfile = z.open(song)
|
||||||
|
self.do_import_file(songfile)
|
||||||
|
if self.commit:
|
||||||
|
self.finish()
|
||||||
|
self.set_defaults()
|
||||||
|
if self.stop_import_flag:
|
||||||
|
success = False
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.info('Direct import %s', filename)
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
unicode(translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'Importing %s...')) % os.path.split(filename)[-1])
|
||||||
|
file = open(filename)
|
||||||
|
self.do_import_file(file)
|
||||||
|
if self.commit:
|
||||||
self.finish()
|
self.finish()
|
||||||
else:
|
self.set_defaults()
|
||||||
log.info('Direct import %s', filename)
|
if not self.commit:
|
||||||
file = open(filename)
|
self.finish()
|
||||||
self.do_import_file(file)
|
return success
|
||||||
if commit:
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
|
|
||||||
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.authors = []
|
||||||
tree = objectify.parse(file)
|
try:
|
||||||
|
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'ccli':u'ccli_number',
|
u'copyright': self.add_copyright,
|
||||||
u'author':self.song_import.parse_author,
|
u'ccli': u'ccli_number',
|
||||||
u'title':u'title',
|
u'author': self.parse_author,
|
||||||
u'aka':u'alternate_title',
|
u'title': u'title',
|
||||||
u'hymn_number':u'song_number'}
|
u'aka': u'alternate_title',
|
||||||
for (attr, fn_or_string) in decode.items():
|
u'hymn_number': u'song_number'
|
||||||
|
}
|
||||||
|
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)
|
lyrics = unicode(root.lyrics)
|
||||||
|
@ -158,6 +203,7 @@ class OpenSongImport(object):
|
||||||
# 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
|
||||||
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,7 +216,6 @@ 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()
|
versetype = thisline[1].upper()
|
||||||
|
@ -186,7 +231,6 @@ class OpenSongImport(object):
|
||||||
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]
|
||||||
|
@ -207,7 +251,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
|
||||||
|
@ -220,24 +264,23 @@ class OpenSongImport(object):
|
||||||
for num in versenums:
|
for num in versenums:
|
||||||
versetag = u'%s%s' % (versetype, num)
|
versetag = u'%s%s' % (versetype, 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()
|
order = order.split()
|
||||||
else:
|
else:
|
||||||
assert len(our_verse_order)>0
|
if len(our_verse_order) > 0:
|
||||||
order = our_verse_order
|
order = our_verse_order
|
||||||
|
else:
|
||||||
|
log.warn(u'No verse order available for %s, skipping.', self.title)
|
||||||
for tag in order:
|
for tag in order:
|
||||||
if len(tag) == 1:
|
if len(tag) == 1:
|
||||||
tag = tag + u'1' # Assume it's no.1 if it's not there
|
tag = tag + u'1' # Assume it's no.1 if it's not there
|
||||||
if not versetags.has_key(tag):
|
if not versetags.has_key(tag):
|
||||||
log.warn(u'Got order %s but not in versetags, skipping', tag)
|
log.warn(u'Got order %s but not in versetags, skipping', tag)
|
||||||
else:
|
else:
|
||||||
self.song_import.verse_order_list.append(tag)
|
self.verse_order_list.append(tag)
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
""" Separate function, allows test suite to not pollute database"""
|
|
||||||
self.song_import.finish()
|
|
||||||
|
|
|
@ -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()
|
||||||
self.open_ooo_file(filename)
|
for filename in self.filenames:
|
||||||
self.process_sof_file()
|
if self.abort:
|
||||||
self.close_ooo_file()
|
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||||
|
return
|
||||||
|
filename = unicode(filename)
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
self.open_ooo_file(filename)
|
||||||
|
if self.document:
|
||||||
|
self.process_sof_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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -24,14 +24,18 @@
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.lib import 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
|
||||||
|
|
||||||
class SongImport(object):
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SongImport(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
Helper class for import a song from a third party source into OpenLP
|
Helper class for import a song from a third party source into OpenLP
|
||||||
|
|
||||||
|
@ -39,7 +43,6 @@ class SongImport(object):
|
||||||
whether the authors etc already exist and add them or refer to them
|
whether the authors etc already exist and add them or refer to them
|
||||||
as necessary
|
as necessary
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
"""
|
"""
|
||||||
Initialise and create defaults for properties
|
Initialise and create defaults for properties
|
||||||
|
@ -48,6 +51,12 @@ class SongImport(object):
|
||||||
database access is performed
|
database access is performed
|
||||||
"""
|
"""
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
|
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):
|
||||||
self.title = u''
|
self.title = u''
|
||||||
self.song_number = u''
|
self.song_number = u''
|
||||||
self.alternate_title = u''
|
self.alternate_title = u''
|
||||||
|
@ -57,17 +66,24 @@ class SongImport(object):
|
||||||
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'))
|
||||||
|
|
||||||
|
def stop_import(self):
|
||||||
|
"""
|
||||||
|
Sets the flag for importers to stop their import
|
||||||
|
"""
|
||||||
|
log.debug(u'Stopping songs import')
|
||||||
|
self.stop_import_flag = True
|
||||||
|
|
||||||
def register(self, import_wizard):
|
def register(self, import_wizard):
|
||||||
self.import_wizard = import_wizard
|
self.import_wizard = import_wizard
|
||||||
|
|
||||||
|
@ -145,8 +161,7 @@ class SongImport(object):
|
||||||
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','):
|
||||||
|
@ -169,7 +184,15 @@ class SongImport(object):
|
||||||
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/
|
||||||
|
@ -181,13 +204,14 @@ class SongImport(object):
|
||||||
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'):
|
||||||
|
@ -223,7 +247,7 @@ class SongImport(object):
|
||||||
"""
|
"""
|
||||||
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()
|
||||||
|
|
||||||
|
@ -264,11 +288,16 @@ class SongImport(object):
|
||||||
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)
|
||||||
|
@ -285,6 +314,7 @@ class SongImport(object):
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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,91 @@
|
||||||
|
%{!?python_sitelib:%global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
|
||||||
|
|
||||||
|
Summary: Open source Church presentation and lyrics projection application
|
||||||
|
Name: OpenLP
|
||||||
|
Version: 1.9.1.1
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Source0: http://downloads.sourceforge.net/openlp/openlp/%{version}/%{name}-%{version}.tar.gz
|
||||||
|
License: GPLv2
|
||||||
|
Group: Applications/Multimedia
|
||||||
|
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
URL: http://openlp.org/
|
||||||
|
|
||||||
|
BuildRequires: desktop-file-utils
|
||||||
|
BuildRequires: python2-devel
|
||||||
|
BuildRequires: python-setuptools
|
||||||
|
|
||||||
|
Requires: PyQt4
|
||||||
|
Requires: phonon
|
||||||
|
Requires: python-BeautifulSoup
|
||||||
|
Requires: python-chardet
|
||||||
|
Requires: python-lxml
|
||||||
|
Requires: python-sqlalchemy
|
||||||
|
Requires: hicolor-icon-theme
|
||||||
|
|
||||||
|
%description
|
||||||
|
OpenLP is a church presentation software, for lyrics projection software,
|
||||||
|
used to display slides of Songs, Bible verses, videos, images, and
|
||||||
|
presentations (if OpenOffice.org is installed) using a computer and projector.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q
|
||||||
|
|
||||||
|
%build
|
||||||
|
python setup.py build
|
||||||
|
|
||||||
|
%install
|
||||||
|
rm -rf %{buildroot}
|
||||||
|
python setup.py install --skip-build -O1 --root %{buildroot}
|
||||||
|
|
||||||
|
install -m644 -p -D resources/images/openlp-logo-16x16.png \
|
||||||
|
%{buildroot}%{_datadir}/icons/hicolor/16x16/apps/openlp.png
|
||||||
|
install -m644 -p -D resources/images/openlp-logo-32x32.png \
|
||||||
|
%{buildroot}%{_datadir}/icons/hicolor/32x32/apps/openlp.png
|
||||||
|
install -m644 -p -D resources/images/openlp-logo-48x48.png \
|
||||||
|
%{buildroot}%{_datadir}/icons/hicolor/48x48/apps/openlp.png
|
||||||
|
install -m644 -p -D resources/images/openlp-logo.svg \
|
||||||
|
%{buildroot}%{_datadir}/icons/hicolor/scalable/apps/openlp.svg
|
||||||
|
|
||||||
|
desktop-file-install \
|
||||||
|
--dir %{buildroot}/%{_datadir}/applications \
|
||||||
|
resources/openlp.desktop
|
||||||
|
|
||||||
|
mv %{buildroot}%{_bindir}/bible-1to2-converter.py \
|
||||||
|
%{buildroot}%{_bindir}/bible-1to2-converter
|
||||||
|
mv %{buildroot}%{_bindir}/openlp-1to2-converter.py \
|
||||||
|
%{buildroot}%{_bindir}/openlp-1to2-converter
|
||||||
|
mv %{buildroot}%{_bindir}/openlp-remoteclient.py \
|
||||||
|
%{buildroot}%{_bindir}/openlp-remoteclient
|
||||||
|
mv %{buildroot}%{_bindir}/openlp.pyw %{buildroot}%{_bindir}/openlp
|
||||||
|
|
||||||
|
|
||||||
|
%post
|
||||||
|
touch --no-create %{_datadir}/icons/hicolor ||:
|
||||||
|
gtk-update-icon-cache -q %{_datadir}/icons/hicolor 2> /dev/null ||:
|
||||||
|
|
||||||
|
%postun
|
||||||
|
touch --no-create %{_datadir}/icons/hicolor ||:
|
||||||
|
gtk-update-icon-cache -q %{_datadir}/icons/hicolor 2> /dev/null ||:
|
||||||
|
|
||||||
|
|
||||||
|
%clean
|
||||||
|
rm -rf %{buildroot}
|
||||||
|
|
||||||
|
%files
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%doc copyright.txt LICENSE
|
||||||
|
%{_bindir}/bible-1to2-converter
|
||||||
|
%{_bindir}/openlp-1to2-converter
|
||||||
|
%{_bindir}/openlp-remoteclient
|
||||||
|
%{_bindir}/openlp
|
||||||
|
%{_datadir}/applications/openlp.desktop
|
||||||
|
%{_datadir}/icons/hicolor/*/apps/openlp.*
|
||||||
|
%{python_sitelib}/openlp/
|
||||||
|
%{python_sitelib}/OpenLP-%{version}*.egg-info
|
||||||
|
%doc documentation/*.txt
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Sun Mar 28 2010 Tim Bentley <timbentley@openlp.org> 1.9.1.1
|
||||||
|
- Initial build version - Alpha 1 Release
|
|
@ -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>
|
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 #
|
||||||
|
|
Loading…
Reference in New Issue