bugfixing and Head

This commit is contained in:
rimach 2011-08-29 21:59:12 +02:00
commit d7f4298fbb
41 changed files with 959 additions and 248 deletions

View File

@ -137,7 +137,7 @@ def image_to_byte(image):
# convert to base64 encoding so does not get missed!
return byte_array.toBase64()
def resize_image(image_path, width, height, background=QtCore.Qt.black):
def resize_image(image_path, width, height, background):
"""
Resize an image to fit on the current screen.

View File

@ -31,11 +31,13 @@ import logging
import os
from PyQt4 import QtCore
from sqlalchemy import create_engine, MetaData
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
from sqlalchemy.pool import NullPool
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.utils import AppLocation, delete_file
log = logging.getLogger(__name__)
@ -59,6 +61,64 @@ def init_db(url, auto_flush=True, auto_commit=False):
autocommit=auto_commit, bind=engine))
return session, metadata
def upgrade_db(url, upgrade):
"""
Upgrade a database.
``url``
The url of the database to upgrade.
``upgrade``
The python module that contains the upgrade instructions.
"""
session, metadata = init_db(url)
class Metadata(BaseModel):
"""
Provides a class for the metadata table.
"""
pass
load_changes = True
try:
tables = upgrade.upgrade_setup(metadata)
except SQLAlchemyError, DBAPIError:
load_changes = False
metadata_table = Table(u'metadata', metadata,
Column(u'key', types.Unicode(64), primary_key=True),
Column(u'value', types.UnicodeText(), default=None)
)
metadata_table.create(checkfirst=True)
mapper(Metadata, metadata_table)
version_meta = session.query(Metadata).get(u'version')
if version_meta is None:
version_meta = Metadata.populate(key=u'version', value=u'0')
version = 0
else:
version = int(version_meta.value)
if version > upgrade.__version__:
return version, upgrade.__version__
version += 1
if load_changes:
while hasattr(upgrade, u'upgrade_%d' % version):
log.debug(u'Running upgrade_%d', version)
try:
getattr(upgrade, u'upgrade_%d' % version) \
(session, metadata, tables)
version_meta.value = unicode(version)
except SQLAlchemyError, DBAPIError:
log.exception(u'Could not run database upgrade script '
'"upgrade_%s", upgrade process has been halted.', version)
break
version += 1
else:
version_meta = Metadata.populate(key=u'version',
value=int(upgrade.__version__))
session.add(version_meta)
session.commit()
return int(version_meta.value), upgrade.__version__
def delete_database(plugin_name, db_file_name=None):
"""
Remove a database file from the system.
@ -79,6 +139,7 @@ def delete_database(plugin_name, db_file_name=None):
AppLocation.get_section_data_path(plugin_name), plugin_name)
return delete_file(db_file_path)
class BaseModel(object):
"""
BaseModel provides a base object with a set of generic functions
@ -93,12 +154,12 @@ class BaseModel(object):
instance.__setattr__(key, value)
return instance
class Manager(object):
"""
Provide generic object persistence management
"""
def __init__(self, plugin_name, init_schema, db_file_name=None):
def __init__(self, plugin_name, init_schema, db_file_name=None,
upgrade_mod=None):
"""
Runs the initialisation process that includes creating the connection
to the database and the tables if they don't exist.
@ -109,6 +170,9 @@ class Manager(object):
``init_schema``
The init_schema function for this database
``upgrade_schema``
The upgrade_schema function for this database
``db_file_name``
The file name to use for this database. Defaults to None resulting
in the plugin_name being used.
@ -134,7 +198,27 @@ class Manager(object):
unicode(settings.value(u'db hostname').toString()),
unicode(settings.value(u'db database').toString()))
settings.endGroup()
if upgrade_mod:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
if db_ver > up_ver:
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'The database being '
'loaded was created in a more recent version of '
'OpenLP. The database is version %d, while OpenLP '
'expects version %d. The database will not be loaded.'
'\n\nDatabase: %s')) % \
(db_ver, up_ver, self.db_url)
)
return
try:
self.session = init_schema(self.db_url)
except:
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'OpenLP cannot load your '
'database.\n\nDatabase: %s')) % self.db_url
)
def save_object(self, object_instance, commit=True):
"""

View File

@ -36,6 +36,7 @@ log = logging.getLogger(__name__)
# FIXME: Add html5 doctype. However, do not break theme gradients.
HTMLSRC = u"""
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
@ -276,25 +277,25 @@ def build_html(item, screen, alert, islive, background, plugins=None, \
image_src = u'src="data:image/png;base64,%s"' % image
else:
image_src = u'style="display:none;"'
plugin_css = u''
plugin_js = u''
plugin_html = u''
css_additions = u''
js_additions = u''
html_additions = u''
if plugins:
for plugin in plugins:
plugin_css += plugin.getDisplayCss()
plugin_js += plugin.getDisplayJavascript()
plugin_html += plugin.getDisplayHtml()
css_additions += plugin.getDisplayCss()
js_additions += plugin.getDisplayJavascript()
html_additions += plugin.getDisplayHtml()
html = HTMLSRC % (build_background_css(item, width, height),
width, height,
plugin_css,
css_additions,
build_alert_css(alert, width),
build_footer_css(item, height),
build_lyrics_css(item, webkitvers),
u'true' if theme and theme.display_slide_transition and islive \
else u'false',
plugin_js,
js_additions,
bgimage_src, image_src,
plugin_html,
html_additions,
build_lyrics_html(item, webkitvers))
return html

View File

@ -36,7 +36,7 @@ import Queue
from PyQt4 import QtCore
from openlp.core.lib import resize_image, image_to_byte
from openlp.core.lib import resize_image, image_to_byte, Receiver
from openlp.core.ui import ScreenList
log = logging.getLogger(__name__)
@ -100,12 +100,14 @@ class Image(object):
variables ``image`` and ``image_bytes`` to ``None`` and add the image object
to the queue of images to process.
"""
def __init__(self, name='', path=''):
def __init__(self, name, path, source, background):
self.name = name
self.path = path
self.image = None
self.image_bytes = None
self.priority = Priority.Normal
self.source = source
self.background = background
class PriorityQueue(Queue.PriorityQueue):
@ -151,6 +153,8 @@ class ImageManager(QtCore.QObject):
self._cache = {}
self._imageThread = ImageThread(self)
self._conversion_queue = PriorityQueue()
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'config_updated'), self.process_updates)
def update_display(self):
"""
@ -162,12 +166,42 @@ class ImageManager(QtCore.QObject):
self.height = current_screen[u'size'].height()
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
self._conversion_queue = PriorityQueue()
for key, image in self._cache.iteritems():
image.priority = Priority.Normal
self._reset_image(image)
def update_images(self, image_type, background):
"""
Border has changed so update all the images affected.
"""
log.debug(u'update_images')
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
for key, image in self._cache.iteritems():
if image.source == image_type:
image.background = background
self._reset_image(image)
def update_image(self, name, image_type, background):
"""
Border has changed so update the image affected.
"""
log.debug(u'update_images')
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
for key, image in self._cache.iteritems():
if image.source == image_type and image.name == name:
image.background = background
self._reset_image(image)
def _reset_image(self, image):
image.image = None
image.image_bytes = None
self._conversion_queue.put((image.priority, image))
self._conversion_queue.modify_priority(image, Priority.Normal)
def process_updates(self):
"""
Flush the queue to updated any data to update
"""
# We want only one thread.
if not self._imageThread.isRunning():
self._imageThread.start()
@ -215,13 +249,13 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.remove(self._cache[name])
del self._cache[name]
def add_image(self, name, path):
def add_image(self, name, path, source, background):
"""
Add image to cache if it is not already there.
"""
log.debug(u'add_image %s:%s' % (name, path))
if not name in self._cache:
image = Image(name, path)
image = Image(name, path, source, background)
self._cache[name] = image
self._conversion_queue.put((image.priority, image))
else:
@ -247,7 +281,8 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[1]
# Generate the QImage for the image.
if image.image is None:
image.image = resize_image(image.path, self.width, self.height)
image.image = resize_image(image.path, self.width, self.height,
image.background)
# Set the priority to Lowest and stop here as we need to process
# more important images first.
if image.priority == Priority.Normal:

View File

@ -27,7 +27,7 @@
import logging
from PyQt4 import QtCore, QtWebKit
from PyQt4 import QtGui, QtCore, QtWebKit
from openlp.core.lib import ServiceItem, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
@ -169,7 +169,8 @@ class Renderer(object):
# if No file do not update cache
if self.theme_data.background_filename:
self.imageManager.add_image(self.theme_data.theme_name,
self.theme_data.background_filename)
self.theme_data.background_filename, u'theme',
QtGui.QColor(self.theme_data.background_border_color))
return self._rect, self._rect_footer
def generate_preview(self, theme_data, force_page=False):
@ -230,15 +231,56 @@ class Renderer(object):
# Clean up line endings.
lines = self._lines_split(text)
pages = self._paginate_slide(lines, line_end)
if len(pages) > 1:
# Songs and Custom
if item.is_capable(ItemCapabilities.AllowsVirtualSplit):
# Do not forget the line breaks!
slides = text.split(u'[---]')
if item.is_capable(ItemCapabilities.AllowsVirtualSplit) and \
len(pages) > 1 and u'[---]' in text:
pages = []
for slide in slides:
lines = slide.strip(u'\n').split(u'\n')
while True:
# Check if the first two potential virtual slides will fit
# (as a whole) on one slide.
html_text = expand_tags(
u'\n'.join(text.split(u'\n[---]\n', 2)[:-1]))
html_text = html_text.replace(u'\n', u'<br>')
if self._text_fits_on_slide(html_text):
# The first two virtual slides fit (as a whole) on one
# slide. Replace the occurrences of [---].
text = text.replace(u'\n[---]', u'', 2)
else:
# The first two virtual slides did not fit as a whole.
# Check if the first virtual slide will fit.
html_text = expand_tags(text.split(u'\n[---]\n', 1)[1])
html_text = html_text.replace(u'\n', u'<br>')
if self._text_fits_on_slide(html_text):
# The first virtual slide fits, so remove it.
text = text.replace(u'\n[---]', u'', 1)
else:
# The first virtual slide does not fit, which means
# we have to render the first virtual slide.
text_contains_break = u'[---]' in text
if text_contains_break:
html_text, text = text.split(u'\n[---]\n', 1)
else:
html_text = text
text = u''
lines = expand_tags(html_text)
lines = lines.strip(u'\n').split(u'\n')
slides = self._paginate_slide(lines, line_end)
if len(slides) > 1 and text:
# Add all slides apart from the last one the
# list.
pages.extend(slides[:-1])
if text_contains_break:
text = slides[-1] + u'\n[---]\n' + text
else:
text = slides[-1] + u'\n'+ text
text = text.replace(u'<br>', u'\n')
else:
pages.extend(slides)
if u'[---]' not in text:
lines = expand_tags(text)
lines = lines.strip(u'\n').split(u'\n')
pages.extend(self._paginate_slide(lines, line_end))
break
new_pages = []
for page in pages:
while page.endswith(u'<br>'):
@ -344,7 +386,7 @@ class Renderer(object):
separator = u'<br>'
html_lines = map(expand_tags, lines)
# Text too long so go to next page.
if self._text_fits_on_slide(separator.join(html_lines)):
if not self._text_fits_on_slide(separator.join(html_lines)):
html_text, previous_raw = self._binary_chop(formatted,
previous_html, previous_raw, html_lines, lines, separator, u'')
else:
@ -377,18 +419,18 @@ class Renderer(object):
line = line.strip()
html_line = expand_tags(line)
# Text too long so go to next page.
if self._text_fits_on_slide(previous_html + html_line):
if not self._text_fits_on_slide(previous_html + html_line):
# Check if there was a verse before the current one and append
# it, when it fits on the page.
if previous_html:
if not self._text_fits_on_slide(previous_html):
if self._text_fits_on_slide(previous_html):
formatted.append(previous_raw)
previous_html = u''
previous_raw = u''
# Now check if the current verse will fit, if it does
# not we have to start to process the verse word by
# word.
if not self._text_fits_on_slide(html_line):
if self._text_fits_on_slide(html_line):
previous_html = html_line + line_end
previous_raw = line + line_end
continue
@ -445,7 +487,7 @@ class Renderer(object):
highest_index = len(html_list) - 1
index = int(highest_index / 2)
while True:
if self._text_fits_on_slide(
if not self._text_fits_on_slide(
previous_html + separator.join(html_list[:index + 1]).strip()):
# We know that it does not fit, so change/calculate the
# new index and highest_index accordingly.
@ -468,7 +510,7 @@ class Renderer(object):
else:
continue
# Check if the remaining elements fit on the slide.
if not self._text_fits_on_slide(
if self._text_fits_on_slide(
separator.join(html_list[index + 1:]).strip()):
previous_html = separator.join(
html_list[index + 1:]).strip() + line_end
@ -491,11 +533,11 @@ class Renderer(object):
returned, otherwise ``False``.
``text``
The text to check. It can contain HTML tags.
The text to check. It may contain HTML tags.
"""
self.web_frame.evaluateJavaScript(u'show_text("%s")' %
text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
return self.web_frame.contentsSize().height() > self.page_height
return self.web_frame.contentsSize().height() <= self.page_height
def _words_split(self, line):
"""

View File

@ -115,6 +115,7 @@ class ServiceItem(object):
self.end_time = 0
self.media_length = 0
self.from_service = False
self.image_border = u'#000000'
self._new_item()
def _new_item(self):
@ -195,7 +196,7 @@ class ServiceItem(object):
self.foot_text = \
u'<br>'.join([footer for footer in self.raw_footer if footer])
def add_from_image(self, path, title):
def add_from_image(self, path, title, background=None):
"""
Add an image slide to the service item.
@ -205,9 +206,12 @@ class ServiceItem(object):
``title``
A title for the slide in the service item.
"""
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path})
self.renderer.imageManager.add_image(title, path)
self.renderer.imageManager.add_image(title, path, u'image',
self.image_border)
self._new_item()
def add_from_text(self, title, raw_slide, verse_tag=None):

