This commit is contained in:
Tim Bentley 2013-07-12 19:13:21 +01:00
commit 38eec999b7
39 changed files with 2355 additions and 746 deletions

View File

@ -38,6 +38,8 @@ from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
from sqlalchemy.pool import NullPool
from alembic.migration import MigrationContext
from alembic.operations import Operations
from openlp.core.lib import translate, Settings
from openlp.core.lib.ui import critical_error_message_box
@ -65,6 +67,17 @@ def init_db(url, auto_flush=True, auto_commit=False):
return session, metadata
def get_upgrade_op(session):
"""
Create a migration context and an operations object for performing upgrades.
``session``
The SQLAlchemy session object.
"""
context = MigrationContext.configure(session.bind.connect())
return Operations(context)
def upgrade_db(url, upgrade):
"""
Upgrade a database.
@ -82,13 +95,7 @@ def upgrade_db(url, upgrade):
Provides a class for the metadata table.
"""
pass
load_changes = False
tables = []
try:
tables = upgrade.upgrade_setup(metadata)
load_changes = True
except (SQLAlchemyError, DBAPIError):
pass
metadata_table = Table(u'metadata', metadata,
Column(u'key', types.Unicode(64), primary_key=True),
Column(u'value', types.UnicodeText(), default=None)
@ -105,22 +112,22 @@ def upgrade_db(url, upgrade):
if version > upgrade.__version__:
return version, upgrade.__version__
version += 1
if load_changes:
try:
while hasattr(upgrade, u'upgrade_%d' % version):
log.debug(u'Running upgrade_%d', version)
try:
upgrade_func = getattr(upgrade, u'upgrade_%d' % version)
upgrade_func(session, metadata, tables)
upgrade_func(session, metadata)
session.commit()
# Update the version number AFTER a commit so that we are sure the previous transaction happened
version_meta.value = unicode(version)
session.commit()
version += 1
except (SQLAlchemyError, DBAPIError):
log.exception(u'Could not run database upgrade script '
'"upgrade_%s", upgrade process has been halted.', version)
log.exception(u'Could not run database upgrade script "upgrade_%s", upgrade process has been halted.',
version)
break
else:
except (SQLAlchemyError, DBAPIError):
version_meta = Metadata.populate(key=u'version', value=int(upgrade.__version__))
session.commit()
return int(version_meta.value), upgrade.__version__

View File

@ -107,7 +107,7 @@ class Renderer(object):
del self._theme_dimensions[old_theme_name]
if theme_name in self._theme_dimensions:
del self._theme_dimensions[theme_name]
if not only_delete:
if not only_delete and theme_name:
self._set_theme(theme_name)
def _set_theme(self, theme_name):

View File

@ -485,6 +485,12 @@ class ServiceItem(object):
"""
return self.unique_identifier != other.unique_identifier
def __hash__(self):
"""
Return the hash for the service item.
"""
return self.unique_identifier
def is_media(self):
"""
Confirms if the ServiceItem is media

View File

@ -244,6 +244,7 @@ class Settings(QtCore.QSettings):
u'shortcuts/printServiceItem': [QtGui.QKeySequence(u'Ctrl+P')],
u'shortcuts/songExportItem': [],
u'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
u'shortcuts/searchShortcut': [QtGui.QKeySequence(u'Ctrl+F')],
u'shortcuts/settingsShortcutsItem': [],
u'shortcuts/settingsImportItem': [],
u'shortcuts/settingsPluginListItem': [QtGui.QKeySequence(u'Alt+F7')],

View File

@ -291,7 +291,7 @@ class Ui_AboutDialog(object):
'but WITHOUT ANY WARRANTY; without even the implied warranty of '
'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See below '
'for more details.')
gpltext = ('GNU GENERAL PUBLIC LICENSE\n'
gpl_text = ('GNU GENERAL PUBLIC LICENSE\n'
'Version 2, June 1991\n'
'\n'
'Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 '
@ -662,7 +662,7 @@ class Ui_AboutDialog(object):
'linking proprietary applications with the library. If this is '
'what you want to do, use the GNU Lesser General Public License '
'instead of this License.')
self.license_text_edit.setPlainText(u'%s\n\n%s\n\n%s\n\n\n%s' % (copyright_note, licence, disclaimer, gpltext))
self.license_text_edit.setPlainText(u'%s\n\n%s\n\n%s\n\n\n%s' % (copyright_note, licence, disclaimer, gpl_text))
self.about_notebook.setTabText(self.about_notebook.indexOf(self.license_tab),
translate('OpenLP.AboutForm', 'License'))
self.volunteer_button.setText(translate('OpenLP.AboutForm', 'Volunteer'))

View File

@ -34,8 +34,8 @@ import re
import os
import platform
import bs4
import sqlalchemy
from bs4 import BeautifulSoup
from lxml import etree
from PyQt4 import Qt, QtCore, QtGui, QtWebKit
@ -145,7 +145,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
u'QtWebkit: %s\n' % WEBKIT_VERSION + \
u'SQLAlchemy: %s\n' % sqlalchemy.__version__ + \
u'SQLAlchemy Migrate: %s\n' % MIGRATE_VERSION + \
u'BeautifulSoup: %s\n' % BeautifulSoup.__version__ + \
u'BeautifulSoup: %s\n' % bs4.__version__ + \
u'lxml: %s\n' % etree.__version__ + \
u'Chardet: %s\n' % CHARDET_VERSION + \
u'PyEnchant: %s\n' % ENCHANT_VERSION + \

View File

@ -68,7 +68,7 @@ class ThemeScreenshotThread(QtCore.QThread):
screenshot = config.get(u'theme_%s' % theme, u'screenshot')
urllib.urlretrieve(u'%s%s' % (self.parent().web, screenshot),
os.path.join(unicode(gettempdir(), get_filesystem_encoding()), u'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.parent().themesListWidget)
item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget)
item.setData(QtCore.Qt.UserRole, filename)
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
@ -76,8 +76,7 @@ class ThemeScreenshotThread(QtCore.QThread):
class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
"""
This is the Theme Import Wizard, which allows easy creation and editing of
OpenLP themes.
This is the Theme Import Wizard, which allows easy creation and editing of OpenLP themes.
"""
log.info(u'ThemeWizardForm loaded')
@ -97,10 +96,11 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self.config.readfp(io.BytesIO(files))
self.update_screen_list_combo()
self.was_download_cancelled = False
self.theme_screenshot_thread = None
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...')
self.cancelButton.clicked.connect(self.onCancelButtonClicked)
self.noInternetFinishButton.clicked.connect(self.onNoInternetFinishButtonClicked)
self.currentIdChanged.connect(self.onCurrentIdChanged)
self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function(u'config_screen_changed', self.update_screen_list_combo)
def exec_(self):
@ -116,9 +116,9 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
"""
self.restart()
check_directory_exists(os.path.join(unicode(gettempdir(), get_filesystem_encoding()), u'openlp'))
self.noInternetFinishButton.setVisible(False)
self.no_internet_finish_button.setVisible(False)
# Check if this is a re-run of the wizard.
self.hasRunWizard = Settings().value(u'core/has run wizard')
self.has_run_wizard = Settings().value(u'core/has run wizard')
# Sort out internet access for downloads
if self.web_access:
songs = self.config.get(u'songs', u'languages')
@ -126,7 +126,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
for song in songs:
title = unicode(self.config.get(u'songs_%s' % song, u'title'), u'utf8')
filename = unicode(self.config.get(u'songs_%s' % song, u'filename'), u'utf8')
item = QtGui.QListWidgetItem(title, self.songsListWidget)
item = QtGui.QListWidgetItem(title, self.songs_list_widget)
item.setData(QtCore.Qt.UserRole, filename)
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
@ -134,7 +134,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
bible_languages = bible_languages.split(u',')
for lang in bible_languages:
language = unicode(self.config.get(u'bibles_%s' % lang, u'title'), u'utf8')
langItem = QtGui.QTreeWidgetItem(self.biblesTreeWidget, [language])
langItem = QtGui.QTreeWidgetItem(self.bibles_tree_widget, [language])
bibles = self.config.get(u'bibles_%s' % lang, u'translations')
bibles = bibles.split(u',')
for bible in bibles:
@ -144,10 +144,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
item.setData(0, QtCore.Qt.UserRole, filename)
item.setCheckState(0, QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.biblesTreeWidget.expandAll()
self.bibles_tree_widget.expandAll()
# Download the theme screenshots.
self.themeScreenshotThread = ThemeScreenshotThread(self)
self.themeScreenshotThread.start()
self.theme_screenshot_thread = ThemeScreenshotThread(self)
self.theme_screenshot_thread.start()
self.application.set_normal_cursor()
def nextId(self):
@ -166,61 +166,60 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
return FirstTimePage.Progress
elif self.currentId() == FirstTimePage.Themes:
self.application.set_busy_cursor()
while not self.themeScreenshotThread.isFinished():
while not self.theme_screenshot_thread.isFinished():
time.sleep(0.1)
self.application.process_events()
# Build the screenshot icons, as this can not be done in the thread.
self._buildThemeScreenshots()
self._build_theme_screenshots()
self.application.set_normal_cursor()
return FirstTimePage.Defaults
else:
return self.currentId() + 1
def onCurrentIdChanged(self, pageId):
def on_current_id_changed(self, page_id):
"""
Detects Page changes and updates as appropriate.
"""
# Keep track of the page we are at. Triggering "Cancel" causes pageId
# to be a -1.
# Keep track of the page we are at. Triggering "Cancel" causes page_id to be a -1.
self.application.process_events()
if pageId != -1:
self.lastId = pageId
if pageId == FirstTimePage.Plugins:
if page_id != -1:
self.last_id = page_id
if page_id == FirstTimePage.Plugins:
# Set the no internet page text.
if self.hasRunWizard:
self.noInternetLabel.setText(self.noInternetText)
if self.has_run_wizard:
self.no_internet_label.setText(self.no_internet_text)
else:
self.noInternetLabel.setText(self.noInternetText + self.cancelWizardText)
elif pageId == FirstTimePage.Defaults:
self.themeComboBox.clear()
for iter in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(iter)
self.no_internet_label.setText(self.no_internet_text + self.cancelWizardText)
elif page_id == FirstTimePage.Defaults:
self.theme_combo_box.clear()
for iter in xrange(self.themes_list_widget.count()):
item = self.themes_list_widget.item(iter)
if item.checkState() == QtCore.Qt.Checked:
self.themeComboBox.addItem(item.text())
if self.hasRunWizard:
self.theme_combo_box.addItem(item.text())
if self.has_run_wizard:
# Add any existing themes to list.
for theme in self.theme_manager.get_themes():
index = self.themeComboBox.findText(theme)
index = self.theme_combo_box.findText(theme)
if index == -1:
self.themeComboBox.addItem(theme)
self.theme_combo_box.addItem(theme)
default_theme = Settings().value(u'themes/global theme')
# Pre-select the current default theme.
index = self.themeComboBox.findText(default_theme)
self.themeComboBox.setCurrentIndex(index)
elif pageId == FirstTimePage.NoInternet:
self.backButton.setVisible(False)
self.nextButton.setVisible(False)
self.noInternetFinishButton.setVisible(True)
if self.hasRunWizard:
self.cancelButton.setVisible(False)
elif pageId == FirstTimePage.Progress:
index = self.theme_combo_box.findText(default_theme)
self.theme_combo_box.setCurrentIndex(index)
elif page_id == FirstTimePage.NoInternet:
self.back_button.setVisible(False)
self.next_button.setVisible(False)
self.no_internet_finish_button.setVisible(True)
if self.has_run_wizard:
self.cancel_button.setVisible(False)
elif page_id == FirstTimePage.Progress:
self.application.set_busy_cursor()
self.repaint()
self.application.process_events()
# Try to give the wizard a chance to redraw itself
time.sleep(0.2)
self._preWizard()
self._performWizard()
self._pre_wizard()
self._perform_wizard()
self._post_wizard()
self.application.set_normal_cursor()
@ -229,70 +228,72 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
The user changed screen resolution or enabled/disabled more screens, so
we need to update the combo box.
"""
self.displayComboBox.clear()
self.displayComboBox.addItems(self.screens.get_screen_list())
self.displayComboBox.setCurrentIndex(self.displayComboBox.count() - 1)
self.display_combo_box.clear()
self.display_combo_box.addItems(self.screens.get_screen_list())
self.display_combo_box.setCurrentIndex(self.display_combo_box.count() - 1)
def onCancelButtonClicked(self):
def on_cancel_button_clicked(self):
"""
Process the triggering of the cancel button.
"""
if self.lastId == FirstTimePage.NoInternet or (self.lastId <= FirstTimePage.Plugins and not self.hasRunWizard):
if self.last_id == FirstTimePage.NoInternet or (self.last_id <= FirstTimePage.Plugins and not self.has_run_wizard):
QtCore.QCoreApplication.exit()
sys.exit()
self.was_download_cancelled = True
while self.themeScreenshotThread.isRunning():
time.sleep(0.1)
# Was the thread created.
if self.theme_screenshot_thread:
while self.theme_screenshot_thread.isRunning():
time.sleep(0.1)
self.application.set_normal_cursor()
def onNoInternetFinishButtonClicked(self):
def on_no_internet_finish_button_clicked(self):
"""
Process the triggering of the "Finish" button on the No Internet page.
"""
self.application.set_busy_cursor()
self._performWizard()
self._perform_wizard()
self.application.set_normal_cursor()
Settings().setValue(u'core/has run wizard', True)
self.close()
def urlGetFile(self, url, fpath):
def url_get_file(self, url, f_path):
""""
Download a file given a URL. The file is retrieved in chunks, giving
the ability to cancel the download at any point.
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
point.
"""
block_count = 0
block_size = 4096
urlfile = urllib2.urlopen(url)
filename = open(fpath, "wb")
url_file = urllib2.urlopen(url)
filename = open(f_path, "wb")
# Download until finished or canceled.
while not self.was_download_cancelled:
data = urlfile.read(block_size)
data = url_file.read(block_size)
if not data:
break
filename.write(data)
block_count += 1
self._downloadProgress(block_count, block_size)
self._download_progress(block_count, block_size)
filename.close()
# Delete file if cancelled, it may be a partial file.
if self.was_download_cancelled:
os.remove(fpath)
os.remove(f_path)
def _buildThemeScreenshots(self):
def _build_theme_screenshots(self):
"""
This method builds the theme screenshots' icons for all items in the
``self.themesListWidget``.
``self.themes_list_widget``.
"""
themes = self.config.get(u'themes', u'files')
themes = themes.split(u',')
for theme in themes:
filename = self.config.get(u'theme_%s' % theme, u'filename')
screenshot = self.config.get(u'theme_%s' % theme, u'screenshot')
for index in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(index)
for index in xrange(self.themes_list_widget.count()):
item = self.themes_list_widget.item(index)
if item.data(QtCore.Qt.UserRole) == filename:
break
item.setIcon(build_icon(os.path.join(unicode(gettempdir(),
get_filesystem_encoding()), u'openlp', screenshot)))
get_filesystem_encoding()), u'openlp', screenshot)))
def _getFileSize(self, url):
"""
@ -305,15 +306,15 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
meta = site.info()
return int(meta.getheaders("Content-Length")[0])
def _downloadProgress(self, count, block_size):
def _download_progress(self, count, block_size):
"""
Calculate and display the download progress.
"""
increment = (count * block_size) - self.previous_size
self._incrementProgressBar(None, increment)
self._increment_progress_bar(None, increment)
self.previous_size = count * block_size
def _incrementProgressBar(self, status_text, increment=1):
def _increment_progress_bar(self, status_text, increment=1):
"""
Update the wizard progress page.
@ -324,28 +325,28 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
The value to increment the progress bar by.
"""
if status_text:
self.progressLabel.setText(status_text)
self.progress_label.setText(status_text)
if increment > 0:
self.progressBar.setValue(self.progressBar.value() + increment)
self.progress_bar.setValue(self.progress_bar.value() + increment)
self.application.process_events()
def _preWizard(self):
def _pre_wizard(self):
"""
Prepare the UI for the process.
"""
self.max_progress = 0
self.finishButton.setVisible(False)
self.finish_button.setVisible(False)
self.application.process_events()
# Loop through the songs list and increase for each selected item
for i in xrange(self.songsListWidget.count()):
for i in xrange(self.songs_list_widget.count()):
self.application.process_events()
item = self.songsListWidget.item(i)
item = self.songs_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole)
size = self._getFileSize(u'%s%s' % (self.web, filename))
self.max_progress += size
# Loop through the Bibles list and increase for each selected item
iterator = QtGui.QTreeWidgetItemIterator(self.biblesTreeWidget)
iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
while iterator.value():
self.application.process_events()
item = iterator.value()
@ -355,9 +356,9 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
self.max_progress += size
iterator += 1
# Loop through the themes list and increase for each selected item
for i in xrange(self.themesListWidget.count()):
for i in xrange(self.themes_list_widget.count()):
self.application.process_events()
item = self.themesListWidget.item(i)
item = self.themes_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole)
size = self._getFileSize(u'%s%s' % (self.web, filename))
@ -365,16 +366,16 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
if self.max_progress:
# Add on 2 for plugins status setting plus a "finished" point.
self.max_progress += 2
self.progressBar.setValue(0)
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(self.max_progress)
self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Setting Up And Downloading'))
self.progressPage.setSubTitle(
self.progress_bar.setValue(0)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(self.max_progress)
self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Setting Up And Downloading'))
self.progress_page.setSubTitle(
translate('OpenLP.FirstTimeWizard', 'Please wait while OpenLP is set up and your data is downloaded.'))
else:
self.progressBar.setVisible(False)
self.progressPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Setting Up'))
self.progressPage.setSubTitle(u'Setup complete.')
self.progress_bar.setVisible(False)
self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Setting Up'))
self.progress_page.setSubTitle(u'Setup complete.')
self.repaint()
self.application.process_events()
# Try to give the wizard a chance to repaint itself
@ -385,44 +386,44 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
Clean up the UI after the process has finished.
"""
if self.max_progress:
self.progressBar.setValue(self.progressBar.maximum())
if self.hasRunWizard:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
self.progress_bar.setValue(self.progress_bar.maximum())
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Download complete. Click the finish button to return to OpenLP.'))
else:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Download complete. Click the finish button to start OpenLP.'))
else:
if self.hasRunWizard:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Click the finish button to return to OpenLP.'))
else:
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard',
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Click the finish button to start OpenLP.'))
self.finishButton.setVisible(True)
self.finishButton.setEnabled(True)
self.cancelButton.setVisible(False)
self.nextButton.setVisible(False)
self.finish_button.setVisible(True)
self.finish_button.setEnabled(True)
self.cancel_button.setVisible(False)
self.next_button.setVisible(False)
self.application.process_events()
def _performWizard(self):
def _perform_wizard(self):
"""
Run the tasks in the wizard.
"""
# Set plugin states
self._incrementProgressBar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...'))
self._setPluginStatus(self.songsCheckBox, u'songs/status')
self._setPluginStatus(self.bibleCheckBox, u'bibles/status')
self._increment_progress_bar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...'))
self._set_plugin_status(self.songs_check_box, u'songs/status')
self._set_plugin_status(self.bible_check_box, u'bibles/status')
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it.
if sys.platform != 'darwin':
self._setPluginStatus(self.presentationCheckBox, u'presentations/status')
self._setPluginStatus(self.imageCheckBox, u'images/status')
self._setPluginStatus(self.mediaCheckBox, u'media/status')
self._setPluginStatus(self.remoteCheckBox, u'remotes/status')
self._setPluginStatus(self.customCheckBox, u'custom/status')
self._setPluginStatus(self.songUsageCheckBox, u'songusage/status')
self._setPluginStatus(self.alertCheckBox, u'alerts/status')
self._set_plugin_status(self.presentation_check_box, u'presentations/status')
self._set_plugin_status(self.image_check_box, u'images/status')
self._set_plugin_status(self.media_check_box, u'media/status')
self._set_plugin_status(self.remote_check_box, u'remotes/status')
self._set_plugin_status(self.custom_check_box, u'custom/status')
self._set_plugin_status(self.song_usage_check_box, u'songusage/status')
self._set_plugin_status(self.alert_check_box, u'alerts/status')
if self.web_access:
# Build directories for downloads
songs_destination = os.path.join(
@ -430,42 +431,42 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
bibles_destination = AppLocation.get_section_data_path(u'bibles')
themes_destination = AppLocation.get_section_data_path(u'themes')
# Download songs
for i in xrange(self.songsListWidget.count()):
item = self.songsListWidget.item(i)
for i in xrange(self.songs_list_widget.count()):
item = self.songs_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked:
filename = item.data(QtCore.Qt.UserRole)
self._incrementProgressBar(self.downloading % filename, 0)
self._increment_progress_bar(self.downloading % filename, 0)
self.previous_size = 0
destination = os.path.join(songs_destination, unicode(filename))
self.urlGetFile(u'%s%s' % (self.web, filename), destination)
self.url_get_file(u'%s%s' % (self.web, filename), destination)
# Download Bibles
bibles_iterator = QtGui.QTreeWidgetItemIterator(
self.biblesTreeWidget)
self.bibles_tree_widget)
while bibles_iterator.value():
item = bibles_iterator.value()
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
bible = item.data(0, QtCore.Qt.UserRole)
self._incrementProgressBar(self.downloading % bible, 0)
self._increment_progress_bar(self.downloading % bible, 0)
self.previous_size = 0
self.urlGetFile(u'%s%s' % (self.web, bible), os.path.join(bibles_destination, bible))
self.url_get_file(u'%s%s' % (self.web, bible), os.path.join(bibles_destination, bible))
bibles_iterator += 1
# Download themes
for i in xrange(self.themesListWidget.count()):
item = self.themesListWidget.item(i)
for i in xrange(self.themes_list_widget.count()):
item = self.themes_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked:
theme = item.data(QtCore.Qt.UserRole)
self._incrementProgressBar(self.downloading % theme, 0)
self._increment_progress_bar(self.downloading % theme, 0)
self.previous_size = 0
self.urlGetFile(u'%s%s' % (self.web, theme), os.path.join(themes_destination, theme))
self.url_get_file(u'%s%s' % (self.web, theme), os.path.join(themes_destination, theme))
# Set Default Display
if self.displayComboBox.currentIndex() != -1:
Settings().setValue(u'core/monitor', self.displayComboBox.currentIndex())
self.screens.set_current_display(self.displayComboBox.currentIndex())
if self.display_combo_box.currentIndex() != -1:
Settings().setValue(u'core/monitor', self.display_combo_box.currentIndex())
self.screens.set_current_display(self.display_combo_box.currentIndex())
# Set Global Theme
if self.themeComboBox.currentIndex() != -1:
Settings().setValue(u'themes/global theme', self.themeComboBox.currentText())
if self.theme_combo_box.currentIndex() != -1:
Settings().setValue(u'themes/global theme', self.theme_combo_box.currentText())
def _setPluginStatus(self, field, tag):
def _set_plugin_status(self, field, tag):
"""
Set the status of a plugin.
"""

