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 logging
|
||||
from optparse import OptionParser
|
||||
from traceback import format_exception
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
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
|
||||
|
||||
log = logging.getLogger()
|
||||
|
@ -128,11 +131,11 @@ class OpenLP(QtGui.QApplication):
|
|||
screens = ScreenList()
|
||||
# Decide how many screens we have and their size
|
||||
for screen in xrange(0, self.desktop().numScreens()):
|
||||
size = self.desktop().screenGeometry(screen);
|
||||
screens.add_screen({u'number': screen,
|
||||
u'size': self.desktop().availableGeometry(screen),
|
||||
u'size': size,
|
||||
u'primary': (self.desktop().primaryScreen() == screen)})
|
||||
log.info(u'Screen %d found with resolution %s',
|
||||
screen, self.desktop().availableGeometry(screen))
|
||||
log.info(u'Screen %d found with resolution %s', screen, size)
|
||||
# start the main app window
|
||||
self.mainWindow = MainWindow(screens, app_version)
|
||||
self.mainWindow.show()
|
||||
|
@ -143,6 +146,13 @@ class OpenLP(QtGui.QApplication):
|
|||
VersionThread(self.mainWindow, app_version).start()
|
||||
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():
|
||||
"""
|
||||
The main function which parses command line options and then runs
|
||||
|
@ -193,7 +203,7 @@ def main():
|
|||
language = LanguageManager.get_language()
|
||||
appTranslator = LanguageManager.get_translator(language)
|
||||
app.installTranslator(appTranslator)
|
||||
|
||||
sys.excepthook = app.hookException
|
||||
sys.exit(app.run())
|
||||
|
||||
if __name__ == u'__main__':
|
||||
|
|
|
@ -35,6 +35,67 @@ from PyQt4 import QtCore, QtGui
|
|||
|
||||
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):
|
||||
"""
|
||||
A special shortcut method to wrap around the Qt4 translation functions.
|
||||
|
@ -166,15 +227,46 @@ def context_menu_separator(base):
|
|||
action.setSeparator(True)
|
||||
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.
|
||||
|
||||
``image``
|
||||
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)
|
||||
if not preview.isNull():
|
||||
# Only resize if different size
|
||||
if preview.width() == width and preview.height == height:
|
||||
return preview
|
||||
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
|
||||
new_image = QtGui.QImage(width, height,
|
||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
new_image.fill(QtCore.Qt.black)
|
||||
new_image.fill(background)
|
||||
painter = QtGui.QPainter(new_image)
|
||||
painter.drawImage((width - realw) / 2, (height - realh) / 2, preview)
|
||||
return new_image
|
||||
|
@ -205,6 +297,26 @@ def check_item_selected(list_widget, message):
|
|||
return False
|
||||
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 settingsmanager import SettingsManager
|
||||
from plugin import PluginStatus, Plugin
|
||||
|
@ -213,6 +325,8 @@ from settingstab import SettingsTab
|
|||
from serviceitem import ServiceItem
|
||||
from serviceitem import ServiceItemType
|
||||
from serviceitem import ItemCapabilities
|
||||
from htmlbuilder import build_html, build_lyrics_format_css, \
|
||||
build_lyrics_outline_css
|
||||
from toolbar import OpenLPToolbar
|
||||
from dockwidget import OpenLPDockWidget
|
||||
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.settingsForm = plugin_helpers[u'settings form']
|
||||
self.mediadock = plugin_helpers[u'toolbox']
|
||||
self.displayManager = plugin_helpers[u'displaymanager']
|
||||
self.pluginManager = plugin_helpers[u'pluginmanager']
|
||||
self.formparent = plugin_helpers[u'formparent']
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
|
|
|
@ -29,9 +29,10 @@ format it for the output display.
|
|||
"""
|
||||
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__)
|
||||
|
||||
|
@ -47,26 +48,13 @@ class Renderer(object):
|
|||
Initialise the renderer.
|
||||
"""
|
||||
self._rect = None
|
||||
self._debug = False
|
||||
self._display_shadow_size_footer = 0
|
||||
self._display_outline_size_footer = 0
|
||||
self.theme_name = None
|
||||
self._theme = None
|
||||
self._bg_image_filename = None
|
||||
self.frame = None
|
||||
self.frame_opaque = None
|
||||
self.bg_frame = 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):
|
||||
"""
|
||||
Set the theme to be used.
|
||||
|
@ -80,145 +68,13 @@ class Renderer(object):
|
|||
self.bg_image = None
|
||||
self._bg_image_filename = None
|
||||
self.theme_name = theme.theme_name
|
||||
self._set_theme_font()
|
||||
if theme.background_type == u'image':
|
||||
if theme.background_filename:
|
||||
self.set_bg_image(theme.background_filename)
|
||||
|
||||
def set_bg_image(self, filename):
|
||||
"""
|
||||
Set a background image.
|
||||
|
||||
``filename``
|
||||
The name of the image file.
|
||||
"""
|
||||
log.debug(u'set bg image %s', filename)
|
||||
self._bg_image_filename = unicode(filename)
|
||||
if self.frame:
|
||||
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
|
||||
self._bg_image_filename = unicode(theme.background_filename)
|
||||
if self.frame:
|
||||
self.bg_image = resize_image(self._bg_image_filename,
|
||||
self.frame.width(),
|
||||
self.frame.height())
|
||||
|
||||
def set_text_rectangle(self, rect_main, rect_footer):
|
||||
"""
|
||||
|
@ -234,363 +90,82 @@ class Renderer(object):
|
|||
self._rect = rect_main
|
||||
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
|
||||
dimensions.
|
||||
Set the size of the slide.
|
||||
|
||||
``lines``
|
||||
The lines to be rendered.
|
||||
``frame_width``
|
||||
The width of the slide.
|
||||
|
||||
``footer_lines``
|
||||
Defaults to *None*. The footer to render.
|
||||
"""
|
||||
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}
|
||||
``frame_height``
|
||||
The height of the slide.
|
||||
|
||||
def _generate_background_frame(self):
|
||||
"""
|
||||
Generate a background frame to the same size as the frame to be used.
|
||||
Results are cached for performance reasons.
|
||||
"""
|
||||
assert(self._theme)
|
||||
if self._theme.background_mode == u'transparent':
|
||||
self.bg_frame = \
|
||||
QtGui.QPixmap(self.frame.width(), self.frame.height())
|
||||
self.bg_frame.fill(QtCore.Qt.transparent)
|
||||
else:
|
||||
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)
|
||||
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._theme.background_type == u'image':
|
||||
self.bg_frame = QtGui.QImage(self.frame.width(),
|
||||
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
log.debug(u'render background %s start', self._theme.background_type)
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self.bg_frame)
|
||||
if self._theme.background_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.begin(self.frame)
|
||||
painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 255)))
|
||||
painter.drawRect(retval)
|
||||
painter.begin(self.bg_frame)
|
||||
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
||||
if self.bg_image:
|
||||
painter.drawImage(0, 0, self.bg_image)
|
||||
painter.end()
|
||||
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:
|
||||
align = self._theme.display_horizontalAlign
|
||||
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
|
||||
self.bg_frame = None
|
||||
|
||||
if self._debug:
|
||||
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):
|
||||
def format_slide(self, words, line_break):
|
||||
"""
|
||||
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
|
||||
if self._theme.font_footer_weight == u'Bold':
|
||||
footer_weight = 75
|
||||
#TODO Add myfont.setPixelSize((screen_height / 100) * font_size)
|
||||
self.footer_font = QtGui.QFont(self._theme.font_footer_name,
|
||||
self._theme.font_footer_proportion, # size
|
||||
footer_weight, # weight
|
||||
self._theme.font_footer_italics) # italic
|
||||
self.footer_font.setPixelSize(self._theme.font_footer_proportion)
|
||||
main_weight = 50
|
||||
if self._theme.font_main_weight == u'Bold':
|
||||
main_weight = 75
|
||||
self.main_font = QtGui.QFont(self._theme.font_main_name,
|
||||
self._theme.font_main_proportion, # size
|
||||
main_weight, # weight
|
||||
self._theme.font_main_italics)# italic
|
||||
self.main_font.setPixelSize(self._theme.font_main_proportion)
|
||||
|
||||
def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False,
|
||||
color=None, outline_size=0):
|
||||
"""
|
||||
Find bounding box of text - as render_single_line. If draw is set,
|
||||
actually draw the text to the current DC as well return width and
|
||||
height of text as a tuple (w, h).
|
||||
|
||||
``line``
|
||||
The line of text to render.
|
||||
|
||||
``footer``
|
||||
The footer text.
|
||||
|
||||
``tlcorner``
|
||||
Defaults to *``(0, 0)``*. The top left corner co-ordinates.
|
||||
|
||||
``draw``
|
||||
Defaults to *False*. Draw the text to the current surface.
|
||||
|
||||
``color``
|
||||
Defaults to *None*. The colour to draw with.
|
||||
"""
|
||||
# setup defaults
|
||||
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')
|
||||
log.debug(u'format_slide - Start')
|
||||
line_end = u''
|
||||
if line_break:
|
||||
line_end = u'<br>'
|
||||
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)
|
||||
web = QtWebKit.QWebView()
|
||||
web.resize(self._rect.width(), self._rect.height())
|
||||
web.setVisible(False)
|
||||
frame = web.page().mainFrame()
|
||||
# Adjust width and height to account for shadow. outline done in css
|
||||
width = self._rect.width() - int(self._theme.display_shadow_size)
|
||||
height = self._rect.height() - int(self._theme.display_shadow_size)
|
||||
shell = u'<html><head><style>#main {%s %s}</style><body>' \
|
||||
u'<div id="main">' % \
|
||||
(build_lyrics_format_css(self._theme, width, height),
|
||||
build_lyrics_outline_css(self._theme))
|
||||
formatted = []
|
||||
html_text = u''
|
||||
styled_text = u''
|
||||
js_height = 'document.getElementById("main").scrollHeight'
|
||||
for line in text:
|
||||
styled_line = expand_tags(line) + line_end
|
||||
styled_text += styled_line
|
||||
html = shell + styled_text + u'</div></body></html>'
|
||||
web.setHtml(html)
|
||||
# Text too long so go to next page
|
||||
text_height = int(frame.evaluateJavaScript(js_height).toString())
|
||||
if text_height > height:
|
||||
formatted.append(html_text)
|
||||
html_text = u''
|
||||
styled_text = styled_line
|
||||
html_text += line + line_end
|
||||
formatted.append(html_text)
|
||||
log.debug(u'format_slide - End')
|
||||
return formatted
|
||||
|
|
|
@ -28,7 +28,8 @@ import logging
|
|||
|
||||
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__)
|
||||
|
||||
|
@ -55,6 +56,8 @@ class RenderManager(object):
|
|||
"""
|
||||
log.debug(u'Initilisation started')
|
||||
self.screens = screens
|
||||
self.display = MainDisplay(self, screens, False)
|
||||
self.display.setup()
|
||||
self.theme_manager = theme_manager
|
||||
self.renderer = Renderer()
|
||||
self.calculate_default(self.screens.current[u'size'])
|
||||
|
@ -63,6 +66,7 @@ class RenderManager(object):
|
|||
self.theme_level = u''
|
||||
self.override_background = None
|
||||
self.themedata = None
|
||||
self.alertTab = None
|
||||
|
||||
def update_display(self):
|
||||
"""
|
||||
|
@ -70,7 +74,10 @@ class RenderManager(object):
|
|||
"""
|
||||
log.debug(u'Update Display')
|
||||
self.calculate_default(self.screens.current[u'size'])
|
||||
self.display = MainDisplay(self, self.screens, False)
|
||||
self.display.setup()
|
||||
self.renderer.bg_frame = None
|
||||
self.themedata = None
|
||||
|
||||
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
|
||||
"""
|
||||
|
@ -86,6 +93,9 @@ class RenderManager(object):
|
|||
"""
|
||||
self.global_theme = global_theme
|
||||
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):
|
||||
"""
|
||||
|
@ -95,18 +105,28 @@ class RenderManager(object):
|
|||
The service-level theme to be set.
|
||||
"""
|
||||
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.
|
||||
Called by the service item when building a display frame
|
||||
|
||||
``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)
|
||||
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
|
||||
elif self.theme_level == ThemeLevel.Service:
|
||||
elif theme_level == ThemeLevel.Service:
|
||||
if self.service_theme == u'':
|
||||
self.theme = self.global_theme
|
||||
else:
|
||||
|
@ -114,20 +134,27 @@ class RenderManager(object):
|
|||
else:
|
||||
if theme:
|
||||
self.theme = theme
|
||||
elif self.theme_level == ThemeLevel.Song or \
|
||||
self.theme_level == ThemeLevel.Service:
|
||||
elif theme_level == ThemeLevel.Song or \
|
||||
theme_level == ThemeLevel.Service:
|
||||
if self.service_theme == u'':
|
||||
self.theme = self.global_theme
|
||||
else:
|
||||
self.theme = self.service_theme
|
||||
else:
|
||||
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)
|
||||
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.renderer.set_theme(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):
|
||||
"""
|
||||
|
@ -163,13 +190,8 @@ class RenderManager(object):
|
|||
The theme to generated a preview for.
|
||||
"""
|
||||
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.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'\
|
||||
'How sweet the sound\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'Public Domain')
|
||||
footer.append(u'CCLI 123456')
|
||||
formatted = self.renderer.format_slide(verse, False)
|
||||
#Only Render the first slide page returned
|
||||
return self.renderer.generate_frame_from_lines(formatted[0],
|
||||
footer)[u'main']
|
||||
# build a service item to generate preview
|
||||
serviceItem = ServiceItem()
|
||||
serviceItem.theme = themedata
|
||||
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.
|
||||
|
||||
|
@ -193,22 +224,7 @@ class RenderManager(object):
|
|||
"""
|
||||
log.debug(u'format slide')
|
||||
self.build_text_rectangle(self.themedata)
|
||||
return self.renderer.format_slide(words, False)
|
||||
|
||||
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)
|
||||
return self.renderer.format_slide(words, line_break)
|
||||
|
||||
def calculate_default(self, screen):
|
||||
"""
|
||||
|
|
|
@ -35,7 +35,7 @@ import uuid
|
|||
|
||||
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__)
|
||||
|
||||
|
@ -57,6 +57,7 @@ class ItemCapabilities(object):
|
|||
RequiresMedia = 4
|
||||
AllowsLoop = 5
|
||||
AllowsAdditions = 6
|
||||
NoLineBreaks = 7
|
||||
|
||||
class ServiceItem(object):
|
||||
"""
|
||||
|
@ -82,6 +83,7 @@ class ServiceItem(object):
|
|||
self.items = []
|
||||
self.iconic_representation = None
|
||||
self.raw_footer = None
|
||||
self.foot_text = None
|
||||
self.theme = None
|
||||
self.service_item_type = None
|
||||
self._raw_frames = []
|
||||
|
@ -91,10 +93,18 @@ class ServiceItem(object):
|
|||
self.from_plugin = False
|
||||
self.capabilities = []
|
||||
self.is_valid = True
|
||||
self.cache = {}
|
||||
self.icon = None
|
||||
self.themedata = None
|
||||
self.main = None
|
||||
self.footer = None
|
||||
self.bg_frame = None
|
||||
|
||||
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())
|
||||
|
||||
def add_capability(self, capability):
|
||||
|
@ -126,36 +136,41 @@ class ServiceItem(object):
|
|||
self.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')
|
||||
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:
|
||||
log.debug(u'Formatting slides')
|
||||
if self.theme is None:
|
||||
self.render_manager.set_override_theme(None)
|
||||
else:
|
||||
self.render_manager.set_override_theme(self.theme)
|
||||
theme = None
|
||||
if self.theme:
|
||||
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:
|
||||
before = time.time()
|
||||
formated = self.render_manager.format_slide(slide[u'raw_slide'])
|
||||
for format in formated:
|
||||
lines = u''
|
||||
title = u''
|
||||
for line in format:
|
||||
if title == u'':
|
||||
title = line
|
||||
lines += line + u'\n'
|
||||
self._display_frames.append({u'title': title,
|
||||
u'text': lines.rstrip(),
|
||||
formatted = self.render_manager \
|
||||
.format_slide(slide[u'raw_slide'], line_break)
|
||||
for page in formatted:
|
||||
self._display_frames.append(
|
||||
{u'title': clean_tags(page),
|
||||
u'text': clean_tags(page.rstrip()),
|
||||
u'html': expand_tags(page.rstrip()),
|
||||
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))
|
||||
elif self.service_item_type == ServiceItemType.Image:
|
||||
self.themedata = self.render_manager.global_theme_data
|
||||
for slide in self._raw_frames:
|
||||
slide[u'image'] = resize_image(slide[u'image'],
|
||||
self.render_manager.width, self.render_manager.height)
|
||||
|
@ -163,29 +178,14 @@ class ServiceItem(object):
|
|||
pass
|
||||
else:
|
||||
log.error(u'Invalid value renderer :%s' % self.service_item_type)
|
||||
|
||||
def render_individual(self, row):
|
||||
"""
|
||||
Takes an array of text and generates an Image from the
|
||||
theme. It assumes the text will fit on the screen as it
|
||||
has generated by the render method above.
|
||||
"""
|
||||
log.debug(u'render individual')
|
||||
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
|
||||
self.title = clean_tags(self.title)
|
||||
self.foot_text = None
|
||||
if self.raw_footer:
|
||||
for foot in self.raw_footer:
|
||||
if not self.foot_text:
|
||||
self.foot_text = foot
|
||||
else:
|
||||
self.foot_text = u'%s<br>%s' % (self.foot_text, foot)
|
||||
|
||||
def add_from_image(self, path, title, image):
|
||||
"""
|
||||
|
@ -375,9 +375,9 @@ class ServiceItem(object):
|
|||
renders it if required.
|
||||
"""
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
|
@ -390,9 +390,3 @@ class ServiceItem(object):
|
|||
Returns the title of the raw frame
|
||||
"""
|
||||
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>
|
||||
<weight>Normal</weight>
|
||||
<italics>False</italics>
|
||||
<indentation>0</indentation>
|
||||
<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 type="footer">
|
||||
<name>Arial</name>
|
||||
|
@ -65,9 +64,8 @@ BLANK_THEME_XML = \
|
|||
<proportion>12</proportion>
|
||||
<weight>Normal</weight>
|
||||
<italics>False</italics>
|
||||
<indentation>0</indentation>
|
||||
<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>
|
||||
<display>
|
||||
<shadow color="#000000" size="5">True</shadow>
|
||||
|
@ -184,7 +182,7 @@ class ThemeXML(object):
|
|||
self.child_element(background, u'filename', filename)
|
||||
|
||||
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):
|
||||
"""
|
||||
Add a Font.
|
||||
|
@ -210,9 +208,6 @@ class ThemeXML(object):
|
|||
``italics``
|
||||
Does the font render to italics Defaults to 0 Normal
|
||||
|
||||
``indentation``
|
||||
Number of characters the wrap line is indented
|
||||
|
||||
``xpos``
|
||||
The X position of the text block.
|
||||
|
||||
|
@ -239,8 +234,6 @@ class ThemeXML(object):
|
|||
#Create italics name element
|
||||
self.child_element(background, u'italics', italics)
|
||||
#Create indentation name element
|
||||
self.child_element(background, u'indentation', unicode(indentation))
|
||||
#Create indentation name element
|
||||
self.child_element(
|
||||
background, u'line_adjustment', unicode(line_adjustment))
|
||||
|
||||
|
|
|
@ -37,13 +37,11 @@ class HideMode(object):
|
|||
Theme = 2
|
||||
Screen = 3
|
||||
|
||||
from maindisplay import MainDisplay
|
||||
from slidecontroller import HideMode
|
||||
from servicenoteform import ServiceNoteForm
|
||||
from serviceitemeditform import ServiceItemEditForm
|
||||
from screen import ScreenList
|
||||
from maindisplay import MainDisplay
|
||||
from maindisplay import VideoDisplay
|
||||
from maindisplay import DisplayManager
|
||||
from amendthemeform import AmendThemeForm
|
||||
from slidecontroller import SlideController
|
||||
from splashscreen import SplashScreen
|
||||
|
@ -56,8 +54,7 @@ from settingsform import SettingsForm
|
|||
from mediadockmanager import MediaDockManager
|
||||
from servicemanager import ServiceManager
|
||||
from thememanager import ThemeManager
|
||||
from mainwindow import MainWindow
|
||||
|
||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow',
|
||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm',
|
||||
'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager',
|
||||
'AmendThemeForm', 'MediaDockManager', 'ServiceItemEditForm']
|
||||
|
|
|
@ -68,17 +68,6 @@ class Ui_AmendThemeDialog(object):
|
|||
self.backgroundLayout.setMargin(8)
|
||||
self.backgroundLayout.setSpacing(8)
|
||||
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.setObjectName(u'backgroundTypeLabel')
|
||||
self.backgroundLayout.setWidget(1, QtGui.QFormLayout.LabelRole,
|
||||
|
@ -216,17 +205,6 @@ class Ui_AmendThemeDialog(object):
|
|||
self.fontMainLineAdjustmentSpinBox.setMinimum(-99)
|
||||
self.mainFontLayout.setWidget(4, QtGui.QFormLayout.FieldRole,
|
||||
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.setObjectName(u'fontMainLinesPageLabel')
|
||||
self.mainFontLayout.addRow(self.fontMainLinesPageLabel)
|
||||
|
@ -661,12 +639,6 @@ class Ui_AmendThemeDialog(object):
|
|||
translate('OpenLP.AmendThemeForm', 'Theme Maintenance'))
|
||||
self.themeNameLabel.setText(
|
||||
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(
|
||||
translate('OpenLP.AmendThemeForm', 'Type:'))
|
||||
self.backgroundTypeComboBox.setItemText(0,
|
||||
|
@ -700,8 +672,6 @@ class Ui_AmendThemeDialog(object):
|
|||
translate('OpenLP.AmendThemeForm', 'Size:'))
|
||||
self.fontMainSizeSpinBox.setSuffix(
|
||||
translate('OpenLP.AmendThemeForm', 'pt'))
|
||||
self.fontMainWrapIndentationLabel.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'Wrap indentation:'))
|
||||
self.fontMainWrapLineAdjustmentLabel.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'Adjust line spacing:'))
|
||||
self.fontMainWeightComboBox.setItemText(0,
|
||||
|
|
|
@ -50,7 +50,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.path = None
|
||||
self.theme = ThemeXML()
|
||||
self.setupUi(self)
|
||||
# define signals
|
||||
# Buttons
|
||||
QtCore.QObject.connect(self.color1PushButton,
|
||||
QtCore.SIGNAL(u'pressed()'), self.onColor1PushButtonClicked)
|
||||
|
@ -68,8 +67,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
QtCore.QObject.connect(self.imageToolButton,
|
||||
QtCore.SIGNAL(u'clicked()'), self.onImageToolButtonClicked)
|
||||
# Combo boxes
|
||||
QtCore.QObject.connect(self.backgroundComboBox,
|
||||
QtCore.SIGNAL(u'activated(int)'), self.onBackgroundComboBoxSelected)
|
||||
QtCore.QObject.connect(self.backgroundTypeComboBox,
|
||||
QtCore.SIGNAL(u'activated(int)'),
|
||||
self.onBackgroundTypeComboBoxSelected)
|
||||
|
@ -109,9 +106,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
QtCore.QObject.connect(self.fontMainLineAdjustmentSpinBox,
|
||||
QtCore.SIGNAL(u'editingFinished()'),
|
||||
self.onFontMainLineAdjustmentSpinBoxChanged)
|
||||
QtCore.QObject.connect(self.fontMainLineSpacingSpinBox,
|
||||
QtCore.SIGNAL(u'editingFinished()'),
|
||||
self.onFontMainLineSpacingSpinBoxChanged)
|
||||
QtCore.QObject.connect(self.fontFooterXSpinBox,
|
||||
QtCore.SIGNAL(u'editingFinished()'),
|
||||
self.onFontFooterXSpinBoxChanged)
|
||||
|
@ -151,30 +145,26 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
new_theme.new_document(theme_name)
|
||||
save_from = None
|
||||
save_to = None
|
||||
if self.theme.background_mode == u'transparent':
|
||||
new_theme.add_background_transparent()
|
||||
if self.theme.background_type == u'solid':
|
||||
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:
|
||||
if self.theme.background_type == u'solid':
|
||||
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:
|
||||
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
|
||||
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),
|
||||
unicode(self.theme.font_main_color),
|
||||
unicode(self.theme.font_main_proportion),
|
||||
unicode(self.theme.font_main_override), u'main',
|
||||
unicode(self.theme.font_main_weight),
|
||||
unicode(self.theme.font_main_italics),
|
||||
unicode(self.theme.font_main_indentation),
|
||||
unicode(self.theme.font_main_line_adjustment),
|
||||
unicode(self.theme.font_main_x),
|
||||
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_weight),
|
||||
unicode(self.theme.font_footer_italics),
|
||||
0, # indentation
|
||||
0, # line adjustment
|
||||
unicode(self.theme.font_footer_x),
|
||||
unicode(self.theme.font_footer_y),
|
||||
|
@ -230,7 +219,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.previewTheme()
|
||||
|
||||
#
|
||||
#Main Font Tab
|
||||
# Main Font Tab
|
||||
#
|
||||
def onFontMainComboBoxSelected(self):
|
||||
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.fontMainLineAdjustmentSpinBox.setValue(
|
||||
self.theme.font_main_line_adjustment)
|
||||
self.fontMainLineSpacingSpinBox.setValue(
|
||||
self.theme.font_main_indentation)
|
||||
self.stateChanging(self.theme)
|
||||
self.previewTheme()
|
||||
|
||||
|
@ -310,20 +297,13 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.fontMainLineAdjustmentSpinBox.value()
|
||||
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):
|
||||
if self.theme.font_main_height != self.fontMainHeightSpinBox.value():
|
||||
self.theme.font_main_height = self.fontMainHeightSpinBox.value()
|
||||
self.previewTheme()
|
||||
|
||||
#
|
||||
#Footer Font Tab
|
||||
# Footer Font Tab
|
||||
#
|
||||
def onFontFooterComboBoxSelected(self):
|
||||
self.theme.font_footer_name = \
|
||||
|
@ -404,20 +384,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.previewTheme()
|
||||
|
||||
#
|
||||
#Background Tab
|
||||
# Background Tab
|
||||
#
|
||||
def onGradientComboBoxSelected(self, currentIndex):
|
||||
self.setBackground(self.backgroundTypeComboBox.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):
|
||||
self.setBackground(currentIndex, self.gradientComboBox.currentIndex())
|
||||
|
||||
|
@ -472,7 +444,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.previewTheme()
|
||||
|
||||
#
|
||||
#Other Tab
|
||||
# Other Tab
|
||||
#
|
||||
def onOutlineCheckBoxChanged(self, value):
|
||||
if value == 2: # checked
|
||||
|
@ -537,16 +509,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.previewTheme()
|
||||
|
||||
#
|
||||
#Local Methods
|
||||
# Local Methods
|
||||
#
|
||||
def paintUi(self, theme):
|
||||
self.stateChanging(theme)
|
||||
self.themeNameEdit.setText(self.theme.theme_name)
|
||||
# Background Tab
|
||||
if self.theme.background_mode == u'opaque':
|
||||
self.backgroundComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
self.backgroundComboBox.setCurrentIndex(1)
|
||||
self.imageLineEdit.setText(u'')
|
||||
if theme.background_type == u'solid':
|
||||
self.backgroundTypeComboBox.setCurrentIndex(0)
|
||||
|
@ -576,8 +544,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.fontMainWeightComboBox.setCurrentIndex(2)
|
||||
else:
|
||||
self.fontMainWeightComboBox.setCurrentIndex(3)
|
||||
self.fontMainLineSpacingSpinBox.setValue(
|
||||
self.theme.font_main_indentation)
|
||||
self.fontMainXSpinBox.setValue(self.theme.font_main_x)
|
||||
self.fontMainYSpinBox.setValue(self.theme.font_main_y)
|
||||
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)
|
||||
|
||||
def stateChanging(self, theme):
|
||||
if theme.background_mode == u'transparent':
|
||||
self.color1Label.setVisible(False)
|
||||
self.color1PushButton.setVisible(False)
|
||||
self.backgroundTypeComboBox.setVisible(True)
|
||||
self.backgroundTypeLabel.setVisible(True)
|
||||
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.color2PushButton.setVisible(False)
|
||||
self.imageLabel.setVisible(False)
|
||||
|
@ -651,53 +623,34 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
|
|||
self.imageFilenameWidget.setVisible(False)
|
||||
self.gradientLabel.setVisible(False)
|
||||
self.gradientComboBox.setVisible(False)
|
||||
self.backgroundTypeComboBox.setVisible(False)
|
||||
self.backgroundTypeLabel.setVisible(False)
|
||||
else:
|
||||
self.backgroundTypeComboBox.setVisible(True)
|
||||
self.backgroundTypeLabel.setVisible(True)
|
||||
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.color2PushButton.setVisible(False)
|
||||
self.imageLabel.setVisible(False)
|
||||
self.imageLineEdit.setVisible(False)
|
||||
self.imageFilenameWidget.setVisible(False)
|
||||
self.gradientLabel.setVisible(False)
|
||||
self.gradientComboBox.setVisible(False)
|
||||
elif theme.background_type == u'gradient':
|
||||
self.color1PushButton.setStyleSheet(u'background-color: %s' \
|
||||
% unicode(theme.background_startColor))
|
||||
self.color2PushButton.setStyleSheet(u'background-color: %s' \
|
||||
% unicode(theme.background_endColor))
|
||||
self.color1Label.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'First color:'))
|
||||
self.color2Label.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'Second color:'))
|
||||
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)
|
||||
elif theme.background_type == u'gradient':
|
||||
self.color1PushButton.setStyleSheet(u'background-color: %s' \
|
||||
% unicode(theme.background_startColor))
|
||||
self.color2PushButton.setStyleSheet(u'background-color: %s' \
|
||||
% unicode(theme.background_endColor))
|
||||
self.color1Label.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'First color:'))
|
||||
self.color2Label.setText(
|
||||
translate('OpenLP.AmendThemeForm', 'Second color:'))
|
||||
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:
|
||||
self.fontMainXSpinBox.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.currentYLayout.addWidget(self.currentYValueLabel)
|
||||
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.setSpacing(0)
|
||||
self.currentHeightLayout.setMargin(0)
|
||||
|
@ -209,19 +222,6 @@ class GeneralTab(SettingsTab):
|
|||
self.currentHeightValueLabel.setObjectName(u'Height')
|
||||
self.currentHeightLayout.addWidget(self.currentHeightValueLabel)
|
||||
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.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox)
|
||||
self.overrideCheckBox.setObjectName(u'overrideCheckBox')
|
||||
|
@ -256,18 +256,6 @@ class GeneralTab(SettingsTab):
|
|||
self.customYValueEdit.setObjectName(u'customYValueEdit')
|
||||
self.customYLayout.addWidget(self.customYValueEdit)
|
||||
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.setSpacing(0)
|
||||
self.customWidthLayout.setMargin(0)
|
||||
|
@ -281,6 +269,18 @@ class GeneralTab(SettingsTab):
|
|||
self.customWidthValueEdit.setObjectName(u'customWidthValueEdit')
|
||||
self.customWidthLayout.addWidget(self.customWidthValueEdit)
|
||||
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)
|
||||
# Bottom spacer
|
||||
self.generalRightSpacer = QtGui.QSpacerItem(20, 40,
|
||||
|
@ -390,26 +390,16 @@ class GeneralTab(SettingsTab):
|
|||
unicode(self.screens.current[u'size'].width()))
|
||||
self.overrideCheckBox.setChecked(settings.value(u'override position',
|
||||
QtCore.QVariant(False)).toBool())
|
||||
if self.overrideCheckBox.isChecked():
|
||||
self.customXValueEdit.setText(settings.value(u'x position',
|
||||
QtCore.QVariant(self.screens.current[u'size'].x())).toString())
|
||||
self.customYValueEdit.setText(settings.value(u'y position',
|
||||
QtCore.QVariant(self.screens.current[u'size'].y())).toString())
|
||||
self.customHeightValueEdit.setText(
|
||||
settings.value(u'height', QtCore.QVariant(
|
||||
self.screens.current[u'size'].height())).toString())
|
||||
self.customWidthValueEdit.setText(
|
||||
settings.value(u'width', QtCore.QVariant(
|
||||
self.screens.current[u'size'].width())).toString())
|
||||
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()))
|
||||
self.customXValueEdit.setText(settings.value(u'x position',
|
||||
QtCore.QVariant(self.screens.current[u'size'].x())).toString())
|
||||
self.customYValueEdit.setText(settings.value(u'y position',
|
||||
QtCore.QVariant(self.screens.current[u'size'].y())).toString())
|
||||
self.customHeightValueEdit.setText(
|
||||
settings.value(u'height', QtCore.QVariant(
|
||||
self.screens.current[u'size'].height())).toString())
|
||||
self.customWidthValueEdit.setText(
|
||||
settings.value(u'width', QtCore.QVariant(
|
||||
self.screens.current[u'size'].width())).toString())
|
||||
settings.endGroup()
|
||||
self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
||||
self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked())
|
||||
|
@ -436,10 +426,8 @@ class GeneralTab(SettingsTab):
|
|||
QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked()))
|
||||
settings.setValue(u'auto preview',
|
||||
QtCore.QVariant(self.autoPreviewCheckBox.isChecked()))
|
||||
settings.setValue(u'loop delay',
|
||||
settings.setValue(u'loop delay',
|
||||
QtCore.QVariant(self.timeoutSpinBox.value()))
|
||||
Receiver.send_message(u'slidecontroller_live_spin_delay',
|
||||
self.timeoutSpinBox.value())
|
||||
settings.setValue(u'ccli number',
|
||||
QtCore.QVariant(self.numberEdit.displayText()))
|
||||
settings.setValue(u'songselect username',
|
||||
|
@ -459,17 +447,18 @@ class GeneralTab(SettingsTab):
|
|||
settings.endGroup()
|
||||
self.screens.display = self.displayOnMonitorCheck.isChecked()
|
||||
# Monitor Number has changed.
|
||||
postUpdate = False
|
||||
if self.screens.monitor_number != self.monitorNumber:
|
||||
self.screens.monitor_number = self.monitorNumber
|
||||
self.screens.set_current_display(self.monitorNumber)
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
Receiver.send_message(u'config_updated')
|
||||
postUpdate = True
|
||||
# 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',
|
||||
self.timeoutSpinBox.value())
|
||||
|
@ -480,12 +469,14 @@ class GeneralTab(SettingsTab):
|
|||
int(self.customYValueEdit.text()),
|
||||
int(self.customWidthValueEdit.text()),
|
||||
int(self.customHeightValueEdit.text()))
|
||||
if self.overrideCheckBox.isChecked():
|
||||
self.screens.set_override_display()
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
else:
|
||||
self.screens.reset_current_display()
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
if self.overrideCheckBox.isChecked():
|
||||
self.screens.set_override_display()
|
||||
else:
|
||||
self.screens.reset_current_display()
|
||||
# Order is important so be careful if you change
|
||||
if self.overrideChanged or postUpdate:
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
self.overrideChanged = False
|
||||
|
||||
def onOverrideCheckBoxToggled(self, checked):
|
||||
"""
|
||||
|
|
|
@ -26,149 +26,46 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit
|
||||
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
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
#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):
|
||||
"""
|
||||
Customised version of QTableWidget which can respond to keyboard
|
||||
events.
|
||||
"""
|
||||
log.info(u'MainDisplay loaded')
|
||||
log.info(u'Display Widget loaded')
|
||||
|
||||
def __init__(self, parent=None, name=None, primary=False):
|
||||
QtGui.QWidget.__init__(self, None)
|
||||
def __init__(self, live, parent=None):
|
||||
QtGui.QGraphicsView.__init__(self)
|
||||
self.parent = parent
|
||||
self.primary = primary
|
||||
self.live = live
|
||||
self.hotkey_map = {
|
||||
QtCore.Qt.Key_Return: 'servicemanager_next_item',
|
||||
QtCore.Qt.Key_Space: 'slidecontroller_live_next_noloop',
|
||||
QtCore.Qt.Key_Enter: 'slidecontroller_live_next_noloop',
|
||||
QtCore.Qt.Key_0: 'servicemanager_next_item',
|
||||
QtCore.Qt.Key_Backspace: 'slidecontroller_live_previous_noloop'}
|
||||
self.setStyleSheet(u'border: none;')
|
||||
|
||||
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):
|
||||
#here accept the event and do something
|
||||
# Here accept the event and do something
|
||||
if event.key() == QtCore.Qt.Key_Up:
|
||||
Receiver.send_message(u'slidecontroller_live_previous')
|
||||
event.accept()
|
||||
|
@ -185,157 +82,277 @@ class DisplayWidget(QtGui.QGraphicsView):
|
|||
Receiver.send_message(self.hotkey_map[event.key()])
|
||||
event.accept()
|
||||
elif event.key() == QtCore.Qt.Key_Escape:
|
||||
self.resetDisplay()
|
||||
self.setVisible(False)
|
||||
self.videoStop()
|
||||
event.accept()
|
||||
event.ignore()
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
This is the form that is used to display things on the projector.
|
||||
"""
|
||||
log.info(u'MainDisplay Loaded')
|
||||
|
||||
def __init__(self, parent, screens):
|
||||
"""
|
||||
The constructor for the display form.
|
||||
|
||||
``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
|
||||
def __init__(self, parent, screens, live):
|
||||
DisplayWidget.__init__(self, live, parent=None)
|
||||
self.parent = parent
|
||||
self.screens = screens
|
||||
self.setupScene()
|
||||
self.setupVideo()
|
||||
self.setupImage()
|
||||
self.setupText()
|
||||
self.setupAlert()
|
||||
self.setupBlank()
|
||||
self.blankFrame = None
|
||||
self.frame = None
|
||||
#Hide desktop for now until we know where to put it
|
||||
#and what size it should be.
|
||||
self.setVisible(False)
|
||||
self.isLive = live
|
||||
self.alertTab = None
|
||||
self.hide_mode = None
|
||||
self.setWindowTitle(u'OpenLP Display')
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
||||
QtCore.Qt.WindowStaysOnTopHint)
|
||||
if self.isLive:
|
||||
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)
|
||||
|
||||
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 ' % (
|
||||
self.screens, self.screens.monitor_number))
|
||||
self.setVisible(False)
|
||||
log.debug(u'Setup live = %s for %s ' % (self.isLive,
|
||||
self.screens.monitor_number))
|
||||
self.screen = self.screens.current
|
||||
#Sort out screen locations and sizes
|
||||
self.setVisible(False)
|
||||
self.setGeometry(self.screen[u'size'])
|
||||
self.scene.setSceneRect(0, 0, self.size().width(),
|
||||
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.scene = QtGui.QGraphicsScene()
|
||||
self.setScene(self.scene)
|
||||
|
||||
def setupVideo(self):
|
||||
self.webView = QtWebKit.QWebView()
|
||||
self.webView = QtWebKit.QGraphicsWebView()
|
||||
self.scene.addItem(self.webView)
|
||||
self.webView.resize(self.screen[u'size'].width(), \
|
||||
self.screen[u'size'].height())
|
||||
self.page = self.webView.page()
|
||||
self.videoDisplay = self.page.mainFrame()
|
||||
self.videoDisplay.setScrollBarPolicy(QtCore.Qt.Vertical,
|
||||
self.frame = self.page.mainFrame()
|
||||
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)
|
||||
self.videoDisplay.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.proxy = QtGui.QGraphicsProxyWidget()
|
||||
self.proxy.setWidget(self.webView)
|
||||
self.proxy.setWindowFlags(QtCore.Qt.Window |
|
||||
QtCore.Qt.FramelessWindowHint)
|
||||
self.proxy.setZValue(1)
|
||||
self.scene.addItem(self.proxy)
|
||||
if self.isLive:
|
||||
# Build the initial frame.
|
||||
self.black = QtGui.QImage(
|
||||
self.screens.current[u'size'].width(),
|
||||
self.screens.current[u'size'].height(),
|
||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
painter_image = QtGui.QPainter()
|
||||
painter_image.begin(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):
|
||||
self.imageDisplay = QtGui.QGraphicsPixmapItem()
|
||||
self.imageDisplay.setZValue(2)
|
||||
self.scene.addItem(self.imageDisplay)
|
||||
def text(self, slide):
|
||||
"""
|
||||
Add the slide text from slideController
|
||||
|
||||
def setupText(self):
|
||||
#self.displayText = QtGui.QGraphicsTextItem()
|
||||
self.displayText = QtGui.QGraphicsPixmapItem()
|
||||
#self.displayText.setPos(0,0)
|
||||
#self.displayText.setTextWidth(self.size().width())
|
||||
self.displayText.setZValue(4)
|
||||
self.scene.addItem(self.displayText)
|
||||
`slide`
|
||||
The slide text to be displayed
|
||||
"""
|
||||
log.debug(u'text')
|
||||
self.frame.evaluateJavaScript(u'show_text("%s")' % \
|
||||
slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
|
||||
return self.preview()
|
||||
|
||||
def setupAlert(self):
|
||||
self.alertText = QtGui.QGraphicsTextItem()
|
||||
self.alertText.setZValue(8)
|
||||
self.scene.addItem(self.alertText)
|
||||
def alert(self, text):
|
||||
"""
|
||||
Add the alert text
|
||||
|
||||
def setupBlank(self):
|
||||
self.displayBlank = QtGui.QGraphicsPixmapItem()
|
||||
self.displayBlank.setZValue(10)
|
||||
self.scene.addItem(self.displayBlank)
|
||||
`slide`
|
||||
The slide text to be displayed
|
||||
"""
|
||||
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):
|
||||
# """
|
||||
# Hides the main display if for the video to be played
|
||||
# """
|
||||
# self.hideDisplay(HideMode.Screen)
|
||||
def image(self, image):
|
||||
"""
|
||||
Add an image as the background. The image is converted to a
|
||||
bytestream on route.
|
||||
|
||||
`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):
|
||||
"""
|
||||
|
@ -343,22 +360,16 @@ class MainDisplay(DisplayWidget):
|
|||
Store the images so they can be replaced when required
|
||||
"""
|
||||
log.debug(u'hideDisplay mode = %d', mode)
|
||||
#self.displayText.setPixmap(self.transparent)
|
||||
if mode == HideMode.Screen:
|
||||
#self.display_image.setPixmap(self.transparent)
|
||||
self.frame.evaluateJavaScript(u'show_blank("desktop");')
|
||||
self.setVisible(False)
|
||||
elif mode == HideMode.Blank:
|
||||
self.displayBlank.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.blankFrame))
|
||||
elif mode == HideMode.Blank or self.initialFrame:
|
||||
self.frame.evaluateJavaScript(u'show_blank("black");')
|
||||
else:
|
||||
if self.parent.renderManager.renderer.bg_frame:
|
||||
self.displayBlank.setPixmap(QtGui.QPixmap.fromImage(
|
||||
self.parent.renderManager.renderer.bg_frame))
|
||||
else:
|
||||
self.displayBlank.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.blankFrame))
|
||||
self.frame.evaluateJavaScript(u'show_blank("theme");')
|
||||
if mode != HideMode.Screen and self.isHidden():
|
||||
self.setVisible(True)
|
||||
self.hide_mode = mode
|
||||
|
||||
def showDisplay(self):
|
||||
"""
|
||||
|
@ -367,275 +378,17 @@ class MainDisplay(DisplayWidget):
|
|||
Make the stored images None to release memory.
|
||||
"""
|
||||
log.debug(u'showDisplay')
|
||||
self.displayBlank.setPixmap(self.transparent)
|
||||
self.frame.evaluateJavaScript('show_blank("show");')
|
||||
if self.isHidden():
|
||||
self.setVisible(True)
|
||||
#Trigger actions when display is active again
|
||||
# Trigger actions when display is active again
|
||||
Receiver.send_message(u'maindisplay_active')
|
||||
|
||||
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()
|
||||
self.hide_mode = None
|
||||
|
||||
class AudioPlayer(QtCore.QObject):
|
||||
"""
|
||||
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')
|
||||
|
||||
|
@ -675,9 +428,9 @@ class AudioPlayer(QtCore.QObject):
|
|||
Set up a video to play from the serviceitem.
|
||||
"""
|
||||
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())
|
||||
self.mediaObject.setCurrentSource(Phonon.MediaSource(file))
|
||||
self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile))
|
||||
self.onMediaPlay()
|
||||
|
||||
def onMediaPlay(self):
|
||||
|
|
|
@ -29,7 +29,7 @@ import logging
|
|||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
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, \
|
||||
SettingsManager, PluginManager, Receiver, translate
|
||||
from openlp.core.utils import AppLocation, add_actions, LanguageManager
|
||||
|
@ -94,8 +94,10 @@ class Ui_MainWindow(object):
|
|||
self.ControlSplitter.setObjectName(u'ControlSplitter')
|
||||
self.MainContentLayout.addWidget(self.ControlSplitter)
|
||||
# Create slide controllers
|
||||
self.PreviewController = SlideController(self, self.settingsmanager)
|
||||
self.LiveController = SlideController(self, self.settingsmanager, True)
|
||||
self.PreviewController = SlideController(self, self.settingsmanager,
|
||||
self.screens)
|
||||
self.LiveController = SlideController(self, self.settingsmanager,
|
||||
self.screens, True)
|
||||
# Create menu
|
||||
self.MenuBar = QtGui.QMenuBar(MainWindow)
|
||||
self.MenuBar.setGeometry(QtCore.QRect(0, 0, 1087, 27))
|
||||
|
@ -509,7 +511,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
self.songsSettingsSection = u'songs'
|
||||
self.serviceNotSaved = False
|
||||
self.settingsmanager = SettingsManager(screens)
|
||||
self.displayManager = DisplayManager(screens)
|
||||
self.aboutForm = AboutForm(self, applicationVersion)
|
||||
self.settingsForm = SettingsForm(self.screens, self, self)
|
||||
self.recentFiles = QtCore.QStringList()
|
||||
|
@ -594,7 +595,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
#ThemeManager needs to call RenderManager
|
||||
self.RenderManager = RenderManager(
|
||||
self.ThemeManagerContents, self.screens)
|
||||
self.displayManager.renderManager = self.RenderManager
|
||||
#Define the media Dock Manager
|
||||
self.mediaDockManager = MediaDockManager(self.MediaToolBox)
|
||||
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'settings form'] = self.settingsForm
|
||||
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'formparent'] = self
|
||||
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
|
||||
"""
|
||||
QtGui.QWidget.show(self)
|
||||
self.displayManager.setup()
|
||||
if self.displayManager.mainDisplay.isVisible():
|
||||
self.displayManager.mainDisplay.setFocus()
|
||||
self.LiveController.display.setup()
|
||||
self.PreviewController.display.setup()
|
||||
if self.LiveController.display.isVisible():
|
||||
self.LiveController.display.setFocus()
|
||||
self.activateWindow()
|
||||
if QtCore.QSettings().value(
|
||||
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
|
||||
their locations
|
||||
"""
|
||||
log.debug(u'screenChanged')
|
||||
self.RenderManager.update_display()
|
||||
self.displayManager.setup()
|
||||
self.setFocus()
|
||||
self.activateWindow()
|
||||
|
||||
|
@ -791,8 +791,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
self.plugin_manager.finalise_plugins()
|
||||
# Save settings
|
||||
self.saveSettings()
|
||||
#Close down the displays
|
||||
self.displayManager.close()
|
||||
#Close down the display
|
||||
self.LiveController.display.close()
|
||||
|
||||
def serviceChanged(self, reset=False, serviceName=None):
|
||||
"""
|
||||
|
|
|
@ -93,7 +93,6 @@ class Ui_PluginViewDialog(object):
|
|||
self.pluginListButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
|
||||
self.pluginListButtonBox.setObjectName(u'pluginListButtonBox')
|
||||
self.pluginLayout.addWidget(self.pluginListButtonBox)
|
||||
|
||||
self.retranslateUi(pluginViewDialog)
|
||||
QtCore.QObject.connect(self.pluginListButtonBox,
|
||||
QtCore.SIGNAL(u'accepted()'), pluginViewDialog.close)
|
||||
|
|
|
@ -58,6 +58,9 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
|
|||
Load the plugin details into the screen
|
||||
"""
|
||||
self.pluginListWidget.clear()
|
||||
self.programaticChange = True
|
||||
self._clearDetails()
|
||||
self.programaticChange = True
|
||||
for plugin in self.parent.plugin_manager.plugins:
|
||||
item = QtGui.QListWidgetItem(self.pluginListWidget)
|
||||
# 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.screen_list = []
|
||||
self.display_count = 0
|
||||
#actual display number
|
||||
# actual display number
|
||||
self.current_display = 0
|
||||
#save config display number
|
||||
# save config display number
|
||||
self.monitor_number = 0
|
||||
|
||||
def add_screen(self, screen):
|
||||
|
|
|
@ -317,9 +317,8 @@ class ServiceManager(QtGui.QWidget):
|
|||
self.serviceItemEditForm.setServiceItem(
|
||||
self.serviceItems[item][u'service_item'])
|
||||
if self.serviceItemEditForm.exec_():
|
||||
self.serviceItems[item][u'service_item'] = \
|
||||
self.serviceItemEditForm.getServiceItem()
|
||||
self.repaintServiceList(item, 0)
|
||||
self.addServiceItem(self.serviceItemEditForm.getServiceItem(),
|
||||
replace=True)
|
||||
|
||||
def nextItem(self):
|
||||
"""
|
||||
|
@ -575,7 +574,7 @@ class ServiceManager(QtGui.QWidget):
|
|||
* An osd which is a pickle of the service items
|
||||
* 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:
|
||||
filename = QtGui.QFileDialog.getSaveFileName(self,
|
||||
translate('OpenLP.ServiceManager', 'Save Service'),
|
||||
|
@ -633,6 +632,8 @@ class ServiceManager(QtGui.QWidget):
|
|||
|
||||
def onLoadService(self, lastService=False):
|
||||
if lastService:
|
||||
if not self.parent.recentFiles:
|
||||
return
|
||||
filename = self.parent.recentFiles[0]
|
||||
else:
|
||||
filename = QtGui.QFileDialog.getOpenFileName(
|
||||
|
@ -756,6 +757,7 @@ class ServiceManager(QtGui.QWidget):
|
|||
"""
|
||||
Set the theme for the current service
|
||||
"""
|
||||
log.debug(u'onThemeComboBoxSelected')
|
||||
self.service_theme = unicode(self.themeComboBox.currentText())
|
||||
self.parent.RenderManager.set_service_theme(self.service_theme)
|
||||
QtCore.QSettings().setValue(
|
||||
|
@ -768,6 +770,7 @@ class ServiceManager(QtGui.QWidget):
|
|||
The theme may have changed in the settings dialog so make
|
||||
sure the theme combo box is in the correct state.
|
||||
"""
|
||||
log.debug(u'themeChange')
|
||||
if self.parent.RenderManager.theme_level == ThemeLevel.Global:
|
||||
self.toolbar.actions[u'ThemeLabel'].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
|
||||
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
|
||||
if self.serviceItems:
|
||||
tempServiceItems = self.serviceItems
|
||||
|
@ -790,8 +794,8 @@ class ServiceManager(QtGui.QWidget):
|
|||
for item in tempServiceItems:
|
||||
self.addServiceItem(
|
||||
item[u'service_item'], False, item[u'expanded'])
|
||||
#Set to False as items may have changed rendering
|
||||
#does not impact the saved song so True may also be valid
|
||||
# Set to False as items may have changed rendering
|
||||
# does not impact the saved song so True may also be valid
|
||||
self.parent.serviceChanged(False, self.serviceName)
|
||||
|
||||
def addServiceItem(self, item, rebuild=False, expand=True, replace=False):
|
||||
|
@ -801,6 +805,7 @@ class ServiceManager(QtGui.QWidget):
|
|||
``item``
|
||||
Service Item to be added
|
||||
"""
|
||||
log.debug(u'addServiceItem')
|
||||
sitem = self.findServiceItem()[0]
|
||||
item.render()
|
||||
if replace:
|
||||
|
@ -873,6 +878,7 @@ class ServiceManager(QtGui.QWidget):
|
|||
ItemCapabilities.AllowsPreview):
|
||||
self.parent.PreviewController.addServiceManagerItem(
|
||||
self.serviceItems[item][u'service_item'], 0)
|
||||
self.parent.LiveController.PreviewListWidget.setFocus()
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self,
|
||||
translate('OpenLP.ServiceManager', 'Missing Display Handler'),
|
||||
|
|
|
@ -30,6 +30,7 @@ import logging
|
|||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||
from settingsdialog import Ui_SettingsDialog
|
||||
|
||||
|
@ -87,6 +88,8 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
|
|||
"""
|
||||
for tabIndex in range(0, self.settingsTabWidget.count()):
|
||||
self.settingsTabWidget.widget(tabIndex).save()
|
||||
# Must go after all settings are save
|
||||
Receiver.send_message(u'config_updated')
|
||||
return QtGui.QDialog.accept(self)
|
||||
|
||||
def postSetUp(self):
|
||||
|
|
|
@ -25,36 +25,17 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import time
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
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, \
|
||||
ItemCapabilities, translate
|
||||
|
||||
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):
|
||||
"""
|
||||
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
|
||||
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.
|
||||
"""
|
||||
|
@ -105,8 +86,10 @@ class SlideController(QtGui.QWidget):
|
|||
self.settingsmanager = settingsmanager
|
||||
self.isLive = isLive
|
||||
self.parent = parent
|
||||
self.mainDisplay = self.parent.displayManager.mainDisplay
|
||||
self.displayManager = self.parent.displayManager
|
||||
self.screens = screens
|
||||
self.ratio = float(self.screens.current[u'size'].width()) / \
|
||||
float(self.screens.current[u'size'].height())
|
||||
self.display = MainDisplay(self, screens, isLive)
|
||||
self.loopList = [
|
||||
u'Start Loop',
|
||||
u'Loop Separator',
|
||||
|
@ -115,13 +98,14 @@ class SlideController(QtGui.QWidget):
|
|||
self.songEditList = [
|
||||
u'Edit Song',
|
||||
]
|
||||
self.volume = 10
|
||||
self.timer_id = 0
|
||||
self.songEdit = False
|
||||
self.selectedRow = 0
|
||||
self.serviceItem = None
|
||||
self.alertTab = None
|
||||
self.Panel = QtGui.QWidget(parent.ControlSplitter)
|
||||
self.slideList = {}
|
||||
self.canDisplay = True
|
||||
# Layout for holding panel
|
||||
self.PanelLayout = QtGui.QVBoxLayout(self.Panel)
|
||||
self.PanelLayout.setSpacing(0)
|
||||
|
@ -133,7 +117,8 @@ class SlideController(QtGui.QWidget):
|
|||
self.split = 1
|
||||
self.typePrefix = u'live'
|
||||
else:
|
||||
self.TypeLabel.setText(translate('OpenLP.SlideController', 'Preview'))
|
||||
self.TypeLabel.setText(translate('OpenLP.SlideController',
|
||||
'Preview'))
|
||||
self.split = 0
|
||||
self.typePrefix = u'preview'
|
||||
self.TypeLabel.setStyleSheet(u'font-weight: bold; font-size: 12pt;')
|
||||
|
@ -177,11 +162,6 @@ class SlideController(QtGui.QWidget):
|
|||
sizeToolbarPolicy.setHeightForWidth(
|
||||
self.Toolbar.sizePolicy().hasHeightForWidth())
|
||||
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(
|
||||
u'Previous Slide', u':/slides/slide_previous.png',
|
||||
translate('OpenLP.SlideController', 'Move to previous'),
|
||||
|
@ -190,11 +170,6 @@ class SlideController(QtGui.QWidget):
|
|||
u'Next Slide', u':/slides/slide_next.png',
|
||||
translate('OpenLP.SlideController', 'Move to next'),
|
||||
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:
|
||||
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
||||
self.HideMenu = QtGui.QToolButton(self.Toolbar)
|
||||
|
@ -213,20 +188,24 @@ class SlideController(QtGui.QWidget):
|
|||
self.ThemeScreen.setCheckable(True)
|
||||
QtCore.QObject.connect(self.ThemeScreen,
|
||||
QtCore.SIGNAL("triggered(bool)"), self.onThemeDisplay)
|
||||
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(
|
||||
u':/slides/slide_desktop.png'), u'Show Desktop', self.HideMenu)
|
||||
self.DesktopScreen.setCheckable(True)
|
||||
QtCore.QObject.connect(self.DesktopScreen,
|
||||
QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
|
||||
if self.screens.display_count > 1:
|
||||
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(
|
||||
u':/slides/slide_desktop.png'), u'Show Desktop',
|
||||
self.HideMenu)
|
||||
self.DesktopScreen.setCheckable(True)
|
||||
QtCore.QObject.connect(self.DesktopScreen,
|
||||
QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
|
||||
self.HideMenu.setDefaultAction(self.BlankScreen)
|
||||
self.HideMenu.menu().addAction(self.BlankScreen)
|
||||
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:
|
||||
self.Toolbar.addToolbarSeparator(u'Close Separator')
|
||||
self.Toolbar.addToolbarButton(
|
||||
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.addToolbarButton(
|
||||
u'Edit Song', u':/general/general_edit.png',
|
||||
|
@ -247,11 +226,12 @@ class SlideController(QtGui.QWidget):
|
|||
self.DelaySpinBox.setMaximum(180)
|
||||
self.Toolbar.addToolbarWidget(
|
||||
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',
|
||||
'Delay between slides in seconds'))
|
||||
self.ControllerLayout.addWidget(self.Toolbar)
|
||||
#Build a Media ToolBar
|
||||
# Build a Media ToolBar
|
||||
self.Mediabar = OpenLPToolbar(self)
|
||||
self.Mediabar.addToolbarButton(
|
||||
u'Media Start', u':/slides/media_playback_start.png',
|
||||
|
@ -271,19 +251,30 @@ class SlideController(QtGui.QWidget):
|
|||
self.seekSlider.setObjectName(u'seekSlider')
|
||||
self.Mediabar.addToolbarWidget(
|
||||
u'Seek Slider', self.seekSlider)
|
||||
self.volumeSlider = Phonon.VolumeSlider()
|
||||
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
|
||||
self.volumeSlider.setObjectName(u'volumeSlider')
|
||||
self.Mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
|
||||
self.volumeSlider = Phonon.VolumeSlider()
|
||||
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
|
||||
self.volumeSlider.setObjectName(u'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)
|
||||
# Build the Song Toolbar
|
||||
if isLive:
|
||||
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.Toolbar.addToolbarWidget(u'Song Menu', self.SongMenu)
|
||||
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'])
|
||||
# Screen preview area
|
||||
self.PreviewFrame = QtGui.QFrame(self.Splitter)
|
||||
|
@ -322,7 +313,7 @@ class SlideController(QtGui.QWidget):
|
|||
self.SlidePreview.setSizePolicy(sizePolicy)
|
||||
self.SlidePreview.setFixedSize(
|
||||
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.setFrameShadow(QtGui.QFrame.Plain)
|
||||
self.SlidePreview.setLineWidth(1)
|
||||
|
@ -390,17 +381,37 @@ class SlideController(QtGui.QWidget):
|
|||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'config_updated'), self.refreshServiceItem)
|
||||
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):
|
||||
"""
|
||||
Handle changes of width from the splitter between the live and preview
|
||||
controller. Event only issues when changes have finished
|
||||
"""
|
||||
log.debug(u'widthChanged live = %s' % self.isLive)
|
||||
width = self.parent.ControlSplitter.sizes()[self.split]
|
||||
height = width * self.parent.RenderManager.screen_ratio
|
||||
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():
|
||||
for framenumber in range(len(self.serviceItem.get_frames())):
|
||||
self.PreviewListWidget.setRowHeight(framenumber, height)
|
||||
|
@ -453,8 +464,6 @@ class SlideController(QtGui.QWidget):
|
|||
if item.is_media():
|
||||
self.Toolbar.setVisible(False)
|
||||
self.Mediabar.setVisible(True)
|
||||
#self.volumeSlider.setAudioOutput(
|
||||
# self.mainDisplay.videoDisplay.audio)
|
||||
|
||||
def enablePreviewToolBar(self, item):
|
||||
"""
|
||||
|
@ -474,22 +483,20 @@ class SlideController(QtGui.QWidget):
|
|||
"""
|
||||
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.is_text() or self.serviceItem.is_image():
|
||||
item = self.serviceItem
|
||||
item.render()
|
||||
self.addServiceManagerItem(item, self.selectedRow)
|
||||
self._processItem(item, self.selectedRow)
|
||||
|
||||
def addServiceItem(self, item):
|
||||
"""
|
||||
Method to install the service item into the controller
|
||||
Called by plugins
|
||||
"""
|
||||
log.debug(u'addServiceItem')
|
||||
before = time.time()
|
||||
log.debug(u'addServiceItem live = %s' % self.isLive)
|
||||
item.render()
|
||||
log.log(15, u'Rendering took %4s' % (time.time() - before))
|
||||
slideno = 0
|
||||
if self.songEdit:
|
||||
slideno = self.selectedRow
|
||||
|
@ -509,8 +516,8 @@ class SlideController(QtGui.QWidget):
|
|||
request the correct toolbar for the plugin.
|
||||
Called by ServiceManager
|
||||
"""
|
||||
log.debug(u'addServiceManagerItem')
|
||||
#If service item is the same as the current on only change slide
|
||||
log.debug(u'addServiceManagerItem live = %s' % self.isLive)
|
||||
# If service item is the same as the current on only change slide
|
||||
if item.__eq__(self.serviceItem):
|
||||
self.PreviewListWidget.selectRow(slideno)
|
||||
self.onSlideSelected()
|
||||
|
@ -522,17 +529,15 @@ class SlideController(QtGui.QWidget):
|
|||
Loads a ServiceItem into the system from ServiceManager
|
||||
Display the slide number passed
|
||||
"""
|
||||
log.debug(u'processManagerItem')
|
||||
log.debug(u'processManagerItem live = %s' % self.isLive)
|
||||
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.is_command():
|
||||
Receiver.send_message(u'%s_stop' %
|
||||
self.serviceItem.name.lower(), [serviceItem, self.isLive])
|
||||
if self.serviceItem.is_media():
|
||||
self.onMediaStop()
|
||||
if serviceItem.is_media():
|
||||
self.onMediaStart(serviceItem)
|
||||
if self.isLive:
|
||||
blanked = self.BlankScreen.isChecked()
|
||||
else:
|
||||
|
@ -541,12 +546,8 @@ class SlideController(QtGui.QWidget):
|
|||
[serviceItem, self.isLive, blanked, slideno])
|
||||
self.slideList = {}
|
||||
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)
|
||||
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.PreviewListWidget.clear()
|
||||
self.PreviewListWidget.setRowCount(0)
|
||||
|
@ -560,20 +561,19 @@ class SlideController(QtGui.QWidget):
|
|||
self.PreviewListWidget.rowCount() + 1)
|
||||
item = QtGui.QTableWidgetItem()
|
||||
slideHeight = 0
|
||||
#It is a based Text Render
|
||||
if self.serviceItem.is_text():
|
||||
if frame[u'verseTag']:
|
||||
bits = frame[u'verseTag'].split(u':')
|
||||
tag = u'%s\n%s' % (bits[0][0], bits[1][0:] )
|
||||
tag1 = u'%s%s' % (bits[0][0], bits[1][0:] )
|
||||
row = tag
|
||||
if self.isLive:
|
||||
if tag1 not in self.slideList:
|
||||
self.slideList[tag1] = framenumber
|
||||
self.SongMenu.menu().addAction(tag1,
|
||||
self.onSongBarHandler)
|
||||
else:
|
||||
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'])
|
||||
else:
|
||||
label = QtGui.QLabel()
|
||||
|
@ -600,15 +600,14 @@ class SlideController(QtGui.QWidget):
|
|||
else:
|
||||
self.PreviewListWidget.selectRow(slideno)
|
||||
self.enableToolBar(serviceItem)
|
||||
# Pass to display for viewing
|
||||
self.display.buildHtml(self.serviceItem)
|
||||
if serviceItem.is_media():
|
||||
self.onMediaStart(serviceItem)
|
||||
self.onSlideSelected()
|
||||
self.PreviewListWidget.setFocus()
|
||||
Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix,
|
||||
[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):
|
||||
"""
|
||||
|
@ -620,7 +619,7 @@ class SlideController(QtGui.QWidget):
|
|||
dataItem = {}
|
||||
if self.serviceItem.is_text():
|
||||
dataItem[u'tag'] = unicode(frame[u'verseTag'])
|
||||
dataItem[u'text'] = unicode(frame[u'text'])
|
||||
dataItem[u'text'] = unicode(frame[u'html'])
|
||||
else:
|
||||
dataItem[u'tag'] = unicode(framenumber)
|
||||
dataItem[u'text'] = u''
|
||||
|
@ -630,7 +629,7 @@ class SlideController(QtGui.QWidget):
|
|||
Receiver.send_message(u'slidecontroller_%s_text_response'
|
||||
% self.typePrefix, data)
|
||||
|
||||
#Screen event methods
|
||||
# Screen event methods
|
||||
def onSlideSelectedFirst(self):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
log.debug(u'mainDisplaySetBackground')
|
||||
if not self.mainDisplay.primary:
|
||||
self.onBlankDisplay(True)
|
||||
log.debug(u'mainDisplaySetBackground live = %s' % self.isLive)
|
||||
if not self.display.primary:
|
||||
self.onHideDisplay(True)
|
||||
|
||||
def onSlideBlank(self):
|
||||
"""
|
||||
|
@ -688,7 +687,8 @@ class SlideController(QtGui.QWidget):
|
|||
self.HideMenu.setDefaultAction(self.BlankScreen)
|
||||
self.BlankScreen.setChecked(checked)
|
||||
self.ThemeScreen.setChecked(False)
|
||||
self.DesktopScreen.setChecked(False)
|
||||
if self.screens.display_count > 1:
|
||||
self.DesktopScreen.setChecked(False)
|
||||
QtCore.QSettings().setValue(
|
||||
self.parent.generalSettingsSection + u'/screen blank',
|
||||
QtCore.QVariant(checked))
|
||||
|
@ -706,7 +706,8 @@ class SlideController(QtGui.QWidget):
|
|||
self.HideMenu.setDefaultAction(self.ThemeScreen)
|
||||
self.BlankScreen.setChecked(False)
|
||||
self.ThemeScreen.setChecked(checked)
|
||||
self.DesktopScreen.setChecked(False)
|
||||
if self.screens.display_count > 1:
|
||||
self.DesktopScreen.setChecked(False)
|
||||
if checked:
|
||||
Receiver.send_message(u'maindisplay_hide', HideMode.Theme)
|
||||
else:
|
||||
|
@ -721,7 +722,8 @@ class SlideController(QtGui.QWidget):
|
|||
self.HideMenu.setDefaultAction(self.DesktopScreen)
|
||||
self.BlankScreen.setChecked(False)
|
||||
self.ThemeScreen.setChecked(False)
|
||||
self.DesktopScreen.setChecked(checked)
|
||||
if self.screens.display_count > 1:
|
||||
self.DesktopScreen.setChecked(checked)
|
||||
if checked:
|
||||
Receiver.send_message(u'maindisplay_hide', HideMode.Screen)
|
||||
else:
|
||||
|
@ -758,13 +760,6 @@ class SlideController(QtGui.QWidget):
|
|||
% self.serviceItem.name.lower(),
|
||||
[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):
|
||||
"""
|
||||
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:
|
||||
self.updatePreview()
|
||||
else:
|
||||
before = time.time()
|
||||
frame = self.serviceItem.get_rendered_frame(row)
|
||||
frame, raw_html = 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):
|
||||
self.SlidePreview.setPixmap(QtGui.QPixmap.fromImage(frame))
|
||||
else:
|
||||
if isinstance(frame[u'main'], basestring):
|
||||
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.SlidePreview.setPixmap(QtGui.QPixmap(frame))
|
||||
self.selectedRow = row
|
||||
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix,
|
||||
row)
|
||||
|
@ -810,8 +796,7 @@ class SlideController(QtGui.QWidget):
|
|||
row)
|
||||
|
||||
def updatePreview(self):
|
||||
rm = self.parent.RenderManager
|
||||
if not rm.screens.current[u'primary']:
|
||||
if not self.screens.current[u'primary']:
|
||||
# Grab now, but try again in a couple of seconds if slide change
|
||||
# is slow
|
||||
QtCore.QTimer.singleShot(0.5, self.grabMainDisplay)
|
||||
|
@ -819,12 +804,12 @@ class SlideController(QtGui.QWidget):
|
|||
else:
|
||||
label = self.PreviewListWidget.cellWidget(
|
||||
self.PreviewListWidget.currentRow(), 1)
|
||||
self.SlidePreview.setPixmap(label.pixmap())
|
||||
if label:
|
||||
self.SlidePreview.setPixmap(label.pixmap())
|
||||
|
||||
def grabMainDisplay(self):
|
||||
rm = self.parent.RenderManager
|
||||
winid = QtGui.QApplication.desktop().winId()
|
||||
rect = rm.screens.current[u'size']
|
||||
rect = self.screens.current[u'size']
|
||||
winimg = QtGui.QPixmap.grabWindow(winid, rect.x(),
|
||||
rect.y(), rect.width(), rect.height())
|
||||
self.SlidePreview.setPixmap(winimg)
|
||||
|
@ -941,7 +926,9 @@ class SlideController(QtGui.QWidget):
|
|||
"""
|
||||
log.debug(u'SlideController onMediaStart')
|
||||
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:
|
||||
self.mediaObject.stop()
|
||||
self.mediaObject.clearQueue()
|
||||
|
@ -951,13 +938,21 @@ class SlideController(QtGui.QWidget):
|
|||
self.seekSlider.show()
|
||||
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):
|
||||
"""
|
||||
Respond to the Pause from the media Toolbar
|
||||
"""
|
||||
log.debug(u'SlideController onMediaPause')
|
||||
if self.isLive:
|
||||
Receiver.send_message(u'videodisplay_pause')
|
||||
self.display.videoPause()
|
||||
else:
|
||||
self.mediaObject.pause()
|
||||
|
||||
|
@ -967,7 +962,7 @@ class SlideController(QtGui.QWidget):
|
|||
"""
|
||||
log.debug(u'SlideController onMediaPlay')
|
||||
if self.isLive:
|
||||
Receiver.send_message(u'videodisplay_play')
|
||||
self.display.videoPlay()
|
||||
else:
|
||||
self.SlidePreview.hide()
|
||||
self.video.show()
|
||||
|
@ -979,7 +974,7 @@ class SlideController(QtGui.QWidget):
|
|||
"""
|
||||
log.debug(u'SlideController onMediaStop')
|
||||
if self.isLive:
|
||||
Receiver.send_message(u'videodisplay_stop')
|
||||
self.display.videoStop()
|
||||
else:
|
||||
self.mediaObject.stop()
|
||||
self.video.hide()
|
||||
|
|
|
@ -139,13 +139,13 @@ class ThemeManager(QtGui.QWidget):
|
|||
"""
|
||||
log.debug(u'changeGlobalFromTab %s', themeName)
|
||||
for count in range (0, self.themeListWidget.count()):
|
||||
#reset the old name
|
||||
# reset the old name
|
||||
item = self.themeListWidget.item(count)
|
||||
oldName = item.text()
|
||||
newName = unicode(item.data(QtCore.Qt.UserRole).toString())
|
||||
if oldName != newName:
|
||||
self.themeListWidget.item(count).setText(newName)
|
||||
#Set the new name
|
||||
# Set the new name
|
||||
if themeName == newName:
|
||||
name = unicode(translate('OpenLP.ThemeManager',
|
||||
'%s (default)')) % newName
|
||||
|
@ -161,11 +161,11 @@ class ThemeManager(QtGui.QWidget):
|
|||
for count in range (0, self.themeListWidget.count()):
|
||||
item = self.themeListWidget.item(count)
|
||||
oldName = item.text()
|
||||
#reset the old name
|
||||
# reset the old name
|
||||
if oldName != unicode(item.data(QtCore.Qt.UserRole).toString()):
|
||||
self.themeListWidget.item(count).setText(
|
||||
unicode(item.data(QtCore.Qt.UserRole).toString()))
|
||||
#Set the new name
|
||||
# Set the new name
|
||||
if count == selected_row:
|
||||
self.global_theme = unicode(
|
||||
self.themeListWidget.item(count).text())
|
||||
|
@ -295,7 +295,7 @@ class ThemeManager(QtGui.QWidget):
|
|||
path = unicode(path)
|
||||
if path:
|
||||
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
|
||||
try:
|
||||
zip = zipfile.ZipFile(themePath, u'w')
|
||||
|
@ -346,11 +346,10 @@ class ThemeManager(QtGui.QWidget):
|
|||
log.debug(u'Load themes from dir')
|
||||
self.themelist = []
|
||||
self.themeListWidget.clear()
|
||||
#root, dirs, files = os.walk(self.path)
|
||||
dirList = os.listdir(self.path)
|
||||
for name in dirList:
|
||||
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)
|
||||
if os.path.exists(theme):
|
||||
textName = os.path.splitext(name)[0]
|
||||
|
@ -660,9 +659,8 @@ class ThemeManager(QtGui.QWidget):
|
|||
"""
|
||||
Call the RenderManager to build a Sample Image
|
||||
"""
|
||||
log.debug(u'generateImage %s ', themedata)
|
||||
frame = self.parent.RenderManager.generate_preview(themedata)
|
||||
return frame
|
||||
log.debug(u'generateImage \n%s ', themedata)
|
||||
return self.parent.RenderManager.generate_preview(themedata)
|
||||
|
||||
def getPreviewImage(self, theme):
|
||||
"""
|
||||
|
@ -732,8 +730,6 @@ class ThemeManager(QtGui.QWidget):
|
|||
theme.display_slideTransition = theme.display_slideTransition
|
||||
theme.font_footer_color = theme.font_footer_color.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_name = theme.font_footer_name.strip()
|
||||
#theme.font_footer_override
|
||||
|
@ -746,7 +742,6 @@ class ThemeManager(QtGui.QWidget):
|
|||
theme.font_main_color = theme.font_main_color.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_indentation = int(theme.font_main_indentation)
|
||||
theme.font_main_name = theme.font_main_name.strip()
|
||||
#theme.font_main_override
|
||||
theme.font_main_proportion = int(theme.font_main_proportion.strip())
|
||||
|
@ -757,3 +752,8 @@ class ThemeManager(QtGui.QWidget):
|
|||
#theme.theme_mode
|
||||
theme.theme_name = theme.theme_name.strip()
|
||||
#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')
|
||||
Plugin.initialise(self)
|
||||
self.toolsAlertItem.setVisible(True)
|
||||
self.liveController.alertTab = self.alertsTab
|
||||
|
||||
def finalise(self):
|
||||
log.info(u'Plugin Finalise')
|
||||
log.info(u'Alerts Finalising')
|
||||
Plugin.finalise(self)
|
||||
self.toolsAlertItem.setVisible(False)
|
||||
|
||||
|
|
|
@ -32,18 +32,9 @@ from openlp.core.lib import Receiver, translate
|
|||
|
||||
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):
|
||||
"""
|
||||
AlertsTab is the Alerts settings tab in the settings dialog.
|
||||
AlertsManager manages the settings of Alerts.
|
||||
"""
|
||||
log.info(u'Alert Manager loaded')
|
||||
|
||||
|
@ -94,10 +85,7 @@ class AlertsManager(QtCore.QObject):
|
|||
return
|
||||
text = self.alertList.pop(0)
|
||||
alertTab = self.parent.alertsTab
|
||||
text = HTMLCODE % (alertTab.font_color, alertTab.bg_color,
|
||||
alertTab.font_face, alertTab.font_size, text)
|
||||
self.parent.previewController.parent.displayManager.addAlert(text,
|
||||
alertTab.location)
|
||||
self.parent.liveController.display.alert(text)
|
||||
# check to see if we have a timer running
|
||||
if self.timer_id == 0:
|
||||
self.timer_id = self.startTimer(int(alertTab.timeout) * 1000)
|
||||
|
@ -111,10 +99,8 @@ class AlertsManager(QtCore.QObject):
|
|||
|
||||
"""
|
||||
log.debug(u'timer event')
|
||||
alertTab = self.parent.alertsTab
|
||||
if event.timerId() == self.timer_id:
|
||||
self.parent.previewController.parent.displayManager.addAlert(u'',
|
||||
alertTab.location)
|
||||
self.parent.liveController.display.alert(u'')
|
||||
self.killTimer(self.timer_id)
|
||||
self.timer_id = 0
|
||||
self.generateAlert()
|
||||
|
|
|
@ -261,7 +261,7 @@ class AlertsTab(SettingsTab):
|
|||
self.font_face = unicode(settings.value(
|
||||
u'font face', QtCore.QVariant(QtGui.QFont().family())).toString())
|
||||
self.location = settings.value(
|
||||
u'location', QtCore.QVariant(0)).toInt()[0]
|
||||
u'location', QtCore.QVariant(1)).toInt()[0]
|
||||
settings.endGroup()
|
||||
self.FontSizeSpinBox.setValue(self.font_size)
|
||||
self.TimeoutSpinBox.setValue(self.timeout)
|
||||
|
@ -296,3 +296,4 @@ class AlertsTab(SettingsTab):
|
|||
self.FontPreview.setFont(font)
|
||||
self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' %
|
||||
(self.bg_color, self.font_color))
|
||||
|
||||
|
|
|
@ -196,10 +196,10 @@ class BiblesTab(SettingsTab):
|
|||
self.show_new_chapters = True
|
||||
|
||||
def onBibleDualCheckBox(self, check_state):
|
||||
self.duel_bibles = False
|
||||
self.dual_bibles = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.duel_bibles = True
|
||||
self.dual_bibles = True
|
||||
|
||||
def load(self):
|
||||
settings = QtCore.QSettings()
|
||||
|
@ -212,12 +212,12 @@ class BiblesTab(SettingsTab):
|
|||
u'verse layout style', QtCore.QVariant(0)).toInt()[0]
|
||||
self.bible_theme = unicode(
|
||||
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()
|
||||
self.NewChaptersCheckBox.setChecked(self.show_new_chapters)
|
||||
self.DisplayStyleComboBox.setCurrentIndex(self.display_style)
|
||||
self.LayoutStyleComboBox.setCurrentIndex(self.layout_style)
|
||||
self.BibleDualCheckBox.setChecked(self.duel_bibles)
|
||||
self.BibleDualCheckBox.setChecked(self.dual_bibles)
|
||||
settings.endGroup()
|
||||
|
||||
def save(self):
|
||||
|
@ -229,7 +229,7 @@ class BiblesTab(SettingsTab):
|
|||
QtCore.QVariant(self.display_style))
|
||||
settings.setValue(u'verse 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.endGroup()
|
||||
|
||||
|
|
|
@ -51,9 +51,9 @@ class CSVBible(BibleDB):
|
|||
if u'booksfile' not in kwargs:
|
||||
raise KeyError(u'You have to supply a file to import books from.')
|
||||
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.')
|
||||
self.versesfile = kwargs[u'versesfile']
|
||||
self.versesfile = kwargs[u'versefile']
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'bibles_stop_import'), self.stop_import)
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ class BibleListView(BaseListWithDnD):
|
|||
self.parent().onListViewResize(event.size().width(),
|
||||
event.size().width())
|
||||
|
||||
|
||||
class BibleMediaItem(MediaManagerItem):
|
||||
"""
|
||||
This is the custom media manager item for Bibles.
|
||||
|
@ -276,8 +275,9 @@ class BibleMediaItem(MediaManagerItem):
|
|||
self.SearchProgress.setObjectName(u'SearchProgress')
|
||||
|
||||
def configUpdated(self):
|
||||
log.debug(u'configUpdated')
|
||||
if QtCore.QSettings().value(self.settingsSection + u'/dual bibles',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
QtCore.QVariant(True)).toBool():
|
||||
self.AdvancedSecondBibleLabel.setVisible(True)
|
||||
self.AdvancedSecondBibleComboBox.setVisible(True)
|
||||
self.QuickSecondVersionLabel.setVisible(True)
|
||||
|
@ -465,20 +465,28 @@ class BibleMediaItem(MediaManagerItem):
|
|||
self.displayResults(bible, dual_bible)
|
||||
|
||||
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')
|
||||
items = self.listView.selectedIndexes()
|
||||
if len(items) == 0:
|
||||
return False
|
||||
has_dual_bible = False
|
||||
bible_text = u''
|
||||
old_chapter = u''
|
||||
raw_footer = []
|
||||
raw_slides = []
|
||||
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
||||
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
||||
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
||||
for item in items:
|
||||
bitem = self.listView.item(item.row())
|
||||
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.
|
||||
for item in items:
|
||||
bitem = self.listView.item(item.row())
|
||||
|
@ -491,7 +499,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||
bible = self._decodeQtObject(reference, 'bible')
|
||||
version = self._decodeQtObject(reference, 'version')
|
||||
copyright = self._decodeQtObject(reference, 'copyright')
|
||||
#permission = self._decodeQtObject(reference, 'permission')
|
||||
permission = self._decodeQtObject(reference, 'permission')
|
||||
text = self._decodeQtObject(reference, 'text')
|
||||
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
||||
if dual_bible:
|
||||
|
@ -499,65 +507,57 @@ class BibleMediaItem(MediaManagerItem):
|
|||
'dual_version')
|
||||
dual_copyright = self._decodeQtObject(reference,
|
||||
'dual_copyright')
|
||||
#dual_permission = self._decodeQtObject(reference,
|
||||
# 'dual_permission')
|
||||
dual_permission = self._decodeQtObject(reference,
|
||||
'dual_permission')
|
||||
dual_text = self._decodeQtObject(reference, 'dual_text')
|
||||
if self.parent.settings_tab.display_style == 1:
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
||||
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
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse)
|
||||
footer = u'%s (%s %s %s)' % (book, version, copyright, permission)
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
if dual_bible:
|
||||
footer = u'%s (%s %s)' % (book, dual_version,
|
||||
dual_copyright)
|
||||
# If not found add second version and copyright to footer.
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
bible_text = u'%s %s \n\n %s %s' % (verse_text, text,
|
||||
verse_text, dual_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:
|
||||
if has_dual_bible:
|
||||
if dual_bible:
|
||||
footer = u'%s (%s %s %s)' % (book, dual_version,
|
||||
dual_copyright, dual_permission)
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
# If there is an old bible_text we have to add it.
|
||||
if bible_text:
|
||||
raw_slides.append(bible_text)
|
||||
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 dual_bible:
|
||||
service_item.title = u'%s (%s, %s) %s' % (book, version,
|
||||
|
@ -568,7 +568,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||
translate('BiblesPlugin.MediaItem', 'etc')) == -1:
|
||||
service_item.title = u'%s, %s' % (service_item.title,
|
||||
translate('BiblesPlugin.MediaItem', 'etc'))
|
||||
# item theme
|
||||
# Service Item: Theme
|
||||
if len(self.parent.settings_tab.bible_theme) == 0:
|
||||
service_item.theme = None
|
||||
else:
|
||||
|
@ -582,14 +582,20 @@ class BibleMediaItem(MediaManagerItem):
|
|||
service_item.raw_footer = raw_footer
|
||||
return True
|
||||
|
||||
def formatVerse(self, old_chapter, chapter, verse, opening, closing):
|
||||
verse_text = opening
|
||||
if old_chapter != chapter:
|
||||
verse_text += chapter + u':'
|
||||
elif not self.parent.settings_tab.show_new_chapters:
|
||||
verse_text += chapter + u':'
|
||||
verse_text += verse
|
||||
verse_text += closing
|
||||
def formatVerse(self, old_chapter, chapter, verse):
|
||||
if not self.parent.settings_tab.show_new_chapters or \
|
||||
old_chapter != chapter:
|
||||
verse_text = chapter + u':' + verse
|
||||
else:
|
||||
verse_text = verse
|
||||
if self.parent.settings_tab.display_style == 1:
|
||||
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
|
||||
|
||||
def reloadBibles(self):
|
||||
|
@ -634,14 +640,14 @@ class BibleMediaItem(MediaManagerItem):
|
|||
for i in range(int(range_from), int(range_to) + 1):
|
||||
combo.addItem(unicode(i))
|
||||
|
||||
def displayResults(self, bible, dual_bible=None):
|
||||
'''
|
||||
Displays the search results in the media manager. All data needed for further
|
||||
action is saved for/in each row.
|
||||
'''
|
||||
def displayResults(self, bible, dual_bible=u''):
|
||||
"""
|
||||
Displays the search results in the media manager. All data needed for
|
||||
further action is saved for/in each row.
|
||||
"""
|
||||
version = self.parent.manager.get_meta_data(bible, u'Version')
|
||||
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:
|
||||
dual_version = self.parent.manager.get_meta_data(dual_bible,
|
||||
u'Version')
|
||||
|
@ -649,43 +655,41 @@ class BibleMediaItem(MediaManagerItem):
|
|||
u'Copyright')
|
||||
dual_permission = self.parent.manager.get_meta_data(dual_bible,
|
||||
u'Permissions')
|
||||
if dual_permission:
|
||||
dual_permission = dual_permission.value
|
||||
else:
|
||||
if not dual_permission:
|
||||
dual_permission = u''
|
||||
# We count the number of rows which are maybe already present.
|
||||
start_count = self.listView.count()
|
||||
for count, verse in enumerate(self.search_results):
|
||||
if dual_bible:
|
||||
vdict = {
|
||||
'book':QtCore.QVariant(verse.book.name),
|
||||
'chapter':QtCore.QVariant(verse.chapter),
|
||||
'verse':QtCore.QVariant(verse.verse),
|
||||
'bible':QtCore.QVariant(bible),
|
||||
'version':QtCore.QVariant(version.value),
|
||||
'copyright':QtCore.QVariant(copyright.value),
|
||||
#'permission':QtCore.QVariant(permission.value),
|
||||
'text':QtCore.QVariant(verse.text),
|
||||
'dual_bible':QtCore.QVariant(dual_bible),
|
||||
'dual_version':QtCore.QVariant(dual_version.value),
|
||||
'dual_copyright':QtCore.QVariant(dual_copyright.value),
|
||||
#'dual_permission':QtCore.QVariant(dual_permission),
|
||||
'dual_text':QtCore.QVariant(
|
||||
'book': QtCore.QVariant(verse.book.name),
|
||||
'chapter': QtCore.QVariant(verse.chapter),
|
||||
'verse': QtCore.QVariant(verse.verse),
|
||||
'bible': QtCore.QVariant(bible),
|
||||
'version': QtCore.QVariant(version.value),
|
||||
'copyright': QtCore.QVariant(copyright.value),
|
||||
'permission': QtCore.QVariant(permission.value),
|
||||
'text': QtCore.QVariant(verse.text),
|
||||
'dual_bible': QtCore.QVariant(dual_bible),
|
||||
'dual_version': QtCore.QVariant(dual_version.value),
|
||||
'dual_copyright': QtCore.QVariant(dual_copyright.value),
|
||||
'dual_permission': QtCore.QVariant(dual_permission.value),
|
||||
'dual_text': QtCore.QVariant(
|
||||
self.dual_search_results[count].text)
|
||||
}
|
||||
bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name,
|
||||
verse.chapter, verse.verse, version.value, dual_version.value)
|
||||
else:
|
||||
vdict = {
|
||||
'book':QtCore.QVariant(verse.book.name),
|
||||
'chapter':QtCore.QVariant(verse.chapter),
|
||||
'verse':QtCore.QVariant(verse.verse),
|
||||
'bible':QtCore.QVariant(bible),
|
||||
'version':QtCore.QVariant(version.value),
|
||||
'copyright':QtCore.QVariant(copyright.value),
|
||||
#'permission':QtCore.QVariant(permission.value),
|
||||
'text':QtCore.QVariant(verse.text),
|
||||
'dual_bible':QtCore.QVariant(dual_bible)
|
||||
'book': QtCore.QVariant(verse.book.name),
|
||||
'chapter': QtCore.QVariant(verse.chapter),
|
||||
'verse': QtCore.QVariant(verse.verse),
|
||||
'bible': QtCore.QVariant(bible),
|
||||
'version': QtCore.QVariant(version.value),
|
||||
'copyright': QtCore.QVariant(copyright.value),
|
||||
'permission': QtCore.QVariant(permission.value),
|
||||
'text': QtCore.QVariant(verse.text),
|
||||
'dual_bible': QtCore.QVariant(dual_bible)
|
||||
}
|
||||
bible_text = u' %s %d:%d (%s)' % (verse.book.name,
|
||||
verse.chapter, verse.verse, version.value)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
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):
|
||||
def setupUi(self, customEditDialog):
|
||||
|
@ -73,7 +73,7 @@ class Ui_CustomEditDialog(object):
|
|||
self.editLayout3.setSpacing(8)
|
||||
self.editLayout3.setMargin(0)
|
||||
self.editLayout3.setObjectName(u'editLayout3')
|
||||
self.verseTextEdit = QtGui.QTextEdit(self.editWidget)
|
||||
self.verseTextEdit = SpellTextEdit(self)
|
||||
self.verseTextEdit.setObjectName(u'verseTextEdit')
|
||||
self.editLayout3.addWidget(self.verseTextEdit)
|
||||
self.buttonWidget = QtGui.QWidget(self.editWidget)
|
||||
|
|
|
@ -110,8 +110,14 @@ class ImageMediaItem(MediaManagerItem):
|
|||
u':/slides/slide_blank.png',
|
||||
translate('ImagePlugin.MediaItem', 'Replace Live Background'),
|
||||
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
|
||||
self.pageLayout.addWidget(self.ImageWidget)
|
||||
self.resetButton.setVisible(False)
|
||||
|
||||
def onDeleteClick(self):
|
||||
"""
|
||||
|
@ -169,6 +175,10 @@ class ImageMediaItem(MediaManagerItem):
|
|||
else:
|
||||
return False
|
||||
|
||||
def onResetClick(self):
|
||||
self.resetButton.setVisible(False)
|
||||
self.parent.liveController.display.resetImage()
|
||||
|
||||
def onReplaceClick(self):
|
||||
if check_item_selected(self.listView,
|
||||
translate('ImagePlugin.MediaItem',
|
||||
|
@ -178,7 +188,8 @@ class ImageMediaItem(MediaManagerItem):
|
|||
bitem = self.listView.item(item.row())
|
||||
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
|
||||
frame = QtGui.QImage(unicode(filename))
|
||||
self.parent.displayManager.displayImageWithText(frame)
|
||||
self.parent.liveController.display.image(frame)
|
||||
self.resetButton.setVisible(True)
|
||||
|
||||
def onPreviewClick(self):
|
||||
MediaManagerItem.onPreviewClick(self)
|
||||
|
|
|
@ -97,8 +97,17 @@ class MediaMediaItem(MediaManagerItem):
|
|||
u':/slides/slide_blank.png',
|
||||
translate('MediaPlugin.MediaItem', 'Replace Live Background'),
|
||||
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
|
||||
self.pageLayout.addWidget(self.ImageWidget)
|
||||
self.resetButton.setVisible(False)
|
||||
|
||||
def onResetClick(self):
|
||||
self.resetButton.setVisible(False)
|
||||
self.parent.liveController.display.resetVideo()
|
||||
|
||||
def onReplaceClick(self):
|
||||
if check_item_selected(self.listView,
|
||||
|
@ -106,7 +115,8 @@ class MediaMediaItem(MediaManagerItem):
|
|||
'You must select a media file to replace the background with.')):
|
||||
item = self.listView.currentItem()
|
||||
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):
|
||||
if item is None:
|
||||
|
|
|
@ -1,119 +1,57 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>OpenLP Controller</title>
|
||||
<script type='text/javascript'>
|
||||
|
||||
function send_event(eventname, data){
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
if(req.readyState==4)
|
||||
response(eventname, req);
|
||||
}
|
||||
var url = '';
|
||||
if(eventname.substr(-8) == '_request')
|
||||
url = 'request';
|
||||
else
|
||||
url = 'send';
|
||||
url += '/' + eventname;
|
||||
if(data!=null)
|
||||
url += '?q=' + escape(data);
|
||||
req.open('GET', url, true);
|
||||
req.send();
|
||||
}
|
||||
function failed_response(eventname, req){
|
||||
switch(eventname){
|
||||
case 'remotes_poll_request':
|
||||
if(req.status==408)
|
||||
send_event("remotes_poll_request");
|
||||
break;
|
||||
}
|
||||
}
|
||||
function response(eventname, req){
|
||||
if(req.status!=200){
|
||||
failed_response(eventname, req);
|
||||
return;
|
||||
}
|
||||
text = req.responseText;
|
||||
switch(eventname){
|
||||
case 'servicemanager_list_request':
|
||||
var data = eval('(' + text + ')');
|
||||
var html = '<table>';
|
||||
for(row in data){
|
||||
html += '<tr onclick="send_event('
|
||||
html += "'servicemanager_set_item', " + row + ')"';
|
||||
if(data[row]['selected'])
|
||||
html += ' style="font-weight: bold"';
|
||||
html += '>'
|
||||
html += '<td>' + (parseInt(row)+1) + '</td>'
|
||||
html += '<td>' + data[row]['title'] + '</td>'
|
||||
html += '<td>' + data[row]['plugin'] + '</td>'
|
||||
html += '<td>' + data[row]['notes'] + '</td>'
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
document.getElementById('service').innerHTML = html;
|
||||
break;
|
||||
case 'slidecontroller_live_text_request':
|
||||
var data = eval('(' + text + ')');
|
||||
var html = '<table>';
|
||||
for(row in data){
|
||||
html += '<tr onclick="send_event('
|
||||
html += "'slidecontroller_live_set', " + row + ')"';
|
||||
if(data[row]['selected'])
|
||||
html += ' style="font-weight: bold"';
|
||||
html += '>';
|
||||
html += '<td>' + data[row]['tag'] + '</td>';
|
||||
html += '<td>' + data[row]['text'].replace(/\n/g, '<br>');
|
||||
html += '</td></tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
document.getElementById('currentitem').innerHTML = html;
|
||||
break;
|
||||
case 'remotes_poll_request':
|
||||
send_event("remotes_poll_request");
|
||||
send_event("servicemanager_list_request");
|
||||
send_event("slidecontroller_live_text_request");
|
||||
break;
|
||||
}
|
||||
}
|
||||
send_event("servicemanager_list_request");
|
||||
send_event("slidecontroller_live_text_request");
|
||||
send_event("remotes_poll_request");
|
||||
</script>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>OpenLP Remote Controller</title>
|
||||
<script type="text/javascript" src="/files/jquery.js"></script>
|
||||
<script type='text/javascript' src="/files/openlp.js"></script>
|
||||
<script type='text/javascript' src="/files/init.js"></script>
|
||||
<link rel="stylesheet" href="/files/style.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>OpenLP Controller</h1>
|
||||
<input type='button' value='<- Previous Slide'
|
||||
onclick='send_event("slidecontroller_live_previous");' />
|
||||
<input type='button' value='Next Slide ->'
|
||||
onclick='send_event("slidecontroller_live_next");' />
|
||||
<br/>
|
||||
<input type='button' value='<- Previous Item'
|
||||
onclick='send_event("servicemanager_previous_item");' />
|
||||
<input type='button' value='Next Item ->'
|
||||
onclick='send_event("servicemanager_next_item");' />
|
||||
<br/>
|
||||
<input type='button' value='Blank'
|
||||
onclick='send_event("slidecontroller_live_blank");' />
|
||||
<input type='button' value='Unblank'
|
||||
onclick='send_event("slidecontroller_live_unblank");' />
|
||||
<br/>
|
||||
<label>Alert text</label><input id='alert' type='text' />
|
||||
<input type='button' value='Send'
|
||||
onclick='send_event("alerts_text",
|
||||
document.getElementById("alert").value);' />
|
||||
<p>Quick Links: <a href="#service-manager">Service Manager</a> | <a href="#slide-controller">Slide Controller</a> | <a href="#miscellaneous">Miscellaneous</a></p>
|
||||
<h2 id="service-manager">Service Manager</h2>
|
||||
<div id="service"></div>
|
||||
<p><em>(Click service item to go live.)</em></p>
|
||||
<fieldset>
|
||||
<legend>Controls</legend>
|
||||
<div id="service-buttons">
|
||||
<input type="button" value="Refresh Service" id="servicemanager_list_request" />
|
||||
</div>
|
||||
<div id="item-buttons">
|
||||
<input type="button" value="<- Previous Item" id="servicemanager_previous_item" />
|
||||
<input type="button" value="Next Item ->" id="servicemanager_next_item" />
|
||||
</div>
|
||||
</fieldset>
|
||||
<hr>
|
||||
<input type='button' value='Order of service'
|
||||
onclick='send_event("servicemanager_list_request");'>
|
||||
<i>(Click service item to go live.)</i>
|
||||
<div id='service'></div>
|
||||
<h2 id="slide-controller">Slide Controller</h2>
|
||||
<div id="current-item"></div>
|
||||
<p><em>(Click verse to display.)</em></p>
|
||||
<fieldset>
|
||||
<legend>Controls</legend>
|
||||
<div id="item-buttons">
|
||||
<input type="button" value="Refresh Item" id="slidecontroller_live_text_request" />
|
||||
</div>
|
||||
<div id="slide-buttons">
|
||||
<input type="button" value="<- Previous Slide" id="slidecontroller_live_previous" />
|
||||
<input type="button" value="Next Slide ->" id="slidecontroller_live_next" />
|
||||
</div>
|
||||
</fieldset>
|
||||
<hr>
|
||||
<input type='button' value='Current item'
|
||||
onclick='send_event("slidecontroller_live_text_request");'>
|
||||
<i>(Click verse to display.)</i>
|
||||
<div id='currentitem'></div>
|
||||
<h2 id="miscellaneous">Miscellaneous</h2>
|
||||
<div id="display-buttons">
|
||||
<input type="button" value="Blank" id="slidecontroller_live_blank" />
|
||||
<input type="button" value="Unblank" id="slidecontroller_live_unblank" />
|
||||
</div>
|
||||
<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>
|
||||
<a href="http://www.openlp.org/">OpenLP website</a>
|
||||
<a href="http://openlp.org/">OpenLP website</a>
|
||||
</body>
|
||||
</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 os
|
||||
import json
|
||||
import urlparse
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from PyQt4 import QtCore, QtNetwork
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
|
@ -37,7 +41,7 @@ from openlp.core.utils import AppLocation
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
class HttpServer(object):
|
||||
"""
|
||||
"""
|
||||
Ability to control OpenLP via a webbrowser
|
||||
e.g. http://localhost:4316/send/slidecontroller_live_next
|
||||
http://localhost:4316/send/alerts_text?q=your%20alert%20text
|
||||
|
@ -59,7 +63,7 @@ class HttpServer(object):
|
|||
def start_tcp(self):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
log.debug(u'Start TCP server')
|
||||
|
@ -67,13 +71,13 @@ class HttpServer(object):
|
|||
self.parent.settingsSection + u'/remote port',
|
||||
QtCore.QVariant(4316)).toInt()[0]
|
||||
self.server = QtNetwork.QTcpServer()
|
||||
self.server.listen(QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any),
|
||||
self.server.listen(QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any),
|
||||
port)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'slidecontroller_live_changed'),
|
||||
QtCore.SIGNAL(u'slidecontroller_live_changed'),
|
||||
self.slide_change)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'slidecontroller_live_started'),
|
||||
QtCore.SIGNAL(u'slidecontroller_live_started'),
|
||||
self.item_change)
|
||||
QtCore.QObject.connect(self.server,
|
||||
QtCore.SIGNAL(u'newConnection()'), self.new_connection)
|
||||
|
@ -92,7 +96,7 @@ class HttpServer(object):
|
|||
"""
|
||||
self.current_item = items[0].title
|
||||
self.send_poll()
|
||||
|
||||
|
||||
def send_poll(self):
|
||||
"""
|
||||
Tell the clients something has changed
|
||||
|
@ -100,7 +104,7 @@ class HttpServer(object):
|
|||
Receiver.send_message(u'remotes_poll_response',
|
||||
{'slide': self.current_slide,
|
||||
'item': self.current_item})
|
||||
|
||||
|
||||
def new_connection(self):
|
||||
"""
|
||||
A new http connection has been made. Create a client object to handle
|
||||
|
@ -110,7 +114,7 @@ class HttpServer(object):
|
|||
socket = self.server.nextPendingConnection()
|
||||
if socket:
|
||||
self.connections.append(HttpConnection(self, socket))
|
||||
|
||||
|
||||
def close_connection(self, connection):
|
||||
"""
|
||||
The connection has been closed. Clean up
|
||||
|
@ -124,9 +128,9 @@ class HttpServer(object):
|
|||
"""
|
||||
log.debug(u'close http server')
|
||||
self.server.close()
|
||||
|
||||
|
||||
class HttpConnection(object):
|
||||
"""
|
||||
"""
|
||||
A single connection, this handles communication between the server
|
||||
and the client
|
||||
"""
|
||||
|
@ -134,7 +138,7 @@ class HttpConnection(object):
|
|||
"""
|
||||
Initialise the http connection. Listen out for socket signals
|
||||
"""
|
||||
log.debug(u'Initialise HttpConnection: %s' %
|
||||
log.debug(u'Initialise HttpConnection: %s' %
|
||||
socket.peerAddress().toString())
|
||||
self.socket = socket
|
||||
self.parent = parent
|
||||
|
@ -180,13 +184,13 @@ class HttpConnection(object):
|
|||
def serve_file(self, filename):
|
||||
"""
|
||||
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.
|
||||
|
||||
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'
|
||||
"""
|
||||
log.debug(u'serve file request %s' % filename)
|
||||
log.debug(u'serve file request %s' % filename)
|
||||
if not filename:
|
||||
filename = u'index.html'
|
||||
path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
|
||||
|
@ -229,8 +233,8 @@ class HttpConnection(object):
|
|||
if not params:
|
||||
return None
|
||||
else:
|
||||
return params['q']
|
||||
|
||||
return params['q']
|
||||
|
||||
def process_event(self, event, params):
|
||||
"""
|
||||
Send a signal to openlp to perform an action.
|
||||
|
@ -239,21 +243,27 @@ class HttpConnection(object):
|
|||
"""
|
||||
log.debug(u'Processing event %s' % event)
|
||||
if params:
|
||||
Receiver.send_message(event, params)
|
||||
else:
|
||||
Receiver.send_message(event)
|
||||
return u'OK'
|
||||
Receiver.send_message(event, params)
|
||||
else:
|
||||
Receiver.send_message(event)
|
||||
return json.dumps([u'OK'])
|
||||
|
||||
def process_request(self, event, params):
|
||||
"""
|
||||
Client has requested data. Send the signal and parameters for openlp
|
||||
to handle, then listen out for a corresponding _request signal
|
||||
to handle, then listen out for a corresponding ``_request`` signal
|
||||
which will have the data to return.
|
||||
For most event timeout after 10 seconds (i.e. incase the signal
|
||||
recipient isn't listening)
|
||||
remotes_poll_request is a special case, this is a ajax long poll which
|
||||
is just waiting for slide change/song change activity. This can wait
|
||||
longer (one minute)
|
||||
|
||||
For most events, timeout after 10 seconds (i.e. in case the signal
|
||||
recipient isn't listening). ``remotes_poll_request`` is a special case
|
||||
however, this is a ajax long poll which is just waiting for slide
|
||||
change/song change activity. This can wait longer (one minute).
|
||||
|
||||
``event``
|
||||
The event from the web page.
|
||||
|
||||
``params``
|
||||
Parameters sent with the event.
|
||||
"""
|
||||
log.debug(u'Processing request %s' % event)
|
||||
if not event.endswith(u'_request'):
|
||||
|
@ -271,14 +281,14 @@ class HttpConnection(object):
|
|||
else:
|
||||
self.timer.start(10000)
|
||||
if params:
|
||||
Receiver.send_message(event, params)
|
||||
else:
|
||||
Receiver.send_message(event)
|
||||
Receiver.send_message(event, params)
|
||||
else:
|
||||
Receiver.send_message(event)
|
||||
return True
|
||||
|
||||
def process_response(self, data):
|
||||
"""
|
||||
The recipient of a _request signal has sent data. Convert this to
|
||||
The recipient of a _request signal has sent data. Convert this to
|
||||
json and return it to client
|
||||
"""
|
||||
log.debug(u'Processing response for %s' % self.event)
|
||||
|
@ -292,7 +302,7 @@ class HttpConnection(object):
|
|||
|
||||
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' + \
|
||||
u'Content-Type: %s\r\n\r\n' % mimetype)
|
||||
|
@ -307,11 +317,11 @@ class HttpConnection(object):
|
|||
|
||||
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
|
||||
"""
|
||||
self.socket.write(u'HTTP/1.1 408 Request Timeout\r\n')
|
||||
|
||||
|
||||
def timeout(self):
|
||||
"""
|
||||
Listener for timeout signal
|
||||
|
@ -320,14 +330,14 @@ class HttpConnection(object):
|
|||
return
|
||||
self.send_408_timeout()
|
||||
self.close()
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
"""
|
||||
The client has disconnected. Tidy up
|
||||
"""
|
||||
log.debug(u'socket disconnected')
|
||||
self.close()
|
||||
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
The server has closed the connection. Tidy up
|
||||
|
|
|
@ -263,8 +263,8 @@ class Ui_EditSongDialog(object):
|
|||
self.SongbookLayout.setSpacing(8)
|
||||
self.SongbookLayout.setObjectName(u'SongbookLayout')
|
||||
self.SongbookCombo = QtGui.QComboBox(self.SongBookGroup)
|
||||
sizePolicy = QtGui.QSizePolicy(
|
||||
QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
|
||||
QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(
|
||||
|
@ -273,6 +273,12 @@ class Ui_EditSongDialog(object):
|
|||
self.SongbookCombo.setSizePolicy(sizePolicy)
|
||||
self.SongbookCombo.setObjectName(u'SongbookCombo')
|
||||
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.AuthorsTabLayout.addWidget(self.TopicBookWidget)
|
||||
self.SongTabWidget.addTab(self.AuthorsTab, u'')
|
||||
|
@ -440,6 +446,8 @@ class Ui_EditSongDialog(object):
|
|||
translate('SongsPlugin.EditSongForm', 'R&emove'))
|
||||
self.SongBookGroup.setTitle(
|
||||
translate('SongsPlugin.EditSongForm', 'Song Book'))
|
||||
self.songBookNumberLabel.setText(translate('SongsPlugin.EditSongForm',
|
||||
'Song No.:'))
|
||||
self.SongTabWidget.setTabText(
|
||||
self.SongTabWidget.indexOf(self.AuthorsTab),
|
||||
translate('SongsPlugin.EditSongForm',
|
||||
|
|
|
@ -49,6 +49,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
"""
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.song = None
|
||||
# can this be automated?
|
||||
self.width = 400
|
||||
self.setupUi(self)
|
||||
|
@ -83,10 +84,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
QtCore.QObject.connect(self.VerseListWidget,
|
||||
QtCore.SIGNAL(u'itemClicked(QTableWidgetItem*)'),
|
||||
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.SIGNAL(u'clicked()'),
|
||||
self.parent.parent.renderManager.theme_manager.onAddTheme)
|
||||
|
@ -157,7 +154,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
def newSong(self):
|
||||
log.debug(u'New Song')
|
||||
self.SongTabWidget.setCurrentIndex(0)
|
||||
self.song = Song()
|
||||
self.TitleEditItem.setText(u'')
|
||||
self.AlternativeEdit.setText(u'')
|
||||
self.CopyrightEditItem.setText(u'')
|
||||
|
@ -278,7 +274,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
item = self.VerseListWidget.item(row, 0)
|
||||
data = unicode(item.data(QtCore.Qt.UserRole).toString())
|
||||
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)
|
||||
self.VerseListWidget.setVerticalHeaderLabels(rowLabel)
|
||||
|
||||
|
@ -298,8 +294,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
else:
|
||||
author = Author.populate(first_name=text.rsplit(u' ', 1)[0],
|
||||
last_name=text.rsplit(u' ', 1)[1], display_name=text)
|
||||
self.songmanager.save_object(author, False)
|
||||
self.song.authors.append(author)
|
||||
self.songmanager.save_object(author)
|
||||
author_item = QtGui.QListWidgetItem(
|
||||
unicode(author.display_name))
|
||||
author_item.setData(QtCore.Qt.UserRole,
|
||||
|
@ -312,13 +307,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
elif item > 0:
|
||||
item_id = (self.AuthorsSelectionComboItem.itemData(item)).toInt()[0]
|
||||
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,
|
||||
translate('SongsPlugin.EditSongForm', 'Error'),
|
||||
translate('SongsPlugin.EditSongForm', 'Error'),
|
||||
translate('SongsPlugin.EditSongForm', 'This author is '
|
||||
'already in the list.'))
|
||||
else:
|
||||
self.song.authors.append(author)
|
||||
author_item = QtGui.QListWidgetItem(unicode(
|
||||
author.display_name))
|
||||
author_item.setData(QtCore.Qt.UserRole,
|
||||
|
@ -340,9 +335,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
def onAuthorRemoveButtonClicked(self):
|
||||
self.AuthorRemoveButton.setEnabled(False)
|
||||
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)
|
||||
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.Yes:
|
||||
topic = Topic.populate(name=text)
|
||||
self.songmanager.save_object(topic, False)
|
||||
self.song.topics.append(topic)
|
||||
self.songmanager.save_object(topic)
|
||||
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
||||
topic_item.setData(QtCore.Qt.UserRole,
|
||||
QtCore.QVariant(topic.id))
|
||||
|
@ -370,13 +361,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
elif item > 0:
|
||||
item_id = (self.SongTopicCombo.itemData(item)).toInt()[0]
|
||||
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,
|
||||
translate('SongsPlugin.EditSongForm', 'Error'),
|
||||
translate('SongsPlugin.EditSongForm', 'This topic is '
|
||||
'already in the list.'))
|
||||
else:
|
||||
self.song.topics.append(topic)
|
||||
topic_item = QtGui.QListWidgetItem(unicode(topic.name))
|
||||
topic_item.setData(QtCore.Qt.UserRole,
|
||||
QtCore.QVariant(topic.id))
|
||||
|
@ -396,27 +387,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
def onTopicRemoveButtonClicked(self):
|
||||
self.TopicRemoveButton.setEnabled(False)
|
||||
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)
|
||||
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):
|
||||
self.VerseEditButton.setEnabled(True)
|
||||
self.VerseDeleteButton.setEnabled(True)
|
||||
|
@ -600,10 +573,17 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
self.CopyrightEditItem.setCursorPosition(pos + 1)
|
||||
|
||||
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.loadAuthors()
|
||||
self.loadBooks()
|
||||
self.loadTopics()
|
||||
if temp_song_book:
|
||||
self.SongbookCombo.setEditText(temp_song_book)
|
||||
|
||||
def onPreview(self, button):
|
||||
"""
|
||||
|
@ -620,6 +600,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
|
||||
def accept(self):
|
||||
log.debug(u'accept')
|
||||
if not self.song:
|
||||
self.song = Song()
|
||||
item = int(self.SongbookCombo.currentIndex())
|
||||
text = unicode(self.SongbookCombo.currentText())
|
||||
if item == 0 and text:
|
||||
|
@ -632,7 +614,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
book = Book.populate(name=text, publisher=u'')
|
||||
self.songmanager.save_object(book)
|
||||
self.song.book = book
|
||||
self.loadBooks()
|
||||
else:
|
||||
return
|
||||
if self.saveSong():
|
||||
|
@ -648,9 +629,22 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
self.song.comments = unicode(self.CommentsEdit.toPlainText())
|
||||
self.song.verse_order = unicode(self.VerseOrderEdit.text())
|
||||
self.song.ccli_number = unicode(self.CCLNumberEdit.text())
|
||||
self.song.song_number = unicode(self.songBookNumberEdit.text())
|
||||
if self._validate_song():
|
||||
self.processLyrics()
|
||||
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)
|
||||
return True
|
||||
return False
|
||||
|
@ -682,5 +676,5 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
|||
|
||||
def processTitle(self):
|
||||
log.debug(u'processTitle')
|
||||
self.song.search_title = \
|
||||
re.sub(r'[\'"`,;:(){}?]+', u'', unicode(self.song.search_title))
|
||||
self.song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
|
||||
unicode(self.song.search_title))
|
||||
|
|
|
@ -26,81 +26,82 @@
|
|||
|
||||
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
|
||||
|
||||
class Ui_EditVerseDialog(object):
|
||||
def setupUi(self, EditVerseDialog):
|
||||
EditVerseDialog.setObjectName(u'EditVerseDialog')
|
||||
EditVerseDialog.resize(474, 442)
|
||||
EditVerseDialog.setModal(True)
|
||||
self.EditVerseLayout = QtGui.QVBoxLayout(EditVerseDialog)
|
||||
self.EditVerseLayout.setSpacing(8)
|
||||
self.EditVerseLayout.setMargin(8)
|
||||
self.EditVerseLayout.setObjectName(u'EditVerseLayout')
|
||||
self.VerseTextEdit = QtGui.QPlainTextEdit(EditVerseDialog)
|
||||
self.VerseTextEdit.setObjectName(u'VerseTextEdit')
|
||||
self.EditVerseLayout.addWidget(self.VerseTextEdit)
|
||||
self.VerseTypeLayout = QtGui.QHBoxLayout()
|
||||
self.VerseTypeLayout.setSpacing(8)
|
||||
self.VerseTypeLayout.setObjectName(u'VerseTypeLayout')
|
||||
self.VerseTypeLabel = QtGui.QLabel(EditVerseDialog)
|
||||
self.VerseTypeLabel.setObjectName(u'VerseTypeLabel')
|
||||
self.VerseTypeLayout.addWidget(self.VerseTypeLabel)
|
||||
self.VerseTypeComboBox = QtGui.QComboBox(EditVerseDialog)
|
||||
self.VerseTypeComboBox.setObjectName(u'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.VerseTypeLayout.addWidget(self.VerseTypeComboBox)
|
||||
self.VerseNumberBox = QtGui.QSpinBox(EditVerseDialog)
|
||||
self.VerseNumberBox.setMinimum(1)
|
||||
self.VerseNumberBox.setObjectName(u'VerseNumberBox')
|
||||
self.VerseTypeLayout.addWidget(self.VerseNumberBox)
|
||||
self.InsertButton = QtGui.QPushButton(EditVerseDialog)
|
||||
self.InsertButton.setIcon(build_icon(u':/general/general_add.png'))
|
||||
self.InsertButton.setObjectName(u'InsertButton')
|
||||
self.VerseTypeLayout.addWidget(self.InsertButton)
|
||||
self.VerseTypeSpacer = QtGui.QSpacerItem(40, 20,
|
||||
def setupUi(self, editVerseDialog):
|
||||
editVerseDialog.setObjectName(u'editVerseDialog')
|
||||
editVerseDialog.resize(474, 442)
|
||||
editVerseDialog.setModal(True)
|
||||
self.editVerseLayout = QtGui.QVBoxLayout(editVerseDialog)
|
||||
self.editVerseLayout.setSpacing(8)
|
||||
self.editVerseLayout.setMargin(8)
|
||||
self.editVerseLayout.setObjectName(u'editVerseLayout')
|
||||
self.verseTextEdit = SpellTextEdit(editVerseDialog)
|
||||
self.verseTextEdit.setObjectName(u'verseTextEdit')
|
||||
self.editVerseLayout.addWidget(self.verseTextEdit)
|
||||
self.verseTypeLayout = QtGui.QHBoxLayout()
|
||||
self.verseTypeLayout.setSpacing(8)
|
||||
self.verseTypeLayout.setObjectName(u'verseTypeLayout')
|
||||
self.verseTypeLabel = QtGui.QLabel(editVerseDialog)
|
||||
self.verseTypeLabel.setObjectName(u'verseTypeLabel')
|
||||
self.verseTypeLayout.addWidget(self.verseTypeLabel)
|
||||
self.verseTypeComboBox = QtGui.QComboBox(editVerseDialog)
|
||||
self.verseTypeComboBox.setObjectName(u'verseTypeComboBox')
|
||||
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.verseTypeLayout.addWidget(self.verseTypeComboBox)
|
||||
self.verseNumberBox = QtGui.QSpinBox(editVerseDialog)
|
||||
self.verseNumberBox.setMinimum(1)
|
||||
self.verseNumberBox.setObjectName(u'verseNumberBox')
|
||||
self.verseTypeLayout.addWidget(self.verseNumberBox)
|
||||
self.insertButton = QtGui.QPushButton(editVerseDialog)
|
||||
self.insertButton.setIcon(build_icon(u':/general/general_add.png'))
|
||||
self.insertButton.setObjectName(u'insertButton')
|
||||
self.verseTypeLayout.addWidget(self.insertButton)
|
||||
self.verseTypeSpacer = QtGui.QSpacerItem(40, 20,
|
||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.VerseTypeLayout.addItem(self.VerseTypeSpacer)
|
||||
self.EditVerseLayout.addLayout(self.VerseTypeLayout)
|
||||
self.EditButtonBox = QtGui.QDialogButtonBox(EditVerseDialog)
|
||||
self.EditButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.EditButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
|
||||
self.verseTypeLayout.addItem(self.verseTypeSpacer)
|
||||
self.editVerseLayout.addLayout(self.verseTypeLayout)
|
||||
self.editButtonBox = QtGui.QDialogButtonBox(editVerseDialog)
|
||||
self.editButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.editButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
|
||||
QtGui.QDialogButtonBox.Save)
|
||||
self.EditButtonBox.setObjectName(u'EditButtonBox')
|
||||
self.EditVerseLayout.addWidget(self.EditButtonBox)
|
||||
self.editButtonBox.setObjectName(u'editButtonBox')
|
||||
self.editVerseLayout.addWidget(self.editButtonBox)
|
||||
|
||||
self.retranslateUi(EditVerseDialog)
|
||||
QtCore.QObject.connect(self.EditButtonBox, QtCore.SIGNAL(u'accepted()'),
|
||||
EditVerseDialog.accept)
|
||||
QtCore.QObject.connect(self.EditButtonBox, QtCore.SIGNAL(u'rejected()'),
|
||||
EditVerseDialog.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(EditVerseDialog)
|
||||
self.retranslateUi(editVerseDialog)
|
||||
QtCore.QObject.connect(self.editButtonBox, QtCore.SIGNAL(u'accepted()'),
|
||||
editVerseDialog.accept)
|
||||
QtCore.QObject.connect(self.editButtonBox, QtCore.SIGNAL(u'rejected()'),
|
||||
editVerseDialog.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(editVerseDialog)
|
||||
|
||||
def retranslateUi(self, EditVerseDialog):
|
||||
EditVerseDialog.setWindowTitle(
|
||||
def retranslateUi(self, editVerseDialog):
|
||||
editVerseDialog.setWindowTitle(
|
||||
translate('SongsPlugin.EditVerseForm', 'Edit Verse'))
|
||||
self.VerseTypeLabel.setText(
|
||||
self.verseTypeLabel.setText(
|
||||
translate('SongsPlugin.EditVerseForm', '&Verse type:'))
|
||||
self.VerseTypeComboBox.setItemText(0,
|
||||
self.verseTypeComboBox.setItemText(0,
|
||||
VerseType.to_string(VerseType.Verse))
|
||||
self.VerseTypeComboBox.setItemText(1,
|
||||
self.verseTypeComboBox.setItemText(1,
|
||||
VerseType.to_string(VerseType.Chorus))
|
||||
self.VerseTypeComboBox.setItemText(2,
|
||||
self.verseTypeComboBox.setItemText(2,
|
||||
VerseType.to_string(VerseType.Bridge))
|
||||
self.VerseTypeComboBox.setItemText(3,
|
||||
self.verseTypeComboBox.setItemText(3,
|
||||
VerseType.to_string(VerseType.PreChorus))
|
||||
self.VerseTypeComboBox.setItemText(4,
|
||||
self.verseTypeComboBox.setItemText(4,
|
||||
VerseType.to_string(VerseType.Intro))
|
||||
self.VerseTypeComboBox.setItemText(5,
|
||||
self.verseTypeComboBox.setItemText(5,
|
||||
VerseType.to_string(VerseType.Ending))
|
||||
self.VerseTypeComboBox.setItemText(6,
|
||||
self.verseTypeComboBox.setItemText(6,
|
||||
VerseType.to_string(VerseType.Other))
|
||||
self.InsertButton.setText(
|
||||
self.insertButton.setText(
|
||||
translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||
|
|
|
@ -45,34 +45,36 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||
"""
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
QtCore.QObject.connect(
|
||||
self.InsertButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onInsertButtonClicked
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.VerseTextEdit,
|
||||
QtCore.QObject.connect(self.verseTextEdit,
|
||||
QtCore.SIGNAL('customContextMenuRequested(QPoint)'),
|
||||
self.contextMenu)
|
||||
QtCore.QObject.connect(self.insertButton, QtCore.SIGNAL(u'clicked()'),
|
||||
self.onInsertButtonClicked)
|
||||
QtCore.QObject.connect(self.verseTextEdit,
|
||||
QtCore.SIGNAL(u'cursorPositionChanged()'),
|
||||
self.onCursorPositionChanged
|
||||
)
|
||||
self.onCursorPositionChanged)
|
||||
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):
|
||||
if self.VerseTextEdit.textCursor().columnNumber() != 0:
|
||||
self.VerseTextEdit.insertPlainText(u'\n')
|
||||
self.VerseTextEdit.insertPlainText(u'---[%s:%s]---\n' % (title, num))
|
||||
self.VerseTextEdit.setFocus()
|
||||
if self.verseTextEdit.textCursor().columnNumber() != 0:
|
||||
self.verseTextEdit.insertPlainText(u'\n')
|
||||
self.verseTextEdit.insertPlainText(u'---[%s:%s]---\n' % (title, num))
|
||||
self.verseTextEdit.setFocus()
|
||||
|
||||
def onInsertButtonClicked(self):
|
||||
if self.VerseTextEdit.textCursor().columnNumber() != 0:
|
||||
self.VerseTextEdit.insertPlainText(u'\n')
|
||||
verse_type = self.VerseTypeComboBox.currentIndex()
|
||||
if self.verseTextEdit.textCursor().columnNumber() != 0:
|
||||
self.verseTextEdit.insertPlainText(u'\n')
|
||||
verse_type = self.verseTypeComboBox.currentIndex()
|
||||
if verse_type == VerseType.Verse:
|
||||
self.insertVerse(VerseType.to_string(VerseType.Verse),
|
||||
self.VerseNumberBox.value())
|
||||
self.verseNumberBox.value())
|
||||
elif verse_type == VerseType.Chorus:
|
||||
self.insertVerse(VerseType.to_string(VerseType.Chorus),
|
||||
self.VerseNumberBox.value())
|
||||
self.verseNumberBox.value())
|
||||
elif verse_type == VerseType.Bridge:
|
||||
self.insertVerse(VerseType.to_string(VerseType.Bridge))
|
||||
elif verse_type == VerseType.PreChorus:
|
||||
|
@ -85,12 +87,12 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||
self.insertVerse(VerseType.to_string(VerseType.Other))
|
||||
|
||||
def onCursorPositionChanged(self):
|
||||
position = self.VerseTextEdit.textCursor().position()
|
||||
text = unicode(self.VerseTextEdit.toPlainText())
|
||||
position = self.verseTextEdit.textCursor().position()
|
||||
text = unicode(self.verseTextEdit.toPlainText())
|
||||
if not text:
|
||||
return
|
||||
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
|
||||
position = text.rfind(u'---[', 0, position)
|
||||
if position == -1:
|
||||
|
@ -106,8 +108,8 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
|||
verse_number = int(match.group(2))
|
||||
verse_type_index = VerseType.from_string(verse_type)
|
||||
if verse_type_index is not None:
|
||||
self.VerseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||
self.VerseNumberBox.setValue(verse_number)
|
||||
self.verseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||
self.verseNumberBox.setValue(verse_number)
|
||||
|
||||
def setVerse(self, text, single=False,
|
||||
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_index = VerseType.from_string(verse_type)
|
||||
if verse_type_index is not None:
|
||||
self.VerseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||
self.VerseNumberBox.setValue(int(verse_number))
|
||||
self.InsertButton.setVisible(False)
|
||||
self.verseTypeComboBox.setCurrentIndex(verse_type_index)
|
||||
self.verseNumberBox.setValue(int(verse_number))
|
||||
self.insertButton.setVisible(False)
|
||||
else:
|
||||
if not text:
|
||||
text = u'---[%s:1]---\n' % VerseType.to_string(VerseType.Verse)
|
||||
self.VerseTypeComboBox.setCurrentIndex(0)
|
||||
self.VerseNumberBox.setValue(1)
|
||||
self.InsertButton.setVisible(True)
|
||||
self.VerseTextEdit.setPlainText(text)
|
||||
self.VerseTextEdit.setFocus(QtCore.Qt.OtherFocusReason)
|
||||
self.VerseTextEdit.moveCursor(QtGui.QTextCursor.End)
|
||||
self.verseTypeComboBox.setCurrentIndex(0)
|
||||
self.verseNumberBox.setValue(1)
|
||||
self.insertButton.setVisible(True)
|
||||
self.verseTextEdit.setPlainText(text)
|
||||
self.verseTextEdit.setFocus(QtCore.Qt.OtherFocusReason)
|
||||
self.verseTextEdit.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
def getVerse(self):
|
||||
return self.VerseTextEdit.toPlainText(), \
|
||||
VerseType.to_string(self.VerseTypeComboBox.currentIndex()), \
|
||||
unicode(self.VerseNumberBox.value())
|
||||
return self.verseTextEdit.toPlainText(), \
|
||||
VerseType.to_string(self.verseTypeComboBox.currentIndex()), \
|
||||
unicode(self.verseNumberBox.value())
|
||||
|
||||
def getVerseAll(self):
|
||||
text = self.VerseTextEdit.toPlainText()
|
||||
text = self.verseTextEdit.toPlainText()
|
||||
if not text.startsWith(u'---['):
|
||||
text = u'---[%s:1]---\n%s' % (VerseType.to_string(VerseType.Verse),
|
||||
text)
|
||||
|
|
|
@ -31,7 +31,6 @@ from PyQt4 import QtCore, QtGui
|
|||
|
||||
from songimportwizard import Ui_SongImportWizard
|
||||
from openlp.core.lib import Receiver, SettingsManager, translate
|
||||
#from openlp.core.utils import AppLocation
|
||||
from openlp.plugins.songs.lib.importer import SongFormat
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -83,6 +82,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
QtCore.QObject.connect(self.wordsOfWorshipRemoveButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
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.SIGNAL(u'clicked()'),
|
||||
self.onSongsOfFellowshipAddButtonClicked)
|
||||
|
@ -130,7 +135,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
self.openLP2BrowseButton.setFocus()
|
||||
return False
|
||||
elif source_format == SongFormat.OpenLP1:
|
||||
if self.openSongFilenameEdit.text().isEmpty():
|
||||
if self.openLP1FilenameEdit.text().isEmpty():
|
||||
QtGui.QMessageBox.critical(self,
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
'No openlp.org 1.x Song Database Selected'),
|
||||
|
@ -160,7 +165,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
self.openSongAddButton.setFocus()
|
||||
return False
|
||||
elif source_format == SongFormat.WordsOfWorship:
|
||||
if self.wordsOfWorshipListWidget.count() == 0:
|
||||
if self.wordsOfWorshipFileListWidget.count() == 0:
|
||||
QtGui.QMessageBox.critical(self,
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
'No Words of Worship Files Selected'),
|
||||
|
@ -277,6 +282,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
def onWordsOfWorshipRemoveButtonClicked(self):
|
||||
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):
|
||||
self.getFiles(
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
|
@ -302,8 +317,8 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
Stop the import on pressing the cancel button.
|
||||
"""
|
||||
log.debug('Cancel button pressed!')
|
||||
if self.currentId() == 3:
|
||||
Receiver.send_message(u'song_stop_import')
|
||||
if self.currentId() == 2:
|
||||
Receiver.send_message(u'songs_stop_import')
|
||||
|
||||
def onCurrentIdChanged(self, id):
|
||||
if id == 2:
|
||||
|
@ -315,6 +330,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
pass
|
||||
|
||||
def setDefaults(self):
|
||||
self.restart()
|
||||
self.formatComboBox.setCurrentIndex(0)
|
||||
self.openLP2FilenameEdit.setText(u'')
|
||||
self.openLP1FilenameEdit.setText(u'')
|
||||
|
@ -357,11 +373,11 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
|||
importer = self.plugin.importSongs(SongFormat.OpenLP2,
|
||||
filename=unicode(self.openLP2FilenameEdit.text())
|
||||
)
|
||||
#elif source_format == SongFormat.OpenLP1:
|
||||
# # Import an openlp.org database
|
||||
# importer = self.plugin.importSongs(SongFormat.OpenLP1,
|
||||
# filename=unicode(self.field(u'openlp1_filename').toString())
|
||||
# )
|
||||
elif source_format == SongFormat.OpenLP1:
|
||||
# Import an openlp.org database
|
||||
importer = self.plugin.importSongs(SongFormat.OpenLP1,
|
||||
filename=unicode(self.openLP1FilenameEdit.text())
|
||||
)
|
||||
elif source_format == SongFormat.OpenLyrics:
|
||||
# Import OpenLyrics songs
|
||||
importer = self.plugin.importSongs(SongFormat.OpenLyrics,
|
||||
|
|
|
@ -215,25 +215,32 @@ class Ui_SongImportWizard(object):
|
|||
self.wordsOfWorshipLayout.setSpacing(8)
|
||||
self.wordsOfWorshipLayout.setMargin(0)
|
||||
self.wordsOfWorshipLayout.setObjectName(u'wordsOfWorshipLayout')
|
||||
self.wordsOfWorshipFileListWidget = QtGui.QListWidget(self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipFileListWidget = QtGui.QListWidget(
|
||||
self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipFileListWidget.setSelectionMode(
|
||||
QtGui.QAbstractItemView.ExtendedSelection)
|
||||
self.wordsOfWorshipFileListWidget.setObjectName(u'wordsOfWorshipFileListWidget')
|
||||
self.wordsOfWorshipFileListWidget.setObjectName(
|
||||
u'wordsOfWorshipFileListWidget')
|
||||
self.wordsOfWorshipLayout.addWidget(self.wordsOfWorshipFileListWidget)
|
||||
self.wordsOfWorshipButtonLayout = QtGui.QHBoxLayout()
|
||||
self.wordsOfWorshipButtonLayout.setSpacing(8)
|
||||
self.wordsOfWorshipButtonLayout.setObjectName(u'wordsOfWorshipButtonLayout')
|
||||
self.wordsOfWorshipAddButton = QtGui.QPushButton(self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipButtonLayout.setObjectName(
|
||||
u'wordsOfWorshipButtonLayout')
|
||||
self.wordsOfWorshipAddButton = QtGui.QPushButton(
|
||||
self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipAddButton.setIcon(openIcon)
|
||||
self.wordsOfWorshipAddButton.setObjectName(u'wordsOfWorshipAddButton')
|
||||
self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipAddButton)
|
||||
self.wordsOfWorshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.wordsOfWorshipButtonLayout.addItem(self.wordsOfWorshipButtonSpacer)
|
||||
self.wordsOfWorshipRemoveButton = QtGui.QPushButton(self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipRemoveButton = QtGui.QPushButton(
|
||||
self.wordsOfWorshipPage)
|
||||
self.wordsOfWorshipRemoveButton.setIcon(deleteIcon)
|
||||
self.wordsOfWorshipRemoveButton.setObjectName(u'wordsOfWorshipRemoveButton')
|
||||
self.wordsOfWorshipButtonLayout.addWidget(self.wordsOfWorshipRemoveButton)
|
||||
self.wordsOfWorshipRemoveButton.setObjectName(
|
||||
u'wordsOfWorshipRemoveButton')
|
||||
self.wordsOfWorshipButtonLayout.addWidget(
|
||||
self.wordsOfWorshipRemoveButton)
|
||||
self.wordsOfWorshipLayout.addLayout(self.wordsOfWorshipButtonLayout)
|
||||
self.formatStackedWidget.addWidget(self.wordsOfWorshipPage)
|
||||
# CCLI File import
|
||||
|
@ -267,30 +274,43 @@ class Ui_SongImportWizard(object):
|
|||
# Songs of Fellowship
|
||||
self.songsOfFellowshipPage = QtGui.QWidget()
|
||||
self.songsOfFellowshipPage.setObjectName(u'songsOfFellowshipPage')
|
||||
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipLayout = QtGui.QVBoxLayout(
|
||||
self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipLayout.setMargin(0)
|
||||
self.songsOfFellowshipLayout.setSpacing(8)
|
||||
self.songsOfFellowshipLayout.setObjectName(u'songsOfFellowshipLayout')
|
||||
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipFileListWidget = QtGui.QListWidget(
|
||||
self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipFileListWidget.setSelectionMode(
|
||||
QtGui.QAbstractItemView.ExtendedSelection)
|
||||
self.songsOfFellowshipFileListWidget.setObjectName(u'songsOfFellowshipFileListWidget')
|
||||
self.songsOfFellowshipLayout.addWidget(self.songsOfFellowshipFileListWidget)
|
||||
self.songsOfFellowshipFileListWidget.setObjectName(
|
||||
u'songsOfFellowshipFileListWidget')
|
||||
self.songsOfFellowshipLayout.addWidget(
|
||||
self.songsOfFellowshipFileListWidget)
|
||||
self.songsOfFellowshipButtonLayout = QtGui.QHBoxLayout()
|
||||
self.songsOfFellowshipButtonLayout.setSpacing(8)
|
||||
self.songsOfFellowshipButtonLayout.setObjectName(u'songsOfFellowshipButtonLayout')
|
||||
self.songsOfFellowshipAddButton = QtGui.QPushButton(self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipButtonLayout.setObjectName(
|
||||
u'songsOfFellowshipButtonLayout')
|
||||
self.songsOfFellowshipAddButton = QtGui.QPushButton(
|
||||
self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipAddButton.setIcon(openIcon)
|
||||
self.songsOfFellowshipAddButton.setObjectName(u'songsOfFellowshipAddButton')
|
||||
self.songsOfFellowshipButtonLayout.addWidget(self.songsOfFellowshipAddButton)
|
||||
self.songsOfFellowshipAddButton.setObjectName(
|
||||
u'songsOfFellowshipAddButton')
|
||||
self.songsOfFellowshipButtonLayout.addWidget(
|
||||
self.songsOfFellowshipAddButton)
|
||||
self.songsOfFellowshipButtonSpacer = QtGui.QSpacerItem(40, 20,
|
||||
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.songsOfFellowshipButtonLayout.addItem(self.songsOfFellowshipButtonSpacer)
|
||||
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipButtonLayout.addItem(
|
||||
self.songsOfFellowshipButtonSpacer)
|
||||
self.songsOfFellowshipRemoveButton = QtGui.QPushButton(
|
||||
self.songsOfFellowshipPage)
|
||||
self.songsOfFellowshipRemoveButton.setIcon(deleteIcon)
|
||||
self.songsOfFellowshipRemoveButton.setObjectName(u'songsOfFellowshipRemoveButton')
|
||||
self.songsOfFellowshipButtonLayout.addWidget(self.songsOfFellowshipRemoveButton)
|
||||
self.songsOfFellowshipLayout.addLayout(self.songsOfFellowshipButtonLayout)
|
||||
self.songsOfFellowshipRemoveButton.setObjectName(
|
||||
u'songsOfFellowshipRemoveButton')
|
||||
self.songsOfFellowshipButtonLayout.addWidget(
|
||||
self.songsOfFellowshipRemoveButton)
|
||||
self.songsOfFellowshipLayout.addLayout(
|
||||
self.songsOfFellowshipButtonLayout)
|
||||
self.formatStackedWidget.addWidget(self.songsOfFellowshipPage)
|
||||
# Generic Document/Presentation import
|
||||
self.genericPage = QtGui.QWidget()
|
||||
|
|
|
@ -404,12 +404,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
|||
book.publisher = temp_publisher
|
||||
|
||||
def mergeAuthors(self, old_author):
|
||||
'''
|
||||
"""
|
||||
Merges two authors into one author.
|
||||
|
||||
``old_author``
|
||||
The author which will be deleted afterwards.
|
||||
'''
|
||||
"""
|
||||
existing_author = self.songmanager.get_object_filtered(Author,
|
||||
and_(Author.first_name == old_author.first_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)
|
||||
|
||||
def mergeTopics(self, old_topic):
|
||||
'''
|
||||
"""
|
||||
Merges two topics into one topic.
|
||||
|
||||
``old_topic``
|
||||
The topic which will be deleted afterwards.
|
||||
'''
|
||||
"""
|
||||
existing_topic = self.songmanager.get_object_filtered(Topic,
|
||||
Topic.name == old_topic.name)
|
||||
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)
|
||||
|
||||
def mergeBooks(self, old_book):
|
||||
'''
|
||||
"""
|
||||
Merges two books into one book.
|
||||
|
||||
``old_book``
|
||||
The book which will be deleted afterwards.
|
||||
'''
|
||||
"""
|
||||
existing_book = self.songmanager.get_object_filtered(Book,
|
||||
and_(Book.name == old_book.name,
|
||||
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``
|
||||
The database to setup
|
||||
"""
|
||||
session, metadata = init_db(url, auto_flush=False)
|
||||
session, metadata = init_db(url)
|
||||
|
||||
# Definition of the "authors" table
|
||||
authors_table = Table(u'authors', metadata,
|
||||
|
|
|
@ -26,9 +26,12 @@
|
|||
|
||||
from opensongimport import OpenSongImport
|
||||
from olpimport import OpenLPSongImport
|
||||
from olp1import import OpenLP1SongImport
|
||||
try:
|
||||
from sofimport import SofImport
|
||||
from oooimport import OooImport
|
||||
from cclifileimport import CCLIFileImport
|
||||
from wowimport import WowImport
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
@ -59,12 +62,18 @@ class SongFormat(object):
|
|||
"""
|
||||
if format == SongFormat.OpenLP2:
|
||||
return OpenLPSongImport
|
||||
if format == SongFormat.OpenLP1:
|
||||
return OpenLP1SongImport
|
||||
elif format == SongFormat.OpenSong:
|
||||
return OpenSongImport
|
||||
elif format == SongFormat.SongsOfFellowship:
|
||||
return SofImport
|
||||
elif format == SongFormat.WordsOfWorship:
|
||||
return WowImport
|
||||
elif format == SongFormat.Generic:
|
||||
return OooImport
|
||||
elif format == SongFormat.CCLI:
|
||||
return CCLIFileImport
|
||||
# else:
|
||||
return None
|
||||
|
||||
|
|
|
@ -359,16 +359,13 @@ class SongMediaItem(MediaManagerItem):
|
|||
author_list = author_list + u', '
|
||||
author_list = author_list + 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(author_list)
|
||||
raw_footer.append(song.copyright )
|
||||
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.audit = [
|
||||
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
|
||||
import song databases from other installations of OpenLP.
|
||||
"""
|
||||
def __init__(self, master_manager, **kwargs):
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the import.
|
||||
|
||||
``master_manager``
|
||||
``manager``
|
||||
The song manager for the running OpenLP installation.
|
||||
|
||||
``source_db``
|
||||
The database providing the data to import.
|
||||
"""
|
||||
SongImport.__init__(self, master_manager)
|
||||
self.master_manager = master_manager
|
||||
SongImport.__init__(self, manager)
|
||||
self.import_source = u'sqlite:///%s' % kwargs[u'filename']
|
||||
log.debug(self.import_source)
|
||||
self.source_session = None
|
||||
|
@ -145,7 +144,12 @@ class OpenLPSongImport(SongImport):
|
|||
mapper(OldTopic, source_topics_table)
|
||||
|
||||
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:
|
||||
self.import_wizard.incrementProgressBar(
|
||||
u'Importing song %s of %s' % (song_count, song_total))
|
||||
new_song = Song()
|
||||
new_song.title = song.title
|
||||
if has_media_files:
|
||||
|
@ -167,7 +171,7 @@ class OpenLPSongImport(SongImport):
|
|||
new_song.ccli_number = song.ccli_number
|
||||
if 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)
|
||||
if existing_author:
|
||||
new_song.authors.append(existing_author)
|
||||
|
@ -177,7 +181,7 @@ class OpenLPSongImport(SongImport):
|
|||
last_name=author.last_name,
|
||||
display_name=author.display_name))
|
||||
else:
|
||||
au = self.master_manager.get_object_filtered(Author,
|
||||
au = self.manager.get_object_filtered(Author,
|
||||
Author.display_name == u'Author Unknown')
|
||||
if au:
|
||||
new_song.authors.append(au)
|
||||
|
@ -185,7 +189,7 @@ class OpenLPSongImport(SongImport):
|
|||
new_song.authors.append(Author.populate(
|
||||
display_name=u'Author Unknown'))
|
||||
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)
|
||||
if existing_song_book:
|
||||
new_song.book = existing_song_book
|
||||
|
@ -194,7 +198,7 @@ class OpenLPSongImport(SongImport):
|
|||
publisher=song.book.publisher)
|
||||
if 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)
|
||||
if existing_topic:
|
||||
new_song.topics.append(existing_topic)
|
||||
|
@ -204,12 +208,16 @@ class OpenLPSongImport(SongImport):
|
|||
# if song.media_files:
|
||||
# for media_file in song.media_files:
|
||||
# existing_media_file = \
|
||||
# self.master_manager.get_object_filtered(MediaFile,
|
||||
# self.manager.get_object_filtered(MediaFile,
|
||||
# MediaFile.file_name == media_file.file_name)
|
||||
# if existing_media_file:
|
||||
# new_song.media_files.append(existing_media_file)
|
||||
# else:
|
||||
# new_song.media_files.append(MediaFile.populate(
|
||||
# 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()
|
||||
return True
|
||||
|
|
|
@ -28,6 +28,7 @@ import os
|
|||
|
||||
from PyQt4 import QtCore
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
from songimport import SongImport
|
||||
|
||||
if os.name == u'nt':
|
||||
|
@ -43,23 +44,32 @@ else:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
class OooImport(object):
|
||||
class OooImport(SongImport):
|
||||
"""
|
||||
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
|
||||
to SongImport for writing song to disk
|
||||
"""
|
||||
SongImport.__init__(self, master_manager)
|
||||
self.song = None
|
||||
self.manager = songmanager
|
||||
self.master_manager = master_manager
|
||||
self.document = None
|
||||
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()
|
||||
for filename in filenames:
|
||||
for filename in self.filenames:
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
filename = unicode(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.open_ooo_file(filename)
|
||||
|
@ -72,6 +82,12 @@ class OooImport(object):
|
|||
self.process_doc()
|
||||
self.close_ooo_file()
|
||||
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):
|
||||
"""
|
||||
|
@ -135,6 +151,9 @@ class OooImport(object):
|
|||
"com.sun.star.presentation.PresentationDocument") and not \
|
||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||
self.close_ooo_file()
|
||||
else:
|
||||
self.import_wizard.incrementProgressBar(
|
||||
u'Processing file ' + filepath, 0)
|
||||
except:
|
||||
pass
|
||||
return
|
||||
|
@ -161,6 +180,9 @@ class OooImport(object):
|
|||
slides = doc.getDrawPages()
|
||||
text = u''
|
||||
for slide_no in range(slides.getCount()):
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
slide = slides.getByIndex(slide_no)
|
||||
slidetext = u''
|
||||
for idx in range(slide.getCount()):
|
||||
|
|
|
@ -28,6 +28,7 @@ import logging
|
|||
import os
|
||||
from zipfile import ZipFile
|
||||
from lxml import objectify
|
||||
from lxml.etree import Error, LxmlError
|
||||
|
||||
from openlp.plugins.songs.lib.songimport import SongImport
|
||||
|
||||
|
@ -36,119 +37,163 @@ log = logging.getLogger(__name__)
|
|||
class OpenSongImportError(Exception):
|
||||
pass
|
||||
|
||||
class OpenSongImport(object):
|
||||
class OpenSongImport(SongImport):
|
||||
"""
|
||||
Import songs exported from OpenSong - the format is described loosly here:
|
||||
http://www.opensong.org/d/manual/song_file_format_specification
|
||||
Import songs exported from OpenSong
|
||||
|
||||
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:
|
||||
<lyrics>
|
||||
[v1]List of words
|
||||
Another Line
|
||||
Verses can be expressed in one of 2 ways, either in complete verses, or by
|
||||
line grouping, i.e. grouping all line 1's of a verse together, all line 2's
|
||||
of a verse together, and so on.
|
||||
|
||||
[v2]Some words for the 2nd verse
|
||||
etc...
|
||||
</lyrics>
|
||||
An example of complete verses::
|
||||
|
||||
The 'v' can be left out - it is implied
|
||||
or:
|
||||
<lyrics>
|
||||
[V]
|
||||
1List of words
|
||||
2Some words for the 2nd Verse
|
||||
<lyrics>
|
||||
[v1]
|
||||
List of words
|
||||
Another Line
|
||||
|
||||
1Another Line
|
||||
2etc...
|
||||
</lyrics>
|
||||
[v2]
|
||||
Some words for the 2nd verse
|
||||
etc...
|
||||
</lyrics>
|
||||
|
||||
Either or both forms can be used in one song. The Number does not
|
||||
necessarily appear at the start of the line
|
||||
The 'v' in the verse specifiers above can be left out, it is implied.
|
||||
|
||||
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:
|
||||
C - Chorus
|
||||
B - Bridge
|
||||
|
||||
Guitar chords can be provided 'above' the lyrics (the line is
|
||||
preceeded by a'.') and _s can be used to signify long-drawn-out
|
||||
words:
|
||||
C
|
||||
Chorus
|
||||
|
||||
. A7 Bm
|
||||
1 Some____ Words
|
||||
B
|
||||
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
|
||||
display order field. The Author and Copyright tags are also
|
||||
imported to the appropriate places.
|
||||
. A7 Bm
|
||||
1 Some____ Words
|
||||
|
||||
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
|
||||
is passed to SongImport for writing song to disk
|
||||
Initialise the class.
|
||||
"""
|
||||
self.songmanager = songmanager
|
||||
SongImport.__init__(self, manager)
|
||||
self.filenames = kwargs[u'filenames']
|
||||
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
|
||||
containing multiple opensong files If the commit parameter is
|
||||
set False, the import will not be committed to the database
|
||||
(useful for test scripts)
|
||||
Import either a single opensong file, or a zipfile containing multiple
|
||||
opensong files. If `self.commit` is set False, the import will not be
|
||||
committed to the database (useful for test scripts).
|
||||
"""
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext.lower() == ".zip":
|
||||
log.info('Zipfile found %s', filename)
|
||||
z = ZipFile(filename, u'r')
|
||||
for song in z.infolist():
|
||||
parts = os.path.split(song.filename)
|
||||
if parts[-1] == u'':
|
||||
#No final part => directory
|
||||
continue
|
||||
songfile = z.open(song)
|
||||
self.do_import_file(songfile)
|
||||
if commit:
|
||||
success = True
|
||||
self.import_wizard.importProgressBar.setMaximum(len(self.filenames))
|
||||
for filename in self.filenames:
|
||||
if self.stop_import_flag:
|
||||
success = False
|
||||
break
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext.lower() == u'.zip':
|
||||
log.debug(u'Zipfile found %s', filename)
|
||||
z = ZipFile(filename, u'r')
|
||||
self.import_wizard.importProgressBar.setMaximum(
|
||||
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()
|
||||
else:
|
||||
log.info('Direct import %s', filename)
|
||||
file = open(filename)
|
||||
self.do_import_file(file)
|
||||
if commit:
|
||||
self.finish()
|
||||
self.set_defaults()
|
||||
if not self.commit:
|
||||
self.finish()
|
||||
return success
|
||||
|
||||
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
Process the OpenSong file - pass in a file-like object,
|
||||
not a filename
|
||||
"""
|
||||
self.song_import = SongImport(self.songmanager)
|
||||
tree = objectify.parse(file)
|
||||
"""
|
||||
self.authors = []
|
||||
try:
|
||||
tree = objectify.parse(file)
|
||||
except Error, LxmlError:
|
||||
log.exception(u'Error parsing XML')
|
||||
return
|
||||
root = tree.getroot()
|
||||
fields = dir(root)
|
||||
decode = {u'copyright':self.song_import.add_copyright,
|
||||
u'ccli':u'ccli_number',
|
||||
u'author':self.song_import.parse_author,
|
||||
u'title':u'title',
|
||||
u'aka':u'alternate_title',
|
||||
u'hymn_number':u'song_number'}
|
||||
for (attr, fn_or_string) in decode.items():
|
||||
decode = {
|
||||
u'copyright': self.add_copyright,
|
||||
u'ccli': u'ccli_number',
|
||||
u'author': self.parse_author,
|
||||
u'title': u'title',
|
||||
u'aka': u'alternate_title',
|
||||
u'hymn_number': u'song_number'
|
||||
}
|
||||
for attr, fn_or_string in decode.items():
|
||||
if attr in fields:
|
||||
ustring = unicode(root.__getattr__(attr))
|
||||
if type(fn_or_string) == type(u''):
|
||||
self.song_import.__setattr__(fn_or_string, ustring)
|
||||
if isinstance(fn_or_string, basestring):
|
||||
setattr(self, fn_or_string, ustring)
|
||||
else:
|
||||
fn_or_string(ustring)
|
||||
if u'theme' in fields:
|
||||
self.song_import.topics.append(unicode(root.theme))
|
||||
if u'alttheme' in fields:
|
||||
self.song_import.topics.append(unicode(root.alttheme))
|
||||
if u'theme' in fields and unicode(root.theme) not in self.topics:
|
||||
self.topics.append(unicode(root.theme))
|
||||
if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
|
||||
self.topics.append(unicode(root.alttheme))
|
||||
# data storage while importing
|
||||
verses = {}
|
||||
lyrics = unicode(root.lyrics)
|
||||
|
@ -158,6 +203,7 @@ class OpenSongImport(object):
|
|||
# in the absence of any other indication, verses are the default,
|
||||
# erm, versetype!
|
||||
versetype = u'V'
|
||||
versenum = None
|
||||
for thisline in lyrics.split(u'\n'):
|
||||
# remove comments
|
||||
semicolon = thisline.find(u';')
|
||||
|
@ -170,7 +216,6 @@ class OpenSongImport(object):
|
|||
if thisline[0] == u'.' or thisline.startswith(u'---') \
|
||||
or thisline.startswith(u'-!!'):
|
||||
continue
|
||||
|
||||
# verse/chorus/etc. marker
|
||||
if thisline[0] == u'[':
|
||||
versetype = thisline[1].upper()
|
||||
|
@ -186,7 +231,6 @@ class OpenSongImport(object):
|
|||
versenum = u'1'
|
||||
continue
|
||||
words = None
|
||||
|
||||
# number at start of line.. it's verse number
|
||||
if thisline[0].isdigit():
|
||||
versenum = thisline[0]
|
||||
|
@ -207,7 +251,7 @@ class OpenSongImport(object):
|
|||
our_verse_order.append(versetag)
|
||||
if 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('_', '')
|
||||
verses[versetype][versenum].append(words)
|
||||
# done parsing
|
||||
|
@ -220,24 +264,23 @@ class OpenSongImport(object):
|
|||
for num in versenums:
|
||||
versetag = u'%s%s' % (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
|
||||
versetags[versetag] = 1
|
||||
# now figure out the presentation order
|
||||
order = []
|
||||
if u'presentation' in fields and root.presentation != u'':
|
||||
order = unicode(root.presentation)
|
||||
order = order.split()
|
||||
else:
|
||||
assert len(our_verse_order)>0
|
||||
order = our_verse_order
|
||||
if len(our_verse_order) > 0:
|
||||
order = our_verse_order
|
||||
else:
|
||||
log.warn(u'No verse order available for %s, skipping.', self.title)
|
||||
for tag in order:
|
||||
if len(tag) == 1:
|
||||
tag = tag + u'1' # Assume it's no.1 if it's not there
|
||||
if not versetags.has_key(tag):
|
||||
log.warn(u'Got order %s but not in versetags, skipping', tag)
|
||||
else:
|
||||
self.song_import.verse_order_list.append(tag)
|
||||
|
||||
def finish(self):
|
||||
""" Separate function, allows test suite to not pollute database"""
|
||||
self.song_import.finish()
|
||||
self.verse_order_list.append(tag)
|
||||
|
|
|
@ -68,19 +68,30 @@ class SofImport(OooImport):
|
|||
It attempts to detect italiced verses, and treats these as choruses in
|
||||
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
|
||||
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.open_ooo_file(filename)
|
||||
self.process_sof_file()
|
||||
self.close_ooo_file()
|
||||
for filename in self.filenames:
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
filename = unicode(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.open_ooo_file(filename)
|
||||
if self.document:
|
||||
self.process_sof_file()
|
||||
self.close_ooo_file()
|
||||
self.close_ooo()
|
||||
self.import_wizard.importProgressBar.setMaximum(1)
|
||||
self.import_wizard.incrementProgressBar(u'', 1)
|
||||
return True
|
||||
|
||||
def process_sof_file(self):
|
||||
"""
|
||||
|
@ -90,6 +101,9 @@ class SofImport(OooImport):
|
|||
self.new_song()
|
||||
paragraphs = self.document.getText().createEnumeration()
|
||||
while paragraphs.hasMoreElements():
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
paragraph = paragraphs.nextElement()
|
||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||
self.process_paragraph(paragraph)
|
||||
|
@ -244,6 +258,7 @@ class SofImport(OooImport):
|
|||
if title.endswith(u','):
|
||||
title = title[:-1]
|
||||
self.song.title = title
|
||||
self.import_wizard.incrementProgressBar(u'Processing song ' + title, 0)
|
||||
|
||||
def add_author(self, text):
|
||||
"""
|
||||
|
|
|
@ -24,14 +24,18 @@
|
|||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
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.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
|
||||
|
||||
class SongImport(object):
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class SongImport(QtCore.QObject):
|
||||
"""
|
||||
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
|
||||
as necessary
|
||||
"""
|
||||
|
||||
def __init__(self, manager):
|
||||
"""
|
||||
Initialise and create defaults for properties
|
||||
|
@ -48,6 +51,12 @@ class SongImport(object):
|
|||
database access is performed
|
||||
"""
|
||||
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.song_number = u''
|
||||
self.alternate_title = u''
|
||||
|
@ -57,17 +66,24 @@ class SongImport(object):
|
|||
self.ccli_number = u''
|
||||
self.authors = []
|
||||
self.topics = []
|
||||
self.media_files = []
|
||||
self.song_book_name = u''
|
||||
self.song_book_pub = u''
|
||||
self.verse_order_list = []
|
||||
self.verses = []
|
||||
self.versecount = 0
|
||||
self.choruscount = 0
|
||||
self.versecounts = {}
|
||||
self.copyright_string = unicode(translate(
|
||||
'SongsPlugin.SongImport', 'copyright'))
|
||||
self.copyright_symbol = unicode(translate(
|
||||
'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):
|
||||
self.import_wizard = import_wizard
|
||||
|
||||
|
@ -145,8 +161,7 @@ class SongImport(object):
|
|||
def parse_author(self, text):
|
||||
"""
|
||||
Add the author. OpenLP stores them individually so split by 'and', '&'
|
||||
and comma.
|
||||
However need to check for 'Mr and Mrs Smith' and turn it to
|
||||
and comma. However need to check for 'Mr and Mrs Smith' and turn it to
|
||||
'Mr Smith' and 'Mrs Smith'.
|
||||
"""
|
||||
for author in text.split(u','):
|
||||
|
@ -169,7 +184,15 @@ class SongImport(object):
|
|||
return
|
||||
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
|
||||
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():
|
||||
self.verse_order_list.append(oldversetag)
|
||||
return
|
||||
if versetag == u'V' or not versetag:
|
||||
self.versecount += 1
|
||||
versetag = u'V' + unicode(self.versecount)
|
||||
if versetag.startswith(u'C'):
|
||||
self.choruscount += 1
|
||||
if versetag == u'C':
|
||||
versetag += unicode(self.choruscount)
|
||||
if versetag[0] in self.versecounts:
|
||||
self.versecounts[versetag[0]] += 1
|
||||
else:
|
||||
self.versecounts[versetag[0]] = 1
|
||||
if len(versetag) == 1:
|
||||
versetag += unicode(self.versecounts[versetag[0]])
|
||||
elif int(versetag[1:]) > self.versecounts[versetag[0]]:
|
||||
self.versecounts[versetag[0]] = int(versetag[1:])
|
||||
self.verses.append([versetag, verse.rstrip()])
|
||||
self.verse_order_list.append(versetag)
|
||||
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
|
||||
"""
|
||||
if len(self.authors) == 0:
|
||||
if not self.authors:
|
||||
self.authors.append(u'Author unknown')
|
||||
self.commit_song()
|
||||
|
||||
|
@ -264,11 +288,16 @@ class SongImport(object):
|
|||
for authortext in self.authors:
|
||||
author = self.manager.get_object_filtered(Author,
|
||||
Author.display_name == authortext)
|
||||
if author is None:
|
||||
if not author:
|
||||
author = Author.populate(display_name = authortext,
|
||||
last_name=authortext.split(u' ')[-1],
|
||||
first_name=u' '.join(authortext.split(u' ')[:-1]))
|
||||
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:
|
||||
song_book = self.manager.get_object_filtered(Book,
|
||||
Book.name == self.song_book_name)
|
||||
|
@ -285,6 +314,7 @@ class SongImport(object):
|
|||
topic = Topic.populate(name=topictext)
|
||||
song.topics.append(topic)
|
||||
self.manager.save_object(song)
|
||||
self.set_defaults()
|
||||
|
||||
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>
|
||||
</qresource>
|
||||
<qresource prefix="graphics">
|
||||
<file>exception.png</file>
|
||||
<file>openlp-about-logo.png</file>
|
||||
<file>openlp-splash-screen.png</file>
|
||||
</qresource>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
--- openlp/core/resources.py.old Mon Jun 21 23:16:19 2010
|
||||
+++ openlp/core/resources.py Mon Jun 21 23:27:48 2010
|
||||
@@ -1,10 +1,31 @@
|
||||
@@ -1,10 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
|
@ -14,8 +14,9 @@
|
|||
+# --------------------------------------------------------------------------- #
|
||||
+# 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 #
|
||||
+# 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 #
|
||||
|
|
Loading…
Reference in New Issue