View File

@ -44,6 +44,7 @@ BLANK_THEME_XML = \
<name> </name>
<background type="image">
<filename></filename>
<borderColor>#000000</borderColor>
</background>
<background type="gradient">
<startColor>#000000</startColor>
@ -282,7 +283,7 @@ class ThemeXML(object):
# Create direction element
self.child_element(background, u'direction', unicode(direction))
def add_background_image(self, filename):
def add_background_image(self, filename, borderColor):
"""
Add a image background.
@ -294,6 +295,8 @@ class ThemeXML(object):
self.theme.appendChild(background)
# Create Filename element
self.child_element(background, u'filename', filename)
# Create endColor element
self.child_element(background, u'borderColor', unicode(borderColor))
def add_font(self, name, color, size, override, fonttype=u'main',
bold=u'False', italics=u'False', line_adjustment=0,
@ -597,7 +600,7 @@ class ThemeXML(object):
self.background_direction)
else:
filename = os.path.split(self.background_filename)[1]
self.add_background_image(filename)
self.add_background_image(filename, self.background_border_color)
self.add_font(self.font_main_name,
self.font_main_color,
self.font_main_size,

View File

@ -116,7 +116,7 @@ class Ui_AboutDialog(object):
u'Scott "sguerrieri" Guerrieri',
u'Matthias "matthub" Hub', u'Meinert "m2j" Jordan',
u'Armin "orangeshirt" K\xf6hler', u'Joshua "milleja46" Miller',
u'Stevan "StevanP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru',
u'Stevan "ElderP" Pettit', u'Mattias "mahfiaz" P\xf5ldaru',
u'Christian "crichter" Richter', u'Philip "Phill" Ridout',
u'Simon "samscudder" Scudder', u'Jeffrey "whydoubt" Smith',
u'Maikel Stuivenberg', u'Frode "frodus" Woldsund']
@ -125,7 +125,7 @@ class Ui_AboutDialog(object):
packagers = ['Thomas "tabthorpe" Abthorpe (FreeBSD)',
u'Tim "TRB143" Bentley (Fedora)',
u'Matthias "matthub" Hub (Mac OS X)',
u'Stevan "StevanP" Pettit (Windows)',
u'Stevan "ElderP" Pettit (Windows)',
u'Raoul "superfly" Snyman (Ubuntu)']
translators = {
u'af': [u'Johan "nuvolari" Mynhardt'],

View File

@ -67,6 +67,7 @@ class Display(QtGui.QGraphicsView):
self.width(), self.height())
self.webView.settings().setAttribute(
QtWebKit.QWebSettings.PluginsEnabled, True)
self.webView.settings().setAttribute(7, True)
self.page = self.webView.page()
self.frame = self.page.mainFrame()
self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
@ -129,6 +130,7 @@ class MainDisplay(Display):
self.screen[u'size'].width(), self.screen[u'size'].height())
self.webView.settings().setAttribute( \
QtWebKit.QWebSettings.PluginsEnabled, True)
self.webView.settings().setAttribute(7, True)
self.page = self.webView.page()
self.frame = self.page.mainFrame()
QtCore.QObject.connect(self.webView,
@ -231,11 +233,11 @@ class MainDisplay(Display):
shrinkItem.setVisible(False)
self.setGeometry(self.screen[u'size'])
def directImage(self, name, path):
def directImage(self, name, path, background):
"""
API for replacement backgrounds so Images are added directly to cache
"""
self.imageManager.add_image(name, path)
self.imageManager.add_image(name, path, u'image', background)
if hasattr(self, u'serviceItem'):
self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name
@ -253,7 +255,7 @@ class MainDisplay(Display):
"""
log.debug(u'image to display')
image = self.imageManager.get_image_bytes(name)
self.resetVideo()
self.controller.mediaManager.video_reset(self.controller)
self.displayImage(image)
return self.preview()

View File