View File

@ -55,200 +55,199 @@ class Ui_FirstTimeWizard(object):
"""
The UI widgets for the first time wizard.
"""
def setupUi(self, FirstTimeWizard):
def setupUi(self, first_time_wizard):
"""
Set up the UI.
"""
FirstTimeWizard.setObjectName(u'FirstTimeWizard')
FirstTimeWizard.resize(550, 386)
FirstTimeWizard.setModal(True)
FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
first_time_wizard.setObjectName(u'first_time_wizard')
first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True)
first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1)
self.finishButton = self.button(QtGui.QWizard.FinishButton)
self.noInternetFinishButton = self.button(QtGui.QWizard.CustomButton1)
self.cancelButton = self.button(QtGui.QWizard.CancelButton)
self.nextButton = self.button(QtGui.QWizard.NextButton)
self.backButton = self.button(QtGui.QWizard.BackButton)
add_welcome_page(FirstTimeWizard, u':/wizards/wizard_firsttime.bmp')
self.finish_button = self.button(QtGui.QWizard.FinishButton)
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
self.cancel_button = self.button(QtGui.QWizard.CancelButton)
self.next_button = self.button(QtGui.QWizard.NextButton)
self.back_button = self.button(QtGui.QWizard.BackButton)
add_welcome_page(first_time_wizard, u':/wizards/wizard_firsttime.bmp')
# The plugins page
self.pluginPage = QtGui.QWizardPage()
self.pluginPage.setObjectName(u'pluginPage')
self.pluginLayout = QtGui.QVBoxLayout(self.pluginPage)
self.pluginLayout.setContentsMargins(40, 15, 40, 0)
self.pluginLayout.setObjectName(u'pluginLayout')
self.songsCheckBox = QtGui.QCheckBox(self.pluginPage)
self.songsCheckBox.setChecked(True)
self.songsCheckBox.setObjectName(u'songsCheckBox')
self.pluginLayout.addWidget(self.songsCheckBox)
self.customCheckBox = QtGui.QCheckBox(self.pluginPage)
self.customCheckBox.setChecked(True)
self.customCheckBox.setObjectName(u'customCheckBox')
self.pluginLayout.addWidget(self.customCheckBox)
self.bibleCheckBox = QtGui.QCheckBox(self.pluginPage)
self.bibleCheckBox.setChecked(True)
self.bibleCheckBox.setObjectName(u'bibleCheckBox')
self.pluginLayout.addWidget(self.bibleCheckBox)
self.imageCheckBox = QtGui.QCheckBox(self.pluginPage)
self.imageCheckBox.setChecked(True)
self.imageCheckBox.setObjectName(u'imageCheckBox')
self.pluginLayout.addWidget(self.imageCheckBox)
self.plugin_page = QtGui.QWizardPage()
self.plugin_page.setObjectName(u'plugin_page')
self.plugin_layout = QtGui.QVBoxLayout(self.plugin_page)
self.plugin_layout.setContentsMargins(40, 15, 40, 0)
self.plugin_layout.setObjectName(u'plugin_layout')
self.songs_check_box = QtGui.QCheckBox(self.plugin_page)
self.songs_check_box.setChecked(True)
self.songs_check_box.setObjectName(u'songs_check_box')
self.plugin_layout.addWidget(self.songs_check_box)
self.custom_check_box = QtGui.QCheckBox(self.plugin_page)
self.custom_check_box.setChecked(True)
self.custom_check_box.setObjectName(u'custom_check_box')
self.plugin_layout.addWidget(self.custom_check_box)
self.bible_check_box = QtGui.QCheckBox(self.plugin_page)
self.bible_check_box.setChecked(True)
self.bible_check_box.setObjectName(u'bible_check_box')
self.plugin_layout.addWidget(self.bible_check_box)
self.image_check_box = QtGui.QCheckBox(self.plugin_page)
self.image_check_box.setChecked(True)
self.image_check_box.setObjectName(u'image_check_box')
self.plugin_layout.addWidget(self.image_check_box)
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it.
if sys.platform != 'darwin':
self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage)
self.presentationCheckBox.setChecked(True)
self.presentationCheckBox.setObjectName(u'presentationCheckBox')
self.pluginLayout.addWidget(self.presentationCheckBox)
self.mediaCheckBox = QtGui.QCheckBox(self.pluginPage)
self.mediaCheckBox.setChecked(True)
self.mediaCheckBox.setObjectName(u'mediaCheckBox')
self.pluginLayout.addWidget(self.mediaCheckBox)
self.remoteCheckBox = QtGui.QCheckBox(self.pluginPage)
self.remoteCheckBox.setObjectName(u'remoteCheckBox')
self.pluginLayout.addWidget(self.remoteCheckBox)
self.songUsageCheckBox = QtGui.QCheckBox(self.pluginPage)
self.songUsageCheckBox.setChecked(True)
self.songUsageCheckBox.setObjectName(u'songUsageCheckBox')
self.pluginLayout.addWidget(self.songUsageCheckBox)
self.alertCheckBox = QtGui.QCheckBox(self.pluginPage)
self.alertCheckBox.setChecked(True)
self.alertCheckBox.setObjectName(u'alertCheckBox')
self.pluginLayout.addWidget(self.alertCheckBox)
FirstTimeWizard.setPage(FirstTimePage.Plugins, self.pluginPage)
self.presentation_check_box = QtGui.QCheckBox(self.plugin_page)
self.presentation_check_box.setChecked(True)
self.presentation_check_box.setObjectName(u'presentation_check_box')
self.plugin_layout.addWidget(self.presentation_check_box)
self.media_check_box = QtGui.QCheckBox(self.plugin_page)
self.media_check_box.setChecked(True)
self.media_check_box.setObjectName(u'media_check_box')
self.plugin_layout.addWidget(self.media_check_box)
self.remote_check_box = QtGui.QCheckBox(self.plugin_page)
self.remote_check_box.setObjectName(u'remote_check_box')
self.plugin_layout.addWidget(self.remote_check_box)
self.song_usage_check_box = QtGui.QCheckBox(self.plugin_page)
self.song_usage_check_box.setChecked(True)
self.song_usage_check_box.setObjectName(u'song_usage_check_box')
self.plugin_layout.addWidget(self.song_usage_check_box)
self.alert_check_box = QtGui.QCheckBox(self.plugin_page)
self.alert_check_box.setChecked(True)
self.alert_check_box.setObjectName(u'alert_check_box')
self.plugin_layout.addWidget(self.alert_check_box)
first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page)
# The "you don't have an internet connection" page.
self.noInternetPage = QtGui.QWizardPage()
self.noInternetPage.setObjectName(u'noInternetPage')
self.noInternetLayout = QtGui.QVBoxLayout(self.noInternetPage)
self.noInternetLayout.setContentsMargins(50, 30, 50, 40)
self.noInternetLayout.setObjectName(u'noInternetLayout')
self.noInternetLabel = QtGui.QLabel(self.noInternetPage)
self.noInternetLabel.setWordWrap(True)
self.noInternetLabel.setObjectName(u'noInternetLabel')
self.noInternetLayout.addWidget(self.noInternetLabel)
FirstTimeWizard.setPage(FirstTimePage.NoInternet, self.noInternetPage)
self.no_internet_page = QtGui.QWizardPage()
self.no_internet_page.setObjectName(u'no_internet_page')
self.no_internet_layout = QtGui.QVBoxLayout(self.no_internet_page)
self.no_internet_layout.setContentsMargins(50, 30, 50, 40)
self.no_internet_layout.setObjectName(u'no_internet_layout')
self.no_internet_label = QtGui.QLabel(self.no_internet_page)
self.no_internet_label.setWordWrap(True)
self.no_internet_label.setObjectName(u'no_internet_label')
self.no_internet_layout.addWidget(self.no_internet_label)
first_time_wizard.setPage(FirstTimePage.NoInternet, self.no_internet_page)
# The song samples page
self.songsPage = QtGui.QWizardPage()
self.songsPage.setObjectName(u'songsPage')
self.songsLayout = QtGui.QVBoxLayout(self.songsPage)
self.songsLayout.setContentsMargins(50, 20, 50, 20)
self.songsLayout.setObjectName(u'songsLayout')
self.songsListWidget = QtGui.QListWidget(self.songsPage)
self.songsListWidget.setAlternatingRowColors(True)
self.songsListWidget.setObjectName(u'songsListWidget')
self.songsLayout.addWidget(self.songsListWidget)
FirstTimeWizard.setPage(FirstTimePage.Songs, self.songsPage)
self.songs_page = QtGui.QWizardPage()
self.songs_page.setObjectName(u'songs_page')
self.songs_layout = QtGui.QVBoxLayout(self.songs_page)
self.songs_layout.setContentsMargins(50, 20, 50, 20)
self.songs_layout.setObjectName(u'songs_layout')
self.songs_list_widget = QtGui.QListWidget(self.songs_page)
self.songs_list_widget.setAlternatingRowColors(True)
self.songs_list_widget.setObjectName(u'songs_list_widget')
self.songs_layout.addWidget(self.songs_list_widget)
first_time_wizard.setPage(FirstTimePage.Songs, self.songs_page)
# The Bible samples page
self.biblesPage = QtGui.QWizardPage()
self.biblesPage.setObjectName(u'biblesPage')
self.biblesLayout = QtGui.QVBoxLayout(self.biblesPage)
self.biblesLayout.setContentsMargins(50, 20, 50, 20)
self.biblesLayout.setObjectName(u'biblesLayout')
self.biblesTreeWidget = QtGui.QTreeWidget(self.biblesPage)
self.biblesTreeWidget.setAlternatingRowColors(True)
self.biblesTreeWidget.header().setVisible(False)
self.biblesTreeWidget.setObjectName(u'biblesTreeWidget')
self.biblesLayout.addWidget(self.biblesTreeWidget)
FirstTimeWizard.setPage(FirstTimePage.Bibles, self.biblesPage)
self.bibles_page = QtGui.QWizardPage()
self.bibles_page.setObjectName(u'bibles_page')
self.bibles_layout = QtGui.QVBoxLayout(self.bibles_page)
self.bibles_layout.setContentsMargins(50, 20, 50, 20)
self.bibles_layout.setObjectName(u'bibles_layout')
self.bibles_tree_widget = QtGui.QTreeWidget(self.bibles_page)
self.bibles_tree_widget.setAlternatingRowColors(True)
self.bibles_tree_widget.header().setVisible(False)
self.bibles_tree_widget.setObjectName(u'bibles_tree_widget')
self.bibles_layout.addWidget(self.bibles_tree_widget)
first_time_wizard.setPage(FirstTimePage.Bibles, self.bibles_page)
# The theme samples page
self.themesPage = QtGui.QWizardPage()
self.themesPage.setObjectName(u'themesPage')
self.themesLayout = QtGui.QVBoxLayout(self.themesPage)
self.themesLayout.setContentsMargins(20, 50, 20, 60)
self.themesLayout.setObjectName(u'themesLayout')
self.themesListWidget = QtGui.QListWidget(self.themesPage)
self.themesListWidget.setViewMode(QtGui.QListView.IconMode)
self.themesListWidget.setMovement(QtGui.QListView.Static)
self.themesListWidget.setFlow(QtGui.QListView.LeftToRight)
self.themesListWidget.setSpacing(4)
self.themesListWidget.setUniformItemSizes(True)
self.themesListWidget.setIconSize(QtCore.QSize(133, 100))
self.themesListWidget.setWrapping(False)
self.themesListWidget.setObjectName(u'themesListWidget')
self.themesLayout.addWidget(self.themesListWidget)
FirstTimeWizard.setPage(FirstTimePage.Themes, self.themesPage)
self.themes_page = QtGui.QWizardPage()
self.themes_page.setObjectName(u'themes_page')
self.themes_layout = QtGui.QVBoxLayout(self.themes_page)
self.themes_layout.setContentsMargins(20, 50, 20, 60)
self.themes_layout.setObjectName(u'themes_layout')
self.themes_list_widget = QtGui.QListWidget(self.themes_page)
self.themes_list_widget.setViewMode(QtGui.QListView.IconMode)
self.themes_list_widget.setMovement(QtGui.QListView.Static)
self.themes_list_widget.setFlow(QtGui.QListView.LeftToRight)
self.themes_list_widget.setSpacing(4)
self.themes_list_widget.setUniformItemSizes(True)
self.themes_list_widget.setIconSize(QtCore.QSize(133, 100))
self.themes_list_widget.setWrapping(False)
self.themes_list_widget.setObjectName(u'themes_list_widget')
self.themes_layout.addWidget(self.themes_list_widget)
first_time_wizard.setPage(FirstTimePage.Themes, self.themes_page)
# the default settings page
self.defaultsPage = QtGui.QWizardPage()
self.defaultsPage.setObjectName(u'defaultsPage')
self.defaultsLayout = QtGui.QFormLayout(self.defaultsPage)
self.defaultsLayout.setContentsMargins(50, 20, 50, 20)
self.defaultsLayout.setObjectName(u'defaultsLayout')
self.displayLabel = QtGui.QLabel(self.defaultsPage)
self.displayLabel.setObjectName(u'displayLabel')
self.displayComboBox = QtGui.QComboBox(self.defaultsPage)
self.displayComboBox.setEditable(False)
self.displayComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert)
self.displayComboBox.setObjectName(u'displayComboBox')
self.defaultsLayout.addRow(self.displayLabel, self.displayComboBox)
self.themeLabel = QtGui.QLabel(self.defaultsPage)
self.themeLabel.setObjectName(u'themeLabel')
self.themeComboBox = QtGui.QComboBox(self.defaultsPage)
self.themeComboBox.setEditable(False)
self.themeComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert)
self.themeComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.themeComboBox.setObjectName(u'themeComboBox')
self.defaultsLayout.addRow(self.themeLabel, self.themeComboBox)
FirstTimeWizard.setPage(FirstTimePage.Defaults, self.defaultsPage)
self.defaults_page = QtGui.QWizardPage()
self.defaults_page.setObjectName(u'defaults_page')
self.defaults_layout = QtGui.QFormLayout(self.defaults_page)
self.defaults_layout.setContentsMargins(50, 20, 50, 20)
self.defaults_layout.setObjectName(u'defaults_layout')
self.display_label = QtGui.QLabel(self.defaults_page)
self.display_label.setObjectName(u'display_label')
self.display_combo_box = QtGui.QComboBox(self.defaults_page)
self.display_combo_box.setEditable(False)
self.display_combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
self.display_combo_box.setObjectName(u'display_combo_box')
self.defaults_layout.addRow(self.display_label, self.display_combo_box)
self.theme_label = QtGui.QLabel(self.defaults_page)
self.theme_label.setObjectName(u'theme_label')
self.theme_combo_box = QtGui.QComboBox(self.defaults_page)
self.theme_combo_box.setEditable(False)
self.theme_combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
self.theme_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.theme_combo_box.setObjectName(u'theme_combo_box')
self.defaults_layout.addRow(self.theme_label, self.theme_combo_box)
first_time_wizard.setPage(FirstTimePage.Defaults, self.defaults_page)
# Progress page
self.progressPage = QtGui.QWizardPage()
self.progressPage.setObjectName(u'progressPage')
self.progressLayout = QtGui.QVBoxLayout(self.progressPage)
self.progressLayout.setMargin(48)
self.progressLayout.setObjectName(u'progressLayout')
self.progressLabel = QtGui.QLabel(self.progressPage)
self.progressLabel.setObjectName(u'progressLabel')
self.progressLayout.addWidget(self.progressLabel)
self.progressBar = QtGui.QProgressBar(self.progressPage)
self.progressBar.setObjectName(u'progressBar')
self.progressLayout.addWidget(self.progressBar)
FirstTimeWizard.setPage(FirstTimePage.Progress, self.progressPage)
self.retranslateUi(FirstTimeWizard)
self.progress_page = QtGui.QWizardPage()
self.progress_page.setObjectName(u'progress_page')
self.progress_layout = QtGui.QVBoxLayout(self.progress_page)
self.progress_layout.setMargin(48)
self.progress_layout.setObjectName(u'progress_layout')
self.progress_label = QtGui.QLabel(self.progress_page)
self.progress_label.setObjectName(u'progress_label')
self.progress_layout.addWidget(self.progress_label)
self.progress_bar = QtGui.QProgressBar(self.progress_page)
self.progress_bar.setObjectName(u'progress_bar')
self.progress_layout.addWidget(self.progress_bar)
first_time_wizard.setPage(FirstTimePage.Progress, self.progress_page)
self.retranslateUi(first_time_wizard)
def retranslateUi(self, FirstTimeWizard):
def retranslateUi(self, first_time_wizard):
"""
Translate the UI on the fly
"""
FirstTimeWizard.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard'))
first_time_wizard.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard'))
self.title_label.setText(u'<span style="font-size:14pt; font-weight:600;">%s</span>' %
translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard'))
self.information_label.setText(translate('OpenLP.FirstTimeWizard',
'This wizard will help you to configure OpenLP for initial use. Click the next button below to start.'))
self.pluginPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
self.pluginPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
self.songsCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
self.customCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Custom Slides'))
self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible'))
self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Images'))
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Custom Slides'))
self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Bible'))
self.image_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Images'))
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it.
if sys.platform != 'darwin':
self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Presentations'))
self.mediaCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)'))
self.remoteCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Allow remote access'))
self.songUsageCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Monitor Song Usage'))
self.alertCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Allow Alerts'))
self.noInternetPage.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))
self.noInternetPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.'))
self.noInternetText = translate('OpenLP.FirstTimeWizard',
'No Internet connection was found. The First Time Wizard needs an '
'Internet connection in order to be able to download sample '
'songs, Bibles and themes. Click the Finish button now to start '
'OpenLP with initial settings and no sample data.\n\nTo re-run the '
'First Time Wizard and import this sample data at a later time, '
'check your Internet connection and re-run this wizard by '
'selecting "Tools/Re-run First Time Wizard" from OpenLP.')
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Presentations'))
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)'))
self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Allow remote access'))
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Monitor Song Usage'))
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Allow Alerts'))
self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))
self.no_internet_page.setSubTitle(
translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.'))
self.no_internet_text = translate('OpenLP.FirstTimeWizard',
'No Internet connection was found. The First Time Wizard needs an Internet connection in order to be able '
'to download sample songs, Bibles and themes. Click the Finish button now to start OpenLP with initial '
'settings and no sample data.\n\nTo re-run the First Time Wizard and import this sample data at a later '
'time, check your Internet connection and re-run this wizard by selecting "Tools/Re-run First Time Wizard" '
'from OpenLP.')
self.cancelWizardText = translate('OpenLP.FirstTimeWizard',
'\n\nTo cancel the First Time Wizard completely (and not start OpenLP), click the Cancel button now.')
self.songsPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))
self.songsPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
self.biblesPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))
self.biblesPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.'))
self.themesPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes'))
self.themesPage.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.'))
self.defaultsPage.setTitle(translate('OpenLP.FirstTimeWizard', 'Default Settings'))
self.defaultsPage.setSubTitle(translate('OpenLP.FirstTimeWizard',
self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))
self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))
self.bibles_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.'))
self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes'))
self.themes_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.'))
self.defaults_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Default Settings'))
self.defaults_page.setSubTitle(translate('OpenLP.FirstTimeWizard',
'Set up default settings to be used by OpenLP.'))
self.displayLabel.setText(translate('OpenLP.FirstTimeWizard', 'Default output display:'))
self.themeLabel.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:'))
self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
FirstTimeWizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))
self.display_label.setText(translate('OpenLP.FirstTimeWizard', 'Default output display:'))
self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:'))
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))

