forked from openlp/openlp
Merged trunk into branch.
This commit is contained in:
commit
4e1868983e
|
@ -1 +1 @@
|
|||
2.1.0-bzr2141
|
||||
2.1.0-bzr2234
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"""
|
||||
Provide HTML Tag management and Formatting Tag access class
|
||||
"""
|
||||
import cPickle
|
||||
import json
|
||||
|
||||
from openlp.core.lib import Settings, translate
|
||||
|
||||
|
@ -66,7 +66,7 @@ class FormattingTags(object):
|
|||
if isinstance(tag[element], unicode):
|
||||
tag[element] = tag[element].encode('utf8')
|
||||
# Formatting Tags were also known as display tags.
|
||||
Settings().setValue(u'displayTags/html_tags', cPickle.dumps(tags) if tags else u'')
|
||||
Settings().setValue(u'formattingTags/html_tags', json.dumps(tags) if tags else u'')
|
||||
|
||||
@staticmethod
|
||||
def load_tags():
|
||||
|
@ -156,13 +156,10 @@ class FormattingTags(object):
|
|||
u'end html': u'', u'protected': True, u'temporary': False})
|
||||
FormattingTags.add_html_tags(base_tags)
|
||||
FormattingTags.add_html_tags(temporary_tags)
|
||||
|
||||
# Formatting Tags were also known as display tags.
|
||||
user_expands = Settings().value(u'displayTags/html_tags')
|
||||
# cPickle only accepts str not unicode strings
|
||||
user_expands_string = str(user_expands)
|
||||
user_expands_string = str(Settings().value(u'formattingTags/html_tags'))
|
||||
if user_expands_string:
|
||||
user_tags = cPickle.loads(user_expands_string)
|
||||
user_tags = json.loads(user_expands_string)
|
||||
for tag in user_tags:
|
||||
for element in tag:
|
||||
if isinstance(tag[element], str):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
|
|
|
@ -728,10 +728,14 @@ class MediaManagerItem(QtGui.QWidget):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
Provide the generic plugin functionality for OpenLP plugins.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore
|
||||
|
||||
|
@ -424,8 +425,11 @@ class Plugin(QtCore.QObject):
|
|||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -103,9 +103,6 @@ class Registry(object):
|
|||
``key``
|
||||
The service to be deleted.
|
||||
"""
|
||||
if self.running_under_test is False:
|
||||
log.error(u'Invalid Method call for key %s' % key)
|
||||
raise KeyError(u'Invalid Method call for key %s' % key)
|
||||
if key in self.service_list:
|
||||
del self.service_list[key]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -115,7 +115,7 @@ class Settings(QtCore.QSettings):
|
|||
u'advanced/single click preview': False,
|
||||
u'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||
u'crashreport/last directory': u'',
|
||||
u'displayTags/html_tags': u'',
|
||||
u'formattingTags/html_tags': u'',
|
||||
u'core/audio repeat list': False,
|
||||
u'core/auto open': False,
|
||||
u'core/auto preview': False,
|
||||
|
@ -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')],
|
||||
|
@ -271,6 +272,7 @@ class Settings(QtCore.QSettings):
|
|||
u'shortcuts/songImportItem': [],
|
||||
u'shortcuts/themeScreen': [QtGui.QKeySequence(u'T')],
|
||||
u'shortcuts/toolsReindexItem': [],
|
||||
u'shortcuts/toolsFindDuplicates': [],
|
||||
u'shortcuts/toolsAlertItem': [QtGui.QKeySequence(u'F7')],
|
||||
u'shortcuts/toolsFirstTimeWizard': [],
|
||||
u'shortcuts/toolsOpenDataFolder': [],
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 + \
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
@ -484,10 +485,14 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
# -*- 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:`listpreviewwidget` is a widget that lists the slides in the slide controller.
|
||||
It is based on a QTableWidget but represents its contents in list form.
|
||||
"""
|
||||
from __future__ import division
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import ImageSource, Registry, ServiceItem
|
||||
|
||||
|
||||
class ListPreviewWidget(QtGui.QTableWidget):
|
||||
def __init__(self, parent, screen_ratio):
|
||||
"""
|
||||
Initializes the widget to default state.
|
||||
An empty ServiceItem is used per default.
|
||||
One needs to call replace_service_manager_item() to make this widget display something.
|
||||
"""
|
||||
super(QtGui.QTableWidget, self).__init__(parent)
|
||||
# Set up the widget.
|
||||
self.setColumnCount(1)
|
||||
self.horizontalHeader().setVisible(False)
|
||||
self.setColumnWidth(0, parent.width())
|
||||
self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
|
||||
self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
|
||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setAlternatingRowColors(True)
|
||||
# Initialize variables.
|
||||
self.service_item = ServiceItem()
|
||||
self.screen_ratio = screen_ratio
|
||||
|
||||
def resizeEvent(self, QResizeEvent):
|
||||
"""
|
||||
Overloaded method from QTableWidget. Will recalculate the layout.
|
||||
"""
|
||||
self.__recalculate_layout()
|
||||
|
||||
def __recalculate_layout(self):
|
||||
"""
|
||||
Recalculates the layout of the table widget. It will set height and width
|
||||
of the table cells. QTableWidget does not adapt the cells to the widget size on its own.
|
||||
"""
|
||||
self.setColumnWidth(0, self.viewport().width())
|
||||
if self.service_item:
|
||||
# Sort out songs, bibles, etc.
|
||||
if self.service_item.is_text():
|
||||
self.resizeRowsToContents()
|
||||
else:
|
||||
# Sort out image heights.
|
||||
for framenumber in range(len(self.service_item.get_frames())):
|
||||
height = self.viewport().width() // self.screen_ratio
|
||||
self.setRowHeight(framenumber, height)
|
||||
|
||||
def screen_size_changed(self, screen_ratio):
|
||||
"""
|
||||
To be called whenever the live screen size changes.
|
||||
Because this makes a layout recalculation necessary.
|
||||
"""
|
||||
self.screen_ratio = screen_ratio
|
||||
self.__recalculate_layout()
|
||||
|
||||
def replace_service_item(self, service_item, width, slideNumber):
|
||||
"""
|
||||
Replaces the current preview items with the ones in service_item.
|
||||
Displays the given slide.
|
||||
"""
|
||||
self.service_item = service_item
|
||||
self.clear()
|
||||
self.setRowCount(0)
|
||||
self.setColumnWidth(0, width)
|
||||
row = 0
|
||||
text = []
|
||||
for framenumber, frame in enumerate(self.service_item.get_frames()):
|
||||
self.setRowCount(self.slide_count() + 1)
|
||||
item = QtGui.QTableWidgetItem()
|
||||
slide_height = 0
|
||||
if self.service_item.is_text():
|
||||
if frame[u'verseTag']:
|
||||
# These tags are already translated.
|
||||
verse_def = frame[u'verseTag']
|
||||
verse_def = u'%s%s' % (verse_def[0], verse_def[1:])
|
||||
two_line_def = u'%s\n%s' % (verse_def[0], verse_def[1:])
|
||||
row = two_line_def
|
||||
else:
|
||||
row += 1
|
||||
item.setText(frame[u'text'])
|
||||
else:
|
||||
label = QtGui.QLabel()
|
||||
label.setMargin(4)
|
||||
if self.service_item.is_media():
|
||||
label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
||||
else:
|
||||
label.setScaledContents(True)
|
||||
if self.service_item.is_command():
|
||||
label.setPixmap(QtGui.QPixmap(frame[u'image']))
|
||||
else:
|
||||
image = self.image_manager.get_image(frame[u'path'], ImageSource.ImagePlugin)
|
||||
label.setPixmap(QtGui.QPixmap.fromImage(image))
|
||||
self.setCellWidget(framenumber, 0, label)
|
||||
slide_height = width // self.screen_ratio
|
||||
row += 1
|
||||
text.append(unicode(row))
|
||||
self.setItem(framenumber, 0, item)
|
||||
if slide_height:
|
||||
self.setRowHeight(framenumber, slide_height)
|
||||
self.setVerticalHeaderLabels(text)
|
||||
if self.service_item.is_text():
|
||||
self.resizeRowsToContents()
|
||||
self.setColumnWidth(0, self.viewport().width())
|
||||
self.setFocus()
|
||||
self.change_slide(slideNumber)
|
||||
|
||||
def change_slide(self, slide):
|
||||
"""
|
||||
Switches to the given row.
|
||||
"""
|
||||
if slide >= self.slide_count():
|
||||
slide = self.slide_count() - 1
|
||||
# Scroll to next item if possible.
|
||||
if slide + 1 < self.slide_count():
|
||||
self.scrollToItem(self.item(slide + 1, 0))
|
||||
self.selectRow(slide)
|
||||
|
||||
def current_slide_number(self):
|
||||
"""
|
||||
Returns the position of the currently active item. Will return -1 if the widget is empty.
|
||||
"""
|
||||
return super(ListPreviewWidget, self).currentRow()
|
||||
|
||||
def slide_count(self):
|
||||
"""
|
||||
Returns the number of slides this widget holds.
|
||||
"""
|
||||
return super(ListPreviewWidget, self).rowCount()
|
||||
|
||||
def _get_image_manager(self):
|
||||
"""
|
||||
Adds the image manager to the class dynamically.
|
||||
"""
|
||||
if not hasattr(self, u'_image_manager'):
|
||||
self._image_manager = Registry().get(u'image_manager')
|
||||
return self._image_manager
|
||||
|
||||
image_manager = property(_get_image_manager)
|
||||
|
|
@ -38,6 +38,7 @@ Some of the code for this form is based on the examples at:
|
|||
from __future__ import division
|
||||
import cgi
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
|
||||
|
@ -288,7 +289,7 @@ class MainDisplay(Display):
|
|||
self.image(path)
|
||||
# Update the preview frame.
|
||||
if self.is_live:
|
||||
self.live_controller.updatePreview()
|
||||
self.live_controller.update_preview()
|
||||
return True
|
||||
|
||||
def image(self, path):
|
||||
|
@ -327,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 = {}
|
||||
|
||||
|
@ -494,11 +498,15 @@ class MainDisplay(Display):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
@ -669,7 +683,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
Check and display message if screen blank on setup.
|
||||
"""
|
||||
settings = Settings()
|
||||
self.live_controller.mainDisplaySetBackground()
|
||||
self.live_controller.main_display_set_background()
|
||||
if settings.value(u'%s/screen blank' % self.general_settings_section):
|
||||
if settings.value(u'%s/blank warning' % self.general_settings_section):
|
||||
QtGui.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Main Display Blanked'),
|
||||
|
@ -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()
|
||||
|
@ -1064,44 +1076,24 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
if self.live_controller.display:
|
||||
self.live_controller.display.close()
|
||||
self.live_controller.display = None
|
||||
if os.name == u'nt':
|
||||
# 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):
|
||||
|
@ -1137,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
|
||||
|
@ -1175,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
|
||||
|
@ -1205,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):
|
||||
|
@ -1226,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
|
||||
|
@ -1374,10 +1363,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
"""
|
||||
The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class.
|
||||
"""
|
||||
import os
|
||||
|
||||
from openlp.core.lib import Registry
|
||||
from openlp.core.ui.media import MediaState
|
||||
|
||||
|
@ -153,10 +155,14 @@ class MediaPlayer(object):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
|
@ -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):
|
||||
|
|
|
@ -30,8 +30,9 @@
|
|||
The actual plugin view form
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from openlp.core.lib import PluginStatus, Registry, translate
|
||||
from plugindialog import Ui_PluginViewDialog
|
||||
|
@ -166,10 +167,14 @@ class PluginForm(QtGui.QDialog, Ui_PluginViewDialog):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -1271,7 +1271,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
newItem.merge(item[u'service_item'])
|
||||
item[u'service_item'] = newItem
|
||||
self.repaint_service_list(item_count + 1, 0)
|
||||
self.live_controller.replaceServiceManagerItem(newItem)
|
||||
self.live_controller.replace_service_manager_item(newItem)
|
||||
self.set_modified()
|
||||
|
||||
def add_service_item(self, item, rebuild=False, expand=None, replace=False, repaint=True, selected=False):
|
||||
|
@ -1293,7 +1293,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
item.merge(self.service_items[sitem][u'service_item'])
|
||||
self.service_items[sitem][u'service_item'] = item
|
||||
self.repaint_service_list(sitem, child)
|
||||
self.live_controller.replaceServiceManagerItem(item)
|
||||
self.live_controller.replace_service_manager_item(item)
|
||||
else:
|
||||
item.render()
|
||||
# nothing selected for dnd
|
||||
|
@ -1316,7 +1316,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
self.repaint_service_list(self.drop_position, -1)
|
||||
# if rebuilding list make sure live is fixed.
|
||||
if rebuild:
|
||||
self.live_controller.replaceServiceManagerItem(item)
|
||||
self.live_controller.replace_service_manager_item(item)
|
||||
self.drop_position = 0
|
||||
self.set_modified()
|
||||
|
||||
|
@ -1327,7 +1327,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
self.application.set_busy_cursor()
|
||||
item, child = self.find_service_item()
|
||||
if self.service_items[item][u'service_item'].is_valid:
|
||||
self.preview_controller.addServiceManagerItem(self.service_items[item][u'service_item'], child)
|
||||
self.preview_controller.add_service_manager_item(self.service_items[item][u'service_item'], child)
|
||||
else:
|
||||
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
|
||||
translate('OpenLP.ServiceManager',
|
||||
|
@ -1365,15 +1365,15 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
child = row
|
||||
self.application.set_busy_cursor()
|
||||
if self.service_items[item][u'service_item'].is_valid:
|
||||
self.live_controller.addServiceManagerItem(self.service_items[item][u'service_item'], child)
|
||||
self.live_controller.add_service_manager_item(self.service_items[item][u'service_item'], child)
|
||||
if Settings().value(self.main_window.general_settings_section + u'/auto preview'):
|
||||
item += 1
|
||||
if self.service_items and item < len(self.service_items) and \
|
||||
self.service_items[item][u'service_item'].is_capable(ItemCapabilities.CanPreview):
|
||||
self.preview_controller.addServiceManagerItem(self.service_items[item][u'service_item'], 0)
|
||||
self.preview_controller.add_service_manager_item(self.service_items[item][u'service_item'], 0)
|
||||
next_item = self.service_manager_list.topLevelItem(item)
|
||||
self.service_manager_list.setCurrentItem(next_item)
|
||||
self.live_controller.preview_list_widget.setFocus()
|
||||
self.live_controller.preview_widget.setFocus()
|
||||
else:
|
||||
critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
|
||||
translate('OpenLP.ServiceManager',
|
||||
|
@ -1592,10 +1592,14 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"""
|
||||
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
|
||||
"""
|
||||
from __future__ import division
|
||||
import os
|
||||
import logging
|
||||
import copy
|
||||
|
@ -41,6 +42,7 @@ from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageS
|
|||
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.utils.actions import ActionList, CategoryOrder
|
||||
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -92,7 +94,7 @@ class SlideController(DisplayController):
|
|||
Registry().register_function(u'bootstrap_post_set_up', self.screen_size_changed)
|
||||
self.screens = ScreenList()
|
||||
try:
|
||||
self.ratio = float(self.screens.current[u'size'].width()) / float(self.screens.current[u'size'].height())
|
||||
self.ratio = self.screens.current[u'size'].width() / self.screens.current[u'size'].height()
|
||||
except ZeroDivisionError:
|
||||
self.ratio = 1
|
||||
self.loop_list = [
|
||||
|
@ -119,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
|
||||
|
@ -159,18 +161,8 @@ class SlideController(DisplayController):
|
|||
self.controller_layout.setSpacing(0)
|
||||
self.controller_layout.setMargin(0)
|
||||
# Controller list view
|
||||
self.preview_list_widget = QtGui.QTableWidget(self.controller)
|
||||
self.preview_list_widget.setColumnCount(1)
|
||||
self.preview_list_widget.horizontalHeader().setVisible(False)
|
||||
self.preview_list_widget.setColumnWidth(0, self.controller.width())
|
||||
self.preview_list_widget.is_live = self.is_live
|
||||
self.preview_list_widget.setObjectName(u'preview_list_widget')
|
||||
self.preview_list_widget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
|
||||
self.preview_list_widget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
|
||||
self.preview_list_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
|
||||
self.preview_list_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.preview_list_widget.setAlternatingRowColors(True)
|
||||
self.controller_layout.addWidget(self.preview_list_widget)
|
||||
self.preview_widget = ListPreviewWidget(self, self.ratio)
|
||||
self.controller_layout.addWidget(self.preview_widget)
|
||||
# Build the full toolbar
|
||||
self.toolbar = OpenLPToolbar(self)
|
||||
size_toolbar_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
|
@ -203,15 +195,15 @@ class SlideController(DisplayController):
|
|||
self.toolbar.add_toolbar_widget(self.hide_menu)
|
||||
self.blank_screen = create_action(self, u'blankScreen',
|
||||
text=translate('OpenLP.SlideController', 'Blank Screen'), icon=u':/slides/slide_blank.png',
|
||||
checked=False, can_shortcuts=True, category=self.category, triggers=self.onBlankDisplay)
|
||||
checked=False, can_shortcuts=True, category=self.category, triggers=self.on_blank_display)
|
||||
self.theme_screen = create_action(self, u'themeScreen',
|
||||
text=translate('OpenLP.SlideController', 'Blank to Theme'), icon=u':/slides/slide_theme.png',
|
||||
checked=False, can_shortcuts=True, category=self.category,
|
||||
triggers=self.onThemeDisplay)
|
||||
triggers=self.on_theme_display)
|
||||
self.desktop_screen = create_action(self, u'desktopScreen',
|
||||
text=translate('OpenLP.SlideController', 'Show Desktop'), icon=u':/slides/slide_desktop.png',
|
||||
checked=False, can_shortcuts=True, category=self.category,
|
||||
triggers=self.onHideDisplay)
|
||||
triggers=self.on_hide_display)
|
||||
self.hide_menu.setDefaultAction(self.blank_screen)
|
||||
self.hide_menu.menu().addAction(self.blank_screen)
|
||||
self.hide_menu.menu().addAction(self.theme_screen)
|
||||
|
@ -239,10 +231,10 @@ class SlideController(DisplayController):
|
|||
self.toolbar.add_toolbar_widget(self.play_slides_menu)
|
||||
self.play_slides_loop = create_action(self, u'playSlidesLoop', text=UiStrings().PlaySlidesInLoop,
|
||||
icon=u':/media/media_time.png', checked=False, can_shortcuts=True,
|
||||
category=self.category, triggers=self.onPlaySlidesLoop)
|
||||
category=self.category, triggers=self.on_play_slides_loop)
|
||||
self.play_slides_once = create_action(self, u'playSlidesOnce', text=UiStrings().PlaySlidesToEnd,
|
||||
icon=u':/media/media_time.png', checked=False, can_shortcuts=True,
|
||||
category=self.category, triggers=self.onPlaySlidesOnce)
|
||||
category=self.category, triggers=self.on_play_slides_once)
|
||||
if Settings().value(self.main_window.advanced_settings_section + u'/slide limits') == SlideLimits.Wrap:
|
||||
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
|
||||
else:
|
||||
|
@ -258,12 +250,12 @@ class SlideController(DisplayController):
|
|||
self.toolbar.add_toolbar_widget(self.delay_spin_box)
|
||||
else:
|
||||
self.toolbar.add_toolbar_action(u'goLive', icon=u':/general/general_live.png',
|
||||
tooltip=translate('OpenLP.SlideController', 'Move to live.'), triggers=self.onGoLive)
|
||||
tooltip=translate('OpenLP.SlideController', 'Move to live.'), triggers=self.on_go_live)
|
||||
self.toolbar.add_toolbar_action(u'addToService', icon=u':/general/general_add.png',
|
||||
tooltip=translate('OpenLP.SlideController', 'Add to Service.'), triggers=self.onPreviewAddToService)
|
||||
tooltip=translate('OpenLP.SlideController', 'Add to Service.'), triggers=self.on_preview_add_to_service)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.add_toolbar_action(u'editSong', icon=u':/general/general_edit.png',
|
||||
tooltip=translate('OpenLP.SlideController', 'Edit and reload song preview.'), triggers=self.onEditSong)
|
||||
tooltip=translate('OpenLP.SlideController', 'Edit and reload song preview.'), triggers=self.on_edit_song)
|
||||
self.controller_layout.addWidget(self.toolbar)
|
||||
# Build the Media Toolbar
|
||||
self.media_controller.register_controller(self)
|
||||
|
@ -281,7 +273,7 @@ class SlideController(DisplayController):
|
|||
icon=u':/slides/media_playback_pause.png', text=translate('OpenLP.SlideController', 'Pause Audio'),
|
||||
tooltip=translate('OpenLP.SlideController', 'Pause audio.'),
|
||||
checked=False, visible=False, category=self.category, context=QtCore.Qt.WindowShortcut,
|
||||
can_shortcuts=True, triggers=self.onAudioPauseClicked)
|
||||
can_shortcuts=True, triggers=self.set_audio_pause_clicked)
|
||||
self.audio_menu = QtGui.QMenu(translate('OpenLP.SlideController', 'Background Audio'), self.toolbar)
|
||||
self.audio_pause_item.setMenu(self.audio_menu)
|
||||
self.audio_pause_item.setParent(self.toolbar)
|
||||
|
@ -290,7 +282,7 @@ class SlideController(DisplayController):
|
|||
self.nextTrackItem = create_action(self, u'nextTrackItem', text=UiStrings().NextTrack,
|
||||
icon=u':/slides/media_playback_next.png',
|
||||
tooltip=translate('OpenLP.SlideController', 'Go to next audio track.'),
|
||||
category=self.category, can_shortcuts=True, triggers=self.onNextTrackClicked)
|
||||
category=self.category, can_shortcuts=True, triggers=self.on_next_track_clicked)
|
||||
self.audio_menu.addAction(self.nextTrackItem)
|
||||
self.trackMenu = self.audio_menu.addMenu(translate('OpenLP.SlideController', 'Tracks'))
|
||||
self.audio_time_label = QtGui.QLabel(u' 00:00 ', self.toolbar)
|
||||
|
@ -352,15 +344,15 @@ class SlideController(DisplayController):
|
|||
{u'key': u'O', u'configurable': True, u'text': translate('OpenLP.SlideController', 'Go to "Other"')}
|
||||
]
|
||||
shortcuts.extend([{u'key': unicode(number)} for number in range(10)])
|
||||
self.preview_list_widget.addActions([create_action(self,
|
||||
self.controller.addActions([create_action(self,
|
||||
u'shortcutAction_%s' % s[u'key'], text=s.get(u'text'),
|
||||
can_shortcuts=True,
|
||||
context=QtCore.Qt.WidgetWithChildrenShortcut,
|
||||
category=self.category if s.get(u'configurable') else None,
|
||||
triggers=self._slideShortcutActivated) for s in shortcuts])
|
||||
self.shortcutTimer.timeout.connect(self._slideShortcutActivated)
|
||||
triggers=self._slide_shortcut_activated) for s in shortcuts])
|
||||
self.shortcutTimer.timeout.connect(self._slide_shortcut_activated)
|
||||
# Signals
|
||||
self.preview_list_widget.clicked.connect(self.onSlideSelected)
|
||||
self.preview_widget.clicked.connect(self.on_slide_selected)
|
||||
if self.is_live:
|
||||
# Need to use event as called across threads and UI is updated
|
||||
QtCore.QObject.connect(self, QtCore.SIGNAL(u'slidecontroller_toggle_display'), self.toggle_display)
|
||||
|
@ -368,13 +360,13 @@ class SlideController(DisplayController):
|
|||
self.toolbar.set_widget_visible(self.loop_list, False)
|
||||
self.toolbar.set_widget_visible(self.wide_menu, False)
|
||||
else:
|
||||
self.preview_list_widget.doubleClicked.connect(self.onGoLiveClick)
|
||||
self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service)
|
||||
self.toolbar.set_widget_visible([u'editSong'], False)
|
||||
if self.is_live:
|
||||
self.setLiveHotkeys(self)
|
||||
self.__addActionsToWidget(self.preview_list_widget)
|
||||
self.set_live_hotkeys(self)
|
||||
self.__add_actions_to_widget(self.controller)
|
||||
else:
|
||||
self.preview_list_widget.addActions([self.nextItem, self.previous_item])
|
||||
self.controller.addActions([self.nextItem, self.previous_item])
|
||||
Registry().register_function(u'slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
||||
Registry().register_function(u'slidecontroller_%s_change' % self.type_prefix, self.on_slide_change)
|
||||
Registry().register_function(u'slidecontroller_%s_blank' % self.type_prefix, self.on_slide_blank)
|
||||
|
@ -387,7 +379,7 @@ class SlideController(DisplayController):
|
|||
QtCore.QObject.connect(self, QtCore.SIGNAL(u'slidecontroller_%s_previous' % self.type_prefix),
|
||||
self.on_slide_selected_previous)
|
||||
|
||||
def _slideShortcutActivated(self):
|
||||
def _slide_shortcut_activated(self):
|
||||
"""
|
||||
Called, when a shortcut has been activated to jump to a chorus, verse,
|
||||
etc.
|
||||
|
@ -433,8 +425,8 @@ class SlideController(DisplayController):
|
|||
if len(matches) == 1:
|
||||
self.shortcutTimer.stop()
|
||||
self.current_shortcut = u''
|
||||
self.__checkUpdateSelectedSlide(self.slideList[matches[0]])
|
||||
self.slideSelected()
|
||||
self.preview_widget.change_slide(self.slideList[matches[0]])
|
||||
self.slide_selected()
|
||||
elif sender_name != u'shortcutTimer':
|
||||
# Start the time as we did not have any match.
|
||||
self.shortcutTimer.start(350)
|
||||
|
@ -443,29 +435,29 @@ class SlideController(DisplayController):
|
|||
if self.current_shortcut in keys:
|
||||
# We had more than one match for example "V1" and "V10", but
|
||||
# "V1" was the slide we wanted to go.
|
||||
self.__checkUpdateSelectedSlide(self.slideList[self.current_shortcut])
|
||||
self.slideSelected()
|
||||
self.preview_widget.change_slide(self.slideList[self.current_shortcut])
|
||||
self.slide_selected()
|
||||
# Reset the shortcut.
|
||||
self.current_shortcut = u''
|
||||
|
||||
def setLiveHotkeys(self, parent=None):
|
||||
def set_live_hotkeys(self, parent=None):
|
||||
"""
|
||||
Set the live hotkeys
|
||||
"""
|
||||
self.previousService = create_action(parent, u'previousService',
|
||||
text=translate('OpenLP.SlideController', 'Previous Service'),
|
||||
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
|
||||
triggers=self.servicePrevious)
|
||||
triggers=self.service_previous)
|
||||
self.nextService = create_action(parent, 'nextService',
|
||||
text=translate('OpenLP.SlideController', 'Next Service'),
|
||||
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
|
||||
triggers=self.serviceNext)
|
||||
triggers=self.service_next)
|
||||
self.escapeItem = create_action(parent, 'escapeItem',
|
||||
text=translate('OpenLP.SlideController', 'Escape Item'),
|
||||
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
|
||||
triggers=self.liveEscape)
|
||||
triggers=self.live_escape)
|
||||
|
||||
def liveEscape(self):
|
||||
def live_escape(self):
|
||||
"""
|
||||
If you press ESC on the live screen it should close the display temporarily.
|
||||
"""
|
||||
|
@ -477,24 +469,24 @@ class SlideController(DisplayController):
|
|||
Toggle the display settings triggered from remote messages.
|
||||
"""
|
||||
if action == u'blank' or action == u'hide':
|
||||
self.onBlankDisplay(True)
|
||||
self.on_blank_display(True)
|
||||
elif action == u'theme':
|
||||
self.onThemeDisplay(True)
|
||||
self.on_theme_display(True)
|
||||
elif action == u'desktop':
|
||||
self.onHideDisplay(True)
|
||||
self.on_hide_display(True)
|
||||
elif action == u'show':
|
||||
self.onBlankDisplay(False)
|
||||
self.onThemeDisplay(False)
|
||||
self.onHideDisplay(False)
|
||||
self.on_blank_display(False)
|
||||
self.on_theme_display(False)
|
||||
self.on_hide_display(False)
|
||||
|
||||
def servicePrevious(self):
|
||||
def service_previous(self):
|
||||
"""
|
||||
Live event to select the previous service item from the service manager.
|
||||
"""
|
||||
self.keypress_queue.append(ServiceItemAction.Previous)
|
||||
self._process_queue()
|
||||
|
||||
def serviceNext(self):
|
||||
def service_next(self):
|
||||
"""
|
||||
Live event to select the next service item from the service manager.
|
||||
"""
|
||||
|
@ -529,15 +521,16 @@ class SlideController(DisplayController):
|
|||
self.display = MainDisplay(self, self.is_live, self)
|
||||
self.display.setup()
|
||||
if self.is_live:
|
||||
self.__addActionsToWidget(self.display)
|
||||
self.__add_actions_to_widget(self.display)
|
||||
self.display.audio_player.connectSlot(QtCore.SIGNAL(u'tick(qint64)'), self.on_audio_time_remaining)
|
||||
# The SlidePreview's ratio.
|
||||
try:
|
||||
self.ratio = float(self.screens.current[u'size'].width()) / float(self.screens.current[u'size'].height())
|
||||
self.ratio = self.screens.current[u'size'].width() / self.screens.current[u'size'].height()
|
||||
except ZeroDivisionError:
|
||||
self.ratio = 1
|
||||
self.media_controller.setup_display(self.display, False)
|
||||
self.preview_size_changed()
|
||||
self.preview_widget.screen_size_changed(self.ratio)
|
||||
self.preview_display.setup()
|
||||
service_item = ServiceItem()
|
||||
self.preview_display.web_view.setHtml(build_html(service_item, self.preview_display.screen, None, self.is_live,
|
||||
|
@ -546,7 +539,7 @@ class SlideController(DisplayController):
|
|||
if self.service_item:
|
||||
self.refresh_service_item()
|
||||
|
||||
def __addActionsToWidget(self, widget):
|
||||
def __add_actions_to_widget(self, widget):
|
||||
"""
|
||||
Add actions to the widget specified by `widget`
|
||||
"""
|
||||
|
@ -561,7 +554,7 @@ class SlideController(DisplayController):
|
|||
splitters is moved or when the screen size is changed. Note, that this
|
||||
method is (also) called frequently from the mainwindow *paintEvent*.
|
||||
"""
|
||||
if self.ratio < float(self.preview_frame.width()) / float(self.preview_frame.height()):
|
||||
if self.ratio < self.preview_frame.width() / self.preview_frame.height():
|
||||
# We have to take the height as limit.
|
||||
max_height = self.preview_frame.height() - self.grid.margin() * 2
|
||||
self.slide_preview.setFixedSize(QtCore.QSize(max_height * self.ratio, max_height))
|
||||
|
@ -573,22 +566,10 @@ 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()}
|
||||
# Make sure that the frames have the correct size.
|
||||
self.preview_list_widget.setColumnWidth(0, self.preview_list_widget.viewport().size().width())
|
||||
if self.service_item:
|
||||
# Sort out songs, bibles, etc.
|
||||
if self.service_item.is_text():
|
||||
self.preview_list_widget.resizeRowsToContents()
|
||||
else:
|
||||
# Sort out image heights.
|
||||
width = self.main_window.controlSplitter.sizes()[self.split]
|
||||
for framenumber in range(len(self.service_item.get_frames())):
|
||||
self.preview_list_widget.setRowHeight(framenumber, width / self.ratio)
|
||||
self.onControllerSizeChanged(self.controller.width())
|
||||
self.preview_display.screen = {u'size': self.preview_display.geometry()}
|
||||
self.on_controller_size_changed(self.controller.width())
|
||||
|
||||
def onControllerSizeChanged(self, width):
|
||||
def on_controller_size_changed(self, width):
|
||||
"""
|
||||
Change layout of display control buttons on controller size change
|
||||
"""
|
||||
|
@ -604,14 +585,15 @@ class SlideController(DisplayController):
|
|||
self.toolbar.set_widget_visible(self.wide_menu, False)
|
||||
self.toolbar.set_widget_visible(self.narrow_menu)
|
||||
|
||||
def onSongBarHandler(self):
|
||||
def on_song_bar_handler(self):
|
||||
"""
|
||||
Some song handler
|
||||
"""
|
||||
request = self.sender().text()
|
||||
slide_no = self.slideList[request]
|
||||
self.__updatePreviewSelection(slide_no)
|
||||
self.slideSelected()
|
||||
width = self.main_window.control_splitter.sizes()[self.split]
|
||||
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
|
||||
self.slide_selected()
|
||||
|
||||
def receive_spin_delay(self):
|
||||
"""
|
||||
|
@ -699,50 +681,48 @@ class SlideController(DisplayController):
|
|||
"""
|
||||
log.debug(u'add_service_item live = %s' % self.is_live)
|
||||
item.render()
|
||||
slideno = 0
|
||||
slide_no = 0
|
||||
if self.song_edit:
|
||||
slideno = self.selected_row
|
||||
slide_no = self.selected_row
|
||||
self.song_edit = False
|
||||
self._process_item(item, slideno)
|
||||
self._process_item(item, slide_no)
|
||||
|
||||
def replaceServiceManagerItem(self, item):
|
||||
def replace_service_manager_item(self, item):
|
||||
"""
|
||||
Replacement item following a remote edit
|
||||
"""
|
||||
if item == self.service_item:
|
||||
self._process_item(item, self.preview_list_widget.currentRow())
|
||||
self._process_item(item, self.preview_widget.current_slide_number())
|
||||
|
||||
def addServiceManagerItem(self, item, slideno):
|
||||
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'addServiceManagerItem 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 should reload the song or not
|
||||
slidenum = slideno
|
||||
if slideno == -1:
|
||||
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
|
||||
# should reload the song or not
|
||||
slidenum = slide_no
|
||||
if slide_no == -1:
|
||||
slidenum = 0
|
||||
# If service item is the same as the current one, only change slide
|
||||
if slideno >= 0 and item == self.service_item:
|
||||
self.__checkUpdateSelectedSlide(slidenum)
|
||||
self.slideSelected()
|
||||
if slide_no >= 0 and item == self.service_item:
|
||||
self.preview_widget.change_slide(slidenum)
|
||||
self.slide_selected()
|
||||
else:
|
||||
self._process_item(item, slidenum)
|
||||
if self.is_live and item.auto_play_slides_loop and item.timed_slide_interval > 0:
|
||||
self.play_slides_loop.setChecked(item.auto_play_slides_loop)
|
||||
self.delay_spin_box.setValue(int(item.timed_slide_interval))
|
||||
self.onPlaySlidesLoop()
|
||||
self.on_play_slides_loop()
|
||||
elif self.is_live and item.auto_play_slides_once and item.timed_slide_interval > 0:
|
||||
self.play_slides_once.setChecked(item.auto_play_slides_once)
|
||||
self.delay_spin_box.setValue(int(item.timed_slide_interval))
|
||||
self.onPlaySlidesOnce()
|
||||
self.on_play_slides_once()
|
||||
|
||||
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()
|
||||
|
@ -750,17 +730,14 @@ class SlideController(DisplayController):
|
|||
# take a copy not a link to the servicemanager copy.
|
||||
self.service_item = copy.copy(service_item)
|
||||
if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||
self._resetBlank()
|
||||
Registry().execute(u'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
|
||||
self._reset_blank()
|
||||
Registry().execute(
|
||||
u'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
|
||||
self.slideList = {}
|
||||
width = self.main_window.controlSplitter.sizes()[self.split]
|
||||
self.preview_list_widget.clear()
|
||||
self.preview_list_widget.setRowCount(0)
|
||||
self.preview_list_widget.setColumnWidth(0, width)
|
||||
if self.is_live:
|
||||
self.song_menu.menu().clear()
|
||||
self.display.audio_player.reset()
|
||||
self.setAudioItemsVisibility(False)
|
||||
self.set_audio_items_visibility(False)
|
||||
self.audio_pause_item.setChecked(False)
|
||||
# If the current item has background audio
|
||||
if self.service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
|
||||
|
@ -770,7 +747,7 @@ class SlideController(DisplayController):
|
|||
for counter in range(len(self.service_item.background_audio)):
|
||||
action = self.trackMenu.addAction(os.path.basename(self.service_item.background_audio[counter]))
|
||||
action.setData(counter)
|
||||
action.triggered.connect(self.onTrackTriggered)
|
||||
action.triggered.connect(self.on_track_triggered)
|
||||
self.display.audio_player.repeat = Settings().value(
|
||||
self.main_window.general_settings_section + u'/audio repeat list')
|
||||
if Settings().value(self.main_window.general_settings_section + u'/audio start paused'):
|
||||
|
@ -778,13 +755,10 @@ class SlideController(DisplayController):
|
|||
self.display.audio_player.pause()
|
||||
else:
|
||||
self.display.audio_player.play()
|
||||
self.setAudioItemsVisibility(True)
|
||||
self.set_audio_items_visibility(True)
|
||||
row = 0
|
||||
text = []
|
||||
width = self.main_window.control_splitter.sizes()[self.split]
|
||||
for framenumber, frame in enumerate(self.service_item.get_frames()):
|
||||
self.preview_list_widget.setRowCount(self.preview_list_widget.rowCount() + 1)
|
||||
item = QtGui.QTableWidgetItem()
|
||||
slideHeight = 0
|
||||
if self.service_item.is_text():
|
||||
if frame[u'verseTag']:
|
||||
# These tags are already translated.
|
||||
|
@ -795,40 +769,18 @@ class SlideController(DisplayController):
|
|||
if verse_def not in self.slideList:
|
||||
self.slideList[verse_def] = framenumber
|
||||
if self.is_live:
|
||||
self.song_menu.menu().addAction(verse_def, self.onSongBarHandler)
|
||||
self.song_menu.menu().addAction(verse_def, self.on_song_bar_handler)
|
||||
else:
|
||||
row += 1
|
||||
self.slideList[unicode(row)] = row - 1
|
||||
item.setText(frame[u'text'])
|
||||
else:
|
||||
label = QtGui.QLabel()
|
||||
label.setMargin(4)
|
||||
if service_item.is_media():
|
||||
label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
||||
else:
|
||||
label.setScaledContents(True)
|
||||
if self.service_item.is_command():
|
||||
label.setPixmap(QtGui.QPixmap(frame[u'image']))
|
||||
else:
|
||||
# If current slide set background to image
|
||||
if framenumber == slideno:
|
||||
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(frame[u'path'],
|
||||
ImageSource.ImagePlugin)
|
||||
image = self.image_manager.get_image(frame[u'path'], ImageSource.ImagePlugin)
|
||||
label.setPixmap(QtGui.QPixmap.fromImage(image))
|
||||
self.preview_list_widget.setCellWidget(framenumber, 0, label)
|
||||
slideHeight = width * (1 / self.ratio)
|
||||
row += 1
|
||||
self.slideList[unicode(row)] = row - 1
|
||||
text.append(unicode(row))
|
||||
self.preview_list_widget.setItem(framenumber, 0, item)
|
||||
if slideHeight:
|
||||
self.preview_list_widget.setRowHeight(framenumber, slideHeight)
|
||||
self.preview_list_widget.setVerticalHeaderLabels(text)
|
||||
if self.service_item.is_text():
|
||||
self.preview_list_widget.resizeRowsToContents()
|
||||
self.preview_list_widget.setColumnWidth(0, self.preview_list_widget.viewport().size().width())
|
||||
self.__updatePreviewSelection(slideno)
|
||||
# If current slide set background to image
|
||||
if not self.service_item.is_command() and framenumber == slideno:
|
||||
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(frame[u'path'],
|
||||
ImageSource.ImagePlugin)
|
||||
self.preview_widget.replace_service_item(self.service_item, width, slideno)
|
||||
self.enable_tool_bar(service_item)
|
||||
# Pass to display for viewing.
|
||||
# Postpone image build, we need to do this later to avoid the theme
|
||||
|
@ -836,9 +788,8 @@ class SlideController(DisplayController):
|
|||
if not self.service_item.is_image():
|
||||
self.display.build_html(self.service_item)
|
||||
if service_item.is_media():
|
||||
self.onMediaStart(service_item)
|
||||
self.slideSelected(True)
|
||||
self.preview_list_widget.setFocus()
|
||||
self.on_media_start(service_item)
|
||||
self.slide_selected(True)
|
||||
if old_item:
|
||||
# Close the old item after the new one is opened
|
||||
# This avoids the service theme/desktop flashing on screen
|
||||
|
@ -847,19 +798,9 @@ class SlideController(DisplayController):
|
|||
if old_item.is_command() and not service_item.is_command():
|
||||
Registry().execute(u'%s_stop' % old_item.name.lower(), [old_item, self.is_live])
|
||||
if old_item.is_media() and not service_item.is_media():
|
||||
self.onMediaClose()
|
||||
self.on_media_close()
|
||||
Registry().execute(u'slidecontroller_%s_started' % self.type_prefix, [service_item])
|
||||
|
||||
def __updatePreviewSelection(self, slideno):
|
||||
"""
|
||||
Utility method to update the selected slide in the list.
|
||||
"""
|
||||
if slideno > self.preview_list_widget.rowCount():
|
||||
self.preview_list_widget.selectRow(
|
||||
self.preview_list_widget.rowCount() - 1)
|
||||
else:
|
||||
self.__checkUpdateSelectedSlide(slideno)
|
||||
|
||||
# Screen event methods
|
||||
def on_slide_selected_index(self, message):
|
||||
"""
|
||||
|
@ -870,49 +811,49 @@ class SlideController(DisplayController):
|
|||
return
|
||||
if self.service_item.is_command():
|
||||
Registry().execute(u'%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
|
||||
self.updatePreview()
|
||||
self.update_preview()
|
||||
else:
|
||||
self.__checkUpdateSelectedSlide(index)
|
||||
self.slideSelected()
|
||||
self.preview_widget.change_slide(index)
|
||||
self.slide_selected()
|
||||
|
||||
def mainDisplaySetBackground(self):
|
||||
def main_display_set_background(self):
|
||||
"""
|
||||
Allow the main display to blank the main display at startup time
|
||||
"""
|
||||
log.debug(u'mainDisplaySetBackground live = %s' % self.is_live)
|
||||
log.debug(u'main_display_set_background live = %s' % self.is_live)
|
||||
display_type = Settings().value(self.main_window.general_settings_section + u'/screen blank')
|
||||
if self.screens.which_screen(self.window()) != self.screens.which_screen(self.display):
|
||||
# Order done to handle initial conversion
|
||||
if display_type == u'themed':
|
||||
self.onThemeDisplay(True)
|
||||
self.on_theme_display(True)
|
||||
elif display_type == u'hidden':
|
||||
self.onHideDisplay(True)
|
||||
self.on_hide_display(True)
|
||||
elif display_type == u'blanked':
|
||||
self.onBlankDisplay(True)
|
||||
self.on_blank_display(True)
|
||||
else:
|
||||
Registry().execute(u'live_display_show')
|
||||
else:
|
||||
self.liveEscape()
|
||||
self.live_escape()
|
||||
|
||||
def on_slide_blank(self):
|
||||
"""
|
||||
Handle the slidecontroller blank event
|
||||
"""
|
||||
self.onBlankDisplay(True)
|
||||
self.on_blank_display(True)
|
||||
|
||||
def on_slide_unblank(self):
|
||||
"""
|
||||
Handle the slidecontroller unblank event
|
||||
"""
|
||||
self.onBlankDisplay(False)
|
||||
self.on_blank_display(False)
|
||||
|
||||
def onBlankDisplay(self, checked=None):
|
||||
def on_blank_display(self, checked=None):
|
||||
"""
|
||||
Handle the blank screen button actions
|
||||
"""
|
||||
if checked is None:
|
||||
checked = self.blank_screen.isChecked()
|
||||
log.debug(u'onBlankDisplay %s' % checked)
|
||||
log.debug(u'on_blank_display %s' % checked)
|
||||
self.hide_menu.setDefaultAction(self.blank_screen)
|
||||
self.blank_screen.setChecked(checked)
|
||||
self.theme_screen.setChecked(False)
|
||||
|
@ -921,17 +862,17 @@ class SlideController(DisplayController):
|
|||
Settings().setValue(self.main_window.general_settings_section + u'/screen blank', u'blanked')
|
||||
else:
|
||||
Settings().remove(self.main_window.general_settings_section + u'/screen blank')
|
||||
self.blankPlugin()
|
||||
self.updatePreview()
|
||||
self.onToggleLoop()
|
||||
self.blank_plugin()
|
||||
self.update_preview()
|
||||
self.on_toggle_loop()
|
||||
|
||||
def onThemeDisplay(self, checked=None):
|
||||
def on_theme_display(self, checked=None):
|
||||
"""
|
||||
Handle the Theme screen button
|
||||
"""
|
||||
if checked is None:
|
||||
checked = self.theme_screen.isChecked()
|
||||
log.debug(u'onThemeDisplay %s' % checked)
|
||||
log.debug(u'on_theme_display %s' % checked)
|
||||
self.hide_menu.setDefaultAction(self.theme_screen)
|
||||
self.blank_screen.setChecked(False)
|
||||
self.theme_screen.setChecked(checked)
|
||||
|
@ -940,17 +881,17 @@ class SlideController(DisplayController):
|
|||
Settings().setValue(self.main_window.general_settings_section + u'/screen blank', u'themed')
|
||||
else:
|
||||
Settings().remove(self.main_window.general_settings_section + u'/screen blank')
|
||||
self.blankPlugin()
|
||||
self.updatePreview()
|
||||
self.onToggleLoop()
|
||||
self.blank_plugin()
|
||||
self.update_preview()
|
||||
self.on_toggle_loop()
|
||||
|
||||
def onHideDisplay(self, checked=None):
|
||||
def on_hide_display(self, checked=None):
|
||||
"""
|
||||
Handle the Hide screen button
|
||||
"""
|
||||
if checked is None:
|
||||
checked = self.desktop_screen.isChecked()
|
||||
log.debug(u'onHideDisplay %s' % checked)
|
||||
log.debug(u'on_hide_display %s' % checked)
|
||||
self.hide_menu.setDefaultAction(self.desktop_screen)
|
||||
self.blank_screen.setChecked(False)
|
||||
self.theme_screen.setChecked(False)
|
||||
|
@ -959,16 +900,16 @@ class SlideController(DisplayController):
|
|||
Settings().setValue(self.main_window.general_settings_section + u'/screen blank', u'hidden')
|
||||
else:
|
||||
Settings().remove(self.main_window.general_settings_section + u'/screen blank')
|
||||
self.hidePlugin(checked)
|
||||
self.updatePreview()
|
||||
self.onToggleLoop()
|
||||
self.hide_plugin(checked)
|
||||
self.update_preview()
|
||||
self.on_toggle_loop()
|
||||
|
||||
def blankPlugin(self):
|
||||
def blank_plugin(self):
|
||||
"""
|
||||
Blank/Hide the display screen within a plugin if required.
|
||||
"""
|
||||
hide_mode = self.hide_mode()
|
||||
log.debug(u'blankPlugin %s ', hide_mode)
|
||||
log.debug(u'blank_plugin %s ', hide_mode)
|
||||
if self.service_item is not None:
|
||||
if hide_mode:
|
||||
if not self.service_item.is_command():
|
||||
|
@ -985,11 +926,11 @@ class SlideController(DisplayController):
|
|||
else:
|
||||
Registry().execute(u'live_display_show')
|
||||
|
||||
def hidePlugin(self, hide):
|
||||
def hide_plugin(self, hide):
|
||||
"""
|
||||
Tell the plugin to hide the display screen.
|
||||
"""
|
||||
log.debug(u'hidePlugin %s ', hide)
|
||||
log.debug(u'hide_plugin %s ', hide)
|
||||
if self.service_item is not None:
|
||||
if hide:
|
||||
Registry().execute(u'live_display_hide', HideMode.Screen)
|
||||
|
@ -1004,20 +945,20 @@ class SlideController(DisplayController):
|
|||
else:
|
||||
Registry().execute(u'live_display_show')
|
||||
|
||||
def onSlideSelected(self):
|
||||
def on_slide_selected(self):
|
||||
"""
|
||||
Slide selected in controller
|
||||
"""
|
||||
self.slideSelected()
|
||||
self.slide_selected()
|
||||
|
||||
def slideSelected(self, start=False):
|
||||
def slide_selected(self, start=False):
|
||||
"""
|
||||
Generate the preview when you click on a slide.
|
||||
if this is the Live Controller also display on the screen
|
||||
"""
|
||||
row = self.preview_list_widget.currentRow()
|
||||
row = self.preview_widget.current_slide_number()
|
||||
self.selected_row = 0
|
||||
if -1 < row < self.preview_list_widget.rowCount():
|
||||
if -1 < row < self.preview_widget.slide_count():
|
||||
if self.service_item.is_command():
|
||||
if self.is_live and not start:
|
||||
Registry().execute(u'%s_slide' % self.service_item.name.lower(),
|
||||
|
@ -1033,9 +974,9 @@ class SlideController(DisplayController):
|
|||
self.display.image(to_display)
|
||||
# reset the store used to display first image
|
||||
self.service_item.bg_image_bytes = None
|
||||
self.updatePreview()
|
||||
self.update_preview()
|
||||
self.selected_row = row
|
||||
self.__checkUpdateSelectedSlide(row)
|
||||
self.preview_widget.change_slide(row)
|
||||
Registry().execute(u'slidecontroller_%s_changed' % self.type_prefix, row)
|
||||
self.display.setFocus()
|
||||
|
||||
|
@ -1043,15 +984,15 @@ class SlideController(DisplayController):
|
|||
"""
|
||||
The slide has been changed. Update the slidecontroller accordingly
|
||||
"""
|
||||
self.__checkUpdateSelectedSlide(row)
|
||||
self.updatePreview()
|
||||
self.preview_widget.change_slide(row)
|
||||
self.update_preview()
|
||||
Registry().execute(u'slidecontroller_%s_changed' % self.type_prefix, row)
|
||||
|
||||
def updatePreview(self):
|
||||
def update_preview(self):
|
||||
"""
|
||||
This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
|
||||
"""
|
||||
log.debug(u'updatePreview %s ' % self.screens.current[u'primary'])
|
||||
log.debug(u'update_preview %s ' % self.screens.current[u'primary'])
|
||||
if not self.screens.current[u'primary'] and self.service_item and \
|
||||
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||
# Grab now, but try again in a couple of seconds if slide change is slow
|
||||
|
@ -1087,24 +1028,24 @@ class SlideController(DisplayController):
|
|||
return
|
||||
Registry().execute(u'%s_next' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||
if self.service_item.is_command() and self.is_live:
|
||||
self.updatePreview()
|
||||
self.update_preview()
|
||||
else:
|
||||
row = self.preview_list_widget.currentRow() + 1
|
||||
if row == self.preview_list_widget.rowCount():
|
||||
row = self.preview_widget.current_slide_number() + 1
|
||||
if row == self.preview_widget.slide_count():
|
||||
if wrap is None:
|
||||
if self.slide_limits == SlideLimits.Wrap:
|
||||
row = 0
|
||||
elif self.is_live and self.slide_limits == SlideLimits.Next:
|
||||
self.serviceNext()
|
||||
self.service_next()
|
||||
return
|
||||
else:
|
||||
row = self.preview_list_widget.rowCount() - 1
|
||||
row = self.preview_widget.slide_count() - 1
|
||||
elif wrap:
|
||||
row = 0
|
||||
else:
|
||||
row = self.preview_list_widget.rowCount() - 1
|
||||
self.__checkUpdateSelectedSlide(row)
|
||||
self.slideSelected()
|
||||
row = self.preview_widget.slide_count() - 1
|
||||
self.preview_widget.change_slide(row)
|
||||
self.slide_selected()
|
||||
|
||||
def on_slide_selected_previous(self):
|
||||
"""
|
||||
|
@ -1114,44 +1055,36 @@ class SlideController(DisplayController):
|
|||
return
|
||||
Registry().execute(u'%s_previous' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||
if self.service_item.is_command() and self.is_live:
|
||||
self.updatePreview()
|
||||
self.update_preview()
|
||||
else:
|
||||
row = self.preview_list_widget.currentRow() - 1
|
||||
row = self.preview_widget.current_slide_number() - 1
|
||||
if row == -1:
|
||||
if self.slide_limits == SlideLimits.Wrap:
|
||||
row = self.preview_list_widget.rowCount() - 1
|
||||
row = self.preview_widget.slide_count() - 1
|
||||
elif self.is_live and self.slide_limits == SlideLimits.Next:
|
||||
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
|
||||
self._process_queue()
|
||||
return
|
||||
else:
|
||||
row = 0
|
||||
self.__checkUpdateSelectedSlide(row)
|
||||
self.slideSelected()
|
||||
self.preview_widget.change_slide(row)
|
||||
self.slide_selected()
|
||||
|
||||
def __checkUpdateSelectedSlide(self, row):
|
||||
"""
|
||||
Check if this slide has been updated
|
||||
"""
|
||||
if row + 1 < self.preview_list_widget.rowCount():
|
||||
self.preview_list_widget.scrollToItem(self.preview_list_widget.item(row + 1, 0))
|
||||
self.preview_list_widget.selectRow(row)
|
||||
|
||||
def onToggleLoop(self):
|
||||
def on_toggle_loop(self):
|
||||
"""
|
||||
Toggles the loop state.
|
||||
"""
|
||||
hide_mode = self.hide_mode()
|
||||
if hide_mode is None and (self.play_slides_loop.isChecked() or self.play_slides_once.isChecked()):
|
||||
self.onStartLoop()
|
||||
self.on_start_loop()
|
||||
else:
|
||||
self.on_stop_loop()
|
||||
|
||||
def onStartLoop(self):
|
||||
def on_start_loop(self):
|
||||
"""
|
||||
Start the timer loop running and store the timer id
|
||||
"""
|
||||
if self.preview_list_widget.rowCount() > 1:
|
||||
if self.preview_widget.slide_count() > 1:
|
||||
self.timer_id = self.startTimer(int(self.delay_spin_box.value()) * 1000)
|
||||
|
||||
def on_stop_loop(self):
|
||||
|
@ -1162,7 +1095,7 @@ class SlideController(DisplayController):
|
|||
self.killTimer(self.timer_id)
|
||||
self.timer_id = 0
|
||||
|
||||
def onPlaySlidesLoop(self, checked=None):
|
||||
def on_play_slides_loop(self, checked=None):
|
||||
"""
|
||||
Start or stop 'Play Slides in Loop'
|
||||
"""
|
||||
|
@ -1170,7 +1103,7 @@ class SlideController(DisplayController):
|
|||
checked = self.play_slides_loop.isChecked()
|
||||
else:
|
||||
self.play_slides_loop.setChecked(checked)
|
||||
log.debug(u'onPlaySlidesLoop %s' % checked)
|
||||
log.debug(u'on_play_slides_loop %s' % checked)
|
||||
if checked:
|
||||
self.play_slides_loop.setIcon(build_icon(u':/media/media_stop.png'))
|
||||
self.play_slides_loop.setText(UiStrings().StopPlaySlidesInLoop)
|
||||
|
@ -1181,9 +1114,9 @@ class SlideController(DisplayController):
|
|||
else:
|
||||
self.play_slides_loop.setIcon(build_icon(u':/media/media_time.png'))
|
||||
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
|
||||
self.onToggleLoop()
|
||||
self.on_toggle_loop()
|
||||
|
||||
def onPlaySlidesOnce(self, checked=None):
|
||||
def on_play_slides_once(self, checked=None):
|
||||
"""
|
||||
Start or stop 'Play Slides to End'
|
||||
"""
|
||||
|
@ -1191,7 +1124,7 @@ class SlideController(DisplayController):
|
|||
checked = self.play_slides_once.isChecked()
|
||||
else:
|
||||
self.play_slides_once.setChecked(checked)
|
||||
log.debug(u'onPlaySlidesOnce %s' % checked)
|
||||
log.debug(u'on_play_slides_once %s' % checked)
|
||||
if checked:
|
||||
self.play_slides_once.setIcon(build_icon(u':/media/media_stop.png'))
|
||||
self.play_slides_once.setText(UiStrings().StopPlaySlidesToEnd)
|
||||
|
@ -1202,15 +1135,15 @@ class SlideController(DisplayController):
|
|||
else:
|
||||
self.play_slides_once.setIcon(build_icon(u':/media/media_time'))
|
||||
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
|
||||
self.onToggleLoop()
|
||||
self.on_toggle_loop()
|
||||
|
||||
def setAudioItemsVisibility(self, visible):
|
||||
def set_audio_items_visibility(self, visible):
|
||||
"""
|
||||
Set the visibility of the audio stuff
|
||||
"""
|
||||
self.toolbar.set_widget_visible(self.audio_list, visible)
|
||||
|
||||
def onAudioPauseClicked(self, checked):
|
||||
def set_audio_pause_clicked(self, checked):
|
||||
"""
|
||||
Pause the audio player
|
||||
"""
|
||||
|
@ -1228,7 +1161,7 @@ class SlideController(DisplayController):
|
|||
if event.timerId() == self.timer_id:
|
||||
self.on_slide_selected_next(self.play_slides_loop.isChecked())
|
||||
|
||||
def onEditSong(self):
|
||||
def on_edit_song(self):
|
||||
"""
|
||||
From the preview display requires the service Item to be editied
|
||||
"""
|
||||
|
@ -1237,14 +1170,14 @@ class SlideController(DisplayController):
|
|||
if new_item:
|
||||
self.add_service_item(new_item)
|
||||
|
||||
def onPreviewAddToService(self):
|
||||
def on_preview_add_to_service(self):
|
||||
"""
|
||||
From the preview display request the Item to be added to service
|
||||
"""
|
||||
if self.service_item:
|
||||
self.service_manager.add_service_item(self.service_item)
|
||||
|
||||
def onGoLiveClick(self):
|
||||
def on_go_live_click(self):
|
||||
"""
|
||||
triggered by clicking the Preview slide items
|
||||
"""
|
||||
|
@ -1254,53 +1187,53 @@ class SlideController(DisplayController):
|
|||
if self.service_item.is_command():
|
||||
Registry().execute(u'%s_stop' % self.service_item.name.lower(), [self.service_item, self.is_live])
|
||||
if self.service_item.is_media():
|
||||
self.onMediaClose()
|
||||
self.onGoLive()
|
||||
self.on_media_close()
|
||||
self.on_go_live()
|
||||
|
||||
def onGoLive(self):
|
||||
def on_go_live(self):
|
||||
"""
|
||||
If preview copy slide item to live controller from Preview Controller
|
||||
"""
|
||||
row = self.preview_list_widget.currentRow()
|
||||
if -1 < row < self.preview_list_widget.rowCount():
|
||||
row = self.preview_widget.current_slide_number()
|
||||
if -1 < row < self.preview_widget.slide_count():
|
||||
if self.service_item.from_service:
|
||||
self.service_manager.preview_live(self.service_item.unique_identifier, row)
|
||||
else:
|
||||
self.live_controller.addServiceManagerItem(self.service_item, row)
|
||||
self.live_controller.add_service_manager_item(self.service_item, row)
|
||||
|
||||
def onMediaStart(self, item):
|
||||
def on_media_start(self, item):
|
||||
"""
|
||||
Respond to the arrival of a media service item
|
||||
"""
|
||||
log.debug(u'SlideController onMediaStart')
|
||||
log.debug(u'SlideController on_media_start')
|
||||
self.media_controller.video(self.controller_type, item, self.hide_mode())
|
||||
if not self.is_live:
|
||||
self.preview_display.show()
|
||||
self.slide_preview.hide()
|
||||
|
||||
def onMediaClose(self):
|
||||
def on_media_close(self):
|
||||
"""
|
||||
Respond to a request to close the Video
|
||||
"""
|
||||
log.debug(u'SlideController onMediaClose')
|
||||
log.debug(u'SlideController on_media_close')
|
||||
self.media_controller.media_reset(self)
|
||||
self.preview_display.hide()
|
||||
self.slide_preview.show()
|
||||
|
||||
def _resetBlank(self):
|
||||
def _reset_blank(self):
|
||||
"""
|
||||
Used by command items which provide their own displays to reset the
|
||||
screen hide attributes
|
||||
"""
|
||||
hide_mode = self.hide_mode()
|
||||
if hide_mode == HideMode.Blank:
|
||||
self.onBlankDisplay(True)
|
||||
self.on_blank_display(True)
|
||||
elif hide_mode == HideMode.Theme:
|
||||
self.onThemeDisplay(True)
|
||||
self.on_theme_display(True)
|
||||
elif hide_mode == HideMode.Screen:
|
||||
self.onHideDisplay(True)
|
||||
self.on_hide_display(True)
|
||||
else:
|
||||
self.hidePlugin(False)
|
||||
self.hide_plugin(False)
|
||||
|
||||
def hide_mode(self):
|
||||
"""
|
||||
|
@ -1317,7 +1250,7 @@ class SlideController(DisplayController):
|
|||
else:
|
||||
return None
|
||||
|
||||
def onNextTrackClicked(self):
|
||||
def on_next_track_clicked(self):
|
||||
"""
|
||||
Go to the next track when next is clicked
|
||||
"""
|
||||
|
@ -1332,7 +1265,7 @@ class SlideController(DisplayController):
|
|||
seconds %= 60
|
||||
self.audio_time_label.setText(u' %02d:%02d ' % (minutes, seconds))
|
||||
|
||||
def onTrackTriggered(self):
|
||||
def on_track_triggered(self):
|
||||
"""
|
||||
Start playing a track
|
||||
"""
|
||||
|
|
|
@ -836,10 +836,14 @@ class ThemeManager(QtGui.QWidget):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -178,8 +178,8 @@ class Ui_ThemeWizard(object):
|
|||
self.lineSpacingLabel = QtGui.QLabel(self.mainAreaPage)
|
||||
self.lineSpacingLabel.setObjectName(u'LineSpacingLabel')
|
||||
self.lineSpacingSpinBox = QtGui.QSpinBox(self.mainAreaPage)
|
||||
self.lineSpacingSpinBox.setMinimum(-50)
|
||||
self.lineSpacingSpinBox.setMaximum(50)
|
||||
self.lineSpacingSpinBox.setMinimum(-250)
|
||||
self.lineSpacingSpinBox.setMaximum(250)
|
||||
self.lineSpacingSpinBox.setObjectName(u'LineSpacingSpinBox')
|
||||
self.mainAreaLayout.addRow(self.lineSpacingLabel, self.lineSpacingSpinBox)
|
||||
self.outlineCheckBox = QtGui.QCheckBox(self.mainAreaPage)
|
||||
|
|
|
@ -75,13 +75,30 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
"""
|
||||
Generic OpenLP wizard to provide generic functionality and a unified look
|
||||
and feel.
|
||||
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``plugin``
|
||||
Plugin this wizard is part of. The plugin will be saved in the "plugin" variable.
|
||||
The plugin will also be used as basis for the file dialog methods this class provides.
|
||||
|
||||
``name``
|
||||
The object name this wizard should have.
|
||||
|
||||
``image``
|
||||
The image to display on the "welcome" page of the wizard. Should be 163x350.
|
||||
|
||||
``add_progress_page``
|
||||
Whether to add a progress page with a progressbar at the end of the wizard.
|
||||
"""
|
||||
def __init__(self, parent, plugin, name, image):
|
||||
def __init__(self, parent, plugin, name, image, add_progress_page=True):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
QtGui.QWizard.__init__(self, parent)
|
||||
self.plugin = plugin
|
||||
self.with_progress_page = add_progress_page
|
||||
self.setObjectName(name)
|
||||
self.open_icon = build_icon(u':/general/general_open.png')
|
||||
self.delete_icon = build_icon(u':/general/general_delete.png')
|
||||
|
@ -92,8 +109,9 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
self.custom_init()
|
||||
self.custom_signals()
|
||||
self.currentIdChanged.connect(self.on_current_id_changed)
|
||||
self.error_copy_to_button.clicked.connect(self.on_error_copy_to_button_clicked)
|
||||
self.error_save_to_button.clicked.connect(self.on_error_save_to_button_clicked)
|
||||
if self.with_progress_page:
|
||||
self.error_copy_to_button.clicked.connect(self.on_error_copy_to_button_clicked)
|
||||
self.error_save_to_button.clicked.connect(self.on_error_save_to_button_clicked)
|
||||
|
||||
def setupUi(self, image):
|
||||
"""
|
||||
|
@ -105,7 +123,8 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.NoBackButtonOnLastPage)
|
||||
add_welcome_page(self, image)
|
||||
self.add_custom_pages()
|
||||
self.add_progress_page()
|
||||
if self.with_progress_page:
|
||||
self.add_progress_page()
|
||||
self.retranslateUi()
|
||||
|
||||
def register_fields(self):
|
||||
|
@ -185,7 +204,7 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
Stop the wizard on cancel button, close button or ESC key.
|
||||
"""
|
||||
log.debug(u'Wizard cancelled by user.')
|
||||
if self.currentPage() == self.progress_page:
|
||||
if self.with_progress_page and self.currentPage() == self.progress_page:
|
||||
Registry().execute(u'openlp_stop_wizard')
|
||||
self.done(QtGui.QDialog.Rejected)
|
||||
|
||||
|
@ -193,14 +212,14 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
"""
|
||||
Perform necessary functions depending on which wizard page is active.
|
||||
"""
|
||||
if self.page(pageId) == self.progress_page:
|
||||
if self.with_progress_page and self.page(pageId) == self.progress_page:
|
||||
self.pre_wizard()
|
||||
self.performWizard()
|
||||
self.post_wizard()
|
||||
else:
|
||||
self.custom_cage_changed(pageId)
|
||||
self.custom_page_changed(pageId)
|
||||
|
||||
def custom_cage_changed(self, pageId):
|
||||
def custom_page_changed(self, pageId):
|
||||
"""
|
||||
Called when changing to a page other than the progress page
|
||||
"""
|
||||
|
@ -301,10 +320,14 @@ class OpenLPWizard(QtGui.QWizard):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -101,46 +101,38 @@ def get_application_version():
|
|||
if APPLICATION_VERSION:
|
||||
return APPLICATION_VERSION
|
||||
if u'--dev-version' in sys.argv or u'-d' in sys.argv:
|
||||
# If we're running the dev version, let's use bzr to get the version.
|
||||
try:
|
||||
# If bzrlib is available, use it.
|
||||
from bzrlib.branch import Branch
|
||||
b = Branch.open_containing('.')[0]
|
||||
b.lock_read()
|
||||
try:
|
||||
# Get the branch's latest revision number.
|
||||
revno = b.revno()
|
||||
# Convert said revision number into a bzr revision id.
|
||||
revision_id = b.dotted_revno_to_revision_id((revno,))
|
||||
# Get a dict of tags, with the revision id as the key.
|
||||
tags = b.tags.get_reverse_tag_dict()
|
||||
# Check if the latest
|
||||
if revision_id in tags:
|
||||
full_version = u'%s' % tags[revision_id][0]
|
||||
else:
|
||||
full_version = '%s-bzr%s' % (sorted(b.tags.get_tag_dict().keys())[-1], revno)
|
||||
finally:
|
||||
b.unlock()
|
||||
except:
|
||||
# Otherwise run the command line bzr client.
|
||||
bzr = Popen((u'bzr', u'tags', u'--sort', u'time'), stdout=PIPE)
|
||||
output, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr tags')
|
||||
lines = output.splitlines()
|
||||
if not lines:
|
||||
tag = u'0.0.0'
|
||||
revision = u'0'
|
||||
else:
|
||||
tag, revision = lines[-1].split()
|
||||
bzr = Popen((u'bzr', u'log', u'--line', u'-r', u'-1'), stdout=PIPE)
|
||||
output, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr log')
|
||||
latest = output.split(u':')[0]
|
||||
full_version = latest == revision and tag or u'%s-bzr%s' % (tag, latest)
|
||||
# NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied
|
||||
# there.
|
||||
|
||||
# Get the revision of this tree.
|
||||
bzr = Popen((u'bzr', u'revno'), stdout=PIPE)
|
||||
tree_revision, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr log')
|
||||
|
||||
# Get all tags.
|
||||
bzr = Popen((u'bzr', u'tags'), stdout=PIPE)
|
||||
output, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr tags')
|
||||
tags = output.splitlines()
|
||||
if not tags:
|
||||
tag_version = u'0.0.0'
|
||||
tag_revision = u'0'
|
||||
else:
|
||||
# Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
|
||||
# another series.
|
||||
tags = [tag for tag in tags if tag.split()[-1].strip() != u'?']
|
||||
# Get the last tag and split it in a revision and tag name.
|
||||
tag_version, tag_revision = tags[-1].split()
|
||||
# If they are equal, then this tree is tarball with the source for the release. We do not want the revision
|
||||
# number in the full version.
|
||||
if tree_revision == tag_revision:
|
||||
full_version = tag_version
|
||||
else:
|
||||
full_version = u'%s-bzr%s' % (tag_version, tree_revision)
|
||||
else:
|
||||
# We're not running the development version, let's use the file.
|
||||
filepath = AppLocation.get_directory(AppLocation.VersionDir)
|
||||
|
@ -354,9 +346,9 @@ def get_uno_instance(resolver):
|
|||
"""
|
||||
log.debug(u'get UNO Desktop Openoffice - resolve')
|
||||
if UNO_CONNECTION_TYPE == u'pipe':
|
||||
return resolver.resolve(u'uno:pipe,name=openlp_pipe; urp;StarOffice.ComponentContext')
|
||||
return resolver.resolve(u'uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext')
|
||||
else:
|
||||
return resolver.resolve(u'uno:socket,host=localhost,port=2002; urp;StarOffice.ComponentContext')
|
||||
return resolver.resolve(u'uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext')
|
||||
|
||||
|
||||
def format_time(text, local_time):
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
@ -191,10 +192,14 @@ class EditBibleForm(QtGui.QDialog, Ui_EditBibleDialog):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -544,11 +544,15 @@ class BibleDB(QtCore.QObject, Manager):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"""
|
||||
The :mod:`http` module enables OpenLP to retrieve scripture from bible websites.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
|
@ -301,11 +302,15 @@ class BGExtract(object):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
@ -362,8 +367,8 @@ class BSExtract(object):
|
|||
The version of the Bible like NIV for New International Version
|
||||
"""
|
||||
log.debug(u'BSExtract.get_books_from_http("%s")', version)
|
||||
urlversion = urllib.quote(version.encode("utf-8"))
|
||||
chapter_url = u'http://m.bibleserver.com/overlay/selectBook?translation=%s' % (urlversion)
|
||||
url_version = urllib.quote(version.encode("utf-8"))
|
||||
chapter_url = u'http://m.bibleserver.com/overlay/selectBook?translation=%s' % (url_version)
|
||||
soup = get_soup_for_bible_ref(chapter_url)
|
||||
if not soup:
|
||||
return None
|
||||
|
@ -377,11 +382,15 @@ class BSExtract(object):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
@ -477,11 +486,15 @@ class CWExtract(object):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
@ -598,9 +611,8 @@ class HTTPBible(BibleDB):
|
|||
if show_error:
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin', 'No Book Found'),
|
||||
translate('BiblesPlugin', 'No matching '
|
||||
'book could be found in this Bible. Check that you '
|
||||
'have spelled the name of the book correctly.'))
|
||||
translate('BiblesPlugin', 'No matching book could be found in this Bible. Check that you have '
|
||||
'spelled the name of the book correctly.'))
|
||||
return []
|
||||
book = db_book.name
|
||||
if BibleDB.get_verse_count(self, book_id, reference[1]) == 0:
|
||||
|
@ -667,14 +679,19 @@ class HTTPBible(BibleDB):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
||||
|
||||
def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre_parse_substitute=None):
|
||||
"""
|
||||
Gets a webpage and returns a parsed and optionally cleaned soup or None.
|
||||
|
@ -724,13 +741,10 @@ def send_error_message(error_type):
|
|||
if error_type == u'download':
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin.HTTPBible', 'Download Error'),
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a '
|
||||
'problem downloading your verse selection. Please check your '
|
||||
'Internet connection, and if this error continues to occur '
|
||||
'please consider reporting a bug.'))
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a problem downloading your verse selection. Please check '
|
||||
'your Internet connection, and if this error continues to occur please consider reporting a bug.'))
|
||||
elif error_type == u'parse':
|
||||
critical_error_message_box(
|
||||
translate('BiblesPlugin.HTTPBible', 'Parse Error'),
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a '
|
||||
'problem extracting your verse selection. If this error continues '
|
||||
'to occur please consider reporting a bug.'))
|
||||
translate('BiblesPlugin.HTTPBible', 'There was a problem extracting your verse selection. If this error '
|
||||
'continues to occur please consider reporting a bug.'))
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -473,7 +473,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||
This boolean is set to True when the list in the interface should be reloaded after saving the new images
|
||||
"""
|
||||
for filename in images_list:
|
||||
if type(filename) is not str and type(filename) is not unicode:
|
||||
if not isinstance(filename, basestring):
|
||||
continue
|
||||
log.debug(u'Adding new image: %s', filename)
|
||||
imageFile = ImageFilenames()
|
||||
|
|
|
@ -80,15 +80,15 @@ class PresentationMediaItem(MediaManagerItem):
|
|||
"""
|
||||
Build the list of file extensions to be used in the Open file dialog.
|
||||
"""
|
||||
file_type = u''
|
||||
file_type_list = u''
|
||||
for controller in self.controllers:
|
||||
if self.controllers[controller].enabled():
|
||||
file_types = self.controllers[controller].supports + self.controllers[controller].also_supports
|
||||
for file_type in file_types:
|
||||
if file_type.find(file_type) == -1:
|
||||
file_type += u'*.%s ' % file_type
|
||||
file_type_list += u'*.%s ' % file_type
|
||||
self.service_manager.supported_suffixes(file_type)
|
||||
self.on_new_file_masks = translate('PresentationPlugin.MediaItem', 'Presentations (%s)') % file_type
|
||||
self.on_new_file_masks = translate('PresentationPlugin.MediaItem', 'Presentations (%s)') % file_type_list
|
||||
|
||||
def required_icons(self):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
# -*- 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 duplicate song removal logic for OpenLP.
|
||||
"""
|
||||
from __future__ import division
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import Registry, translate
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.utils import AppLocation
|
||||
from openlp.plugins.songs.lib import delete_song
|
||||
from openlp.plugins.songs.lib.db import Song, MediaFile
|
||||
from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget
|
||||
from openlp.plugins.songs.lib.songcompare import songs_probably_equal
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
"""
|
||||
This is the Duplicate Song Removal Wizard. It provides functionality to
|
||||
search for and remove duplicate songs in the database.
|
||||
"""
|
||||
log.info(u'DuplicateSongRemovalForm loaded')
|
||||
|
||||
def __init__(self, plugin):
|
||||
"""
|
||||
Instantiate the wizard, and run any extra setup we need to.
|
||||
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``plugin``
|
||||
The songs plugin.
|
||||
"""
|
||||
self.duplicate_song_list = []
|
||||
self.review_current_count = 0
|
||||
self.review_total_count = 0
|
||||
# Used to interrupt ongoing searches when cancel is clicked.
|
||||
self.break_search = False
|
||||
OpenLPWizard.__init__(self, self.main_window, plugin, u'duplicateSongRemovalWizard',
|
||||
u':/wizards/wizard_duplicateremoval.bmp', False)
|
||||
self.setMinimumWidth(730)
|
||||
|
||||
def custom_signals(self):
|
||||
"""
|
||||
Song wizard specific signals.
|
||||
"""
|
||||
self.finish_button.clicked.connect(self.on_wizard_exit)
|
||||
self.cancel_button.clicked.connect(self.on_wizard_exit)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
Add song wizard specific pages.
|
||||
"""
|
||||
# Add custom pages.
|
||||
self.searching_page = QtGui.QWizardPage()
|
||||
self.searching_page.setObjectName(u'searching_page')
|
||||
self.searching_vertical_layout = QtGui.QVBoxLayout(self.searching_page)
|
||||
self.searching_vertical_layout.setObjectName(u'searching_vertical_layout')
|
||||
self.duplicate_search_progress_bar = QtGui.QProgressBar(self.searching_page)
|
||||
self.duplicate_search_progress_bar.setObjectName(u'duplicate_search_progress_bar')
|
||||
self.duplicate_search_progress_bar.setFormat(WizardStrings.PercentSymbolFormat)
|
||||
self.searching_vertical_layout.addWidget(self.duplicate_search_progress_bar)
|
||||
self.found_duplicates_edit = QtGui.QPlainTextEdit(self.searching_page)
|
||||
self.found_duplicates_edit.setUndoRedoEnabled(False)
|
||||
self.found_duplicates_edit.setReadOnly(True)
|
||||
self.found_duplicates_edit.setObjectName(u'found_duplicates_edit')
|
||||
self.searching_vertical_layout.addWidget(self.found_duplicates_edit)
|
||||
self.searching_page_id = self.addPage(self.searching_page)
|
||||
self.review_page = QtGui.QWizardPage()
|
||||
self.review_page.setObjectName(u'review_page')
|
||||
self.review_layout = QtGui.QVBoxLayout(self.review_page)
|
||||
self.review_layout.setObjectName(u'review_layout')
|
||||
self.review_scroll_area = QtGui.QScrollArea(self.review_page)
|
||||
self.review_scroll_area.setObjectName(u'review_scroll_area')
|
||||
self.review_scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.review_scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.review_scroll_area.setWidgetResizable(True)
|
||||
self.review_scroll_area_widget = QtGui.QWidget(self.review_scroll_area)
|
||||
self.review_scroll_area_widget.setObjectName(u'review_scroll_area_widget')
|
||||
self.review_scroll_area_layout = QtGui.QHBoxLayout(self.review_scroll_area_widget)
|
||||
self.review_scroll_area_layout.setObjectName(u'review_scroll_area_layout')
|
||||
self.review_scroll_area_layout.setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
|
||||
self.review_scroll_area_layout.setMargin(0)
|
||||
self.review_scroll_area_layout.setSpacing(0)
|
||||
self.review_scroll_area.setWidget(self.review_scroll_area_widget)
|
||||
self.review_layout.addWidget(self.review_scroll_area)
|
||||
self.review_page_id = self.addPage(self.review_page)
|
||||
# Add a dummy page to the end, to prevent the finish button to appear and the next button do disappear on the
|
||||
#review page.
|
||||
self.dummy_page = QtGui.QWizardPage()
|
||||
self.dummy_page_id = self.addPage(self.dummy_page)
|
||||
|
||||
def retranslateUi(self):
|
||||
"""
|
||||
Song wizard localisation.
|
||||
"""
|
||||
self.setWindowTitle(translate(u'Wizard', u'Wizard'))
|
||||
self.title_label.setText(WizardStrings.HeaderStyle % translate(u'OpenLP.Ui',
|
||||
u'Welcome to the Duplicate Song Removal Wizard'))
|
||||
self.information_label.setText(translate("Wizard",
|
||||
u'This wizard will help you to remove duplicate songs from the song database. You will have a chance to '
|
||||
u'review every potential duplicate song before it is deleted. So no songs will be deleted without your '
|
||||
u'explicit approval.'))
|
||||
self.searching_page.setTitle(translate(u'Wizard', u'Searching for duplicate songs.'))
|
||||
self.searching_page.setSubTitle(translate(u'Wizard', u'Please wait while your songs database is analyzed.'))
|
||||
self.update_review_counter_text()
|
||||
self.review_page.setSubTitle(translate(u'Wizard',
|
||||
u'Here you can decide which songs to remove and which ones to keep.'))
|
||||
|
||||
def update_review_counter_text(self):
|
||||
"""
|
||||
Set the wizard review page header text.
|
||||
"""
|
||||
self.review_page.setTitle(translate(u'Wizard', u'Review duplicate songs (%s/%s)') % \
|
||||
(self.review_current_count, self.review_total_count))
|
||||
|
||||
def custom_page_changed(self, page_id):
|
||||
"""
|
||||
Called when changing the wizard page.
|
||||
|
||||
``page_id``
|
||||
ID of the page the wizard changed to.
|
||||
"""
|
||||
# Hide back button.
|
||||
self.button(QtGui.QWizard.BackButton).hide()
|
||||
if page_id == self.searching_page_id:
|
||||
self.application.set_busy_cursor()
|
||||
try:
|
||||
self.button(QtGui.QWizard.NextButton).hide()
|
||||
# Search duplicate songs.
|
||||
max_songs = self.plugin.manager.get_object_count(Song)
|
||||
if max_songs == 0 or max_songs == 1:
|
||||
self.duplicate_search_progress_bar.setMaximum(1)
|
||||
self.duplicate_search_progress_bar.setValue(1)
|
||||
self.notify_no_duplicates()
|
||||
return
|
||||
# With x songs we have x*(x - 1) / 2 comparisons.
|
||||
max_progress_count = max_songs * (max_songs - 1) // 2
|
||||
self.duplicate_search_progress_bar.setMaximum(max_progress_count)
|
||||
songs = self.plugin.manager.get_all_objects(Song)
|
||||
for outer_song_counter in range(max_songs - 1):
|
||||
for inner_song_counter in range(outer_song_counter + 1, max_songs):
|
||||
if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]):
|
||||
duplicate_added = self.add_duplicates_to_song_list(songs[outer_song_counter],
|
||||
songs[inner_song_counter])
|
||||
if duplicate_added:
|
||||
self.found_duplicates_edit.appendPlainText(songs[outer_song_counter].title + " = " +
|
||||
songs[inner_song_counter].title)
|
||||
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
|
||||
# The call to process_events() will keep the GUI responsive.
|
||||
self.application.process_events()
|
||||
if self.break_search:
|
||||
return
|
||||
self.review_total_count = len(self.duplicate_song_list)
|
||||
if self.review_total_count == 0:
|
||||
self.notify_no_duplicates()
|
||||
else:
|
||||
self.button(QtGui.QWizard.NextButton).show()
|
||||
finally:
|
||||
self.application.set_normal_cursor()
|
||||
elif page_id == self.review_page_id:
|
||||
self.process_current_duplicate_entry()
|
||||
|
||||
def notify_no_duplicates(self):
|
||||
"""
|
||||
Notifies the user, that there were no duplicates found in the database.
|
||||
"""
|
||||
self.button(QtGui.QWizard.FinishButton).show()
|
||||
self.button(QtGui.QWizard.FinishButton).setEnabled(True)
|
||||
self.button(QtGui.QWizard.NextButton).hide()
|
||||
self.button(QtGui.QWizard.CancelButton).hide()
|
||||
QtGui.QMessageBox.information(self, translate(u'Wizard', u'Information'),
|
||||
translate(u'Wizard', u'No duplicate songs have been found in the database.'),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||
|
||||
def add_duplicates_to_song_list(self, search_song, duplicate_song):
|
||||
"""
|
||||
Inserts a song duplicate (two similar songs) to the duplicate song list.
|
||||
If one of the two songs is already part of the duplicate song list,
|
||||
don't add another duplicate group but add the other song to that group.
|
||||
Returns True if at least one of the songs was added, False if both were already
|
||||
member of a group.
|
||||
|
||||
``search_song``
|
||||
The song we searched the duplicate for.
|
||||
|
||||
``duplicate_song``
|
||||
The duplicate song.
|
||||
"""
|
||||
duplicate_group_found = False
|
||||
duplicate_added = False
|
||||
for duplicate_group in self.duplicate_song_list:
|
||||
# Skip the first song in the duplicate lists, since the first one has to be an earlier song.
|
||||
if search_song in duplicate_group and not duplicate_song in duplicate_group:
|
||||
duplicate_group.append(duplicate_song)
|
||||
duplicate_group_found = True
|
||||
duplicate_added = True
|
||||
break
|
||||
elif not search_song in duplicate_group and duplicate_song in duplicate_group:
|
||||
duplicate_group.append(search_song)
|
||||
duplicate_group_found = True
|
||||
duplicate_added = True
|
||||
break
|
||||
elif search_song in duplicate_group and duplicate_song in duplicate_group:
|
||||
duplicate_group_found = True
|
||||
duplicate_added = False
|
||||
break
|
||||
if not duplicate_group_found:
|
||||
self.duplicate_song_list.append([search_song, duplicate_song])
|
||||
duplicate_added = True
|
||||
return duplicate_added
|
||||
|
||||
def on_wizard_exit(self):
|
||||
"""
|
||||
Once the wizard is finished, refresh the song list,
|
||||
since we potentially removed songs from it.
|
||||
"""
|
||||
self.break_search = True
|
||||
self.plugin.media_item.on_search_text_button_clicked()
|
||||
|
||||
def setDefaults(self):
|
||||
"""
|
||||
Set default form values for the song import wizard.
|
||||
"""
|
||||
self.restart()
|
||||
self.duplicate_search_progress_bar.setValue(0)
|
||||
self.found_duplicates_edit.clear()
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Controls whether we should switch to the next wizard page. This method loops
|
||||
on the review page as long as there are more song duplicates to review.
|
||||
"""
|
||||
if self.currentId() == self.review_page_id:
|
||||
# As long as it's not the last duplicate list entry we revisit the review page.
|
||||
if len(self.duplicate_song_list) == 1:
|
||||
return True
|
||||
else:
|
||||
self.proceed_to_next_review()
|
||||
return False
|
||||
return OpenLPWizard.validateCurrentPage(self)
|
||||
|
||||
def remove_button_clicked(self, song_review_widget):
|
||||
"""
|
||||
Removes a song from the database, removes the GUI element representing the
|
||||
song on the review page, and disable the remove button if only one duplicate
|
||||
is left.
|
||||
|
||||
``song_review_widget``
|
||||
The SongReviewWidget whose song we should delete.
|
||||
"""
|
||||
# Remove song from duplicate song list.
|
||||
self.duplicate_song_list[-1].remove(song_review_widget.song)
|
||||
# Remove song from the database.
|
||||
delete_song(song_review_widget.song.id, self.plugin)
|
||||
# Remove GUI elements for the song.
|
||||
self.review_scroll_area_layout.removeWidget(song_review_widget)
|
||||
song_review_widget.setParent(None)
|
||||
# Check if we only have one duplicate left:
|
||||
# 2 stretches + 1 SongReviewWidget = 3
|
||||
# The SongReviewWidget is then at position 1.
|
||||
if len(self.duplicate_song_list[-1]) == 1:
|
||||
self.review_scroll_area_layout.itemAt(1).widget().song_remove_button.setEnabled(False)
|
||||
|
||||
def proceed_to_next_review(self):
|
||||
"""
|
||||
Removes the previous review UI elements and calls process_current_duplicate_entry.
|
||||
"""
|
||||
# Remove last duplicate group.
|
||||
self.duplicate_song_list.pop()
|
||||
# Remove all previous elements.
|
||||
for i in reversed(range(self.review_scroll_area_layout.count())):
|
||||
item = self.review_scroll_area_layout.itemAt(i)
|
||||
if isinstance(item, QtGui.QWidgetItem):
|
||||
# The order is important here, if the .setParent(None) call is done
|
||||
# before the .removeItem() call, a segfault occurs.
|
||||
widget = item.widget()
|
||||
self.review_scroll_area_layout.removeItem(item)
|
||||
widget.setParent(None)
|
||||
else:
|
||||
self.review_scroll_area_layout.removeItem(item)
|
||||
# Process next set of duplicates.
|
||||
self.process_current_duplicate_entry()
|
||||
|
||||
def process_current_duplicate_entry(self):
|
||||
"""
|
||||
Update the review counter in the wizard header, add song widgets for
|
||||
the current duplicate group to review, if it's the last
|
||||
duplicate song group, hide the "next" button and show the "finish" button.
|
||||
"""
|
||||
# Update the counter.
|
||||
self.review_current_count = self.review_total_count - (len(self.duplicate_song_list) - 1)
|
||||
self.update_review_counter_text()
|
||||
# Add song elements to the UI.
|
||||
if len(self.duplicate_song_list) > 0:
|
||||
self.review_scroll_area_layout.addStretch(1)
|
||||
for duplicate in self.duplicate_song_list[-1]:
|
||||
song_review_widget = SongReviewWidget(self.review_page, duplicate)
|
||||
song_review_widget.song_remove_button_clicked.connect(self.remove_button_clicked)
|
||||
self.review_scroll_area_layout.addWidget(song_review_widget)
|
||||
self.review_scroll_area_layout.addStretch(1)
|
||||
# Change next button to finish button on last review.
|
||||
if len(self.duplicate_song_list) == 1:
|
||||
self.button(QtGui.QWizard.FinishButton).show()
|
||||
self.button(QtGui.QWizard.FinishButton).setEnabled(True)
|
||||
self.button(QtGui.QWizard.NextButton).hide()
|
||||
self.button(QtGui.QWizard.CancelButton).hide()
|
||||
|
||||
def _get_main_window(self):
|
||||
"""
|
||||
Adds the main window to the class dynamically.
|
||||
"""
|
||||
if not hasattr(self, u'_main_window'):
|
||||
self._main_window = Registry().get(u'main_window')
|
||||
return self._main_window
|
||||
|
||||
main_window = property(_get_main_window)
|
||||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
from sqlalchemy.sql import and_
|
||||
|
@ -525,10 +526,14 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the application to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
A widget representing a song in the duplicate song removal wizard review page.
|
||||
"""
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
from openlp.plugins.songs.lib.xml import SongXML
|
||||
|
||||
|
||||
class SongReviewWidget(QtGui.QWidget):
|
||||
"""
|
||||
A widget representing a song on the duplicate song review page.
|
||||
It displays most of the information a song contains and
|
||||
provides a "remove" button to remove the song from the database.
|
||||
The remove logic is not implemented here, but a signal is provided
|
||||
when the remove button is clicked.
|
||||
"""
|
||||
|
||||
# Signals have to be class variables and not instance variables. Otherwise
|
||||
# they are not registered by Qt (missing emit and connect methods are artifacts of this).
|
||||
# To use SongReviewWidget as a signal parameter one would have to assigning the class
|
||||
# variable after the class is declared. While this is possible, it also messes Qts meta
|
||||
# object system up. The result is an
|
||||
# "Object::connect: Use the SIGNAL macro to bind SongReviewWidget::(QWidget*)" error on
|
||||
# connect calls.
|
||||
# That's why we cheat a little and use QWidget instead of SongReviewWidget as parameter.
|
||||
# While not being entirely correct, it does work.
|
||||
song_remove_button_clicked = QtCore.pyqtSignal(QtGui.QWidget)
|
||||
|
||||
def __init__(self, parent, song):
|
||||
"""
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``song``
|
||||
The Song which this SongReviewWidget should represent.
|
||||
"""
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.song = song
|
||||
self.setupUi()
|
||||
self.retranslateUi()
|
||||
self.song_remove_button.clicked.connect(self.on_remove_button_clicked)
|
||||
|
||||
def setupUi(self):
|
||||
self.song_vertical_layout = QtGui.QVBoxLayout(self)
|
||||
self.song_vertical_layout.setObjectName(u'song_vertical_layout')
|
||||
self.song_group_box = QtGui.QGroupBox(self)
|
||||
self.song_group_box.setObjectName(u'song_group_box')
|
||||
self.song_group_box.setFixedWidth(300)
|
||||
self.song_group_box_layout = QtGui.QVBoxLayout(self.song_group_box)
|
||||
self.song_group_box_layout.setObjectName(u'song_group_box_layout')
|
||||
self.song_info_form_layout = QtGui.QFormLayout()
|
||||
self.song_info_form_layout.setObjectName(u'song_info_form_layout')
|
||||
# Add title widget.
|
||||
self.song_title_label = QtGui.QLabel(self)
|
||||
self.song_title_label.setObjectName(u'song_title_label')
|
||||
self.song_info_form_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.song_title_label)
|
||||
self.song_title_content = QtGui.QLabel(self)
|
||||
self.song_title_content.setObjectName(u'song_title_content')
|
||||
self.song_title_content.setText(self.song.title)
|
||||
self.song_title_content.setWordWrap(True)
|
||||
self.song_info_form_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.song_title_content)
|
||||
# Add alternate title widget.
|
||||
self.song_alternate_title_label = QtGui.QLabel(self)
|
||||
self.song_alternate_title_label.setObjectName(u'song_alternate_title_label')
|
||||
self.song_info_form_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.song_alternate_title_label)
|
||||
self.song_alternate_title_content = QtGui.QLabel(self)
|
||||
self.song_alternate_title_content.setObjectName(u'song_alternate_title_content')
|
||||
self.song_alternate_title_content.setText(self.song.alternate_title)
|
||||
self.song_alternate_title_content.setWordWrap(True)
|
||||
self.song_info_form_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.song_alternate_title_content)
|
||||
# Add CCLI number widget.
|
||||
self.song_ccli_number_label = QtGui.QLabel(self)
|
||||
self.song_ccli_number_label.setObjectName(u'song_ccli_number_label')
|
||||
self.song_info_form_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.song_ccli_number_label)
|
||||
self.song_ccli_number_content = QtGui.QLabel(self)
|
||||
self.song_ccli_number_content.setObjectName(u'song_ccli_number_content')
|
||||
self.song_ccli_number_content.setText(self.song.ccli_number)
|
||||
self.song_ccli_number_content.setWordWrap(True)
|
||||
self.song_info_form_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.song_ccli_number_content)
|
||||
# Add copyright widget.
|
||||
self.song_copyright_label = QtGui.QLabel(self)
|
||||
self.song_copyright_label.setObjectName(u'song_copyright_label')
|
||||
self.song_info_form_layout.setWidget(3, QtGui.QFormLayout.LabelRole, self.song_copyright_label)
|
||||
self.song_copyright_content = QtGui.QLabel(self)
|
||||
self.song_copyright_content.setObjectName(u'song_copyright_content')
|
||||
self.song_copyright_content.setWordWrap(True)
|
||||
self.song_copyright_content.setText(self.song.copyright)
|
||||
self.song_info_form_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self.song_copyright_content)
|
||||
# Add comments widget.
|
||||
self.song_comments_label = QtGui.QLabel(self)
|
||||
self.song_comments_label.setObjectName(u'song_comments_label')
|
||||
self.song_info_form_layout.setWidget(4, QtGui.QFormLayout.LabelRole, self.song_comments_label)
|
||||
self.song_comments_content = QtGui.QLabel(self)
|
||||
self.song_comments_content.setObjectName(u'song_comments_content')
|
||||
self.song_comments_content.setText(self.song.comments)
|
||||
self.song_comments_content.setWordWrap(True)
|
||||
self.song_info_form_layout.setWidget(4, QtGui.QFormLayout.FieldRole, self.song_comments_content)
|
||||
# Add authors widget.
|
||||
self.song_authors_label = QtGui.QLabel(self)
|
||||
self.song_authors_label.setObjectName(u'song_authors_label')
|
||||
self.song_info_form_layout.setWidget(5, QtGui.QFormLayout.LabelRole, self.song_authors_label)
|
||||
self.song_authors_content = QtGui.QLabel(self)
|
||||
self.song_authors_content.setObjectName(u'song_authors_content')
|
||||
self.song_authors_content.setWordWrap(True)
|
||||
authors_text = u', '.join([author.display_name for author in self.song.authors])
|
||||
self.song_authors_content.setText(authors_text)
|
||||
self.song_info_form_layout.setWidget(5, QtGui.QFormLayout.FieldRole, self.song_authors_content)
|
||||
# Add verse order widget.
|
||||
self.song_verse_order_label = QtGui.QLabel(self)
|
||||
self.song_verse_order_label.setObjectName(u'song_verse_order_label')
|
||||
self.song_info_form_layout.setWidget(6, QtGui.QFormLayout.LabelRole, self.song_verse_order_label)
|
||||
self.song_verse_order_content = QtGui.QLabel(self)
|
||||
self.song_verse_order_content.setObjectName(u'song_verse_order_content')
|
||||
self.song_verse_order_content.setText(self.song.verse_order)
|
||||
self.song_verse_order_content.setWordWrap(True)
|
||||
self.song_info_form_layout.setWidget(6, QtGui.QFormLayout.FieldRole, self.song_verse_order_content)
|
||||
self.song_group_box_layout.addLayout(self.song_info_form_layout)
|
||||
# Add verses widget.
|
||||
self.song_info_verse_list_widget = QtGui.QTableWidget(self.song_group_box)
|
||||
self.song_info_verse_list_widget.setColumnCount(1)
|
||||
self.song_info_verse_list_widget.horizontalHeader().setVisible(False)
|
||||
self.song_info_verse_list_widget.setObjectName(u'song_info_verse_list_widget')
|
||||
self.song_info_verse_list_widget.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
|
||||
self.song_info_verse_list_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
|
||||
self.song_info_verse_list_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.song_info_verse_list_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.song_info_verse_list_widget.setAlternatingRowColors(True)
|
||||
song_xml = SongXML()
|
||||
verses = song_xml.get_verses(self.song.lyrics)
|
||||
self.song_info_verse_list_widget.setRowCount(len(verses))
|
||||
song_tags = []
|
||||
for verse_number, verse in enumerate(verses):
|
||||
item = QtGui.QTableWidgetItem()
|
||||
item.setText(verse[1])
|
||||
self.song_info_verse_list_widget.setItem(verse_number, 0, item)
|
||||
|
||||
# We cannot use from_loose_input() here, because database
|
||||
# is supposed to contain English lowercase singlechar tags.
|
||||
verse_tag = verse[0][u'type']
|
||||
verse_index = None
|
||||
if len(verse_tag) > 1:
|
||||
verse_index = VerseType.from_translated_string(verse_tag)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_string(verse_tag, None)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_tag(verse_tag)
|
||||
verse_tag = VerseType.translated_tags[verse_index].upper()
|
||||
song_tags.append(unicode(verse_tag + verse[0]['label']))
|
||||
self.song_info_verse_list_widget.setVerticalHeaderLabels(song_tags)
|
||||
# Resize table fields to content and table to columns
|
||||
self.song_info_verse_list_widget.setColumnWidth(0, self.song_group_box.width())
|
||||
self.song_info_verse_list_widget.resizeRowsToContents()
|
||||
# The 6 is a trial and error value since verticalHeader().length() + offset() is a little bit to small.
|
||||
# It seems there is no clean way to determine the real height of the table contents.
|
||||
# The "correct" value slightly fluctuates depending on the theme used, in the worst case
|
||||
# Some pixels are missing at the bottom of the table, but all themes I tried still allowed
|
||||
# to read the last verse line, so I'll just leave it at that.
|
||||
self.song_info_verse_list_widget.setFixedHeight(self.song_info_verse_list_widget.verticalHeader().length() +
|
||||
self.song_info_verse_list_widget.verticalHeader().offset() + 6)
|
||||
self.song_group_box_layout.addWidget(self.song_info_verse_list_widget)
|
||||
self.song_group_box_layout.addStretch()
|
||||
self.song_vertical_layout.addWidget(self.song_group_box)
|
||||
self.song_remove_button = QtGui.QPushButton(self)
|
||||
self.song_remove_button.setObjectName(u'song_remove_button')
|
||||
self.song_remove_button.setIcon(build_icon(u':/songs/song_delete.png'))
|
||||
self.song_remove_button.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
self.song_vertical_layout.addWidget(self.song_remove_button, alignment = QtCore.Qt.AlignHCenter)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.song_remove_button.setText(u'Remove')
|
||||
self.song_title_label.setText(u'Title:')
|
||||
self.song_alternate_title_label.setText(u'Alternate Title:')
|
||||
self.song_ccli_number_label.setText(u'CCLI Number:')
|
||||
self.song_verse_order_label.setText(u'Verse Order:')
|
||||
self.song_copyright_label.setText(u'Copyright:')
|
||||
self.song_comments_label.setText(u'Comments:')
|
||||
self.song_authors_label.setText(u'Authors:')
|
||||
|
||||
def on_remove_button_clicked(self):
|
||||
"""
|
||||
Signal emitted when the "remove" button is clicked.
|
||||
"""
|
||||
self.song_remove_button_clicked.emit(self)
|
|
@ -29,15 +29,21 @@
|
|||
"""
|
||||
The :mod:`~openlp.plugins.songs.lib` module contains a number of library functions and classes used in the Songs plugin.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.utils import CONTROL_CHARS
|
||||
from openlp.core.utils import AppLocation, CONTROL_CHARS
|
||||
from openlp.plugins.songs.lib.db import MediaFile, Song
|
||||
from db import Author
|
||||
from ui import SongStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
WHITESPACE = re.compile(r'[\W_]+', re.UNICODE)
|
||||
APOSTROPHE = re.compile(u'[\'`’ʻ′]', re.UNICODE)
|
||||
PATTERN = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
|
||||
|
@ -593,3 +599,29 @@ def strip_rtf(text, default_encoding=None):
|
|||
text = u''.join(out)
|
||||
return text, default_encoding
|
||||
|
||||
|
||||
def delete_song(song_id, song_plugin):
|
||||
"""
|
||||
Deletes a song from the database. Media files associated to the song
|
||||
are removed prior to the deletion of the song.
|
||||
|
||||
``song_id``
|
||||
The ID of the song to delete.
|
||||
|
||||
``song_plugin``
|
||||
The song plugin instance.
|
||||
"""
|
||||
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
||||
for media_file in media_files:
|
||||
try:
|
||||
os.remove(media_file.file_name)
|
||||
except:
|
||||
log.exception('Could not remove file: %s', media_file.file_name)
|
||||
try:
|
||||
save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id))
|
||||
if os.path.exists(save_path):
|
||||
os.rmdir(save_path)
|
||||
except OSError:
|
||||
log.exception(u'Could not remove directory: %s', save_path)
|
||||
song_plugin.manager.delete_object(Song, song_id)
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ from openlp.plugins.songs.forms.editsongform import EditSongForm
|
|||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||
from openlp.plugins.songs.lib.db import Author, Song, Book, MediaFile
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML
|
||||
|
@ -368,19 +368,7 @@ class SongMediaItem(MediaManagerItem):
|
|||
self.main_window.display_progress_bar(len(items))
|
||||
for item in items:
|
||||
item_id = item.data(QtCore.Qt.UserRole)
|
||||
media_files = self.plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == item_id)
|
||||
for media_file in media_files:
|
||||
try:
|
||||
os.remove(media_file.file_name)
|
||||
except:
|
||||
log.exception('Could not remove file: %s', media_file.file_name)
|
||||
try:
|
||||
save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(item_id))
|
||||
if os.path.exists(save_path):
|
||||
os.rmdir(save_path)
|
||||
except OSError:
|
||||
log.exception(u'Could not remove directory: %s', save_path)
|
||||
self.plugin.manager.delete_object(Song, item_id)
|
||||
delete_song(item_id, self.plugin)
|
||||
self.main_window.increment_progress_bar()
|
||||
self.main_window.finished_progress_bar()
|
||||
self.application.set_normal_cursor()
|
||||
|
|
|
@ -84,10 +84,14 @@ class OpenLyricsExport(object):
|
|||
|
||||
def _get_application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
if os.name == u'nt':
|
||||
return Registry().get(u'application')
|
||||
else:
|
||||
if not hasattr(self, u'_application'):
|
||||
self._application = Registry().get(u'application')
|
||||
return self._application
|
||||
|
||||
application = property(_get_application)
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
# -*- 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:`songcompare` module provides functionality to search for
|
||||
duplicate songs. It has one single :function:`songs_probably_equal`.
|
||||
|
||||
The algorithm is based on the diff algorithm.
|
||||
First a diffset is calculated for two songs.
|
||||
To compensate for typos all differences that are smaller than a
|
||||
limit (<max_typo_size) and are surrounded by larger equal blocks
|
||||
(>min_fragment_size) are removed and the surrounding equal parts are merged.
|
||||
Finally two conditions can qualify a song tuple to be a duplicate:
|
||||
1. There is a block of equal content that is at least min_block_size large.
|
||||
This condition should hit for all larger songs that have a long enough
|
||||
equal part. Even if only one verse is equal this condition should still hit.
|
||||
2. Two thirds of the smaller song is contained in the larger song.
|
||||
This condition should hit if one of the two songs (or both) is small (smaller
|
||||
than the min_block_size), but most of the song is contained in the other song.
|
||||
"""
|
||||
from __future__ import division
|
||||
import difflib
|
||||
|
||||
|
||||
MIN_FRAGMENT_SIZE = 5
|
||||
MIN_BLOCK_SIZE = 70
|
||||
MAX_TYPO_SIZE = 3
|
||||
|
||||
|
||||
def songs_probably_equal(song1, song2):
|
||||
"""
|
||||
Calculate and return whether two songs are probably equal.
|
||||
|
||||
``song1``
|
||||
The first song to compare.
|
||||
|
||||
``song2``
|
||||
The second song to compare.
|
||||
"""
|
||||
if len(song1.search_lyrics) < len(song2.search_lyrics):
|
||||
small = song1.search_lyrics
|
||||
large = song2.search_lyrics
|
||||
else:
|
||||
small = song2.search_lyrics
|
||||
large = song1.search_lyrics
|
||||
differ = difflib.SequenceMatcher(a=large, b=small)
|
||||
diff_tuples = differ.get_opcodes()
|
||||
diff_no_typos = _remove_typos(diff_tuples)
|
||||
# Check 1: Similarity based on the absolute length of equal parts.
|
||||
# Calculate the total length of all equal blocks of the set.
|
||||
# Blocks smaller than min_block_size are not counted.
|
||||
length_of_equal_blocks = 0
|
||||
for element in diff_no_typos:
|
||||
if element[0] == "equal" and _op_length(element) >= MIN_BLOCK_SIZE:
|
||||
length_of_equal_blocks += _op_length(element)
|
||||
if length_of_equal_blocks >= MIN_BLOCK_SIZE:
|
||||
return True
|
||||
# Check 2: Similarity based on the relative length of the longest equal block.
|
||||
# Calculate the length of the largest equal block of the diff set.
|
||||
length_of_longest_equal_block = 0
|
||||
for element in diff_no_typos:
|
||||
if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block:
|
||||
length_of_longest_equal_block = _op_length(element)
|
||||
if length_of_equal_blocks >= MIN_BLOCK_SIZE or length_of_longest_equal_block > len(small) * 2 // 3:
|
||||
return True
|
||||
# Both checks failed. We assume the songs are not equal.
|
||||
return False
|
||||
|
||||
|
||||
def _op_length(opcode):
|
||||
"""
|
||||
Return the length of a given difference.
|
||||
|
||||
``opcode``
|
||||
The difference.
|
||||
"""
|
||||
return max(opcode[2] - opcode[1], opcode[4] - opcode[3])
|
||||
|
||||
|
||||
def _remove_typos(diff):
|
||||
"""
|
||||
Remove typos from a diff set. A typo is a small difference (<max_typo_size)
|
||||
surrounded by larger equal passages (>min_fragment_size).
|
||||
|
||||
``diff``
|
||||
The diff set to remove the typos from.
|
||||
"""
|
||||
# Remove typo at beginning of the string.
|
||||
if len(diff) >= 2:
|
||||
if diff[0][0] != "equal" and _op_length(diff[0]) <= MAX_TYPO_SIZE and \
|
||||
_op_length(diff[1]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[0]
|
||||
# Remove typos in the middle of the string.
|
||||
if len(diff) >= 3:
|
||||
for index in range(len(diff) - 3, -1, -1):
|
||||
if _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[index + 1][0] != "equal" and _op_length(diff[index + 1]) <= MAX_TYPO_SIZE and \
|
||||
_op_length(diff[index + 2]) >= MIN_FRAGMENT_SIZE:
|
||||
del diff[index + 1]
|
||||
# Remove typo at the end of the string.
|
||||
if len(diff) >= 2:
|
||||
if _op_length(diff[-2]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[-1][0] != "equal" and _op_length(diff[-1]) <= MAX_TYPO_SIZE:
|
||||
del diff[-1]
|
||||
|
||||
# Merge the bordering equal passages that occured by removing differences.
|
||||
for index in range(len(diff) - 2, -1, -1):
|
||||
if diff[index][0] == "equal" and _op_length(diff[index]) >= MIN_FRAGMENT_SIZE and \
|
||||
diff[index + 1][0] == "equal" and _op_length(diff[index + 1]) >= MIN_FRAGMENT_SIZE:
|
||||
diff[index] = ("equal", diff[index][1], diff[index + 1][2], diff[index][3],
|
||||
diff[index + 1][4])
|
||||
del diff[index + 1]
|
||||
|
||||
return diff
|
|
@ -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()
|
|
@ -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()))
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@ from openlp.plugins.songs.lib.importer import SongFormat
|
|||
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
|
||||
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
||||
from openlp.plugins.songs.lib.songstab import SongsTab
|
||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__default_settings__ = {
|
||||
|
@ -97,10 +99,12 @@ class SongsPlugin(Plugin):
|
|||
self.song_import_item.setVisible(True)
|
||||
self.song_export_item.setVisible(True)
|
||||
self.tools_reindex_item.setVisible(True)
|
||||
self.tools_find_duplicates.setVisible(True)
|
||||
action_list = ActionList.get_instance()
|
||||
action_list.add_action(self.song_import_item, UiStrings().Import)
|
||||
action_list.add_action(self.song_export_item, UiStrings().Export)
|
||||
action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
|
||||
action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
|
||||
|
||||
def add_import_menu_item(self, import_menu):
|
||||
"""
|
||||
|
@ -136,7 +140,7 @@ class SongsPlugin(Plugin):
|
|||
|
||||
def add_tools_menu_item(self, tools_menu):
|
||||
"""
|
||||
Give the alerts plugin the opportunity to add items to the
|
||||
Give the Songs plugin the opportunity to add items to the
|
||||
**Tools** menu.
|
||||
|
||||
``tools_menu``
|
||||
|
@ -150,6 +154,12 @@ class SongsPlugin(Plugin):
|
|||
statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'),
|
||||
visible=False, triggers=self.on_tools_reindex_item_triggered)
|
||||
tools_menu.addAction(self.tools_reindex_item)
|
||||
self.tools_find_duplicates = create_action(tools_menu, u'toolsFindDuplicates',
|
||||
text=translate('SongsPlugin', 'Find &Duplicate Songs'),
|
||||
statustip=translate('SongsPlugin',
|
||||
'Find and remove duplicate songs in the song database.'),
|
||||
visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
|
||||
tools_menu.addAction(self.tools_find_duplicates)
|
||||
|
||||
def on_tools_reindex_item_triggered(self):
|
||||
"""
|
||||
|
@ -169,6 +179,12 @@ class SongsPlugin(Plugin):
|
|||
self.manager.save_objects(songs)
|
||||
self.media_item.on_search_text_button_clicked()
|
||||
|
||||
def on_tools_find_duplicates_triggered(self):
|
||||
"""
|
||||
Search for duplicates in the song database.
|
||||
"""
|
||||
DuplicateSongRemovalForm(self).exec_()
|
||||
|
||||
def on_song_import_item_clicked(self):
|
||||
if self.media_item:
|
||||
self.media_item.on_import_click()
|
||||
|
@ -287,10 +303,12 @@ class SongsPlugin(Plugin):
|
|||
self.song_import_item.setVisible(False)
|
||||
self.song_export_item.setVisible(False)
|
||||
self.tools_reindex_item.setVisible(False)
|
||||
self.tools_find_duplicates.setVisible(False)
|
||||
action_list = ActionList.get_instance()
|
||||
action_list.remove_action(self.song_import_item, UiStrings().Import)
|
||||
action_list.remove_action(self.song_export_item, UiStrings().Export)
|
||||
action_list.remove_action(self.tools_reindex_item, UiStrings().Tools)
|
||||
action_list.remove_action(self.tools_find_duplicates, UiStrings().Tools)
|
||||
Plugin.finalise(self)
|
||||
|
||||
def new_service_created(self):
|
||||
|
|
|
@ -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''))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<file>topic_maintenance.png</file>
|
||||
<file>song_author_edit.png</file>
|
||||
<file>song_book_edit.png</file>
|
||||
<file>song_delete.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="images">
|
||||
<file>image_group.png</file>
|
||||
|
@ -101,6 +102,7 @@
|
|||
<file>wizard_importbible.bmp</file>
|
||||
<file>wizard_firsttime.bmp</file>
|
||||
<file>wizard_createtheme.bmp</file>
|
||||
<file>wizard_duplicateremoval.bmp</file>
|
||||
</qresource>
|
||||
<qresource prefix="services">
|
||||
<file>service_collapse_all.png</file>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
|
@ -85,6 +85,7 @@ MODULES = [
|
|||
'migrate',
|
||||
'uno',
|
||||
'icu',
|
||||
'bs4',
|
||||
]
|
||||
|
||||
|
||||
|
@ -98,9 +99,9 @@ OPTIONAL_MODULES = [
|
|||
w = sys.stdout.write
|
||||
|
||||
def check_vers(version, required, text):
|
||||
if type(version) is not str:
|
||||
if not isinstance(version, str):
|
||||
version = '.'.join(map(str, version))
|
||||
if type(required) is not str:
|
||||
if not isinstance(required, str):
|
||||
required = '.'.join(map(str, required))
|
||||
w(' %s >= %s ... ' % (text, required))
|
||||
if LooseVersion(version) >= LooseVersion(required):
|
||||
|
|
70
setup.py
70
setup.py
|
@ -27,12 +27,15 @@
|
|||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
import re
|
||||
from setuptools import setup, find_packages
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
VERSION_FILE = 'openlp/.version'
|
||||
SPLIT_ALPHA_DIGITS = re.compile(r'(\d+|\D+)')
|
||||
|
||||
|
||||
def try_int(s):
|
||||
"""
|
||||
Convert string s to an integer if possible. Fail silently and return
|
||||
|
@ -46,6 +49,7 @@ def try_int(s):
|
|||
except Exception:
|
||||
return s
|
||||
|
||||
|
||||
def natural_sort_key(s):
|
||||
"""
|
||||
Return a tuple by which s is sorted.
|
||||
|
@ -55,6 +59,7 @@ def natural_sort_key(s):
|
|||
"""
|
||||
return map(try_int, SPLIT_ALPHA_DIGITS.findall(s))
|
||||
|
||||
|
||||
def natural_compare(a, b):
|
||||
"""
|
||||
Compare two strings naturally and return the result.
|
||||
|
@ -67,6 +72,7 @@ def natural_compare(a, b):
|
|||
"""
|
||||
return cmp(natural_sort_key(a), natural_sort_key(b))
|
||||
|
||||
|
||||
def natural_sort(seq, compare=natural_compare):
|
||||
"""
|
||||
Returns a copy of seq, sorted by natural string sort.
|
||||
|
@ -76,38 +82,50 @@ def natural_sort(seq, compare=natural_compare):
|
|||
temp.sort(compare)
|
||||
return temp
|
||||
|
||||
# NOTE: The following code is a duplicate of the code in openlp/core/utils/__init__.py. Any fix applied here should also
|
||||
# be applied there.
|
||||
try:
|
||||
# Try to import Bazaar
|
||||
from bzrlib.branch import Branch
|
||||
b = Branch.open_containing('.')[0]
|
||||
b.lock_read()
|
||||
try:
|
||||
# Get the branch's latest revision number.
|
||||
revno = b.revno()
|
||||
# Convert said revision number into a bzr revision id.
|
||||
revision_id = b.dotted_revno_to_revision_id((revno,))
|
||||
# Get a dict of tags, with the revision id as the key.
|
||||
tags = b.tags.get_reverse_tag_dict()
|
||||
# Check if the latest
|
||||
if revision_id in tags:
|
||||
version = u'%s' % tags[revision_id][0]
|
||||
else:
|
||||
version = '%s-bzr%s' % \
|
||||
(natural_sort(b.tags.get_tag_dict().keys())[-1], revno)
|
||||
ver_file = open(VERSION_FILE, u'w')
|
||||
ver_file.write(version)
|
||||
ver_file.close()
|
||||
finally:
|
||||
b.unlock()
|
||||
# Get the revision of this tree.
|
||||
bzr = Popen((u'bzr', u'revno'), stdout=PIPE)
|
||||
tree_revision, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr log')
|
||||
|
||||
# Get all tags.
|
||||
bzr = Popen((u'bzr', u'tags'), stdout=PIPE)
|
||||
output, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception(u'Error running bzr tags')
|
||||
tags = output.splitlines()
|
||||
if not tags:
|
||||
tag_version = u'0.0.0'
|
||||
tag_revision = u'0'
|
||||
else:
|
||||
# Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
|
||||
# another series.
|
||||
tags = [tag for tag in tags if tag.split()[-1].strip() != u'?']
|
||||
# Get the last tag and split it in a revision and tag name.
|
||||
tag_version, tag_revision = tags[-1].split()
|
||||
# If they are equal, then this tree is tarball with the source for the release. We do not want the revision number
|
||||
# in the version string.
|
||||
if tree_revision == tag_revision:
|
||||
version_string = tag_version
|
||||
else:
|
||||
version_string = u'%s-bzr%s' % (tag_version, tree_revision)
|
||||
ver_file = open(VERSION_FILE, u'w')
|
||||
ver_file.write(version_string)
|
||||
except:
|
||||
ver_file = open(VERSION_FILE, u'r')
|
||||
version = ver_file.read().strip()
|
||||
version_string = ver_file.read().strip()
|
||||
finally:
|
||||
ver_file.close()
|
||||
|
||||
|
||||
setup(
|
||||
name='OpenLP',
|
||||
version=version,
|
||||
version=version_string,
|
||||
description="Open source Church presentation and lyrics projection application.",
|
||||
long_description="""\
|
||||
OpenLP (previously openlp.org) is free church presentation software, or lyrics projection software, used to display slides of songs, Bible verses, videos, images, and even presentations (if PowerPoint is installed) for church worship using a computer and a data projector.""",
|
||||
|
@ -157,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: -*-
|
||||
|
|
|
@ -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)
|
|
@ -33,11 +33,11 @@ class TestFormattingTags(TestCase):
|
|||
"""
|
||||
with patch(u'openlp.core.lib.translate') as mocked_translate, \
|
||||
patch(u'openlp.core.lib.settings') as mocked_settings, \
|
||||
patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle:
|
||||
patch(u'openlp.core.lib.formattingtags.json') as mocked_json:
|
||||
# GIVEN: Our mocked modules and functions.
|
||||
mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate
|
||||
mocked_settings.value.return_value = u''
|
||||
mocked_cPickle.load.return_value = []
|
||||
mocked_json.load.return_value = []
|
||||
|
||||
# WHEN: Get the display tags.
|
||||
FormattingTags.load_tags()
|
||||
|
@ -54,11 +54,11 @@ class TestFormattingTags(TestCase):
|
|||
"""
|
||||
with patch(u'openlp.core.lib.translate') as mocked_translate, \
|
||||
patch(u'openlp.core.lib.settings') as mocked_settings, \
|
||||
patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle:
|
||||
patch(u'openlp.core.lib.formattingtags.json') as mocked_json:
|
||||
# GIVEN: Our mocked modules and functions.
|
||||
mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate
|
||||
mocked_settings.value.return_value = u''
|
||||
mocked_cPickle.loads.side_effect = [[], [TAG]]
|
||||
mocked_json.loads.side_effect = [[], [TAG]]
|
||||
|
||||
# WHEN: Get the display tags.
|
||||
FormattingTags.load_tags()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
Package to test the openlp.core.lib package.
|
||||
"""
|
||||
import os
|
||||
import cPickle
|
||||
from unittest import TestCase
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
|
||||
from tests.utils.osdinteraction import read_service_from_file
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
|
||||
|
||||
VERSE = u'The Lord said to {r}Noah{/r}: \n'\
|
||||
|
@ -18,8 +19,6 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n'\
|
|||
'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):
|
||||
|
||||
|
@ -78,7 +77,7 @@ class TestServiceItem(TestCase):
|
|||
service_item.name = u'test'
|
||||
|
||||
# WHEN: adding image to a service item
|
||||
test_image = os.path.join(TEST_PATH, u'church.jpg')
|
||||
test_image = os.path.join(TEST_RESOURCES_PATH, u'church.jpg')
|
||||
service_item.add_from_image(test_image, u'Image Title')
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
|
@ -133,8 +132,8 @@ class TestServiceItem(TestCase):
|
|||
service_item.name = u'test'
|
||||
|
||||
# WHEN: adding image to a service item
|
||||
test_file = os.path.join(TEST_PATH, u'church.jpg')
|
||||
service_item.add_from_command(TEST_PATH, u'church.jpg', test_file)
|
||||
test_file = os.path.join(TEST_RESOURCES_PATH, u'church.jpg')
|
||||
service_item.add_from_command(TEST_RESOURCES_PATH, u'church.jpg', test_file)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
|
@ -151,7 +150,7 @@ class TestServiceItem(TestCase):
|
|||
assert len(service) == 2, u'The saved service should have two parts'
|
||||
assert service[u'header'][u'name'] == u'test', u'A test plugin should be returned'
|
||||
assert service[u'data'][0][u'title'] == u'church.jpg', u'The first title name should be "church,jpg"'
|
||||
assert service[u'data'][0][u'path'] == TEST_PATH, u'The path should match the input path'
|
||||
assert service[u'data'][0][u'path'] == TEST_RESOURCES_PATH, u'The path should match the input path'
|
||||
assert service[u'data'][0][u'image'] == test_file, u'The image should match the full path to image'
|
||||
|
||||
# WHEN validating a service item
|
||||
|
@ -170,13 +169,12 @@ class TestServiceItem(TestCase):
|
|||
"""
|
||||
Test the Service Item - adding a custom slide from a saved service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
# GIVEN: A new service item
|
||||
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.osd')
|
||||
service_item.set_from_service(line)
|
||||
service = read_service_from_file(u'serviceitem_custom_1.osd')
|
||||
service_item.set_from_service(service[0])
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
|
@ -195,18 +193,17 @@ class TestServiceItem(TestCase):
|
|||
"""
|
||||
Test the Service Item - adding an image from a saved service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
# GIVEN: A new service item
|
||||
image_name = u'image_1.jpg'
|
||||
test_file = os.path.join(TEST_PATH, image_name)
|
||||
test_file = os.path.join(TEST_RESOURCES_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.osd')
|
||||
service = read_service_from_file(u'serviceitem_image_1.osd')
|
||||
with patch('os.path.exists'):
|
||||
service_item.set_from_service(line, TEST_PATH)
|
||||
service_item.set_from_service(service[0], TEST_RESOURCES_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'
|
||||
|
@ -229,7 +226,7 @@ class TestServiceItem(TestCase):
|
|||
"""
|
||||
Test the Service Item - adding an image from a saved local service
|
||||
"""
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
# GIVEN: A new service item
|
||||
image_name1 = u'image_1.jpg'
|
||||
image_name2 = u'image_2.jpg'
|
||||
test_file1 = os.path.join(u'/home/openlp', image_name1)
|
||||
|
@ -238,12 +235,11 @@ class TestServiceItem(TestCase):
|
|||
frame_array2 = {u'path': test_file2, u'title': image_name2}
|
||||
|
||||
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_2.osd')
|
||||
service = read_service_from_file(u'serviceitem_image_2.osd')
|
||||
with patch('os.path.exists'):
|
||||
service_item.set_from_service(line)
|
||||
service_item.set_from_service(service[0])
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
|
@ -276,26 +272,13 @@ class TestServiceItem(TestCase):
|
|||
service_item.add_icon = MagicMock()
|
||||
|
||||
# WHEN: adding an media from a saved Service and mocked exists
|
||||
line = self.convert_file_service_item(u'migrate_video_20_22.osd')
|
||||
line = read_service_from_file(u'migrate_video_20_22.osd')
|
||||
with patch('os.path.exists'):
|
||||
service_item.set_from_service(line, TEST_PATH)
|
||||
service_item.set_from_service(line[0], TEST_RESOURCES_PATH)
|
||||
|
||||
# THEN: We should get back a converted service item
|
||||
assert service_item.is_valid is True, u'The new service item should be valid'
|
||||
assert service_item.processor is None, u'The Processor should have been set'
|
||||
assert service_item.title is None, u'The title should be set to a value'
|
||||
assert service_item.processor == u'VLC', u'The Processor should have been set'
|
||||
assert service_item.title is not None, u'The title should be set to a value'
|
||||
assert service_item.is_capable(ItemCapabilities.HasDetailedTitleDisplay) is False, \
|
||||
u'The Capability should have been removed'
|
||||
|
||||
def convert_file_service_item(self, name):
|
||||
service_file = os.path.join(TEST_PATH, name)
|
||||
try:
|
||||
open_file = open(service_file, u'r')
|
||||
items = cPickle.load(open_file)
|
||||
first_line = items[0]
|
||||
except IOError:
|
||||
first_line = u''
|
||||
finally:
|
||||
open_file.close()
|
||||
return first_line
|
||||
|
||||
|
|
|
@ -4,15 +4,37 @@ This module contains tests for the lib submodule of the Songs plugin.
|
|||
|
||||
from unittest import TestCase
|
||||
|
||||
from mock import patch
|
||||
from mock import patch, MagicMock
|
||||
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title
|
||||
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
|
||||
|
||||
|
||||
class TestLib(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Mock up two songs and provide a set of lyrics for the songs_probably_equal tests.
|
||||
"""
|
||||
self.full_lyrics =u'''amazing grace how sweet the sound that saved a wretch like me i once was lost but now am
|
||||
found was blind but now i see twas grace that taught my heart to fear and grace my fears relieved how
|
||||
precious did that grace appear the hour i first believed through many dangers toils and snares i have already
|
||||
come tis grace that brought me safe thus far and grace will lead me home'''
|
||||
self.short_lyrics =u'''twas grace that taught my heart to fear and grace my fears relieved how precious did that
|
||||
grace appear the hour i first believed'''
|
||||
self.error_lyrics =u'''amazing how sweet the trumpet that saved a wrench like me i once was losst but now am
|
||||
found waf blind but now i see it was grace that taught my heart to fear and grace my fears relieved how
|
||||
precious did that grace appppppppear the hour i first believedxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx snares i have
|
||||
already come to this grace that brought me safe so far and grace will lead me home'''
|
||||
self.different_lyrics=u'''on a hill far away stood an old rugged cross the emblem of suffering and shame and i love
|
||||
that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the old rugged
|
||||
cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it some day for a
|
||||
crown'''
|
||||
self.song1 = MagicMock()
|
||||
self.song2 = MagicMock()
|
||||
|
||||
def clean_string_test(self):
|
||||
"""
|
||||
Test the clean_string() function
|
||||
|
@ -39,6 +61,160 @@ class TestLib(TestCase):
|
|||
# THEN: The string should be cleaned up
|
||||
self.assertEqual(result, u'This is a dirty string', u'The title should be cleaned up properly: "%s"' % result)
|
||||
|
||||
def songs_probably_equal_same_song_test(self):
|
||||
"""
|
||||
Test the songs_probably_equal function with twice the same song.
|
||||
"""
|
||||
# GIVEN: Two equal songs.
|
||||
self.song1.search_lyrics = self.full_lyrics
|
||||
self.song2.search_lyrics = self.full_lyrics
|
||||
|
||||
# WHEN: We compare those songs for equality.
|
||||
result = songs_probably_equal(self.song1, self.song2)
|
||||
|
||||
# THEN: The result should be True.
|
||||
assert result == True, u'The result should be True'
|
||||
|
||||
def songs_probably_equal_short_song_test(self):
|
||||
"""
|
||||
Test the songs_probably_equal function with a song and a shorter version of the same song.
|
||||
"""
|
||||
# GIVEN: A song and a short version of the same song.
|
||||
self.song1.search_lyrics = self.full_lyrics
|
||||
self.song2.search_lyrics = self.short_lyrics
|
||||
|
||||
# WHEN: We compare those songs for equality.
|
||||
result = songs_probably_equal(self.song1, self.song2)
|
||||
|
||||
# THEN: The result should be True.
|
||||
assert result == True, u'The result should be True'
|
||||
|
||||
def songs_probably_equal_error_song_test(self):
|
||||
"""
|
||||
Test the songs_probably_equal function with a song and a very erroneous version of the same song.
|
||||
"""
|
||||
# GIVEN: A song and the same song with lots of errors.
|
||||
self.song1.search_lyrics = self.full_lyrics
|
||||
self.song2.search_lyrics = self.error_lyrics
|
||||
|
||||
# WHEN: We compare those songs for equality.
|
||||
result = songs_probably_equal(self.song1, self.song2)
|
||||
|
||||
# THEN: The result should be True.
|
||||
assert result == True, u'The result should be True'
|
||||
|
||||
def songs_probably_equal_different_song_test(self):
|
||||
"""
|
||||
Test the songs_probably_equal function with two different songs.
|
||||
"""
|
||||
# GIVEN: Two different songs.
|
||||
self.song1.search_lyrics = self.full_lyrics
|
||||
self.song2.search_lyrics = self.different_lyrics
|
||||
|
||||
# WHEN: We compare those songs for equality.
|
||||
result = songs_probably_equal(self.song1, self.song2)
|
||||
|
||||
# THEN: The result should be False.
|
||||
assert result == False, u'The result should be False'
|
||||
|
||||
def remove_typos_beginning_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a typo at the beginning.
|
||||
"""
|
||||
# GIVEN: A diffset with a difference at the beginning.
|
||||
diff = [('replace', 0, 2, 0, 1), ('equal', 2, 11, 1, 10)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(diff)
|
||||
|
||||
# THEN: There should be no typos at the beginning anymore.
|
||||
assert len(result) == 1, u'The result should contain only one element.'
|
||||
assert result[0][0] == 'equal', u'The result should contain an equal element.'
|
||||
|
||||
def remove_typos_beginning_negated_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a large difference at the beginning.
|
||||
"""
|
||||
# GIVEN: A diffset with a large difference at the beginning.
|
||||
diff = [('replace', 0, 20, 0, 1), ('equal', 20, 29, 1, 10)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(list(diff))
|
||||
|
||||
# THEN: There diff should not have changed.
|
||||
assert result == diff
|
||||
|
||||
def remove_typos_end_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a typo at the end.
|
||||
"""
|
||||
# GIVEN: A diffset with a difference at the end.
|
||||
diff = [('equal', 0, 10, 0, 10), ('replace', 10, 12, 10, 11)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(diff)
|
||||
|
||||
# THEN: There should be no typos at the end anymore.
|
||||
assert len(result) == 1, u'The result should contain only one element.'
|
||||
assert result[0][0] == 'equal', u'The result should contain an equal element.'
|
||||
|
||||
def remove_typos_end_negated_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a large difference at the end.
|
||||
"""
|
||||
# GIVEN: A diffset with a large difference at the end.
|
||||
diff = [('equal', 0, 10, 0, 10), ('replace', 10, 20, 10, 1)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(list(diff))
|
||||
|
||||
# THEN: There diff should not have changed.
|
||||
assert result == diff
|
||||
|
||||
def remove_typos_middle_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a typo in the middle.
|
||||
"""
|
||||
# GIVEN: A diffset with a difference in the middle.
|
||||
diff = [('equal', 0, 10, 0, 10), ('replace', 10, 12, 10, 11), ('equal', 12, 22, 11, 21)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(diff)
|
||||
|
||||
# THEN: There should be no typos in the middle anymore. The remaining equals should have been merged.
|
||||
assert len(result) is 1, u'The result should contain only one element.'
|
||||
assert result[0][0] == 'equal', u'The result should contain an equal element.'
|
||||
assert result[0][1] == 0, u'The start indices should be kept.'
|
||||
assert result[0][2] == 22, u'The stop indices should be kept.'
|
||||
assert result[0][3] == 0, u'The start indices should be kept.'
|
||||
assert result[0][4] == 21, u'The stop indices should be kept.'
|
||||
|
||||
def remove_typos_beginning_negated_test(self):
|
||||
"""
|
||||
Test the _remove_typos function with a large difference in the middle.
|
||||
"""
|
||||
# GIVEN: A diffset with a large difference in the middle.
|
||||
diff = [('equal', 0, 10, 0, 10), ('replace', 10, 20, 10, 11), ('equal', 20, 30, 11, 21)]
|
||||
|
||||
# WHEN: We remove the typos in there.
|
||||
result = _remove_typos(list(diff))
|
||||
|
||||
# THEN: There diff should not have changed.
|
||||
assert result == diff
|
||||
|
||||
def op_length_test(self):
|
||||
"""
|
||||
Test the _op_length function.
|
||||
"""
|
||||
# GIVEN: A diff entry.
|
||||
diff_entry = ('replace', 0, 2, 4, 14)
|
||||
|
||||
# WHEN: We calculate the length of that diff.
|
||||
result = _op_length(diff_entry)
|
||||
|
||||
# THEN: The maximum length should be returned.
|
||||
assert result == 10, u'The length should be 10.'
|
||||
|
||||
|
||||
class TestVerseType(TestCase):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
"""
|
||||
Package to test the openlp.core.ui.listpreviewwidget.
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from openlp.core.lib import Registry, ServiceItem
|
||||
from openlp.core.ui import listpreviewwidget
|
||||
from tests.utils.osdinteraction import read_service_from_file
|
||||
|
||||
class TestListPreviewWidget(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the UI.
|
||||
"""
|
||||
Registry.create()
|
||||
self.app = QtGui.QApplication([])
|
||||
self.main_window = QtGui.QMainWindow()
|
||||
self.image = QtGui.QImage(1, 1, QtGui.QImage.Format_RGB32)
|
||||
self.image_manager = MagicMock()
|
||||
self.image_manager.get_image.return_value = self.image
|
||||
Registry().register(u'image_manager', self.image_manager)
|
||||
self.preview_widget = listpreviewwidget.ListPreviewWidget(self.main_window, 2)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault.
|
||||
"""
|
||||
del self.preview_widget
|
||||
del self.main_window
|
||||
del self.app
|
||||
|
||||
def initial_slide_count_test(self):
|
||||
"""
|
||||
Test the inital slide count.
|
||||
"""
|
||||
# GIVEN: A new ListPreviewWidget instance.
|
||||
# WHEN: No SlideItem has been added yet.
|
||||
# THEN: The count of items should be zero.
|
||||
self.assertEqual(self.preview_widget.slide_count(), 0,
|
||||
u'The slide list should be empty.')
|
||||
|
||||
def initial_slide_number_test(self):
|
||||
"""
|
||||
Test the inital slide number.
|
||||
"""
|
||||
# GIVEN: A new ListPreviewWidget instance.
|
||||
# WHEN: No SlideItem has been added yet.
|
||||
# THEN: The number of the current item should be -1.
|
||||
self.assertEqual(self.preview_widget.current_slide_number(), -1,
|
||||
u'The slide number should be -1.')
|
||||
|
||||
def replace_service_item_test(self):
|
||||
"""
|
||||
Test item counts and current number with a service item.
|
||||
"""
|
||||
# GIVEN: A ServiceItem with two frames.
|
||||
service_item = ServiceItem(None)
|
||||
service = read_service_from_file(u'serviceitem_image_2.osd')
|
||||
with patch('os.path.exists'):
|
||||
service_item.set_from_service(service[0])
|
||||
# WHEN: Added to the preview widget.
|
||||
self.preview_widget.replace_service_item(service_item, 1, 1)
|
||||
# THEN: The slide count and number should fit.
|
||||
self.assertEqual(self.preview_widget.slide_count(), 2,
|
||||
u'The slide count should be 2.')
|
||||
self.assertEqual(self.preview_widget.current_slide_number(), 1,
|
||||
u'The current slide number should be 1.')
|
||||
|
||||
def change_slide_test(self):
|
||||
"""
|
||||
Test the change_slide method.
|
||||
"""
|
||||
# GIVEN: A ServiceItem with two frames content.
|
||||
service_item = ServiceItem(None)
|
||||
service = read_service_from_file(u'serviceitem_image_2.osd')
|
||||
with patch('os.path.exists'):
|
||||
service_item.set_from_service(service[0])
|
||||
# WHEN: Added to the preview widget and switched to the second frame.
|
||||
self.preview_widget.replace_service_item(service_item, 1, 0)
|
||||
self.preview_widget.change_slide(1)
|
||||
# THEN: The current_slide_number should reflect the change.
|
||||
self.assertEqual(self.preview_widget.current_slide_number(), 1,
|
||||
u'The current slide number should be 1.')
|
|
@ -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)
|
||||
|
|
@ -0,0 +1 @@
|
|||
__author__ = 'tim'
|
|
@ -0,0 +1,73 @@
|
|||
"""
|
||||
Package to test the openlp.plugin.bible.lib.https package.
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from mock import MagicMock
|
||||
|
||||
from openlp.core.lib import Registry
|
||||
from openlp.plugins.bibles.lib.http import BGExtract, CWExtract
|
||||
|
||||
|
||||
class TestBibleHTTP(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the Registry
|
||||
"""
|
||||
Registry.create()
|
||||
Registry().register(u'service_list', MagicMock())
|
||||
Registry().register(u'application', MagicMock())
|
||||
|
||||
def bible_gateway_extract_books_test(self):
|
||||
"""
|
||||
Test the Bible Gateway retrieval of book list for NIV bible
|
||||
"""
|
||||
# GIVEN: A new Bible Gateway extraction class
|
||||
handler = BGExtract()
|
||||
|
||||
# WHEN: The Books list is called
|
||||
books = handler.get_books_from_http(u'NIV')
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert len(books) == 66, u'The bible should not have had any books added or removed'
|
||||
|
||||
def bible_gateway_extract_verse_test(self):
|
||||
"""
|
||||
Test the Bible Gateway retrieval of verse list for NIV bible John 3
|
||||
"""
|
||||
# GIVEN: A new Bible Gateway extraction class
|
||||
handler = BGExtract()
|
||||
|
||||
# WHEN: The Books list is called
|
||||
results = handler.get_bible_chapter(u'NIV', u'John', 3)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert len(results.verselist) == 36, u'The book of John should not have had any verses added or removed'
|
||||
|
||||
def crosswalk_extract_books_test(self):
|
||||
"""
|
||||
Test Crosswalk retrieval of book list for NIV bible
|
||||
"""
|
||||
# GIVEN: A new Bible Gateway extraction class
|
||||
handler = CWExtract()
|
||||
|
||||
# WHEN: The Books list is called
|
||||
books = handler.get_books_from_http(u'niv')
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert len(books) == 66, u'The bible should not have had any books added or removed'
|
||||
|
||||
def crosswalk_extract_verse_test(self):
|
||||
"""
|
||||
Test Crosswalk retrieval of verse list for NIV bible John 3
|
||||
"""
|
||||
# GIVEN: A new Bible Gateway extraction class
|
||||
handler = CWExtract()
|
||||
|
||||
# WHEN: The Books list is called
|
||||
results = handler.get_bible_chapter(u'niv', u'john', 3)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert len(results.verselist) == 36, u'The book of John should not have had any verses added or removed'
|
||||
|
|
@ -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.'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
import os
|
||||
|
||||
OPENLP_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..'))
|
||||
TEST_RESOURCES_PATH = os.path.join(OPENLP_PATH, u'tests', u'resources')
|
|
@ -0,0 +1,49 @@
|
|||
# -*- 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:`osdinteraction` provides miscellaneous functions for interacting with
|
||||
OSD files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import cPickle
|
||||
|
||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||
|
||||
|
||||
def read_service_from_file(file_name):
|
||||
"""
|
||||
Reads an OSD file and returns the first service item found therein.
|
||||
@param file_name: File name of an OSD file residing in the tests/resources folder.
|
||||
@return: The service contained in the file.
|
||||
"""
|
||||
service_file = os.path.join(TEST_RESOURCES_PATH, file_name)
|
||||
with open(service_file, u'r') as open_file:
|
||||
service = cPickle.load(open_file)
|
||||
return service
|
Loading…
Reference in New Issue