@ -30,11 +30,13 @@ import os
import sys
import shutil
from tempfile import gettempdir
from datetime import datetime
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \
PluginManager, Receiver, translate, ImageManager, PluginStatus
PluginManager, Receiver, translate, ImageManager, PluginStatus, \
SettingsManager
from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \
icon_action, shortcut_action
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
@ -285,6 +287,10 @@ class Ui_MainWindow(object):
self.settingsConfigureItem = icon_action(mainWindow,
u'settingsConfigureItem', u':/system/system_settings.png',
category=UiStrings().Settings)
self.settingsImportItem = base_action(mainWindow,
u'settingsImportItem', category=UiStrings().Settings)
self.settingsExportItem = base_action(mainWindow,
u'settingsExportItem', category=UiStrings().Settings)
action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu)
self.aboutItem = shortcut_action(mainWindow, u'aboutItem',
[QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked,
@ -302,10 +308,10 @@ class Ui_MainWindow(object):
u':/system/system_online_help.png', category=UiStrings().Help)
self.webSiteItem = base_action(
mainWindow, u'webSiteItem', category=UiStrings().Help)
add_actions(self.fileImportMenu,
(self.importThemeItem, self.importLanguageItem))
add_actions(self.fileExportMenu,
(self.exportThemeItem, self.exportLanguageItem))
add_actions(self.fileImportMenu, (self.settingsImportItem, None,
self.importThemeItem, self.importLanguageItem))
add_actions(self.fileExportMenu, (self.settingsExportItem, None,
self.exportThemeItem, self.exportLanguageItem))
add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem,
self.fileSaveItem, self.fileSaveAsItem,
self.recentFilesMenu.menuAction(), None,
@ -358,6 +364,7 @@ class Ui_MainWindow(object):
self.importLanguageItem.setVisible(False)
self.exportLanguageItem.setVisible(False)
self.setLockPanel(panelLocked)
self.settingsImported = False
def retranslateUi(self, mainWindow):
"""
@ -421,6 +428,15 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Configure &Formatting Tags...'))
self.settingsConfigureItem.setText(
translate('OpenLP.MainWindow', '&Configure OpenLP...'))
self.settingsExportItem.setStatusTip(translate('OpenLP.MainWindow',
'Export OpenLP settings to a specified *.config file'))
self.settingsExportItem.setText(
translate('OpenLP.MainWindow', 'Settings'))
self.settingsImportItem.setStatusTip(translate('OpenLP.MainWindow',
'Import OpenLP settings from a specified *.config file previously '
'exported on this or another machine'))
self.settingsImportItem.setText(
translate('OpenLP.MainWindow', 'Settings'))
self.viewMediaManagerItem.setText(
translate('OpenLP.MainWindow', '&Media Manager'))
self.viewMediaManagerItem.setToolTip(
@ -524,8 +540,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# (not for use by plugins)
self.uiSettingsSection = u'user interface'
self.generalSettingsSection = u'general'
self.serviceSettingsSection = u'servicemanager'
self.advancedlSettingsSection = u'advanced'
self.servicemanagerSettingsSection = u'servicemanager'
self.songsSettingsSection = u'songs'
self.themesSettingsSection = u'themes'
self.displayTagsSection = u'displayTags'
self.headerSection = u'SettingsImport'
self.serviceNotSaved = False
self.aboutForm = AboutForm(self)
self.settingsForm = SettingsForm(self, self)
@ -575,6 +595,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked)
QtCore.QObject.connect(self.settingsShortcutsItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsShortcutsItemClicked)
QtCore.QObject.connect(self.settingsImportItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsImportItemClicked)
QtCore.QObject.connect(self.settingsExportItem,
QtCore.SIGNAL(u'triggered()'), self.onSettingsExportItemClicked)
# i18n set signals for languages
self.languageGroup.triggered.connect(LanguageManager.set_language)
QtCore.QObject.connect(self.modeDefaultItem,
@ -771,6 +795,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.themeManagerContents.loadThemes(True)
Receiver.send_message(u'theme_update_global',
self.themeManagerContents.global_theme)
# Check if any Bibles downloaded. If there are, they will be
# processed.
Receiver.send_message(u'bibles_load_list', True)
def blankCheck(self):
"""
@ -872,6 +899,172 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if self.shortcutForm.exec_():
self.shortcutForm.save()
def onSettingsImportItemClicked(self):
"""
Import settings from an export INI file
"""
answer = QtGui.QMessageBox.critical(self,
translate('OpenLP.MainWindow', 'Import settings?'),
translate('OpenLP.MainWindow',
'Are you sure you want to import settings?\n\n'
'Importing settings will make permanent changes to your current '
'OpenLP configuration.\n\n'
'Importing incorrect settings may cause erratic behaviour or '
'OpenLP to terminate abnormally.'),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if answer == QtGui.QMessageBox.No:
return
importFileName = unicode(QtGui.QFileDialog.getOpenFileName(self,
translate('OpenLP.MainWindow', 'Open File'),
'',
translate('OpenLP.MainWindow',
'OpenLP Export Settings Files (*.conf)')))
if not importFileName:
return
settingSections = []
# Add main sections.
settingSections.extend([self.generalSettingsSection])
settingSections.extend([self.advancedlSettingsSection])
settingSections.extend([self.uiSettingsSection])
settingSections.extend([self.servicemanagerSettingsSection])
settingSections.extend([self.themesSettingsSection])
settingSections.extend([self.displayTagsSection])
settingSections.extend([self.headerSection])
# Add plugin sections.
for plugin in self.pluginManager.plugins:
settingSections.extend([plugin.name])
settings = QtCore.QSettings()
importSettings = QtCore.QSettings(importFileName,
QtCore.QSettings.IniFormat)
importKeys = importSettings.allKeys()
for sectionKey in importKeys:
# We need to handle the really bad files.
try:
section, key = sectionKey.split(u'/')
except ValueError:
section = u'unknown'
key = u''
# Switch General back to lowercase.
if section == u'General':
section = u'general'
sectionKey = section + "/" + key
# Make sure it's a valid section for us.
if not section in settingSections:
QtGui.QMessageBox.critical(self,
translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow',
'The file you selected does appear to be a valid OpenLP '
'settings file.\n\n'
'Section [%s] is not valid \n\n'
'Processing has terminated and no changed have been made.'
% section),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Ok))
return
# We have a good file, import it.
for sectionKey in importKeys:
value = importSettings.value(sectionKey)
settings.setValue(u'%s' % (sectionKey) ,
QtCore.QVariant(value))
now = datetime.now()
settings.beginGroup(self.headerSection)
settings.setValue( u'file_imported' , QtCore.QVariant(importFileName))
settings.setValue(u'file_date_imported',
now.strftime("%Y-%m-%d %H:%M"))
settings.endGroup()
settings.sync()
# We must do an immediate restart or current configuration will
# overwrite what was just imported when application terminates
# normally. We need to exit without saving configuration.
QtGui.QMessageBox.information(self,
translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow',
'OpenLP will now close. Imported settings will '
'be applied the next time you start OpenLP.'),
QtGui.QMessageBox.StandardButtons(
QtGui.QMessageBox.Ok))
self.settingsImported = True
self.cleanUp()
QtCore.QCoreApplication.exit()
def onSettingsExportItemClicked(self, exportFileName=None):
"""
Export settings to an INI file
"""
if not exportFileName:
exportFileName = unicode(QtGui.QFileDialog.getSaveFileName(self,
translate('OpenLP.MainWindow', 'Export Settings File'), '',
translate('OpenLP.MainWindow',
'OpenLP Export Settings File (*.conf)')))
if not exportFileName:
return
# Make sure it's an .ini file.
if not exportFileName.endswith(u'conf'):
exportFileName = exportFileName + u'.conf'
temp_file = os.path.join(unicode(gettempdir()),
u'openlp', u'exportIni.tmp')
self.saveSettings()
settingSections = []
# Add main sections.
settingSections.extend([self.generalSettingsSection])
settingSections.extend([self.advancedlSettingsSection])
settingSections.extend([self.uiSettingsSection])
settingSections.extend([self.servicemanagerSettingsSection])
settingSections.extend([self.themesSettingsSection])
settingSections.extend([self.displayTagsSection])
# Add plugin sections.
for plugin in self.pluginManager.plugins:
settingSections.extend([plugin.name])
# Delete old files if found.
if os.path.exists(temp_file):
os.remove(temp_file)
if os.path.exists(exportFileName):
os.remove(exportFileName)
settings = QtCore.QSettings()
settings.remove(self.headerSection)
# Get the settings.
keys = settings.allKeys()
exportSettings = QtCore.QSettings(temp_file,
QtCore.QSettings.IniFormat)
# Add a header section.
# This is to insure it's our ini file for import.
now = datetime.now()
applicationVersion = get_application_version()
# Write INI format using Qsettings.
# Write our header.
exportSettings.beginGroup(self.headerSection)
exportSettings.setValue(u'Make_Changes', u'At_Own_RISK')
exportSettings.setValue(u'type', u'OpenLP_settings_export')
exportSettings.setValue(u'file_date_created',
now.strftime("%Y-%m-%d %H:%M"))
exportSettings.setValue(u'version', applicationVersion[u'full'])
exportSettings.endGroup()
# Write all the sections and keys.
for sectionKey in keys:
section, key = sectionKey.split(u'/')
keyValue = settings.value(sectionKey)
sectionKey = section + u"/" + key
# Change the service section to servicemanager.
if section == u'service':
sectionKey = u'servicemanager/' + key
exportSettings.setValue(sectionKey, keyValue)
exportSettings.sync()
# Temp INI file has been written. Blanks in keys are now '%20'.
# Read the temp file and output the user's INI file with blanks to
# make it more readable.
tempIni = open(temp_file, u'r')
exportIni = open(exportFileName, u'w')
for fileRecord in tempIni:
fileRecord = fileRecord.replace(u'%20', u' ')
exportIni.write(fileRecord)
tempIni.close()
exportIni.close()
os.remove(temp_file)
return
def onModeDefaultItemClicked(self):
"""
Put OpenLP into "Default" view mode.
@ -924,6 +1117,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
Hook to close the main window and display windows on exit
"""
# If we just did a settings import, close without saving changes.
if self.settingsImported:
event.accept()
if self.serviceManagerContents.isModified():
ret = self.serviceManagerContents.saveModifiedService()
if ret == QtGui.QMessageBox.Save:
@ -1121,6 +1317,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
Save the main window settings.
"""
# Exit if we just did a settings import.
if self.settingsImported:
return
log.debug(u'Saving QSettings')
settings = QtCore.QSettings()
settings.beginGroup(self.generalSettingsSection)

View File

@ -39,10 +39,12 @@ class MediaState(object):
class MediaType(object):
"""
"""
Audio = 0
Video = 1
Unused = 0
Audio = 1
Video = 2
Cd = 3
Dvd = 4
Folder = 5
class MediaInfo(object):
"""
@ -50,7 +52,7 @@ class MediaInfo(object):
"""
file_info = None
volume = 100
isFlash = False
is_flash = False
is_background = False
length = 0
start_time = 0

View File

@ -49,7 +49,6 @@ class MediaAPI(object):
"""
return False
def setup(self, display):
"""
Create the related widgets for the current display
@ -118,21 +117,19 @@ class MediaAPI(object):
"""
pass
def getDisplayCss(self):
def get_media_display_css(self):
"""
Add css style sheets to htmlbuilder
"""
return u''
def getDisplayJavascript(self):
def get_media_display_javascript(self):
"""
Add javascript functions to htmlbuilder
"""
return u''
def getDisplayHtml(self):
def get_media_display_html(self):
"""
Add html code to htmlbuilder
"""

View File