View File

@ -328,6 +328,9 @@ class MainDisplay(Display):
self.display_image(self.service_item.bg_image_bytes)
else:
self.display_image(None)
# Update the preview frame.
if self.is_live:
self.live_controller.update_preview()
# clear the cache
self.override = {}

View File

@ -100,10 +100,10 @@ class Ui_MainWindow(object):
self.main_contentLayout.setMargin(0)
self.main_contentLayout.setObjectName(u'main_contentLayout')
main_window.setCentralWidget(self.main_content)
self.controlSplitter = QtGui.QSplitter(self.main_content)
self.controlSplitter.setOrientation(QtCore.Qt.Horizontal)
self.controlSplitter.setObjectName(u'controlSplitter')
self.main_contentLayout.addWidget(self.controlSplitter)
self.control_splitter = QtGui.QSplitter(self.main_content)
self.control_splitter.setOrientation(QtCore.Qt.Horizontal)
self.control_splitter.setObjectName(u'control_splitter')
self.main_contentLayout.addWidget(self.control_splitter)
# Create slide controllers
self.preview_controller = SlideController(self)
self.live_controller = SlideController(self, True)
@ -113,9 +113,9 @@ class Ui_MainWindow(object):
panel_locked = Settings().value(u'user interface/lock panel')
self.live_controller.panel.setVisible(live_visible)
# Create menu
self.menuBar = QtGui.QMenuBar(main_window)
self.menuBar.setObjectName(u'menuBar')
self.file_menu = QtGui.QMenu(self.menuBar)
self.menu_bar = QtGui.QMenuBar(main_window)
self.menu_bar.setObjectName(u'menu_bar')
self.file_menu = QtGui.QMenu(self.menu_bar)
self.file_menu.setObjectName(u'fileMenu')
self.recent_files_menu = QtGui.QMenu(self.file_menu)
self.recent_files_menu.setObjectName(u'recentFilesMenu')
@ -124,22 +124,22 @@ class Ui_MainWindow(object):
self.file_export_menu = QtGui.QMenu(self.file_menu)
self.file_export_menu.setObjectName(u'file_export_menu')
# View Menu
self.view_menu = QtGui.QMenu(self.menuBar)
self.view_menu = QtGui.QMenu(self.menu_bar)
self.view_menu.setObjectName(u'viewMenu')
self.view_modeMenu = QtGui.QMenu(self.view_menu)
self.view_modeMenu.setObjectName(u'viewModeMenu')
# Tools Menu
self.tools_menu = QtGui.QMenu(self.menuBar)
self.tools_menu = QtGui.QMenu(self.menu_bar)
self.tools_menu.setObjectName(u'tools_menu')
# Settings Menu
self.settings_menu = QtGui.QMenu(self.menuBar)
self.settings_menu = QtGui.QMenu(self.menu_bar)
self.settings_menu.setObjectName(u'settingsMenu')
self.settings_language_menu = QtGui.QMenu(self.settings_menu)
self.settings_language_menu.setObjectName(u'settingsLanguageMenu')
# Help Menu
self.help_menu = QtGui.QMenu(self.menuBar)
self.help_menu = QtGui.QMenu(self.menu_bar)
self.help_menu.setObjectName(u'helpMenu')
main_window.setMenuBar(self.menuBar)
main_window.setMenuBar(self.menu_bar)
self.status_bar = QtGui.QStatusBar(main_window)
self.status_bar.setObjectName(u'status_bar')
main_window.setStatusBar(self.status_bar)
@ -237,7 +237,7 @@ class Ui_MainWindow(object):
self.view_live_panel = create_action(main_window, u'viewLivePanel',
can_shortcuts=True, checked=live_visible,
category=UiStrings().View, triggers=self.set_live_panel_visibility)
self.lockPanel = create_action(main_window, u'lockPanel',
self.lock_panel = create_action(main_window, u'lockPanel',
can_shortcuts=True, checked=panel_locked,
category=UiStrings().View,
triggers=self.set_lock_panel)
@ -310,6 +310,10 @@ class Ui_MainWindow(object):
can_shortcuts=True,
category=UiStrings().Help, triggers=self.on_online_help_clicked)
self.web_site_item = create_action(main_window, u'webSiteItem', can_shortcuts=True, category=UiStrings().Help)
# Shortcuts not connected to buttons or menu entires.
self.search_shortcut_action = create_action(main_window,
u'searchShortcut', can_shortcuts=True, category=translate('OpenLP.MainWindow', 'General'),
triggers=self.on_search_shortcut_triggered)
add_actions(self.file_import_menu, (self.settings_import_item, None, self.import_theme_item,
self.import_language_item))
add_actions(self.file_export_menu, (self.settings_export_item, None, self.export_theme_item,
@ -321,7 +325,7 @@ class Ui_MainWindow(object):
add_actions(self.view_modeMenu, (self.mode_default_Item, self.mode_setup_item, self.mode_live_item))
add_actions(self.view_menu, (self.view_modeMenu.menuAction(), None, self.view_media_manager_item,
self.view_service_manager_item, self.view_theme_manager_item, None, self.view_preview_panel,
self.view_live_panel, None, self.lockPanel))
self.view_live_panel, None, self.lock_panel))
# i18n add Language Actions
add_actions(self.settings_language_menu, (self.auto_language_item, None))
add_actions(self.settings_language_menu, self.language_group.actions())
@ -342,8 +346,9 @@ class Ui_MainWindow(object):
self.about_item))
else:
add_actions(self.help_menu, (self.on_line_help_item, None, self.web_site_item, self.about_item))
add_actions(self.menuBar, (self.file_menu.menuAction(), self.view_menu.menuAction(),
add_actions(self.menu_bar, (self.file_menu.menuAction(), self.view_menu.menuAction(),
self.tools_menu.menuAction(), self.settings_menu.menuAction(), self.help_menu.menuAction()))
add_actions(self, [self.search_shortcut_action])
# Initialise the translation
self.retranslateUi(main_window)
self.media_tool_box.setCurrentIndex(0)
@ -356,12 +361,11 @@ class Ui_MainWindow(object):
self.set_lock_panel(panel_locked)
self.settingsImported = False
def retranslateUi(self, mainWindow):
def retranslateUi(self, main_window):
"""
Set up the translation system
"""
mainWindow.mainTitle = UiStrings().OLPV2x
mainWindow.setWindowTitle(mainWindow.mainTitle)
main_window.setWindowTitle(UiStrings().OLPV2x)
self.file_menu.setTitle(translate('OpenLP.MainWindow', '&File'))
self.file_import_menu.setTitle(translate('OpenLP.MainWindow', '&Import'))
self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
@ -423,8 +427,8 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Toggle the visibility of the preview panel.'))
self.view_live_panel.setText(translate('OpenLP.MainWindow', '&Live Panel'))
self.view_live_panel.setToolTip(translate('OpenLP.MainWindow', 'Toggle Live Panel'))
self.lockPanel.setText(translate('OpenLP.MainWindow', 'L&ock Panels'))
self.lockPanel.setStatusTip(translate('OpenLP.MainWindow', 'Prevent the panels being moved.'))
self.lock_panel.setText(translate('OpenLP.MainWindow', 'L&ock Panels'))
self.lock_panel.setStatusTip(translate('OpenLP.MainWindow', 'Prevent the panels being moved.'))
self.view_live_panel.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the live panel.'))
self.settingsPluginListItem.setText(translate('OpenLP.MainWindow', '&Plugin List'))
self.settingsPluginListItem.setStatusTip(translate('OpenLP.MainWindow', 'List the Plugins'))
@ -433,6 +437,9 @@ class Ui_MainWindow(object):
if os.name == u'nt':
self.offlineHelpItem.setText(translate('OpenLP.MainWindow', '&User Guide'))
self.on_line_help_item.setText(translate('OpenLP.MainWindow', '&Online Help'))
self.search_shortcut_action.setText(UiStrings().Search)
self.search_shortcut_action.setToolTip(
translate('OpenLP.MainWindow', 'Jump to the search box of the current active plugin.'))
self.web_site_item.setText(translate('OpenLP.MainWindow', '&Web Site'))
for item in self.language_group.actions():
item.setText(item.objectName())
@ -467,8 +474,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
"""
This constructor sets up the interface, the various managers, and the
plugins.
This constructor sets up the interface, the various managers, and the plugins.
"""
QtGui.QMainWindow.__init__(self)
Registry().register(u'main_window', self)
@ -491,7 +497,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.new_data_path = None
self.copy_data = False
Settings().set_up_default_values()
self.service_not_saved = False
self.about_form = AboutForm(self)
self.media_controller = MediaController()
self.settings_form = SettingsForm(self)
@ -536,22 +541,31 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Registry().register_function(u'theme_update_global', self.default_theme_changed)
Registry().register_function(u'openlp_version_check', self.version_notice)
Registry().register_function(u'config_screen_changed', self.screen_changed)
Registry().register_function(u'bootstrap_post_set_up', self.restore_current_media_manager_item)
self.renderer = Renderer()
log.info(u'Load data from Settings')
if Settings().value(u'advanced/save current plugin'):
savedPlugin = Settings().value(u'advanced/current media plugin')
if savedPlugin != -1:
self.media_tool_box.setCurrentIndex(savedPlugin)
# Reset the cursor
self.application.set_normal_cursor()
def setAutoLanguage(self, value):
def restore_current_media_manager_item(self):
"""
Set the language to automatic.
Called on start up to restore the last active media plugin.
"""
self.language_group.setDisabled(value)
LanguageManager.auto_language = value
LanguageManager.set_language(self.language_group.checkedAction())
log.info(u'Load data from Settings')
if Settings().value(u'advanced/save current plugin'):
saved_plugin_id = Settings().value(u'advanced/current media plugin')
if saved_plugin_id != -1:
self.media_tool_box.setCurrentIndex(saved_plugin_id)
def on_search_shortcut_triggered(self):
"""
Called when the search shotcut has been pressed.
"""
# Make sure the media_dock is visible.
if not self.media_manager_dock.isVisible():
self.media_manager_dock.setVisible(True)
widget = self.media_tool_box.currentWidget()
if widget:
widget.on_focus()
def on_media_tool_box_changed(self, index):
"""
@ -966,8 +980,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
self.setViewMode(False, True, False, False, True, u'live')
def setViewMode(self, media=True, service=True, theme=True, preview=True,
live=True, mode=u''):
def setViewMode(self, media=True, service=True, theme=True, preview=True, live=True, mode=u''):
"""
Set OpenLP to a different view mode.
"""
@ -982,8 +995,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def screen_changed(self):
"""
The screen has changed so we have to update components such as the
renderer.
The screen has changed so we have to update components such as the renderer.
"""
log.debug(u'screen_changed')
self.application.set_busy_cursor()
@ -1068,43 +1080,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# Needed for Windows to stop crashes on exit
Registry().remove(u'application')
def service_changed(self, reset=False, serviceName=None):
def set_service_modified(self, modified, file_name):
"""
Hook to change the main window title when the service changes
``reset``
Shows if the service has been cleared or saved
``serviceName``
The name of the service (if it has one)
"""
if not serviceName:
service_name = u'(unsaved service)'
else:
service_name = serviceName
if reset:
self.service_not_saved = False
title = u'%s - %s' % (self.mainTitle, service_name)
else:
self.service_not_saved = True
title = u'%s - %s*' % (self.mainTitle, service_name)
self.setWindowTitle(title)
def set_service_modified(self, modified, fileName):
"""
This method is called from the ServiceManager to set the title of the
main window.
This method is called from the ServiceManager to set the title of the main window.
``modified``
Whether or not this service has been modified.
``fileName``
``file_name``
The file name of the service file.
"""
if modified:
title = u'%s - %s*' % (self.mainTitle, fileName)
title = u'%s - %s*' % (UiStrings().OLPV2x, file_name)
else:
title = u'%s - %s' % (self.mainTitle, fileName)
title = u'%s - %s' % (UiStrings().OLPV2x, file_name)
self.setWindowTitle(title)
def show_status_message(self, message):
@ -1140,8 +1129,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def set_preview_panel_visibility(self, visible):
"""
Sets the visibility of the preview panel including saving the setting
and updating the menu.
Sets the visibility of the preview panel including saving the setting and updating the menu.
``visible``
A bool giving the state to set the panel to
@ -1178,8 +1166,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def set_live_panel_visibility(self, visible):
"""
Sets the visibility of the live panel including saving the setting and
updating the menu.
Sets the visibility of the live panel including saving the setting and updating the menu.
``visible``
A bool giving the state to set the panel to
@ -1208,7 +1195,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.restoreState(settings.value(u'main window state'))
self.live_controller.splitter.restoreState(settings.value(u'live splitter geometry'))
self.preview_controller.splitter.restoreState(settings.value(u'preview splitter geometry'))
self.controlSplitter.restoreState(settings.value(u'main window splitter geometry'))
self.control_splitter.restoreState(settings.value(u'main window splitter geometry'))
settings.endGroup()
def save_settings(self):
@ -1229,13 +1216,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
settings.setValue(u'main window geometry', self.saveGeometry())
settings.setValue(u'live splitter geometry', self.live_controller.splitter.saveState())
settings.setValue(u'preview splitter geometry', self.preview_controller.splitter.saveState())
settings.setValue(u'main window splitter geometry', self.controlSplitter.saveState())
settings.setValue(u'main window splitter geometry', self.control_splitter.saveState())
settings.endGroup()
def update_recent_files_menu(self):
"""
Updates the recent file menu with the latest list of service files
accessed.
Updates the recent file menu with the latest list of service files accessed.
"""
recent_file_count = Settings().value(u'advanced/recent file count')
existing_recent_files = [recentFile for recentFile in self.recent_files

View File

@ -44,113 +44,57 @@ VIDEO_CSS = u"""
z-index:3;
background-color: %(bgcolor)s;
}
#video1 {
background-color: %(bgcolor)s;
z-index:4;
}
#video2 {
#video {
background-color: %(bgcolor)s;
z-index:4;
}
"""
VIDEO_JS = u"""
var video_timer = null;
var current_video = '1';
function show_video(state, path, volume, loop, variable_value){
// Sometimes video.currentTime stops slightly short of video.duration and video.ended is intermittent!
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
// onended event, hence the setInterval()
// In addition, setting the currentTime attribute to zero to restart
// the video raises an INDEX_SIZE_ERROR: DOM Exception 1
// To complicate it further, sometimes vid.currentTime stops
// slightly short of vid.duration and vid.ended is intermittent!
//
// Note, currently the background may go black between loops. Not
// desirable. Need to investigate using two <video>'s, and hiding/
// preloading one, and toggle between the two when looping.
if(current_video=='1'){
var vid = document.getElementById('video1');
var vid2 = document.getElementById('video2');
} else {
var vid = document.getElementById('video2');
var vid2 = document.getElementById('video1');
}
var video = document.getElementById('video');
if(volume != null){
vid.volume = volume;
vid2.volume = volume;
video.volume = volume;
}
switch(state){
case 'init':
vid.src = 'file:///' + path;
vid2.src = 'file:///' + path;
if(loop == null) loop = false;
vid.looping = loop;
vid2.looping = loop;
vid.load();
break;
case 'load':
vid2.style.visibility = 'hidden';
vid2.load();
video.src = 'file:///' + path;
if(loop == true) {
video.loop = true;
}
video.load();
break;
case 'play':
vid.play();
if(vid.looping){
video_timer = setInterval(
function() {
show_video('poll');
}, 200);
}
video.play();
break;
case 'pause':
if(video_timer!=null){
clearInterval(video_timer);
video_timer = null;
}
vid.pause();
video.pause();
break;
case 'stop':
show_video('pause');
vid.currentTime = 0;
break;
case 'poll':
if(vid.ended||vid.currentTime+0.2>vid.duration)
show_video('swap');
break;
case 'swap':
show_video('pause');
if(current_video=='1')
current_video = '2';
else
current_video = '1';
show_video('load');
show_video('play');
show_video('setVisible',null,null,null,'visible');
video.currentTime = 0;
break;
case 'close':
show_video('stop');
vid.src = '';
vid2.src = '';
video.src = '';
break;
case 'length':
return vid.duration;
case 'currentTime':
return vid.currentTime;
return video.duration;
case 'current_time':
return video.currentTime;
case 'seek':
// doesnt work currently
vid.currentTime = varVal;
video.currentTime = variable_value;
break;
case 'isEnded':
return vid.ended;
return video.ended;
case 'setVisible':
vid.style.visibility = varVal;
video.style.visibility = variable_value;
break;
case 'setBackBoard':
var back = document.getElementById('videobackboard');
back.style.visibility = varVal;
back.style.visibility = variable_value;
break;
}
}
@ -158,10 +102,7 @@ VIDEO_JS = u"""
VIDEO_HTML = u"""
<div id="videobackboard" class="size" style="visibility:hidden"></div>
<video id="video1" class="size" style="visibility:hidden" autobuffer preload>
</video>
<video id="video2" class="size" style="visibility:hidden" autobuffer preload>
</video>
<video id="video" class="size" style="visibility:hidden" autobuffer preload></video>
"""
FLASH_CSS = u"""
@ -173,25 +114,21 @@ FLASH_CSS = u"""
FLASH_JS = u"""
function getFlashMovieObject(movieName)
{
if (window.document[movieName])
{
if (window.document[movieName]){
return window.document[movieName];
}
if (document.embeds && document.embeds[movieName])
if (document.embeds && document.embeds[movieName]){
return document.embeds[movieName];
}
}
function show_flash(state, path, volume, varVal){
function show_flash(state, path, volume, variable_value){
var text = document.getElementById('flash');
var flashMovie = getFlashMovieObject("OpenLPFlashMovie");
var src = "src = 'file:///" + path + "'";
var view_parm = " wmode='opaque'" +
" width='100%%'" +
" height='100%%'";
var swf_parm = " name='OpenLPFlashMovie'" +
" autostart='true' loop='false' play='true'" +
" hidden='false' swliveconnect='true' allowscriptaccess='always'" +
" volume='" + volume + "'";
var view_parm = " wmode='opaque'" + " width='100%%'" + " height='100%%'";
var swf_parm = " name='OpenLPFlashMovie'" + " autostart='true' loop='false' play='true'" +
" hidden='false' swliveconnect='true' allowscriptaccess='always'" + " volume='" + volume + "'";
switch(state){
case 'load':
@ -217,15 +154,16 @@ FLASH_JS = u"""
break;
case 'length':
return flashMovie.TotalFrames();
case 'currentTime':
case 'current_time':
return flashMovie.CurrentFrame();
case 'seek':
// flashMovie.GotoFrame(varVal);
// flashMovie.GotoFrame(variable_value);
break;
case 'isEnded':
return false;//TODO check flash end
//TODO check flash end
return false;
case 'setVisible':
text.style.visibility = varVal;
text.style.visibility = variable_value;
break;
}
}
@ -338,7 +276,7 @@ class WebkitPlayer(MediaPlayer):
controller.media_info.is_flash = True
js = u'show_flash("load","%s");' % (path.replace(u'\\', u'\\\\'))
else:
js = u'show_video("init", "%s", %s, %s);' % (path.replace(u'\\', u'\\\\'), str(vol), loop)
js = u'show_video("load", "%s", %s, %s);' % (path.replace(u'\\', u'\\\\'), str(vol), loop)
display.frame.evaluateJavaScript(js)
return True
@ -447,25 +385,25 @@ class WebkitPlayer(MediaPlayer):
"""
controller = display.controller
if controller.media_info.is_flash:
currentTime = display.frame.evaluateJavaScript(u'show_flash("currentTime");')
current_time = display.frame.evaluateJavaScript(u'show_flash("current_time");')
length = display.frame.evaluateJavaScript(u'show_flash("length");')
else:
if display.frame.evaluateJavaScript(u'show_video("isEnded");'):
self.stop(display)
currentTime = display.frame.evaluateJavaScript(u'show_video("currentTime");')
current_time = display.frame.evaluateJavaScript(u'show_video("current_time");')
# check if conversion was ok and value is not 'NaN'
if currentTime and currentTime != float('inf'):
currentTime = int(currentTime * 1000)
if current_time and current_time != float('inf'):
current_time = int(current_time * 1000)
length = display.frame.evaluateJavaScript(u'show_video("length");')
# check if conversion was ok and value is not 'NaN'
if length and length != float('inf'):
length = int(length * 1000)
if currentTime > 0:
if current_time:
controller.media_info.length = length
controller.seek_slider.setMaximum(length)
if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True)
controller.seek_slider.setSliderPosition(currentTime)
controller.seek_slider.setSliderPosition(current_time)
controller.seek_slider.blockSignals(False)
def get_info(self):

View File

@ -35,6 +35,7 @@ import logging
import os
import shutil
import zipfile
import json
from tempfile import mkstemp
from datetime import datetime, timedelta
@ -458,7 +459,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
path_file_name = unicode(self.file_name())
path, file_name = os.path.split(path_file_name)
base_name = os.path.splitext(file_name)[0]
service_file_name = '%s.osd' % base_name
service_file_name = '%s.osj' % base_name
log.debug(u'ServiceManager.save_file - %s', path_file_name)
Settings().setValue(self.main_window.service_manager_settings_section + u'/last directory', path)
service = []
@ -512,7 +513,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
file_size = os.path.getsize(file_item)
total_size += file_size
log.debug(u'ServiceManager.save_file - ZIP contents size is %i bytes' % total_size)
service_content = cPickle.dumps(service)
service_content = json.dumps(service)
# Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be extracted using unzip in UNIX.
allow_zip_64 = (total_size > 2147483648 + len(service_content))
log.debug(u'ServiceManager.save_file - allowZip64 is %s' % allow_zip_64)
@ -572,7 +573,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
path_file_name = unicode(self.file_name())
path, file_name = os.path.split(path_file_name)
base_name = os.path.splitext(file_name)[0]
service_file_name = '%s.osd' % base_name
service_file_name = '%s.osj' % base_name
log.debug(u'ServiceManager.save_file - %s', path_file_name)
Settings().setValue(self.main_window.service_manager_settings_section + u'/last directory', path)
service = []
@ -585,7 +586,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
#TODO: check for file item on save.
service.append({u'serviceitem': service_item})
self.main_window.increment_progress_bar()
service_content = cPickle.dumps(service)
service_content = json.dumps(service)
zip_file = None
success = True
self.main_window.increment_progress_bar()
@ -698,11 +699,14 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
log.debug(u'Extract file: %s', osfile)
zip_info.filename = osfile
zip_file.extract(zip_info, self.servicePath)
if osfile.endswith(u'osd'):
if osfile.endswith(u'osj') or osfile.endswith(u'osd'):
p_file = os.path.join(self.servicePath, osfile)
if 'p_file' in locals():
file_to = open(p_file, u'r')
items = cPickle.load(file_to)
if p_file.endswith(u'osj'):
items = json.load(file_to)
else:
items = cPickle.load(file_to)
file_to.close()
self.new_file()
self.set_file_name(file_name)

View File

@ -129,11 +129,18 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
continue
item = QtGui.QTreeWidgetItem([category.name])
for action in category.actions:
actionText = REMOVE_AMPERSAND.sub('', action.text())
actionItem = QtGui.QTreeWidgetItem([actionText])
actionItem.setIcon(0, action.icon())
actionItem.setData(0, QtCore.Qt.UserRole, action)
item.addChild(actionItem)
action_text = REMOVE_AMPERSAND.sub('', action.text())
action_item = QtGui.QTreeWidgetItem([action_text])
action_item.setIcon(0, action.icon())
action_item.setData(0, QtCore.Qt.UserRole, action)
tool_tip_text = action.toolTip()
# Only display tool tips if they are helpful.
if tool_tip_text != action_text:
# Display the tool tip in all three colums.
action_item.setToolTip(0, tool_tip_text)
action_item.setToolTip(1, tool_tip_text)
action_item.setToolTip(2, tool_tip_text)
item.addChild(action_item)
self.treeWidget.addTopLevelItem(item)
item.setExpanded(True)
self.refreshShortcutList()

View File

@ -121,7 +121,7 @@ class SlideController(DisplayController):
self.service_item = None
self.slide_limits = None
self.update_slide_limits()
self.panel = QtGui.QWidget(parent.controlSplitter)
self.panel = QtGui.QWidget(parent.control_splitter)
self.slideList = {}
self.slide_count = 0
self.slide_image = None
@ -566,8 +566,7 @@ class SlideController(DisplayController):
max_width = self.preview_frame.width() - self.grid.margin() * 2
self.slide_preview.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
self.preview_display.screen = {
u'size': self.preview_display.geometry()}
self.preview_display.screen = {u'size': self.preview_display.geometry()}
self.on_controller_size_changed(self.controller.width())
def on_controller_size_changed(self, width):
@ -592,7 +591,7 @@ class SlideController(DisplayController):
"""
request = self.sender().text()
slide_no = self.slideList[request]
width = self.main_window.controlSplitter.sizes()[self.split]
width = self.main_window.control_splitter.sizes()[self.split]
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
self.slide_selected()
@ -697,9 +696,8 @@ class SlideController(DisplayController):
def add_service_manager_item(self, item, slide_no):
"""
Method to install the service item into the controller and
request the correct toolbar for the plugin.
Called by ServiceManager
Method to install the service item into the controller and request the correct toolbar for the plugin. Called by
:class:`~openlp.core.ui.ServiceManager`
"""
log.debug(u'add_service_manager_item live = %s' % self.is_live)
# If no valid slide number is specified we take the first one, but we remember the initial value to see if we
@ -724,8 +722,7 @@ class SlideController(DisplayController):
def _process_item(self, service_item, slideno):
"""
Loads a ServiceItem into the system from ServiceManager
Display the slide number passed
Loads a ServiceItem into the system from ServiceManager. Display the slide number passed.
"""
log.debug(u'processManagerItem live = %s' % self.is_live)
self.on_stop_loop()
@ -734,7 +731,8 @@ class SlideController(DisplayController):
self.service_item = copy.copy(service_item)
if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
self._reset_blank()
Registry().execute(u'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
Registry().execute(
u'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
self.slideList = {}
if self.is_live:
self.song_menu.menu().clear()
@ -759,7 +757,7 @@ class SlideController(DisplayController):
self.display.audio_player.play()
self.set_audio_items_visibility(True)
row = 0
width = self.main_window.controlSplitter.sizes()[self.split]
width = self.main_window.control_splitter.sizes()[self.split]
for framenumber, frame in enumerate(self.service_item.get_frames()):
if self.service_item.is_text():
if frame[u'verseTag']:

View File

@ -53,27 +53,25 @@ log = logging.getLogger(__name__)
class AppLocation(object):
"""
The :class:`AppLocation` class is a static class which retrieves a
directory based on the directory type.
The :class:`AppLocation` class is a static class which retrieves a directory based on the directory type.
"""
AppDir = 1
ConfigDir = 2
DataDir = 3
PluginsDir = 4
VersionDir = 5
CacheDir = 6
LanguageDir = 7
DataDir = 2
PluginsDir = 3
VersionDir = 4
CacheDir = 5
LanguageDir = 6
# Base path where data/config/cache dir is located
BaseDir = None
@staticmethod
def get_directory(dir_type=1):
def get_directory(dir_type=AppDir):
"""
Return the appropriate directory according to the directory type.
``dir_type``
The directory type you want, for instance the data directory.
The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
"""
if dir_type == AppLocation.AppDir:
return _get_frozen_path(os.path.abspath(os.path.split(sys.argv[0])[0]), os.path.split(openlp.__file__)[0])
@ -161,16 +159,13 @@ def _get_os_dir_path(dir_type):
return os.path.join(unicode(os.getenv(u'HOME'), encoding), u'Library', u'Application Support', u'openlp')
else:
if dir_type == AppLocation.LanguageDir:
prefixes = [u'/usr/local', u'/usr']
for prefix in prefixes:
for prefix in [u'/usr/local', u'/usr']:
directory = os.path.join(prefix, u'share', u'openlp')
if os.path.exists(directory):
return directory
return os.path.join(u'/usr', u'share', u'openlp')
if XDG_BASE_AVAILABLE:
if dir_type == AppLocation.ConfigDir:
return os.path.join(unicode(BaseDirectory.xdg_config_home, encoding), u'openlp')
elif dir_type == AppLocation.DataDir:
if dir_type == AppLocation.DataDir:
return os.path.join(unicode(BaseDirectory.xdg_data_home, encoding), u'openlp')
elif dir_type == AppLocation.CacheDir:
return os.path.join(unicode(BaseDirectory.xdg_cache_home, encoding), u'openlp')

View File

@ -37,28 +37,13 @@ __version__ = 1
log = logging.getLogger(__name__)
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.
"""
# Don't define the "metadata" table, as the upgrade mechanism already
# defines it.
tables = {
u'book': Table(u'book', metadata, autoload=True),
u'verse': Table(u'verse', metadata, autoload=True)
}
return tables
def upgrade_1(session, metadata, tables):
def upgrade_1(session, metadata):
"""
Version 1 upgrade.
This upgrade renames a number of keys to a single naming convention.
"""
metadata_table = metadata.tables[u'metadata']
metadata_table = Table(u'metadata', metadata, autoload=True)
# Copy "Version" to "name" ("version" used by upgrade system)
# TODO: Clean up in a subsequent release of OpenLP (like 2.0 final)
session.execute(insert(metadata_table).values(

View File

@ -100,8 +100,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
self.credit_edit.setText(self.custom_slide.credits)
custom_XML = CustomXMLParser(self.custom_slide.text)
slide_list = custom_XML.get_verses()
for slide in slide_list:
self.slide_list_view.addItem(slide[1])
self.slide_list_view.addItems([slide[1] for slide in slide_list])
theme = self.custom_slide.theme_name
find_and_set_in_combo_box(self.theme_combo_box, theme)
self.title_edit.setFocus()

View File

@ -41,14 +41,19 @@ class CustomSlide(BaseModel):
"""
CustomSlide model
"""
# By default sort the customs by its title considering language specific
# characters.
# By default sort the customs by its title considering language specific characters.
def __lt__(self, other):
return get_locale_key(self.title) < get_locale_key(other.title)
def __eq__(self, other):
return get_locale_key(self.title) == get_locale_key(other.title)
def __hash__(self):
"""
Return the hash for a custom slide.
"""
return self.id
def init_schema(url):
"""

View File

@ -174,13 +174,13 @@ class PPTViewer(QtGui.QWidget):
int(self.widthEdit.text()), int(self.heightEdit.text()))
filename = str(self.pptEdit.text().replace(u'/', u'\\'))
folder = str(self.folderEdit.text().replace(u'/', u'\\'))
print filename, folder
print(filename, folder)
self.pptid = self.pptdll.OpenPPT(filename, None, rect, folder)
print u'id: ' + unicode(self.pptid)
print(u'id: ' + unicode(self.pptid))
if oldid >= 0:
self.pptdll.ClosePPT(oldid);
slides = self.pptdll.GetSlideCount(self.pptid)
print u'slidecount: ' + unicode(slides)
print(u'slidecount: ' + unicode(slides))
self.total.setNum(self.pptdll.GetSlideCount(self.pptid))
self.updateCurrSlide()
@ -188,14 +188,14 @@ class PPTViewer(QtGui.QWidget):
if self.pptid < 0:
return
slide = unicode(self.pptdll.GetCurrentSlide(self.pptid))
print u'currslide: ' + slide
print(u'currslide: ' + slide)
self.slideEdit.setText(slide)
app.processEvents()
def gotoClick(self):
if self.pptid < 0:
return
print self.slideEdit.text()
print(self.slideEdit.text())
self.pptdll.GotoSlide(self.pptid, int(self.slideEdit.text()))
self.updateCurrSlide()
app.processEvents()
@ -207,7 +207,7 @@ class PPTViewer(QtGui.QWidget):
if __name__ == '__main__':
pptdll = cdll.LoadLibrary(r'pptviewlib.dll')
pptdll.SetDebug(1)
print u'Begin...'
print(u'Begin...')
app = QtGui.QApplication(sys.argv)
window = PPTViewer()
window.pptdll = pptdll

View File

@ -275,7 +275,6 @@ class Ui_EditSongDialog(object):
self.bottom_layout.setObjectName(u'bottom_layout')
self.warning_label = QtGui.QLabel(edit_song_dialog)
self.warning_label.setObjectName(u'warning_label')
self.warning_label.setVisible(False)
self.bottom_layout.addWidget(self.warning_label)
self.button_box = create_button_box(edit_song_dialog, u'button_box', [u'cancel', u'save'])
self.bottom_layout.addWidget(self.button_box)
@ -323,8 +322,10 @@ class Ui_EditSongDialog(object):
self.from_media_button.setText(translate('SongsPlugin.EditSongForm', 'Add &Media'))
self.audio_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
self.audio_remove_all_button.setText(translate('SongsPlugin.EditSongForm', 'Remove &All'))
self.warning_label.setText(
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> Not all of the verses are in use.'))
self.not_all_verses_used_warning = \
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> Not all of the verses are in use.')
self.no_verse_order_entered_warning = \
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
def create_combo_box(parent, name):

View File

@ -456,6 +456,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.title_edit.setFocus()
# Hide or show the preview button.
self.preview_button.setVisible(preview)
# Check if all verse tags are used.
self.on_verse_order_text_changed(self.verse_order_edit.text())
def tag_rows(self):
"""
@ -683,21 +685,33 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_edit_button.setEnabled(False)
self.verse_delete_button.setEnabled(False)
def on_verse_order_text_changed(self, text):
verses = []
verse_names = []
order = self._extract_verse_order(text)
"""
Checks if the verse order is complete or missing. Shows a error message according to the state of the verse
order.
``text``
The text of the verse order edit (ignored).
"""
# Extract all verses which were used in the order.
verses_in_order = self._extract_verse_order(self.verse_order_edit.text())
# Find the verses which were not used in the order.
verses_not_used = []
for index in range(self.verse_list_widget.rowCount()):
verse = self.verse_list_widget.item(index, 0)
verse = verse.data(QtCore.Qt.UserRole)
if verse not in verse_names:
verses.append(verse)
verse_names.append(u'%s%s' % (VerseType.translated_tag(verse[0]), verse[1:]))
verses_not_used = []
for verse in verses:
if not verse in order:
if verse not in verses_in_order:
verses_not_used.append(verse)
self.warning_label.setVisible(len(verses_not_used) > 0)
# Set the label text.
label_text = u''
# No verse order was entered.
if not verses_in_order:
label_text = self.no_verse_order_entered_warning
# The verse order does not contain all verses.
elif verses_not_used:
label_text = self.not_all_verses_used_warning
self.warning_label.setText(label_text)
def on_copyright_insert_button_triggered(self):
text = self.copyright_edit.text()

View File

@ -74,7 +74,13 @@ if os.name == u'nt':
HAS_MEDIASHOUT = True
except ImportError:
log.exception('Error importing %s', 'MediaShoutImport')
HAS_WORSHIPCENTERPRO = False
if os.name == u'nt':
try:
from worshipcenterproimport import WorshipCenterProImport
HAS_WORSHIPCENTERPRO = True
except ImportError:
log.exception('Error importing %s', 'WorshipCenterProImport')
class SongFormatSelect(object):
"""
@ -157,7 +163,8 @@ class SongFormat(object):
SongsOfFellowship = 14
SundayPlus = 15
WordsOfWorship = 16
ZionWorx = 17
WorshipCenterPro = 17
ZionWorx = 18
# Set optional attribute defaults
__defaults__ = {
@ -300,6 +307,16 @@ class SongFormat(object):
u'filter': u'%s (*.wsg *.wow-song)' %
translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
},
WorshipCenterPro: {
u'name': u'WorshipCenter Pro',
u'prefix': u'worshipCenterPro',
u'canDisable': True,
u'selectMode': SongFormatSelect.SingleFile,
u'filter': u'%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'WorshipCenter Pro Song Files'),
u'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
'The WorshipCenter Pro importer is only supported on Windows. It has been disabled due to a missing '
'Python module. If you want to use this importer, you will need to install the "pyodbc" module.')
},
ZionWorx: {
u'class': ZionWorxImport,
u'name': u'ZionWorx',
@ -336,6 +353,7 @@ class SongFormat(object):
SongFormat.SongsOfFellowship,
SongFormat.SundayPlus,
SongFormat.WordsOfWorship,
SongFormat.WorshipCenterPro,
SongFormat.ZionWorx
]
@ -385,5 +403,9 @@ if HAS_OOO:
SongFormat.set(SongFormat.MediaShout, u'availability', HAS_MEDIASHOUT)
if HAS_MEDIASHOUT:
SongFormat.set(SongFormat.MediaShout, u'class', MediaShoutImport)
SongFormat.set(SongFormat.WorshipCenterPro, u'availability', HAS_WORSHIPCENTERPRO)
if HAS_WORSHIPCENTERPRO:
SongFormat.set(SongFormat.WorshipCenterPro, u'class', WorshipCenterProImport)
__all__ = [u'SongFormat', u'SongFormatSelect']

View File

@ -1,131 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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 openlp.plugins.songs.lib.opensongimport import OpenSongImport
from openlp.core.lib.db import Manager
from openlp.plugins.songs.lib.db import init_schema
import logging
LOG_FILENAME = 'test.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO)
# Stubs to replace the UI functions for raw testing
class wizard_stub:
def __init__(self):
self.progressBar=progbar_stub()
def incrementProgressBar(self, str):
pass
class progbar_stub:
def __init__(self):
pass
def setMaximum(self, arg):
pass
def test():
manager = Manager(u'songs', init_schema)
o = OpenSongImport(manager, filenames=[u'test.opensong'])
o.import_wizard = wizard_stub()
o.commit = False
o.do_import()
o.print_song()
assert o.copyright == u'2010 Martin Thompson'
assert o.authors == [u'MartiÑ Thómpson', u'Martin2 Thómpson']
assert o.title == u'Martins Test'
assert o.alternate_title == u''
assert o.song_number == u'1'
assert [u'C1', u'Chorus 1'] in o.verses
assert [u'C2', u'Chorus 2'] in o.verses
assert not [u'C3', u'Chorus 3'] in o.verses
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
assert [u'V3A', u'V3 Line 1\nV3 Line 2'] in o.verses
assert [u'RAP1', u'Rap 1 Line 1\nRap 1 Line 2'] in o.verses
assert [u'RAP2', u'Rap 2 Line 1\nRap 2 Line 2'] in o.verses
assert [u'RAP3', u'Rap 3 Line 1\nRap 3 Line 2'] in o.verses
assert [u'X1', u'Unreferenced verse line 1'] in o.verses
assert o.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3A', u'B1', u'V1', u'T1', u'RAP1', u'RAP2', u'RAP3']
assert o.ccli_number == u'Blah'
assert o.topics == [u'TestTheme', u'TestAltTheme']
o.filenames = [u'test.opensong.zip']
o.set_defaults()
o.do_import()
o.print_song()
assert o.copyright == u'2010 Martin Thompson'
assert o.authors == [u'MartiÑ Thómpson']
assert o.title == u'Martins Test'
assert o.alternate_title == u''
assert o.song_number == u'1'
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
assert [u'C1', u'Chorus 1'] in o.verses
assert [u'C2', u'Chorus 2'] in o.verses
assert not [u'C3', u'Chorus 3'] in o.verses
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
print o.verse_order_list
assert o.verse_order_list == [u'V1', u'C1', u'V2', u'C2', u'V3', u'B1', u'V1']
o.filenames = [u'test2.opensong']
o.set_defaults()
o.do_import()
o.print_song()
assert o.copyright == u'2010 Martin Thompson'
assert o.authors == [u'Martin Thompson']
assert o.title == u'Martins 2nd Test'
assert o.alternate_title == u''
assert o.song_number == u'2'
print o.verses
assert [u'B1', u'Bridge 1\nBridge 1 line 2'] in o.verses
assert [u'C1', u'Chorus 1'] in o.verses
assert [u'C2', u'Chorus 2'] in o.verses
assert not [u'C3', u'Chorus 3'] in o.verses
assert [u'V1', u'v1 Line 1\nV1 Line 2'] in o.verses
assert [u'V2', u'v2 Line 1\nV2 Line 2'] in o.verses
print o.verse_order_list
assert o.verse_order_list == [u'V1', u'V2', u'B1', u'C1', u'C2']
o.filenames = [u'test3.opensong']
o.set_defaults()
o.do_import()
o.print_song()
assert o.copyright == u'2010'
assert o.authors == [u'Martin Thompson']
assert o.title == u'Test single verse'
assert o.alternate_title == u''
assert o.ccli_number == u'123456'
assert o.verse_order_list == [u'V1']
assert o.topics == [u'Worship: Declaration']
print o.verses[0]
assert [u'V1', u'Line 1\nLine 2'] in o.verses
print "Tests passed"
if __name__ == "__main__":
test()

View File

@ -31,31 +31,15 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the Songs plugin
"""
from sqlalchemy import Column, Table, types
from sqlalchemy.sql.expression import func
from migrate.changeset.constraint import ForeignKeyConstraint
from sqlalchemy import Column, types
from sqlalchemy.sql.expression import func, false, null, text
from openlp.core.lib.db import get_upgrade_op
__version__ = 3
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):
def upgrade_1(session, metadata):
"""
Version 1 upgrade.
@ -67,30 +51,35 @@ def upgrade_1(session, metadata, tables):
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'])
Column(u'weight', types.Integer(), default=0).create(table=tables[u'media_files'])
op = get_upgrade_op(session)
op.drop_table(u'media_files_songs')
op.add_column(u'media_files', Column(u'song_id', types.Integer(), server_default=null()))
op.add_column(u'media_files', Column(u'weight', types.Integer(), server_default=text(u'0')))
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()
op.create_foreign_key(u'fk_media_files_song_id', u'media_files', u'songs', [u'song_id', u'id'])
def upgrade_2(session, metadata, tables):
def upgrade_2(session, metadata):
"""
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'])
Column(u'last_modified', types.DateTime(), default=func.now()).create(table=tables[u'songs'])
op = get_upgrade_op(session)
op.add_column(u'songs', Column(u'create_date', types.DateTime(), default=func.now()))
op.add_column(u'songs', Column(u'last_modified', types.DateTime(), default=func.now()))
def upgrade_3(session, metadata, tables):
def upgrade_3(session, metadata):
"""
Version 3 upgrade.
This upgrade adds a temporary song flag to the songs table
"""
Column(u'temporary', types.Boolean(), default=False).create(table=tables[u'songs'])
op = get_upgrade_op(session)
if metadata.bind.url.get_dialect().name == 'sqlite':
op.add_column(u'songs', Column(u'temporary', types.Boolean(create_constraint=False), server_default=false()))
else:
op.add_column(u'songs', Column(u'temporary', types.Boolean(), server_default=false()))

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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:`worshipcenterpro` module provides the functionality for importing
a WorshipCenter Pro database into the OpenLP database.
"""
import logging
import pyodbc
from openlp.core.lib import translate
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
class WorshipCenterProImport(SongImport):
"""
The :class:`WorshipCenterProImport` class provides the ability to import the
WorshipCenter Pro Access Database
"""
def __init__(self, manager, **kwargs):
"""
Initialise the WorshipCenter Pro importer.
"""
SongImport.__init__(self, manager, **kwargs)
def doImport(self):
"""
Receive a single file to import.
"""
try:
conn = pyodbc.connect(u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s' % self.import_source)
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError), e:
log.warn(u'Unable to connect the WorshipCenter Pro database %s. %s', self.import_source, unicode(e))
# Unfortunately no specific exception type
self.logError(self.import_source,
translate('SongsPlugin.WorshipCenterProImport', 'Unable to connect the WorshipCenter Pro database.'))
return
cursor = conn.cursor()
cursor.execute(u'SELECT ID, Field, Value FROM __SONGDATA')
records = cursor.fetchall()
songs = {}
for record in records:
id = record.ID
if not songs.has_key(id):
songs[id] = {}
songs[id][record.Field] = record.Value
self.import_wizard.progress_bar.setMaximum(len(songs))
for song in songs:
if self.stop_import_flag:
break
self.setDefaults()
self.title = songs[song][u'TITLE']
lyrics = songs[song][u'LYRICS'].strip(u'&crlf;&crlf;')
for verse in lyrics.split(u'&crlf;&crlf;'):
verse = verse.replace(u'&crlf;', u'\n')
self.addVerse(verse)
self.finish()

View File

@ -30,30 +30,19 @@
The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the SongsUsage plugin
"""
from openlp.core.lib.db import get_upgrade_op
from sqlalchemy import Column, Table, types
from sqlalchemy import Column, types
__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):
def upgrade_1(session, metadata):
"""
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)
op = get_upgrade_op(session)
op.add_column(u'songusage_data', Column(u'plugin_name', types.Unicode(20), server_default=u''))
op.add_column(u'songusage_data', Column(u'source', types.Unicode(10), server_default=u''))

View File

@ -62,6 +62,7 @@ WIN32_MODULES = [
'win32com',
'win32ui',
'pywintypes',
'pyodbc',
]
MODULES = [
@ -85,6 +86,7 @@ MODULES = [
'migrate',
'uno',
'icu',
'bs4',
]

View File

@ -175,6 +175,8 @@ OpenLP (previously openlp.org) is free church presentation software, or lyrics p
zip_safe=False,
install_requires=[
# -*- Extra requirements: -*-
'sqlalchemy',
'alembic'
],
entry_points="""
# -*- Entry points: -*-

View File

@ -0,0 +1,84 @@
"""
Package to test the openlp.core.lib package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from sqlalchemy.pool import NullPool
from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy import MetaData
from openlp.core.lib.db import init_db, get_upgrade_op
class TestDB(TestCase):
"""
A test case for all the tests for the :mod:`~openlp.core.lib.db` module.
"""
def init_db_calls_correct_functions_test(self):
"""
Test that the init_db function makes the correct function calls
"""
# GIVEN: Mocked out SQLAlchemy calls and return objects, and an in-memory SQLite database URL
with patch(u'openlp.core.lib.db.create_engine') as mocked_create_engine, \
patch(u'openlp.core.lib.db.MetaData') as MockedMetaData, \
patch(u'openlp.core.lib.db.sessionmaker') as mocked_sessionmaker, \
patch(u'openlp.core.lib.db.scoped_session') as mocked_scoped_session:
mocked_engine = MagicMock()
mocked_metadata = MagicMock()
mocked_sessionmaker_object = MagicMock()
mocked_scoped_session_object = MagicMock()
mocked_create_engine.return_value = mocked_engine
MockedMetaData.return_value = mocked_metadata
mocked_sessionmaker.return_value = mocked_sessionmaker_object
mocked_scoped_session.return_value = mocked_scoped_session_object
db_url = u'sqlite://'
# WHEN: We try to initialise the db
session, metadata = init_db(db_url)
# THEN: We should see the correct function calls
mocked_create_engine.assert_called_with(db_url, poolclass=NullPool)
MockedMetaData.assert_called_with(bind=mocked_engine)
mocked_sessionmaker.assert_called_with(autoflush=True, autocommit=False, bind=mocked_engine)
mocked_scoped_session.assert_called_with(mocked_sessionmaker_object)
self.assertIs(session, mocked_scoped_session_object, u'The ``session`` object should be the mock')
self.assertIs(metadata, mocked_metadata, u'The ``metadata`` object should be the mock')
def init_db_defaults_test(self):
"""
Test that initialising an in-memory SQLite database via ``init_db`` uses the defaults
"""
# GIVEN: An in-memory SQLite URL
db_url = u'sqlite://'
# WHEN: The database is initialised through init_db
session, metadata = init_db(db_url)
# THEN: Valid session and metadata objects should be returned
self.assertIsInstance(session, ScopedSession, u'The ``session`` object should be a ``ScopedSession`` instance')
self.assertIsInstance(metadata, MetaData, u'The ``metadata`` object should be a ``MetaData`` instance')
def get_upgrade_op_test(self):
"""
Test that the ``get_upgrade_op`` function creates a MigrationContext and an Operations object
"""
# GIVEN: Mocked out alembic classes and a mocked out SQLAlchemy session object
with patch(u'openlp.core.lib.db.MigrationContext') as MockedMigrationContext, \
patch(u'openlp.core.lib.db.Operations') as MockedOperations:
mocked_context = MagicMock()
mocked_op = MagicMock()
mocked_connection = MagicMock()
MockedMigrationContext.configure.return_value = mocked_context
MockedOperations.return_value = mocked_op
mocked_session = MagicMock()
mocked_session.bind.connect.return_value = mocked_connection
# WHEN: get_upgrade_op is executed with the mocked session object
op = get_upgrade_op(mocked_session)
# THEN: The op object should be mocked_op, and the correction function calls should have been made
self.assertIs(op, mocked_op, u'The return value should be the mocked object')
mocked_session.bind.connect.assert_called_with()
MockedMigrationContext.configure.assert_called_with(mocked_connection)
MockedOperations.assert_called_with(mocked_context)

View File

@ -16,6 +16,7 @@ from openlp.core.lib import str_to_bool, create_thumb, translate, check_director
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestLib(TestCase):
def str_to_bool_with_bool_test(self):

View File

@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
"""
Package to test the openlp.core.lib package.
"""
import os
import io
import cPickle
import json
import tempfile
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
from lxml import objectify, etree
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
'The Lord said to {g}Noah{/g}:\n'\
'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n'\
'Get those children out of the muddy, muddy \n'\
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
class TestServiceItem(TestCase):
def setUp(self):
"""
Set up the Registry
"""
Registry.create()
mocked_renderer = MagicMock()
mocked_renderer.format_slide.return_value = [VERSE]
Registry().register(u'renderer', mocked_renderer)
Registry().register(u'image_manager', MagicMock())
def serviceitem_basic_test(self):
"""
Test the Service Item - basic test
"""
# GIVEN: A new service item
# WHEN: A service item is created (without a plugin)
service_item = ServiceItem(None)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert service_item.missing_frames() is True, u'There should not be any frames in the service item'
def serviceitem_load_custom_from_service_test(self):
"""
Test the Service Item - adding a custom slide from a saved service
"""
# GIVEN: A new service item and a mocked add icon function
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service
line = self.convert_file_service_item(u'serviceitem_custom_1.osj')
service_item.set_from_service(line)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert len(service_item._display_frames) == 0, u'The service item should have no display frames'
assert len(service_item.capabilities) == 5, u'There should be 5 default custom item capabilities'
service_item.render(True)
assert service_item.get_display_title() == u'Test Custom', u'The title should be "Test Custom"'
assert service_item.get_frames()[0][u'text'] == VERSE[:-1], \
u'The returned text matches the input, except the last line feed'
assert service_item.get_rendered_frame(1) == VERSE.split(u'\n', 1)[0], u'The first line has been returned'
assert service_item.get_frame_title(0) == u'Slide 1', u'"Slide 1" has been returned as the title'
assert service_item.get_frame_title(1) == u'Slide 2', u'"Slide 2" has been returned as the title'
assert service_item.get_frame_title(2) == u'', u'Blank has been returned as the title of slide 3'
def serviceitem_load_image_from_service_test(self):
"""
Test the Service Item - adding an image from a saved service
"""
# GIVEN: A new service item and a mocked add icon function
image_name = u'image_1.jpg'
test_file = os.path.join(TEST_PATH, image_name)
frame_array = {u'path': test_file, u'title': image_name}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_1.osj')
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, u'The new service item should be valid'
assert service_item.get_rendered_frame(0) == test_file, u'The first frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array, u'The return should match frame array1'
assert service_item.get_frame_path(0) == test_file, u'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name, u'The frame title should match the image name'
assert service_item.get_display_title() == image_name, u'The display title should match the first image name'
assert service_item.is_image() is True, u'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
u'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
u'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
u'This service item should be able to be run in a can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
u'This service item should be able to have new items added to it'
def serviceitem_load_image_from_local_service_test(self):
"""
Test the Service Item - adding an image from a saved local service
"""
# GIVEN: A new service item and a mocked add icon function
image_name1 = u'image_1.jpg'
image_name2 = u'image_2.jpg'
test_file1 = os.path.join(u'/home/openlp', image_name1)
test_file2 = os.path.join(u'/home/openlp', image_name2)
frame_array1 = {u'path': test_file1, u'title': image_name1}
frame_array2 = {u'path': test_file2, u'title': image_name2}
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
service_item2 = ServiceItem(None)
service_item2.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item(u'serviceitem_image_2.osj')
line2 = self.convert_file_service_item(u'serviceitem_image_2.osj', 1)
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item2.set_from_service(line2)
service_item.set_from_service(line)
# THEN: We should get back a valid service item
# This test is copied from service_item.py, but is changed since to conform to
# new layout of service item. The layout use in serviceitem_image_2.osd is actually invalid now.
assert service_item.is_valid is True, u'The first service item should be valid'
assert service_item2.is_valid is True, u'The second service item should be valid'
assert service_item.get_rendered_frame(0) == test_file1, u'The first frame should match the path to the image'
assert service_item2.get_rendered_frame(0) == test_file2, u'The Second frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array1, u'The return should match the frame array1'
assert service_item2.get_frames()[0] == frame_array2, u'The return should match the frame array2'
assert service_item.get_frame_path(0) == test_file1, u'The frame path should match the full path to the image'
assert service_item2.get_frame_path(0) == test_file2, u'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name1, u'The 1st frame title should match the image name'
assert service_item2.get_frame_title(0) == image_name2, u'The 2nd frame title should match the image name'
assert service_item.title.lower() == service_item.name, \
u'The plugin name should match the display title, as there are > 1 Images'
assert service_item.is_image() is True, u'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
u'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
u'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
u'This service item should be able to be run in a can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
u'This service item should be able to have new items added to it'
def serviceitem_convert_osd2osj_test(self):
"""
Test the Service Item - load a osd to service_item, convert to json,
load again to service_item and compare the old and new service_item.
"""
# GIVEN: A valid osd (python pickle format) service in file
service_file = os.path.join(TEST_PATH, u'serviceitem_osd2osj.osd')
osd_service_items = []
try:
open_file = open(service_file, u'r')
osd_service_items = cPickle.load(open_file)
except IOError:
osd_service_items = []
finally:
open_file.close()
# WHEN: Dumping loaded osd service to json format, and save to file and reloading to service
json_service_content = json.dumps(osd_service_items)
open_file = None
open_filename = u''
try:
(open_filep, open_filename) = tempfile.mkstemp()
open_file = open(open_filename, u'w')
open_file.write(json_service_content)
open_file.close()
open_file = open(open_filename, u'r')
json_service_content = open_file.read()
except IOError:
json_service_content = u''
finally:
open_file.close()
os.remove(open_filename)
osj_service_items = json.loads(json_service_content)
# THEN: The service loaded from osj (json format) should be the same as the service loaded from the original osd (python pickle format)
# Loop over every item and compare the osj with osd version
for osd_item, osj_item in zip(osd_service_items, osj_service_items):
# Create service item objects
service_item_osd = ServiceItem()
service_item_osd.add_icon = MagicMock()
service_item_osj = ServiceItem()
service_item_osj.add_icon = MagicMock()
with patch(u'openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item_osd.set_from_service(osd_item, TEST_PATH)
service_item_osj.set_from_service(osj_item, TEST_PATH)
# Check that the exported/imported attributes are the same
assert service_item_osj.is_valid is True, u'The osj service item should be valid'
assert service_item_osd.is_valid is True, u'The osd service item should be valid'
assert service_item_osj.name == service_item_osd.name , u'The osd and the osj attribute name should be the same!'
assert service_item_osj.theme == service_item_osd.theme , u'The osd and the osj attribute theme should be the same!'
assert service_item_osj.title == service_item_osd.title , u'The osd and the osj attribute title should be the same!'
assert service_item_osj.icon == service_item_osd.icon , u'The osd and the osj attribute icon should be the same!'
assert service_item_osj.raw_footer == service_item_osd.raw_footer , u'The osd and the osj attribute raw_footer should be the same!'
assert service_item_osj.service_item_type == service_item_osd.service_item_type , u'The osd and the osj attribute service_item_type should be the same!'
assert service_item_osj.audit == service_item_osd.audit , u'The osd and the osj attribute audit should be the same!'
assert service_item_osj.notes == service_item_osd.notes , u'The osd and the osj attribute notes should be the same!'
assert service_item_osj.from_plugin == service_item_osd.from_plugin , u'The osd and the osj attribute from_plugin should be the same!'
assert service_item_osj.capabilities == service_item_osd.capabilities , u'The osd and the osj attribute capabilities should be the same!'
assert service_item_osj.search_string == service_item_osd.search_string , u'The osd and the osj attribute search_string should be the same!'
assert service_item_osj.data_string == service_item_osd.data_string , u'The osd and the osj attribute data_string should be the same!'
# Notice that xml_version from osd needs to be utf-8 decoded, since unicode-characters
# is written as byte-codes by pickle, while json can escape unicode-characters
if service_item_osd.xml_version:
assert service_item_osj.xml_version == service_item_osd.xml_version.decode(u'utf-8') , u'The osd and the osj attribute xml_version should be the same!'
assert service_item_osj.auto_play_slides_once == service_item_osd.auto_play_slides_once , u'The osd and the osj attribute auto_play_slides_once should be the same!'
assert service_item_osj.auto_play_slides_loop == service_item_osd.auto_play_slides_loop , u'The osd and the osj attribute auto_play_slides_loop should be the same!'
assert service_item_osj.timed_slide_interval == service_item_osd.timed_slide_interval , u'The osd and the osj attribute timed_slide_interval should be the same!'
assert service_item_osj.start_time == service_item_osd.start_time , u'The osd and the osj attribute start_time should be the same!'
assert service_item_osj.end_time == service_item_osd.end_time , u'The osd and the osj attribute end_time should be the same!'
assert service_item_osj.media_length == service_item_osd.media_length , u'The osd and the osj attribute media_length should be the same!'
assert service_item_osj.background_audio == service_item_osd.background_audio , u'The osd and the osj attribute background_audio should be the same!'
assert service_item_osj.theme_overwritten == service_item_osd.theme_overwritten , u'The osd and the osj attribute theme_overwritten should be the same!'
assert service_item_osj.will_auto_start == service_item_osd.will_auto_start , u'The osd and the osj attribute will_auto_start should be the same!'
assert service_item_osj.processor == service_item_osd.processor , u'The osd and the osj attribute processor should be the same!'
def convert_file_service_item(self, name, row=0):
service_file = os.path.join(TEST_PATH, name)
try:
open_file = open(service_file, u'r')
items = json.load(open_file)
first_line = items[row]
except IOError:
first_line = u''
finally:
open_file.close()
return first_line

View File

@ -7,7 +7,7 @@ from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtGui
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, ServiceItem, Settings
@ -32,6 +32,7 @@ class TestMediaItem(TestCase):
fd, self.ini_file = mkstemp(u'.ini')
Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance()
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
def tearDown(self):
"""

View File

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
"""
This module contains tests for the WorshipCenter Pro song importer.
"""
from unittest import TestCase
from mock import patch, MagicMock
import pyodbc
from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport
class TestRecord(object):
"""
Microsoft Access Driver is not available on non Microsoft Systems for this reason the :class:`TestRecord` is used
to simulate a recordset that would be returned by pyobdc.
"""
def __init__(self, id, field, value):
# The case of the following instance variables is important as it needs to be the same as the ones in use in the
# WorshipCenter Pro database.
self.ID = id
self.Field = field
self.Value = value
class WorshipCenterProImportLogger(WorshipCenterProImport):
"""
This class logs changes in the title instance variable
"""
_title_assignment_list = []
def __init__(self, manager):
WorshipCenterProImport.__init__(self, manager)
@property
def title(self):
return self._title_assignment_list[-1]
@title.setter
def title(self, title):
self._title_assignment_list.append(title)
RECORDSET_TEST_DATA = [TestRecord(1, u'TITLE', u'Amazing Grace'),
TestRecord(1, u'LYRICS',
u'Amazing grace! How&crlf;sweet the sound&crlf;That saved a wretch like me!&crlf;'
u'I once was lost,&crlf;but now am found;&crlf;Was blind, but now I see.&crlf;&crlf;'
u'\'Twas grace that&crlf;taught my heart to fear,&crlf;And grace my fears relieved;&crlf;'
u'How precious did&crlf;that grace appear&crlf;The hour I first believed.&crlf;&crlf;'
u'Through many dangers,&crlf;toils and snares,&crlf;I have already come;&crlf;'
u'\'Tis grace hath brought&crlf;me safe thus far,&crlf;'
u'And grace will lead me home.&crlf;&crlf;The Lord has&crlf;promised good to me,&crlf;'
u'His Word my hope secures;&crlf;He will my Shield&crlf;and Portion be,&crlf;'
u'As long as life endures.&crlf;&crlf;Yea, when this flesh&crlf;and heart shall fail,&crlf;'
u'And mortal life shall cease,&crlf;I shall possess,&crlf;within the veil,&crlf;'
u'A life of joy and peace.&crlf;&crlf;The earth shall soon&crlf;dissolve like snow,&crlf;'
u'The sun forbear to shine;&crlf;But God, Who called&crlf;me here below,&crlf;'
u'Shall be forever mine.&crlf;&crlf;When we\'ve been there&crlf;ten thousand years,&crlf;'
u'Bright shining as the sun,&crlf;We\'ve no less days to&crlf;sing God\'s praise&crlf;'
u'Than when we\'d first begun.&crlf;&crlf;'),
TestRecord(2, u'TITLE', u'Beautiful Garden Of Prayer, The'),
TestRecord(2, u'LYRICS',
u'There\'s a garden where&crlf;Jesus is waiting,&crlf;'
u'There\'s a place that&crlf;is wondrously fair,&crlf;For it glows with the&crlf;'
u'light of His presence.&crlf;\'Tis the beautiful&crlf;garden of prayer.&crlf;&crlf;'
u'Oh, the beautiful garden,&crlf;the garden of prayer!&crlf;Oh, the beautiful&crlf;'
u'garden of prayer!&crlf;There my Savior awaits,&crlf;and He opens the gates&crlf;'
u'To the beautiful&crlf;garden of prayer.&crlf;&crlf;There\'s a garden where&crlf;'
u'Jesus is waiting,&crlf;And I go with my&crlf;burden and care,&crlf;'
u'Just to learn from His&crlf;lips words of comfort&crlf;In the beautiful&crlf;'
u'garden of prayer.&crlf;&crlf;There\'s a garden where&crlf;Jesus is waiting,&crlf;'
u'And He bids you to come,&crlf;meet Him there;&crlf;Just to bow and&crlf;'
u'receive a new blessing&crlf;In the beautiful&crlf;garden of prayer.&crlf;&crlf;')]
SONG_TEST_DATA = [{u'title': u'Amazing Grace',
u'verses': [
(u'Amazing grace! How\nsweet the sound\nThat saved a wretch like me!\nI once was lost,\n'
u'but now am found;\nWas blind, but now I see.'),
(u'\'Twas grace that\ntaught my heart to fear,\nAnd grace my fears relieved;\nHow precious did\n'
u'that grace appear\nThe hour I first believed.'),
(u'Through many dangers,\ntoils and snares,\nI have already come;\n\'Tis grace hath brought\n'
u'me safe thus far,\nAnd grace will lead me home.'),
(u'The Lord has\npromised good to me,\nHis Word my hope secures;\n'
u'He will my Shield\nand Portion be,\nAs long as life endures.'),
(u'Yea, when this flesh\nand heart shall fail,\nAnd mortal life shall cease,\nI shall possess,\n'
u'within the veil,\nA life of joy and peace.'),
(u'The earth shall soon\ndissolve like snow,\nThe sun forbear to shine;\nBut God, Who called\n'
u'me here below,\nShall be forever mine.'),
(u'When we\'ve been there\nten thousand years,\nBright shining as the sun,\n'
u'We\'ve no less days to\nsing God\'s praise\nThan when we\'d first begun.')]},
{u'title': u'Beautiful Garden Of Prayer, The',
u'verses': [
(u'There\'s a garden where\nJesus is waiting,\nThere\'s a place that\nis wondrously fair,\n'
u'For it glows with the\nlight of His presence.\n\'Tis the beautiful\ngarden of prayer.'),
(u'Oh, the beautiful garden,\nthe garden of prayer!\nOh, the beautiful\ngarden of prayer!\n'
u'There my Savior awaits,\nand He opens the gates\nTo the beautiful\ngarden of prayer.'),
(u'There\'s a garden where\nJesus is waiting,\nAnd I go with my\nburden and care,\n'
u'Just to learn from His\nlips words of comfort\nIn the beautiful\ngarden of prayer.'),
(u'There\'s a garden where\nJesus is waiting,\nAnd He bids you to come,\nmeet Him there;\n'
u'Just to bow and\nreceive a new blessing\nIn the beautiful\ngarden of prayer.')]}]
class TestWorshipCenterProSongImport(TestCase):
"""
Test the functions in the :mod:`worshipcenterproimport` module.
"""
def create_importer_test(self):
"""
Test creating an instance of the WorshipCenter Pro file importer
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'):
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = WorshipCenterProImport(mocked_manager)
# THEN: The importer object should not be None
self.assertIsNotNone(importer, u'Import should not be none')
def pyodbc_exception_test(self):
"""
Test that exceptions raised by pyodbc are handled
"""
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module, a mocked out translate method,
# a mocked "manager" and a mocked out logError method.
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.pyodbc.connect') as mocked_pyodbc_connect, \
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
mocked_manager = MagicMock()
mocked_log_error = MagicMock()
mocked_translate.return_value = u'Translated Text'
importer = WorshipCenterProImport(mocked_manager)
importer.logError = mocked_log_error
importer.import_source = u'import_source'
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
mocked_pyodbc_connect.side_effect = pyodbc_errors
# WHEN: Calling the doImport method
for effect in pyodbc_errors:
return_value = importer.doImport()
# THEN: doImport should return None, and pyodbc, translate & logError are called with known calls
self.assertIsNone(return_value, u'doImport should return None when pyodbc raises an exception.')
mocked_pyodbc_connect.assert_called_with( u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
mocked_translate.assert_called_with('SongsPlugin.WorshipCenterProImport',
'Unable to connect the WorshipCenter Pro database.')
mocked_log_error.assert_called_with(u'import_source', u'Translated Text')
def song_import_test(self):
"""
Test that a simulated WorshipCenter Pro recordset is imported correctly
"""
# GIVEN: A mocked out SongImport class, a mocked out pyodbc module with a simulated recordset, a mocked out
# translate method, a mocked "manager", addVerse method & mocked_finish method.
with patch(u'openlp.plugins.songs.lib.worshipcenterproimport.SongImport'), \
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.pyodbc') as mocked_pyodbc, \
patch(u'openlp.plugins.songs.lib.worshipcenterproimport.translate') as mocked_translate:
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
mocked_add_verse = MagicMock()
mocked_finish = MagicMock()
mocked_pyodbc.connect().cursor().fetchall.return_value = RECORDSET_TEST_DATA
mocked_translate.return_value = u'Translated Text'
importer = WorshipCenterProImportLogger(mocked_manager)
importer.import_source = u'import_source'
importer.import_wizard = mocked_import_wizard
importer.addVerse = mocked_add_verse
importer.stop_import_flag = False
importer.finish = mocked_finish
# WHEN: Calling the doImport method
return_value = importer.doImport()
# THEN: doImport should return None, and pyodbc, import_wizard, importer.title and addVerse are called with
# known calls
self.assertIsNone(return_value, u'doImport should return None when pyodbc raises an exception.')
mocked_pyodbc.connect.assert_called_with(u'DRIVER={Microsoft Access Driver (*.mdb)};DBQ=import_source')
mocked_pyodbc.connect().cursor.assert_any_call()
mocked_pyodbc.connect().cursor().execute.assert_called_with(u'SELECT ID, Field, Value FROM __SONGDATA')
mocked_pyodbc.connect().cursor().fetchall.assert_any_call()
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(2)
add_verse_call_count = 0
for song_data in SONG_TEST_DATA:
title_value = song_data[u'title']
self.assertIn(title_value, importer._title_assignment_list,
u'title should have been set to %s' % title_value)
verse_calls = song_data[u'verses']
add_verse_call_count += len(verse_calls)
for call in verse_calls:
mocked_add_verse.assert_any_call(call)
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
u'Incorrect number of calls made to addVerse')

View File

@ -0,0 +1,60 @@
"""
Package to test the openlp.core.ui.mainwindow package.
"""
from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtGui
from openlp.core.lib import Registry
from openlp.core.ui.mainwindow import MainWindow
class TestMainWindow(TestCase):
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.registry = Registry()
self.app = QtGui.QApplication([])
# Mock cursor busy/normal methods.
self.app.set_busy_cursor = MagicMock()
self.app.set_normal_cursor = MagicMock()
self.app.args =[]
Registry().register(u'application', self.app)
# Mock classes and methods used by mainwindow.
with patch(u'openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \
patch(u'openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \
patch(u'openlp.core.ui.mainwindow.SlideController') as mocked_slide_controller, \
patch(u'openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \
patch(u'openlp.core.ui.mainwindow.QtGui.QToolBox') as mocked_q_tool_box_class, \
patch(u'openlp.core.ui.mainwindow.QtGui.QMainWindow.addDockWidget') as mocked_add_dock_method, \
patch(u'openlp.core.ui.mainwindow.ServiceManager') as mocked_service_manager, \
patch(u'openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
patch(u'openlp.core.ui.mainwindow.Renderer') as mocked_renderer:
self.main_window = MainWindow()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.main_window
del self.app
def restore_current_media_manager_item_test(self):
"""
Regression test for bug #1152509.
"""
# GIVEN: Mocked Settings().value method.
with patch(u'openlp.core.ui.mainwindow.Settings.value') as mocked_value:
# save current plugin: True; current media plugin: 2
mocked_value.side_effect = [True, 2]
# WHEN: Call the restore method.
Registry().execute(u'bootstrap_post_set_up')
# THEN: The current widget should have been set.
self.main_window.media_tool_box.setCurrentIndex.assert_called_with(2)

View File

@ -44,4 +44,67 @@ class TestEditSongForm(TestCase):
self.assertFalse(self.form.topic_remove_button.isEnabled(), u'The topic remove button should not be enabled')
def is_verse_edit_form_executed_test(self):
pass
pass
def verse_order_no_warning_test(self):
"""
Test if the verse order warning is not shown
"""
# GIVEN: Mocked methods.
given_verse_order = u'V1 V2'
self.form.verse_list_widget.rowCount = MagicMock(return_value=2)
# Mock out the verse.
first_verse = MagicMock()
first_verse.data = MagicMock(return_value=u'V1')
second_verse = MagicMock()
second_verse.data = MagicMock(return_value= u'V2')
self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse])
self.form._extract_verse_order = MagicMock(return_value=given_verse_order.split())
# WHEN: Call the method.
self.form.on_verse_order_text_changed(given_verse_order)
# THEN: No text should be shown.
assert self.form.warning_label.text() == u'', u'There should be no warning.'
def verse_order_incomplete_warning_test(self):
"""
Test if the verse-order-incomple warning is shown
"""
# GIVEN: Mocked methods.
given_verse_order = u'V1'
self.form.verse_list_widget.rowCount = MagicMock(return_value=2)
# Mock out the verse.
first_verse = MagicMock()
first_verse.data = MagicMock(return_value=u'V1')
second_verse = MagicMock()
second_verse.data = MagicMock(return_value= u'V2')
self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse])
self.form._extract_verse_order = MagicMock(return_value=[given_verse_order])
# WHEN: Call the method.
self.form.on_verse_order_text_changed(given_verse_order)
# THEN: The verse-order-incomplete text should be shown.
assert self.form.warning_label.text() == self.form.not_all_verses_used_warning, \
u'The verse-order-incomplete warning should be shown.'
def bug_1170435_test(self):
"""
Regression test for bug 1170435 (test if "no verse order" message is shown)
"""
# GIVEN: Mocked methods.
given_verse_order = u''
self.form.verse_list_widget.rowCount = MagicMock(return_value=1)
# Mock out the verse. (We want a verse type to be returned).
mocked_verse = MagicMock()
mocked_verse.data = MagicMock(return_value=u'V1')
self.form.verse_list_widget.item = MagicMock(return_value=mocked_verse)
self.form._extract_verse_order = MagicMock(return_value=[])
self.form.verse_order_edit.text = MagicMock(return_value=given_verse_order)
# WHEN: Call the method.
self.form.on_verse_order_text_changed(given_verse_order)
# THEN: The no-verse-order message should be shown.
assert self.form.warning_label.text() == self.form.no_verse_order_entered_warning, \
u'The no-verse-order message should be shown.'

View File

@ -0,0 +1 @@
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Test Custom", "capabilities": [2, 1, 5, 13, 8], "theme": null, "background_audio": [], "icon": ":/plugins/plugin_custom.png", "type": 1, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "custom", "footer": ["Test Custom Credits"], "notes": "", "plugin": "custom", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"verseTag": null, "raw_slide": "Slide 1", "title": "Slide 1"}, {"verseTag": null, "raw_slide": "Slide 2", "title": "Slide 2"}]}}]

View File

@ -0,0 +1 @@
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": ["image_1.jpg"]}}]

View File

@ -0,0 +1 @@
[{"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"path": "/home/openlp/image_1.jpg", "title": "image_1.jpg"}]}}, {"serviceitem": {"header": {"xml_version": null, "auto_play_slides_loop": false, "auto_play_slides_once": false, "will_auto_start": false, "title": "Images", "capabilities": [3, 1, 5, 6], "theme": -1, "background_audio": [], "icon": ":/plugins/plugin_images.png", "type": 2, "start_time": 0, "from_plugin": false, "media_length": 0, "data": "", "timed_slide_interval": 0, "audit": "", "search": "", "name": "images", "footer": [], "notes": "", "plugin": "images", "theme_overwritten": false, "end_time": 0, "processor": null}, "data": [{"path": "/home/openlp/image_2.jpg", "title": "image_2.jpg"}]}}]

File diff suppressed because it is too large Load Diff