@ -32,7 +32,7 @@ from PyQt4 import QtCore, QtGui, QtWebKit
from openlp.core.lib import OpenLPToolbar, Receiver, translate
from openlp.core.lib.ui import UiStrings, critical_error_message_box
from openlp.core.ui.media import MediaAPI, MediaState, MediaInfo
from openlp.core.ui.media import MediaAPI, MediaState, MediaInfo, MediaType
from openlp.core.utils import AppLocation
log = logging.getLogger(__name__)
@ -61,12 +61,12 @@ class MediaManager(object):
self.APIs = {}
self.controller = []
self.curDisplayMediaAPI = {}
#Timer for video state
# Timer for video state
self.Timer = QtCore.QTimer()
self.Timer.setInterval(200)
self.withLivePreview = False
self.checkPreConditions()
#Signals
self.check_available_media_apis()
# Signals
QtCore.QObject.connect(self.Timer,
QtCore.SIGNAL("timeout()"), self.video_state)
QtCore.QObject.connect(Receiver.get_receiver(),
@ -86,7 +86,7 @@ class MediaManager(object):
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'media_unblank'), self.video_unblank)
def registerControllers(self, controller):
def register_controllers(self, controller):
"""
Register each media API controller (Webkit, Phonon, etc) and
store for later use
@ -94,12 +94,12 @@ class MediaManager(object):
if controller.check_available():
self.APIs[controller.name] = controller
def checkPreConditions(self):
def check_available_media_apis(self):
"""
Check to see if we have any media API's available
If Not do not install the plugin.
"""
log.debug(u'checkPreConditions')
log.debug(u'check_available_media_apis')
controller_dir = os.path.join(
AppLocation.get_directory(AppLocation.AppDir),
u'core', u'ui', u'media')
@ -119,7 +119,7 @@ class MediaManager(object):
controller_classes = MediaAPI.__subclasses__()
for controller_class in controller_classes:
controller = controller_class(self)
self.registerControllers(controller)
self.register_controllers(controller)
if self.APIs:
return True
else:
@ -143,34 +143,34 @@ class MediaManager(object):
if not isAnyonePlaying:
self.Timer.stop()
def getDisplayCss(self):
def get_media_display_css(self):
"""
Add css style sheets to htmlbuilder
"""
css = u'';
for api in self.APIs.values():
css += api.getDisplayCss()
css += api.get_media_display_css()
return css
def getDisplayJavascript(self):
def get_media_display_javascript(self):
"""
Add javascript functions to htmlbuilder
"""
js = u''
for api in self.APIs.values():
js += api.getDisplayJavascript()
js += api.get_media_display_javascript()
return js
def getDisplayHtml(self):
def get_media_display_html(self):
"""
Add html code to htmlbuilder
"""
html = u''
for api in self.APIs.values():
html += api.getDisplayHtml()
html += api.get_media_display_html()
return html
def addControllerItems(self, controller, control_panel):
def add_controller_items(self, controller, control_panel):
self.controller.append(controller)
self.setup_generic_controls(controller, control_panel)
for api in self.APIs.values():
@ -210,7 +210,7 @@ class MediaManager(object):
controller.mediabar.addToolbarWidget(u'Audio Volume', controller.volumeSlider)
control_panel.addWidget(controller.mediabar)
controller.mediabar.setVisible(False)
#Signals
# Signals
QtCore.QObject.connect(controller.seekSlider,
QtCore.SIGNAL(u'sliderMoved(int)'), controller.sendToPlugins)
QtCore.QObject.connect(controller.volumeSlider,
@ -277,20 +277,26 @@ class MediaManager(object):
display = controller.previewDisplay
isValid = self.check_file_type(controller, display)
if not isValid:
#Media could not be loaded correctly
# Media could not be loaded correctly
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Unsupported File'),
unicode(translate('MediaPlugin.MediaItem',
'Unsupported File')))
return False
#now start playing
self.video_play([controller])
# now start playing
if self.video_play([controller]):
self.video_pause([controller])
self.video_seek([controller, [0]])
self.video_play([controller])
if self.video_play([controller]):
self.set_controls_visible(controller, True)
log.debug(u'use %s controller' % self.curDisplayMediaAPI[display])
return True
else:
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Unsupported File'),
unicode(translate('MediaPlugin.MediaItem',
'Unsupported File')))
return False
def check_file_type(self, controller, display):
"""
@ -310,6 +316,12 @@ class MediaManager(object):
self.resize(controller, display, api)
if api.load(display):
self.curDisplayMediaAPI[display] = api
controller.media_info.media_type = MediaType.Video
return True
if suffix in api.audio_extensions_list:
if api.load(display):
self.curDisplayMediaAPI[display] = api
controller.media_info.media_type = MediaType.Audio
return True
# no valid api found
return False
@ -325,17 +337,19 @@ class MediaManager(object):
if controller.isLive:
if controller.hideMenu.defaultAction().isChecked():
controller.hideMenu.defaultAction().trigger()
#Receiver.send_message(u'maindisplay_show')
self.curDisplayMediaAPI[display].play(display)
if not self.curDisplayMediaAPI[display].play(display):
return False
self.curDisplayMediaAPI[display].set_visible(display, True)
# Start Timer for ui updates
if not self.Timer.isActive():
self.Timer.start()
return True
def video_pause(self, msg):
"""
Responds to the request to pause a loaded video
"""
log.debug(u'videoPause')
log.debug(u'video_pause')
controller = msg[0]
for display in self.curDisplayMediaAPI.keys():
if display.controller == controller:
@ -383,6 +397,7 @@ class MediaManager(object):
if display.controller == controller:
display.override = {}
self.curDisplayMediaAPI[display].reset(display)
self.curDisplayMediaAPI[display].set_visible(display, False)
del self.curDisplayMediaAPI[display]
self.set_controls_visible(controller, False)
@ -428,7 +443,7 @@ class MediaManager(object):
if display.controller == controller:
if self.curDisplayMediaAPI[display] \
.state == MediaState.Paused:
self.curDisplayMediaAPI[display].play(display)
if self.curDisplayMediaAPI[display].play(display):
self.curDisplayMediaAPI[display] \
.set_visible(display, True)

View File

@ -76,7 +76,6 @@ class PhononAPI(MediaAPI):
ext = u'*%s' % extension
if ext not in list:
list.append(ext)
#self.parent.parent.serviceManagerContents.supportedSuffixes(extension[1:])
log.info(u'MediaPlugin: %s extensions: %s' % (mimetype,
u' '.join(extensions)))
# Add extensions for this mimetype from self.additional_extensions.
@ -87,7 +86,6 @@ class PhononAPI(MediaAPI):
ext = u'*%s' % extension
if ext not in list:
list.append(ext)
#self.parent.parent.serviceManagerContents.supportedSuffixes(extension[1:])
log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype,
u' '.join(self.additional_extensions[mimetype])))
@ -133,19 +131,21 @@ class PhononAPI(MediaAPI):
volume = controller.media_info.volume
path = controller.media_info.file_info.absoluteFilePath()
display.mediaObject.setCurrentSource(Phonon.MediaSource(path))
if not self.mediaStateWait(display, Phonon.StoppedState):
if not self.media_state_wait(display, Phonon.StoppedState):
return False
self.volume(display, volume)
return True
def mediaStateWait(self, display, mediaState):
def media_state_wait(self, display, mediaState):
"""
Wait for the video to change its state
Wait no longer than 5 seconds.
"""
start = datetime.now()
while display.mediaObject.state() != mediaState:
if display.mediaObject.state() == Phonon.ErrorState:
current_state = display.mediaObject.state()
while current_state != mediaState:
current_state = display.mediaObject.state()
if current_state == Phonon.ErrorState:
return False
Receiver.send_message(u'openlp_process_events')
if (datetime.now() - start).seconds > 5:
@ -162,7 +162,7 @@ class PhononAPI(MediaAPI):
controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
display.mediaObject.play()
if self.mediaStateWait(display, Phonon.PlayingState):
if self.media_state_wait(display, Phonon.PlayingState):
if start_time > 0:
self.seek(display, controller.media_info.start_time*1000)
self.volume(display, controller.media_info.volume)
@ -170,10 +170,14 @@ class PhononAPI(MediaAPI):
int(display.mediaObject.totalTime()/1000)
controller.seekSlider.setMaximum(controller.media_info.length*1000)
self.state = MediaState.Playing
self.set_visible(display, True)
#self.set_visible(display, True)
return True
else:
return False
def pause(self, display):
display.mediaObject.pause()
if self.media_state_wait(display, Phonon.PausedState):
self.state = MediaState.Paused
def stop(self, display):

View File

@ -44,6 +44,10 @@ class WebkitAPI(MediaAPI):
MediaAPI.__init__(self, parent, u'Webkit')
self.parent = parent
self.canBackground = True
self.audio_extensions_list = [
u'*.mp3'
, u'*.ogg'
]
self.video_extensions_list = [
u'*.3gp'
, u'*.3gpp'
@ -62,8 +66,6 @@ class WebkitAPI(MediaAPI):
, u'*.m4v'
, u'*.mkv'
, u'*.mp4'
, u'*.mp3'
, u'*.ogg'
, u'*.ogv'
, u'*.webm'
, u'*.swf', u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
@ -73,7 +75,7 @@ class WebkitAPI(MediaAPI):
# no special controls
pass
def getDisplayCss(self):
def get_media_display_css(self):
"""
Add css style sheets to htmlbuilder
"""
@ -91,7 +93,7 @@ class WebkitAPI(MediaAPI):
return css
def getDisplayJavascript(self):
def get_media_display_javascript(self):
"""
Add javascript functions to htmlbuilder
"""
@ -99,7 +101,7 @@ class WebkitAPI(MediaAPI):
var video_timer = null;
var current_video = '1';
function show_video(state, path, volume, loop, seekVal){
function show_video(state, path, volume, loop, varVal){
// Note, the preferred method for looping would be to use the
// video tag loop attribute.
// But QtWebKit doesn't support this. Neither does it support the
@ -139,7 +141,7 @@ class WebkitAPI(MediaAPI):
break;
case 'play':
vid.play();
vid.style.visibility = 'visible';
//vid.style.visibility = 'visible';
if(vid.looping){
video_timer = setInterval(
function() {
@ -156,7 +158,6 @@ class WebkitAPI(MediaAPI):
break;
case 'stop':
show_video('pause');
vid.style.visibility = 'hidden';
break;
case 'poll':
if(vid.ended||vid.currentTime+0.2>vid.duration)
@ -182,7 +183,10 @@ class WebkitAPI(MediaAPI):
return vid.currentTime;
case 'seek':
// doesnt work currently
//vid.currentTime = seekVal;
//vid.currentTime = varVal;
break;
case 'setVisible':
vid.style.visibility = varVal;
break;
}
}
@ -197,8 +201,7 @@ class WebkitAPI(MediaAPI):
return document.embeds[movieName];
}
// http://www.adobe.com/support/flash/publishexport/scriptingwithflash/scriptingwithflash_03.html
function show_flash(state, path, volume, seekVal){
function show_flash(state, path, volume, varVal){
var text = document.getElementById('flash');
var flashMovie = getFlashMovieObject("OpenLPFlashMovie");
var src = "src = 'file:///" + path + "'";
@ -214,27 +217,22 @@ class WebkitAPI(MediaAPI):
case 'load':
text.innerHTML = "<embed " + src + view_parm + swf_parm + "/>";
flashMovie = getFlashMovieObject("OpenLPFlashMovie");
text.style.visibility = 'visible';
flashMovie.Play();
break;
case 'play':
text.style.visibility = 'visible';
flashMovie.Play();
break;
case 'pause':
flashMovie.StopPlay();
text.style.visibility = 'hidden';
break;
case 'stop':
flashMovie.StopPlay();
text.style.visibility = 'hidden';
tempHtml = text.innerHTML;
text.innerHTML = '';
text.innerHTML = tempHtml;
break;
case 'close':
flashMovie.StopPlay();
text.style.visibility = 'hidden';
text.innerHTML = '';
break;
case 'length':
@ -242,7 +240,10 @@ class WebkitAPI(MediaAPI):
case 'currentTime':
return flashMovie.CurrentFrame();
case 'seek':
// flashMovie.GotoFrame(seekVal);
// flashMovie.GotoFrame(varVal);
break;
case 'setVisible':
text.style.visibility = varVal;
break;
}
}
@ -250,7 +251,7 @@ class WebkitAPI(MediaAPI):
return js
def getDisplayHtml(self):
def get_media_display_html(self):
"""
Add html code to htmlbuilder
"""
@ -267,7 +268,6 @@ class WebkitAPI(MediaAPI):
display.webView.resize(display.size())
display.webView.raise_()
self.hasOwnWidget = False
#display.webView.hide()
def check_available(self):
return True
@ -287,7 +287,7 @@ class WebkitAPI(MediaAPI):
loop = u'false'
display.webView.setVisible(True)
if controller.media_info.file_info.suffix() == u'swf':
controller.media_info.isFlash = True
controller.media_info.is_flash = True
js = u'show_flash("load","%s");' % \
(path.replace(u'\\', u'\\\\'))
else:
@ -305,17 +305,18 @@ class WebkitAPI(MediaAPI):
display.webLoaded = True
length = 0
self.set_visible(display, True)
if controller.media_info.isFlash:
if controller.media_info.is_flash:
display.frame.evaluateJavaScript(u'show_flash("play");')
else:
display.frame.evaluateJavaScript(u'show_video("play");')
#TODO add playing check and get the correct media length
# TODO add playing check and get the correct media length
controller.media_info.length = length
self.state = MediaState.Playing
return True
def pause(self, display):
controller = display.controller
if controller.media_info.isFlash:
if controller.media_info.is_flash:
display.frame.evaluateJavaScript(u'show_flash("pause");')
else:
display.frame.evaluateJavaScript(u'show_video("pause");')
@ -323,7 +324,7 @@ class WebkitAPI(MediaAPI):
def stop(self, display):
controller = display.controller
if controller.media_info.isFlash:
if controller.media_info.is_flash:
display.frame.evaluateJavaScript(u'show_flash("stop");')
else:
display.frame.evaluateJavaScript(u'show_video("stop");')
@ -334,13 +335,13 @@ class WebkitAPI(MediaAPI):
# 1.0 is the highest value
if display.hasAudio:
vol = float(vol) / float(100)
if not controller.media_info.isFlash:
if not controller.media_info.is_flash:
display.frame.evaluateJavaScript(
u'show_video(null, null, %s);' % str(vol))
def seek(self, display, seekVal):
controller = display.controller
if controller.media_info.isFlash:
if controller.media_info.is_flash:
seek = seekVal
display.frame.evaluateJavaScript( \
u'show_flash("seek", null, null, "%s");' % (seek))
@ -351,19 +352,28 @@ class WebkitAPI(MediaAPI):
def reset(self, display):
controller = display.controller
if controller.media_info.isFlash:
if controller.media_info.is_flash:
display.frame.evaluateJavaScript(u'show_flash("close");')
else:
display.frame.evaluateJavaScript(u'show_video("close");')
self.state = MediaState.Off
def set_visible(self, display, status):
if self.hasOwnWidget:
display.webView.setVisible(status)
controller = display.controller
if status:
is_visible = "visible"
else:
is_visible = "hidden"
if controller.media_info.is_flash:
display.frame.evaluateJavaScript(u'show_flash( \
"setVisible", null, null, "%s");' % (is_visible))
else:
display.frame.evaluateJavaScript(u'show_video( \
"setVisible", null, null, null, "%s");' % (is_visible))
def update_ui(self, display):
controller = display.controller
if controller.media_info.isFlash:
if controller.media_info.is_flash:
currentTime = display.frame.evaluateJavaScript( \
u'show_flash("currentTime");').toInt()[0]
length = display.frame.evaluateJavaScript( \
@ -372,12 +382,12 @@ class WebkitAPI(MediaAPI):
(currentTime, ok) = display.frame.evaluateJavaScript( \
u'show_video("currentTime");').toFloat()
# check if conversion was ok and value is not 'NaN'
if ok and currentTime == currentTime:
if ok and currentTime != float('inf'):
currentTime = int(currentTime*1000)
(length, ok) = display.frame.evaluateJavaScript( \
u'show_video("length");').toFloat()
# check if conversion was ok and value is not 'NaN'
if ok and length == length:
if ok and length != float('inf'):
length = int(length*1000)
if currentTime > 0:
controller.media_info.length = length

View File

@ -31,7 +31,7 @@ import os
from PyQt4 import QtCore, QtGui
from lxml import html
from openlp.core.lib import translate, get_text_file_string
from openlp.core.lib import translate, get_text_file_string, Receiver
from openlp.core.lib.ui import UiStrings
from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize
from openlp.core.utils import AppLocation
@ -327,12 +327,14 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
"""
Copies the display text to the clipboard as plain text
"""
self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toPlainText())
def copyHtmlText(self):
"""
Copies the display text to the clipboard as Html
"""
self.update_song_usage()
self.mainWindow.clipboard.setText(self.document.toHtml())
def printServiceOrder(self):
@ -341,6 +343,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
"""
if not self.printDialog.exec_():
return
self.update_song_usage()
# Print the document.
self.document.print_(self.printer)
@ -397,3 +400,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
settings.setValue(u'print notes',
QtCore.QVariant(self.notesCheckBox.isChecked()))
settings.endGroup()
def update_song_usage(self):
for index, item in enumerate(self.serviceManager.serviceItems):
# Trigger Audit requests
Receiver.send_message(u'print_service_started',
[item[u'service_item']])

View File

@ -290,7 +290,7 @@ class ServiceManager(QtGui.QWidget):
QtCore.SIGNAL(u'service_item_update'), self.serviceItemUpdate)
# Last little bits of setting up
self.service_theme = unicode(QtCore.QSettings().value(
self.mainwindow.serviceSettingsSection + u'/service theme',
self.mainwindow.servicemanagerSettingsSection + u'/service theme',
QtCore.QVariant(u'')).toString())
self.servicePath = AppLocation.get_section_data_path(u'servicemanager')
# build the drag and drop context menu
@ -371,7 +371,7 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.setServiceModified(self.isModified(),
self.shortFileName())
QtCore.QSettings(). \
setValue(u'service/last file',QtCore.QVariant(fileName))
setValue(u'servicemanager/last file',QtCore.QVariant(fileName))
def fileName(self):
"""
@ -429,14 +429,15 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow,
translate('OpenLP.ServiceManager', 'Open File'),
SettingsManager.get_last_dir(
self.mainwindow.serviceSettingsSection),
self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager',
'OpenLP Service Files (*.osz)')))
if not fileName:
return False
else:
fileName = loadFile
SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection,
SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection,
split_filename(fileName)[0])
self.loadFile(fileName)
@ -461,7 +462,7 @@ class ServiceManager(QtGui.QWidget):
self.setFileName(u'')
self.setModified(False)
QtCore.QSettings(). \
setValue(u'service/last file',QtCore.QVariant(u''))
setValue(u'servicemanager/last file',QtCore.QVariant(u''))
def saveFile(self):
"""
@ -474,7 +475,8 @@ class ServiceManager(QtGui.QWidget):
(basename, extension) = os.path.splitext(file_name)
service_file_name = basename + '.osd'
log.debug(u'ServiceManager.saveFile - %s' % path_file_name)
SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection,
SettingsManager.set_last_dir(
self.mainwindow.servicemanagerSettingsSection,
path)
service = []
write_list = []
@ -562,7 +564,7 @@ class ServiceManager(QtGui.QWidget):
fileName = unicode(QtGui.QFileDialog.getSaveFileName(self.mainwindow,
UiStrings().SaveService,
SettingsManager.get_last_dir(
self.mainwindow.serviceSettingsSection),
self.mainwindow.servicemanagerSettingsSection),
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)')))
if not fileName:
return False
@ -624,7 +626,7 @@ class ServiceManager(QtGui.QWidget):
self.mainwindow.addRecentFile(fileName)
self.setModified(False)
QtCore.QSettings().setValue(
'service/last file', QtCore.QVariant(fileName))
'servicemanager/last file', QtCore.QVariant(fileName))
else:
critical_error_message_box(
message=translate('OpenLP.ServiceManager',
@ -666,7 +668,7 @@ class ServiceManager(QtGui.QWidget):
present.
"""
fileName = QtCore.QSettings(). \
value(u'service/last file',QtCore.QVariant(u'')).toString()
value(u'servicemanager/last file',QtCore.QVariant(u'')).toString()
if fileName:
self.loadFile(fileName)
@ -1008,7 +1010,8 @@ class ServiceManager(QtGui.QWidget):
self.service_theme = unicode(self.themeComboBox.currentText())
self.mainwindow.renderer.set_service_theme(self.service_theme)
QtCore.QSettings().setValue(
self.mainwindow.serviceSettingsSection + u'/service theme',
self.mainwindow.servicemanagerSettingsSection +
u'/service theme',
QtCore.QVariant(self.service_theme))
self.regenerateServiceItems()

View File

@ -253,7 +253,7 @@ class SlideController(Controller):
self.onEditSong)
self.controllerLayout.addWidget(self.toolbar)
# Build the Media Toolbar
self.mediaManager.addControllerItems(self, self.controllerLayout)
self.mediaManager.add_controller_items(self, self.controllerLayout)
if self.isLive:
# Build the Song Toolbar
self.songMenu = QtGui.QToolButton(self.toolbar)
@ -512,9 +512,13 @@ class SlideController(Controller):
self.toolbar.makeWidgetsVisible(self.loopList)
if item.is_media():
self.mediabar.setVisible(True)
self.previousItem.setVisible(False)
self.nextItem.setVisible(False)
else:
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
self.previousItem.setVisible(True)
self.nextItem.setVisible(True)
self.toolbar.show()
self.toolbar.show()

View File

@ -66,6 +66,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.onGradientComboBoxCurrentIndexChanged)
QtCore.QObject.connect(self.colorButton,
QtCore.SIGNAL(u'clicked()'), self.onColorButtonClicked)
QtCore.QObject.connect(self.imageColorButton,
QtCore.SIGNAL(u'clicked()'), self.onImageColorButtonClicked)
QtCore.QObject.connect(self.gradientStartButton,
QtCore.SIGNAL(u'clicked()'), self.onGradientStartButtonClicked)
QtCore.QObject.connect(self.gradientEndButton,
@ -330,6 +332,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.theme.background_end_color)
self.setField(u'background_type', QtCore.QVariant(1))
else:
self.imageColorButton.setStyleSheet(u'background-color: %s' %
self.theme.background_border_color)
self.imageFileEdit.setText(self.theme.background_filename)
self.setField(u'background_type', QtCore.QVariant(2))
if self.theme.background_direction == \
@ -464,6 +468,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self._colorButton(self.theme.background_color)
self.setBackgroundPageValues()
def onImageColorButtonClicked(self):
"""
Background / Gradient 1 Color button pushed.
"""
self.theme.background_border_color = \
self._colorButton(self.theme.background_border_color)
self.setBackgroundPageValues()
def onGradientStartButtonClicked(self):
"""
Gradient 2 Color button pushed.
@ -564,7 +576,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
def accept(self):
"""
Lets save the them as Finish has been pressed
Lets save the theme as Finish has been pressed
"""
# Save the theme name
self.theme.theme_name = unicode(self.field(u'name').toString())

View File

@ -610,6 +610,11 @@ class ThemeManager(QtGui.QWidget):
and to trigger the reload of the theme list
"""
self._writeTheme(theme, imageFrom, imageTo)
if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.update_image(theme.theme_name,
u'theme', QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.process_updates()
self.loadThemes()
def _writeTheme(self, theme, imageFrom, imageTo):

View File

@ -105,6 +105,11 @@ class Ui_ThemeWizard(object):
self.imageLayout = QtGui.QFormLayout(self.imageWidget)
self.imageLayout.setMargin(0)
self.imageLayout.setObjectName(u'ImageLayout')
self.imageColorLabel = QtGui.QLabel(self.colorWidget)
self.imageColorLabel.setObjectName(u'ImageColorLabel')
self.imageColorButton = QtGui.QPushButton(self.colorWidget)
self.imageColorButton.setObjectName(u'ImageColorButton')
self.imageLayout.addRow(self.imageColorLabel, self.imageColorButton)
self.imageLabel = QtGui.QLabel(self.imageWidget)
self.imageLabel.setObjectName(u'ImageLabel')
self.imageFileLayout = QtGui.QHBoxLayout()
@ -118,7 +123,7 @@ class Ui_ThemeWizard(object):
build_icon(u':/general/general_open.png'))
self.imageFileLayout.addWidget(self.imageBrowseButton)
self.imageLayout.addRow(self.imageLabel, self.imageFileLayout)
self.imageLayout.setItem(1, QtGui.QFormLayout.LabelRole, self.spacer)
self.imageLayout.setItem(2, QtGui.QFormLayout.LabelRole, self.spacer)
self.backgroundStack.addWidget(self.imageWidget)
self.backgroundLayout.addLayout(self.backgroundStack)
themeWizard.addPage(self.backgroundPage)
@ -443,6 +448,8 @@ class Ui_ThemeWizard(object):
translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right'))
self.gradientComboBox.setItemText(BackgroundGradientType.LeftBottom,
translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right'))
self.imageColorLabel.setText(
translate(u'OpenLP.ThemeWizard', 'Background color:'))
self.imageLabel.setText(u'%s:' % UiStrings().Image)
self.mainAreaPage.setTitle(
translate('OpenLP.ThemeWizard', 'Main Area Font Details'))

View File

@ -391,10 +391,13 @@ class BibleMediaItem(MediaManagerItem):
elif len(bibles):
self.initialiseAdvancedBible(bibles[0])
def reloadBibles(self):
def reloadBibles(self, process=False):
log.debug(u'Reloading Bibles')
self.plugin.manager.reload_bibles()
self.loadBibles()
# If called from first time wizard re-run, process any new bibles.
if process:
self.plugin.appStartup()
self.updateAutoCompleter()
def initialiseAdvancedBible(self, bible):

View File

@ -217,8 +217,7 @@ class CustomMediaItem(MediaManagerItem):
for item in self.listView.selectedIndexes()]
for id in id_list:
self.plugin.manager.delete_object(CustomSlide, id)
for row in row_list:
self.listView.takeItem(row)
self.onSearchTextButtonClick()
def onFocus(self):
self.searchTextEdit.setFocus()

View File

@ -25,10 +25,13 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate
from openlp.plugins.images.lib import ImageMediaItem
from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
log = logging.getLogger(__name__)
@ -36,10 +39,13 @@ class ImagePlugin(Plugin):
log.info(u'Image Plugin loaded')
def __init__(self, plugin_helpers):
Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem)
Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem,
ImageTab)
self.weight = -7
self.icon_path = u':/plugins/plugin_images.png'
self.icon = build_icon(self.icon_path)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'image_updated'), self.image_updated)
def about(self):
about_text = translate('ImagePlugin', '<strong>Image Plugin</strong>'
@ -81,3 +87,13 @@ class ImagePlugin(Plugin):
'Add the selected image to the service.')
}
self.setPluginUiTextStrings(tooltips)
def image_updated(self):
"""
Triggered by saving and changing the image border. Sets the images in
image manager to require updates. Actual update is triggered by the
last part of saving the config.
"""
background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000')))
self.liveController.imageManager.update_images(u'image', background)

View File

@ -26,3 +26,4 @@
###############################################################################
from mediaitem import ImageMediaItem
from imagetab import ImageTab

View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 SettingsTab, translate, Receiver
from openlp.core.lib.ui import UiStrings, create_valign_combo
class ImageTab(SettingsTab):
"""
ImageTab is the images settings tab in the settings dialog.
"""
def __init__(self, parent, name, visible_title, icon_path):
SettingsTab.__init__(self, parent, name, visible_title, icon_path)
def setupUi(self):
self.setObjectName(u'ImagesTab')
SettingsTab.setupUi(self)
self.bgColorGroupBox = QtGui.QGroupBox(self.leftColumn)
self.bgColorGroupBox.setObjectName(u'FontGroupBox')
self.formLayout = QtGui.QFormLayout(self.bgColorGroupBox)
self.formLayout.setObjectName(u'FormLayout')
self.colorLayout = QtGui.QHBoxLayout()
self.backgroundColorLabel = QtGui.QLabel(self.bgColorGroupBox)
self.backgroundColorLabel.setObjectName(u'BackgroundColorLabel')
self.colorLayout.addWidget(self.backgroundColorLabel)
self.backgroundColorButton = QtGui.QPushButton(self.bgColorGroupBox)
self.backgroundColorButton.setObjectName(u'BackgroundColorButton')
self.colorLayout.addWidget(self.backgroundColorButton)
self.formLayout.addRow(self.colorLayout)
self.informationLabel = QtGui.QLabel(self.bgColorGroupBox)
self.informationLabel.setObjectName(u'InformationLabel')
self.formLayout.addRow(self.informationLabel)
self.leftLayout.addWidget(self.bgColorGroupBox)
self.leftLayout.addStretch()
self.rightColumn.setSizePolicy(
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.rightLayout.addStretch()
# Signals and slots
QtCore.QObject.connect(self.backgroundColorButton,
QtCore.SIGNAL(u'pressed()'), self.onbackgroundColorButtonClicked)
def retranslateUi(self):
self.bgColorGroupBox.setTitle(
translate('ImagesPlugin.ImageTab', 'Background Color'))
self.backgroundColorLabel.setText(
translate('ImagesPlugin.ImageTab', 'Default Color:'))
self.informationLabel.setText(
translate('ImagesPlugin.ImageTab', 'Provides border where image '
'is not the correct dimensions for the screen when resized.'))
def onbackgroundColorButtonClicked(self):
new_color = QtGui.QColorDialog.getColor(
QtGui.QColor(self.bg_color), self)
if new_color.isValid():
self.bg_color = new_color.name()
self.backgroundColorButton.setStyleSheet(
u'background-color: %s' % self.bg_color)
def load(self):
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
self.bg_color = unicode(settings.value(
u'background color', QtCore.QVariant(u'#000000')).toString())
self.initial_color = self.bg_color
settings.endGroup()
self.backgroundColorButton.setStyleSheet(
u'background-color: %s' % self.bg_color)
def save(self):
settings = QtCore.QSettings()
settings.beginGroup(self.settingsSection)
settings.setValue(u'background color', QtCore.QVariant(self.bg_color))
settings.endGroup()
if self.initial_color != self.bg_color:
Receiver.send_message(u'image_updated')

View File

@ -140,6 +140,8 @@ class ImageMediaItem(MediaManagerItem):
self.plugin.formparent.finishedProgressBar()
def generateSlideData(self, service_item, item=None, xmlVersion=False):
background = QtGui.QColor(QtCore.QSettings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000')))
if item:
items = [item]
else:
@ -183,7 +185,7 @@ class ImageMediaItem(MediaManagerItem):
for bitem in items:
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
(path, name) = os.path.split(filename)
service_item.add_from_image(filename, name)
service_item.add_from_image(filename, name, background)
return True
def onResetClick(self):
@ -206,13 +208,16 @@ class ImageMediaItem(MediaManagerItem):
if check_item_selected(self.listView,
translate('ImagePlugin.MediaItem',
'You must select an image to replace the background with.')):
background = QtGui.QColor(QtCore.QSettings().value(
self.settingsSection + u'/background color',
QtCore.QVariant(u'#000000')))
item = self.listView.selectedIndexes()[0]
bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
(path, name) = os.path.split(filename)
if self.plugin.liveController.display.directImage(name,
filename):
filename, background):
self.resetAction.setVisible(True)
else:
critical_error_message_box(UiStrings().LiveBGError,

View File

@ -56,11 +56,15 @@ class MediaMediaItem(MediaManagerItem):
self.mediaObject = None
self.mediaController = Controller(parent)
self.mediaController.controllerLayout = QtGui.QVBoxLayout()
self.plugin.mediaManager.addControllerItems(self.mediaController, self.mediaController.controllerLayout)
self.plugin.mediaManager.set_controls_visible(self.mediaController, False)
self.mediaController.previewDisplay = Display(self.mediaController, False, self.mediaController, self.plugin.pluginManager.plugins)
self.plugin.mediaManager.add_controller_items(self.mediaController, \
self.mediaController.controllerLayout)
self.plugin.mediaManager.set_controls_visible(self.mediaController, \
False)
self.mediaController.previewDisplay = Display(self.mediaController, \
False, self.mediaController, self.plugin.pluginManager.plugins)
self.mediaController.previewDisplay.setup()
self.plugin.mediaManager.setup_display(self.mediaController.previewDisplay)
self.plugin.mediaManager.setup_display( \
self.mediaController.previewDisplay)
self.mediaController.previewDisplay.hide()
QtCore.QObject.connect(Receiver.get_receiver(),
@ -100,7 +104,7 @@ class MediaMediaItem(MediaManagerItem):
def onResetClick(self):
"""
Called to reset the Live backgound with the media selected,
Called to reset the Live background with the media selected,
"""
self.plugin.liveController.mediaManager.video_reset( \
self.plugin.liveController)
@ -114,7 +118,7 @@ class MediaMediaItem(MediaManagerItem):
def onReplaceClick(self):
"""
Called to replace Live backgound with the media selected.
Called to replace Live background with the media selected.
"""
if check_item_selected(self.listView,
translate('MediaPlugin.MediaItem',
@ -122,7 +126,7 @@ class MediaMediaItem(MediaManagerItem):
item = self.listView.currentItem()
filename = unicode(item.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
(path, name) = os.path.split(filename)
#(path, name) = os.path.split(filename)
if self.plugin.liveController.mediaManager.video( \
self.plugin.liveController, filename, True, True):
self.resetAction.setVisible(True)
@ -158,6 +162,8 @@ class MediaMediaItem(MediaManagerItem):
if self.mediaLength > 0:
service_item.add_capability(
ItemCapabilities.AllowsVariableStartTime)
else:
return False
service_item.media_length = self.mediaLength
service_item.title = unicode(self.plugin.nameStrings[u'singular'])
service_item.add_capability(ItemCapabilities.RequiresMedia)

View File

@ -53,7 +53,6 @@ class MediaTab(SettingsTab):
self.ApiCheckBoxes[api.name] = checkbox
self.mediaApiLayout.addWidget(checkbox)
self.leftLayout.addWidget(self.mediaAPIGroupBox)
self.apiOrderGroupBox = QtGui.QGroupBox(self.leftColumn)
self.apiOrderGroupBox.setObjectName(u'apiOrderGroupBox')
self.apiOrderLayout = QtGui.QVBoxLayout(self.apiOrderGroupBox)
@ -67,7 +66,6 @@ class MediaTab(SettingsTab):
sizePolicy.setHeightForWidth(self.apiOrderlistWidget. \
sizePolicy().hasHeightForWidth())
self.apiOrderlistWidget.setSizePolicy(sizePolicy)
self.apiOrderlistWidget.setVerticalScrollBarPolicy( \
QtCore.Qt.ScrollBarAsNeeded)
self.apiOrderlistWidget.setHorizontalScrollBarPolicy( \

View File

@ -99,21 +99,22 @@ class MediaPlugin(Plugin):
"""
log.info(u'Media Finalising')
self.mediaManager.finalise()
Plugin.finalise(self)
def getDisplayCss(self):
"""
Add css style sheets to htmlbuilder
"""
return self.mediaManager.getDisplayCss()
return self.mediaManager.get_media_display_css()
def getDisplayJavascript(self):
"""
Add javascript functions to htmlbuilder
"""
return self.mediaManager.getDisplayJavascript()
return self.mediaManager.get_media_display_javascript()
def getDisplayHtml(self):
"""
Add html code to htmlbuilder
"""
return self.mediaManager.getDisplayHtml()
return self.mediaManager.get_media_display_html()

View File

@ -170,8 +170,8 @@ class SongExportForm(OpenLPWizard):
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
self.informationLabel.setText(
translate('SongsPlugin.ExportWizardForm', 'This wizard will help to'
' export your songs to the open and free OpenLyrics worship song '
'format.'))
' export your songs to the open and free <strong>OpenLyrics'
'</strong> worship song format.'))
self.availableSongsPage.setTitle(
translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
self.availableSongsPage.setSubTitle(
@ -285,7 +285,9 @@ class SongExportForm(OpenLPWizard):
self, songs, unicode(self.directoryLineEdit.text()))
if exporter.do_export():
self.progressLabel.setText(
translate('SongsPlugin.SongExportForm', 'Finished export.'))
translate('SongsPlugin.SongExportForm', 'Finished export. To '
'import these files use the <strong>OpenLyrics</strong> '
'importer.'))
else:
self.progressLabel.setText(
translate('SongsPlugin.SongExportForm',

View File

@ -31,6 +31,7 @@ the Songs plugin
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation
from sqlalchemy.sql.expression import func
from openlp.core.lib.db import BaseModel, init_db
@ -70,7 +71,6 @@ class Topic(BaseModel):
"""
pass
def init_schema(url):
"""
Setup the songs database connection and initialise the database schema.
@ -111,10 +111,6 @@ def init_schema(url):
* file_name
* type
**media_files_songs Table**
* media_file_id
* song_id
**song_books Table**
The *song_books* table holds a list of books that a congregation gets
their songs from, or old hymnals now no longer used. This table has the
@ -162,7 +158,7 @@ def init_schema(url):
# Definition of the "authors" table
authors_table = Table(u'authors', metadata,
Column(u'id', types.Integer, primary_key=True),
Column(u'id', types.Integer(), primary_key=True),
Column(u'first_name', types.Unicode(128)),
Column(u'last_name', types.Unicode(128)),
Column(u'display_name', types.Unicode(255), index=True, nullable=False)
@ -170,22 +166,25 @@ def init_schema(url):
# Definition of the "media_files" table
media_files_table = Table(u'media_files', metadata,
Column(u'id', types.Integer, primary_key=True),
Column(u'id', types.Integer(), primary_key=True),
Column(u'song_id', types.Integer(), ForeignKey(u'songs.id'),
default=None),
Column(u'file_name', types.Unicode(255), nullable=False),
Column(u'type', types.Unicode(64), nullable=False, default=u'audio')
Column(u'type', types.Unicode(64), nullable=False, default=u'audio'),
Column(u'weight', types.Integer(), default=0)
)
# Definition of the "song_books" table
song_books_table = Table(u'song_books', metadata,
Column(u'id', types.Integer, primary_key=True),
Column(u'id', types.Integer(), primary_key=True),
Column(u'name', types.Unicode(128), nullable=False),
Column(u'publisher', types.Unicode(128))
)
# Definition of the "songs" table
songs_table = Table(u'songs', metadata,
Column(u'id', types.Integer, primary_key=True),
Column(u'song_book_id', types.Integer,
Column(u'id', types.Integer(), primary_key=True),
Column(u'song_book_id', types.Integer(),
ForeignKey(u'song_books.id'), default=None),
Column(u'title', types.Unicode(255), nullable=False),
Column(u'alternate_title', types.Unicode(255)),
@ -197,36 +196,31 @@ def init_schema(url):
Column(u'song_number', types.Unicode(64)),
Column(u'theme_name', types.Unicode(128)),
Column(u'search_title', types.Unicode(255), index=True, nullable=False),
Column(u'search_lyrics', types.UnicodeText, nullable=False)
Column(u'search_lyrics', types.UnicodeText, nullable=False),
Column(u'create_date', types.DateTime(), default=func.now()),
Column(u'last_modified', types.DateTime(), default=func.now(),
onupdate=func.now())
)
# Definition of the "topics" table
topics_table = Table(u'topics', metadata,
Column(u'id', types.Integer, primary_key=True),
Column(u'id', types.Integer(), primary_key=True),
Column(u'name', types.Unicode(128), index=True, nullable=False)
)
# Definition of the "authors_songs" table
authors_songs_table = Table(u'authors_songs', metadata,
Column(u'author_id', types.Integer,
Column(u'author_id', types.Integer(),
ForeignKey(u'authors.id'), primary_key=True),
Column(u'song_id', types.Integer,
ForeignKey(u'songs.id'), primary_key=True)
)
# Definition of the "media_files_songs" table
media_files_songs_table = Table(u'media_files_songs', metadata,
Column(u'media_file_id', types.Integer,
ForeignKey(u'media_files.id'), primary_key=True),
Column(u'song_id', types.Integer,
Column(u'song_id', types.Integer(),
ForeignKey(u'songs.id'), primary_key=True)
)
# Definition of the "songs_topics" table
songs_topics_table = Table(u'songs_topics', metadata,
Column(u'song_id', types.Integer,
Column(u'song_id', types.Integer(),
ForeignKey(u'songs.id'), primary_key=True),
Column(u'topic_id', types.Integer,
Column(u'topic_id', types.Integer(),
ForeignKey(u'topics.id'), primary_key=True)
)
@ -238,8 +232,7 @@ def init_schema(url):
'authors': relation(Author, backref='songs',
secondary=authors_songs_table, lazy=False),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs',
secondary=media_files_songs_table),
'media_files': relation(MediaFile, backref='songs'),
'topics': relation(Topic, backref='songs',
secondary=songs_topics_table)
})

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin
"""
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.sql.expression import func
from migrate import changeset
from migrate.changeset.constraint import ForeignKeyConstraint
__version__ = 2
def upgrade_setup(metadata):
"""
Set up the latest revision all tables, with reflection, needed for the
upgrade process. If you want to drop a table, you need to remove it from
here, and add it to your upgrade function.
"""
tables = {
u'authors': Table(u'authors', metadata, autoload=True),
u'media_files': Table(u'media_files', metadata, autoload=True),
u'song_books': Table(u'song_books', metadata, autoload=True),
u'songs': Table(u'songs', metadata, autoload=True),
u'topics': Table(u'topics', metadata, autoload=True),
u'authors_songs': Table(u'authors_songs', metadata, autoload=True),
u'songs_topics': Table(u'songs_topics', metadata, autoload=True)
}
return tables
def upgrade_1(session, metadata, tables):
"""
Version 1 upgrade.
This upgrade removes the many-to-many relationship between songs and
media_files and replaces it with a one-to-many, which is far more
representative of the real relationship between the two entities.
In order to facilitate this one-to-many relationship, a song_id column is
added to the media_files table, and a weight column so that the media
files can be ordered.
"""
Table(u'media_files_songs', metadata, autoload=True).drop(checkfirst=True)
Column(u'song_id', types.Integer(), default=None)\
.create(table=tables[u'media_files'], populate_default=True)
Column(u'weight', types.Integer(), default=0)\
.create(table=tables[u'media_files'], populate_default=True)
if metadata.bind.url.get_dialect().name != 'sqlite':
# SQLite doesn't support ALTER TABLE ADD CONSTRAINT
ForeignKeyConstraint([u'song_id'], [u'songs.id'],
table=tables[u'media_files']).create()
def upgrade_2(session, metadata, tables):
"""
Version 2 upgrade.
This upgrade adds a create_date and last_modified date to the songs table
"""
Column(u'create_date', types.DateTime(), default=func.now())\
.create(table=tables[u'songs'], populate_default=True)
Column(u'last_modified', types.DateTime(), default=func.now())\
.create(table=tables[u'songs'], populate_default=True)

View File

@ -36,7 +36,8 @@ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import UiStrings, base_action, icon_action
from openlp.core.utils.actions import ActionList
from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab
from openlp.plugins.songs.lib import clean_song, upgrade, SongMediaItem, \
SongsTab
from openlp.plugins.songs.lib.db import init_schema, Song
from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
@ -58,8 +59,8 @@ class SongsPlugin(Plugin):
Create and set up the Songs plugin.
"""
Plugin.__init__(self, u'songs', plugin_helpers, SongMediaItem, SongsTab)
self.manager = Manager(u'songs', init_schema, upgrade_mod=upgrade)
self.weight = -10
self.manager = Manager(u'songs', init_schema)
self.icon_path = u':/plugins/plugin_songs.png'
self.icon = build_icon(self.icon_path)

View File

@ -117,9 +117,11 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog):
try:
fileHandle = open(outname, u'w')
for instance in usage:
record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n' % (
instance.usagedate, instance.usagetime, instance.title,
instance.copyright, instance.ccl_number, instance.authors)
record = u'\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
u'\"%s\",\"%s\"\n' % ( instance.usagedate,
instance.usagetime, instance.title, instance.copyright,
instance.ccl_number, instance.authors,
instance.plugin_name, instance.source)
fileHandle.write(record.encode(u'utf-8'))
Receiver.send_message(u'openlp_information_message', {
u'title': translate('SongUsagePlugin.SongUsageDetailForm',

View File

@ -56,7 +56,9 @@ def init_schema(url):
Column(u'title', types.Unicode(255), nullable=False),
Column(u'authors', types.Unicode(255), nullable=False),
Column(u'copyright', types.Unicode(255)),
Column(u'ccl_number', types.Unicode(65))
Column(u'ccl_number', types.Unicode(65)),
Column(u'plugin_name', types.Unicode(20)),
Column(u'source', types.Unicode(10))
)
mapper(SongUsageItem, songusage_table)

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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:`upgrade` module provides a way for the database and schema that is the
backend for the SongsUsage plugin
"""
from sqlalchemy import Column, Table, types
from migrate import changeset
__version__ = 1
def upgrade_setup(metadata):
"""
Set up the latest revision all tables, with reflection, needed for the
upgrade process. If you want to drop a table, you need to remove it from
here, and add it to your upgrade function.
"""
tables = {
u'songusage_data': Table(u'songusage_data', metadata, autoload=True)
}
return tables
def upgrade_1(session, metadata, tables):
"""
Version 1 upgrade.
This upgrade adds two new fields to the songusage database
"""
Column(u'plugin_name', types.Unicode(20), default=u'') \
.create(table=tables[u'songusage_data'], populate_default=True)
Column(u'source', types.Unicode(10), default=u'') \
.create(table=tables[u'songusage_data'], populate_default=True)

View File

@ -37,6 +37,7 @@ from openlp.core.lib.ui import base_action, shortcut_action
from openlp.core.utils.actions import ActionList
from openlp.plugins.songusage.forms import SongUsageDetailForm, \
SongUsageDeleteForm
from openlp.plugins.songusage.lib import upgrade
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
log = logging.getLogger(__name__)
@ -46,11 +47,11 @@ class SongUsagePlugin(Plugin):
def __init__(self, plugin_helpers):
Plugin.__init__(self, u'songusage', plugin_helpers)
self.manager = Manager(u'songusage', init_schema, upgrade_mod=upgrade)
self.weight = -4
self.icon = build_icon(u':/plugins/plugin_songusage.png')
self.activeIcon = build_icon(u':/songusage/song_usage_active.png')
self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
self.manager = None
self.songUsageActive = False
def addToolsMenuItem(self, tools_menu):
@ -121,7 +122,10 @@ class SongUsagePlugin(Plugin):
Plugin.initialise(self)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_live_started'),
self.onReceiveSongUsage)
self.displaySongUsage)
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'print_service_started'),
self.printSongUsage)
self.songUsageActive = QtCore.QSettings().value(
self.settingsSection + u'/active',
QtCore.QVariant(False)).toBool()
@ -134,8 +138,6 @@ class SongUsagePlugin(Plugin):
translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageReport,
translate('SongUsagePlugin', 'Song Usage'))
if self.manager is None:
self.manager = Manager(u'songusage', init_schema)
self.songUsageDeleteForm = SongUsageDeleteForm(self.manager,
self.formparent)
self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent)
@ -194,10 +196,21 @@ class SongUsagePlugin(Plugin):
self.songUsageStatus.blockSignals(False)
def onReceiveSongUsage(self, item):
def displaySongUsage(self, item):
"""
Song Usage for live song from SlideController
Song Usage for which has been displayed
"""
self._add_song_usage(unicode(translate('SongUsagePlugin',
'display')), item)
def printSongUsage(self, item):
"""
Song Usage for which has been printed
"""
self._add_song_usage(unicode(translate('SongUsagePlugin',
'printed')), item)
def _add_song_usage(self, source, item):
audit = item[0].audit
if self.songUsageActive and audit:
song_usage_item = SongUsageItem()
@ -207,6 +220,8 @@ class SongUsagePlugin(Plugin):
song_usage_item.copyright = audit[2]
song_usage_item.ccl_number = audit[3]
song_usage_item.authors = u' '.join(audit[1])
song_usage_item.plugin_name = item[0].name
song_usage_item.source = source
self.manager.save_object(song_usage_item)
def onSongUsageDelete(self):

View File

@ -11,7 +11,7 @@ Package: openlp
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python-qt4,
python-qt4-phonon, python-sqlalchemy, python-chardet, python-beautifulsoup,
python-lxml, python-sqlite, python-enchant
python-lxml, python-sqlite, python-enchant, python-mako, python-migrate
Conflicts: python-openlp
Description: Church lyrics projection application
OpenLP is free church presentation software, or lyrics projection software,

View File

@ -46,14 +46,14 @@ VERS = {
'sqlalchemy': '0.5',
# pyenchant 1.6 required on Windows
'enchant': '1.6' if is_win else '1.3'
}
}
# pywin32
WIN32_MODULES = [
'win32com',
'win32ui',
'pywintypes',
]
]
MODULES = [
'PyQt4',
@ -72,7 +72,8 @@ MODULES = [
'enchant',
'BeautifulSoup',
'mako',
]
'migrate',
]
OPTIONAL_MODULES = [

View File

@ -186,25 +186,6 @@ def update_export_at_pootle(source_filename):
page = urllib.urlopen(REVIEW_URL)
page.close()
def download_file(source_filename, dest_filename):
"""
Download a file and save it to disk.
``source_filename``
The file to download.
``dest_filename``
The new local file name.
"""
print_verbose(u'Downloading from: %s' % (SERVER_URL + source_filename))
page = urllib.urlopen(SERVER_URL + source_filename)
content = page.read().decode('utf8')
page.close()
file = open(dest_filename, u'w')
file.write(content.encode('utf8'))
file.close()
def download_translations():
"""
This method downloads the translation files from the Pootle server.
@ -219,7 +200,7 @@ def download_translations():
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n',
language_file)
print_verbose(u'Get Translation File: %s' % filename)
download_file(language_file, filename)
urllib.urlretrieve(SERVER_URL + language_file, filename)
print_quiet(u' Done.')
def prepare_project():
@ -304,7 +285,7 @@ def create_translation(language):
if not language.endswith(u'.ts'):
language += u'.ts'
filename = os.path.join(os.path.abspath(u'..'), u'resources', u'i18n', language)
download_file(u'en.ts', filename)
urllib.urlretrieve(SERVER_URL + u'en.ts', filename)
print_quiet(u' ** Please Note **')
print_quiet(u' In order to get this file into OpenLP and onto the '
u'Pootle translation server you will need to subscribe to the '