This commit is contained in:
Philip Ridout 2013-10-05 08:54:14 +01:00
commit 3b9df96b42
57 changed files with 2648 additions and 1400 deletions

View File

@ -27,3 +27,4 @@ openlp.pro
tests.kdev4
*.nja
*.orig
__pycache__

View File

@ -36,7 +36,7 @@ from openlp.core.lib import Settings, translate
class FormattingTags(object):
"""
Static Class to HTML Tags to be access around the code the list is managed by the Options Tab.
Static Class for HTML Tags to be access around the code the list is managed by the Options Tab.
"""
html_expands = []
@ -48,22 +48,15 @@ class FormattingTags(object):
return FormattingTags.html_expands
@staticmethod
def save_html_tags():
def save_html_tags(new_tags):
"""
Saves all formatting tags except protected ones.
Saves all formatting tags except protected ones
`new_tags`
The tags to be saved..
"""
tags = []
for tag in FormattingTags.html_expands:
if not tag['protected'] and not tag.get('temporary'):
# Using dict ensures that copy is made and encoding of values a little later does not affect tags in
# the original list
tags.append(dict(tag))
tag = tags[-1]
# Remove key 'temporary' from tags. It is not needed to be saved.
if 'temporary' in tag:
del tag['temporary']
# Formatting Tags were also known as display tags.
Settings().setValue('formattingTags/html_tags', json.dumps(tags) if tags else '')
Settings().setValue('formattingTags/html_tags', json.dumps(new_tags) if new_tags else '')
@staticmethod
def load_tags():

View File

@ -82,10 +82,17 @@ class MediaManagerItem(QtGui.QWidget):
"""
Constructor to create the media manager item.
"""
super(MediaManagerItem, self).__init__()
super(MediaManagerItem, self).__init__(parent)
self.plugin = plugin
self._setup()
self.setup_item()
def _setup(self):
"""
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
"""
self.hide()
self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
self.plugin = plugin
visible_title = self.plugin.get_string(StringContent.VisibleName)
self.title = str(visible_title['title'])
Registry().register(self.plugin.name, self)
@ -106,6 +113,12 @@ class MediaManagerItem(QtGui.QWidget):
QtCore.QObject.connect(self, QtCore.SIGNAL('%s_go_live' % self.plugin.name), self.go_live_remote)
QtCore.QObject.connect(self, QtCore.SIGNAL('%s_add_to_service' % self.plugin.name), self.add_to_service_remote)
def setup_item(self):
"""
Override this for additional Plugin setup
"""
pass
def required_icons(self):
"""
This method is called to define the icons for the plugin. It provides a default set and the plugin is able to

View File

@ -95,6 +95,7 @@ from .aboutform import AboutForm
from .pluginform import PluginForm
from .settingsform import SettingsForm
from .formattingtagform import FormattingTagForm
from .formattingtagcontroller import FormattingTagController
from .shortcutlistform import ShortcutListForm
from .mediadockmanager import MediaDockManager
from .servicemanager import ServiceManager
@ -104,4 +105,4 @@ __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideCon
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'ThemeForm',
'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay', 'Display', 'ServiceNoteForm',
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
'FormattingTagForm', 'ShortcutListForm']
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController']

View File

@ -75,12 +75,6 @@ try:
ICU_VERSION = 'OK'
except ImportError:
ICU_VERSION = '-'
try:
import cherrypy
CHERRYPY_VERSION = cherrypy.__version__
except ImportError:
CHERRYPY_VERSION = '-'
try:
WEBKIT_VERSION = QtWebKit.qWebKitVersion()
except AttributeError:
@ -140,7 +134,6 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
'Chardet: %s\n' % CHARDET_VERSION + \
'PyEnchant: %s\n' % ENCHANT_VERSION + \
'Mako: %s\n' % MAKO_VERSION + \
'CherryPy: %s\n' % CHERRYPY_VERSION + \
'pyICU: %s\n' % ICU_VERSION + \
'pyUNO bridge: %s\n' % self._pyuno_import() + \
'VLC: %s\n' % VLC_VERSION

View File

@ -0,0 +1,176 @@
# -*- 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:`formattingtagform` provides an Tag Edit facility. The Base set are protected and included each time loaded.
Custom tags can be defined and saved. The Custom Tag arrays are saved in a pickle so QSettings works on them. Base Tags
cannot be changed.
"""
import re
from openlp.core.lib import FormattingTags, translate
class FormattingTagController(object):
"""
The :class:`FormattingTagController` manages the non UI functions .
"""
def __init__(self):
"""
Initiator
"""
self.html_tag_regex = re.compile(r'<(?:(?P<close>/(?=[^\s/>]+>))?'
r'(?P<tag>[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P<empty>/)?'
r'|(?P<cdata>!\[CDATA\[(?:(?!\]\]>).)*\]\])'
r'|(?P<procinst>\?(?:(?!\?>).)*\?)'
r'|(?P<comment>!--(?:(?!-->).)*--))>', re.UNICODE)
self.html_regex = re.compile(r'^(?:[^<>]*%s)*[^<>]*$' % self.html_tag_regex.pattern)
def pre_save(self):
"""
Cleanup the array before save validation runs
"""
self.protected_tags = [tag for tag in FormattingTags.html_expands if tag.get('protected')]
self.custom_tags = []
def validate_for_save(self, desc, tag, start_html, end_html):
"""
Validate a custom tag and add to the tags array if valid..
`desc`
Explanation of the tag.
`tag`
The tag in the song used to mark the text.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
for linenumber, html1 in enumerate(self.protected_tags):
if self._strip(html1['start tag']) == tag:
return translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag
if self._strip(html1['desc']) == desc:
return translate('OpenLP.FormattingTagForm', 'Description %s already defined.') % tag
for linenumber, html1 in enumerate(self.custom_tags):
if self._strip(html1['start tag']) == tag:
return translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag
if self._strip(html1['desc']) == desc:
return translate('OpenLP.FormattingTagForm', 'Description %s already defined.') % tag
tag = {
'desc': desc,
'start tag': '{%s}' % tag,
'start html': start_html,
'end tag': '{/%s}' % tag,
'end html': end_html,
'protected': False,
'temporary': False
}
self.custom_tags.append(tag)
def save_tags(self):
"""
Save the new tags if they are valid.
"""
FormattingTags.save_html_tags(self.custom_tags)
FormattingTags.load_tags()
def _strip(self, tag):
"""
Remove tag wrappers for editing.
`tag`
Tag to be stripped
"""
tag = tag.replace('{', '')
tag = tag.replace('}', '')
return tag
def start_html_to_end_html(self, start_html):
"""
Return the end HTML for a given start HTML or None if invalid.
`start_html`
The start html tag.
"""
end_tags = []
match = self.html_regex.match(start_html)
if match:
match = self.html_tag_regex.search(start_html)
while match:
if match.group('tag'):
tag = match.group('tag').lower()
if match.group('close'):
if match.group('empty') or not end_tags or end_tags.pop() != tag:
return
elif not match.group('empty'):
end_tags.append(tag)
match = self.html_tag_regex.search(start_html, match.end())
return ''.join(map(lambda tag: '</%s>' % tag, reversed(end_tags)))
def start_tag_changed(self, start_html, end_html):
"""
Validate the HTML tags when the start tag has been changed.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
end = self.start_html_to_end_html(start_html)
if not end_html:
if not end:
return translate('OpenLP.FormattingTagForm', 'Start tag %s is not valid HTML' % start_html), None
return None, end
return None, None
def end_tag_changed(self, start_html, end_html):
"""
Validate the HTML tags when the end tag has been changed.
`start_html`
The start html tag.
`end_html`
The end html tag.
"""
end = self.start_html_to_end_html(start_html)
if not end_html:
return None, end
if end and end != end_html:
return translate('OpenLP.FormattingTagForm',
'End tag %s does not match end tag for start tag %s' % (end, start_html)), None
return None, None

View File

@ -31,7 +31,7 @@ The UI widgets for the formatting tags window.
"""
from PyQt4 import QtCore, QtGui
from openlp.core.lib import UiStrings, translate
from openlp.core.lib import UiStrings, translate, build_icon
from openlp.core.lib.ui import create_button_box
@ -45,12 +45,34 @@ class Ui_FormattingTagDialog(object):
"""
formatting_tag_dialog.setObjectName('formatting_tag_dialog')
formatting_tag_dialog.resize(725, 548)
self.list_data_grid_layout = QtGui.QGridLayout(formatting_tag_dialog)
self.list_data_grid_layout = QtGui.QVBoxLayout(formatting_tag_dialog)
self.list_data_grid_layout.setMargin(8)
self.list_data_grid_layout.setObjectName('list_data_grid_layout')
self.tag_table_widget_read_label = QtGui.QLabel()
self.list_data_grid_layout.addWidget(self.tag_table_widget_read_label)
self.tag_table_widget_read = QtGui.QTableWidget(formatting_tag_dialog)
self.tag_table_widget_read.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tag_table_widget_read.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.tag_table_widget_read.setAlternatingRowColors(True)
self.tag_table_widget_read.setCornerButtonEnabled(False)
self.tag_table_widget_read.setObjectName('tag_table_widget_read')
self.tag_table_widget_read.setColumnCount(4)
self.tag_table_widget_read.setRowCount(0)
self.tag_table_widget_read.horizontalHeader().setStretchLastSection(True)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(0, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(1, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(2, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget_read.setHorizontalHeaderItem(3, item)
self.list_data_grid_layout.addWidget(self.tag_table_widget_read)
self.tag_table_widget_label = QtGui.QLabel()
self.list_data_grid_layout.addWidget(self.tag_table_widget_label)
self.tag_table_widget = QtGui.QTableWidget(formatting_tag_dialog)
self.tag_table_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tag_table_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.tag_table_widget.setEditTriggers(QtGui.QAbstractItemView.AllEditTriggers)
self.tag_table_widget.setAlternatingRowColors(True)
self.tag_table_widget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.tag_table_widget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
@ -67,59 +89,26 @@ class Ui_FormattingTagDialog(object):
self.tag_table_widget.setHorizontalHeaderItem(2, item)
item = QtGui.QTableWidgetItem()
self.tag_table_widget.setHorizontalHeaderItem(3, item)
self.list_data_grid_layout.addWidget(self.tag_table_widget, 0, 0, 1, 1)
self.horizontal_layout = QtGui.QHBoxLayout()
self.horizontal_layout.setObjectName('horizontal_layout')
spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontal_layout.addItem(spacer_item)
self.delete_push_button = QtGui.QPushButton(formatting_tag_dialog)
self.delete_push_button.setObjectName('delete_push_button')
self.horizontal_layout.addWidget(self.delete_push_button)
self.list_data_grid_layout.addLayout(self.horizontal_layout, 1, 0, 1, 1)
self.edit_group_box = QtGui.QGroupBox(formatting_tag_dialog)
self.edit_group_box.setObjectName('edit_group_box')
self.data_grid_layout = QtGui.QGridLayout(self.edit_group_box)
self.data_grid_layout.setObjectName('data_grid_layout')
self.description_label = QtGui.QLabel(self.edit_group_box)
self.description_label.setAlignment(QtCore.Qt.AlignCenter)
self.description_label.setObjectName('description_label')
self.data_grid_layout.addWidget(self.description_label, 0, 0, 1, 1)
self.description_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.description_line_edit.setObjectName('description_line_edit')
self.data_grid_layout.addWidget(self.description_line_edit, 0, 1, 2, 1)
self.new_push_button = QtGui.QPushButton(self.edit_group_box)
self.new_push_button.setObjectName('new_push_button')
self.data_grid_layout.addWidget(self.new_push_button, 0, 2, 2, 1)
self.tag_label = QtGui.QLabel(self.edit_group_box)
self.tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.tag_label.setObjectName('tag_label')
self.data_grid_layout.addWidget(self.tag_label, 2, 0, 1, 1)
self.tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.tag_line_edit.setMaximumSize(QtCore.QSize(50, 16777215))
self.tag_line_edit.setMaxLength(5)
self.tag_line_edit.setObjectName('tag_line_edit')
self.data_grid_layout.addWidget(self.tag_line_edit, 2, 1, 1, 1)
self.start_tag_label = QtGui.QLabel(self.edit_group_box)
self.start_tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.start_tag_label.setObjectName('start_tag_label')
self.data_grid_layout.addWidget(self.start_tag_label, 3, 0, 1, 1)
self.start_tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.start_tag_line_edit.setObjectName('start_tag_line_edit')
self.data_grid_layout.addWidget(self.start_tag_line_edit, 3, 1, 1, 1)
self.end_tag_label = QtGui.QLabel(self.edit_group_box)
self.end_tag_label.setAlignment(QtCore.Qt.AlignCenter)
self.end_tag_label.setObjectName('end_tag_label')
self.data_grid_layout.addWidget(self.end_tag_label, 4, 0, 1, 1)
self.end_tag_line_edit = QtGui.QLineEdit(self.edit_group_box)
self.end_tag_line_edit.setObjectName('end_tag_line_edit')
self.data_grid_layout.addWidget(self.end_tag_line_edit, 4, 1, 1, 1)
self.save_push_button = QtGui.QPushButton(self.edit_group_box)
self.save_push_button.setObjectName('save_push_button')
self.data_grid_layout.addWidget(self.save_push_button, 4, 2, 1, 1)
self.list_data_grid_layout.addWidget(self.edit_group_box, 2, 0, 1, 1)
self.button_box = create_button_box(formatting_tag_dialog, 'button_box', ['close'])
self.list_data_grid_layout.addWidget(self.button_box, 3, 0, 1, 1)
self.list_data_grid_layout.addWidget(self.tag_table_widget)
self.edit_button_layout = QtGui.QHBoxLayout()
self.new_button = QtGui.QPushButton(formatting_tag_dialog)
self.new_button.setIcon(build_icon(':/general/general_new.png'))
self.new_button.setObjectName('new_button')
self.edit_button_layout.addWidget(self.new_button)
self.delete_button = QtGui.QPushButton(formatting_tag_dialog)
self.delete_button.setIcon(build_icon(':/general/general_delete.png'))
self.delete_button.setObjectName('delete_button')
self.edit_button_layout.addWidget(self.delete_button)
self.edit_button_layout.addStretch()
self.list_data_grid_layout.addLayout(self.edit_button_layout)
self.button_box = create_button_box(formatting_tag_dialog, 'button_box',
['cancel', 'save', 'defaults'])
self.save_button = self.button_box.button(QtGui.QDialogButtonBox.Save)
self.save_button.setObjectName('save_button')
self.restore_button = self.button_box.button(QtGui.QDialogButtonBox.RestoreDefaults)
self.restore_button.setIcon(build_icon(':/general/general_revert.png'))
self.restore_button.setObjectName('restore_button')
self.list_data_grid_layout.addWidget(self.button_box)
self.retranslateUi(formatting_tag_dialog)
def retranslateUi(self, formatting_tag_dialog):
@ -127,14 +116,19 @@ class Ui_FormattingTagDialog(object):
Translate the UI on the fly
"""
formatting_tag_dialog.setWindowTitle(translate('OpenLP.FormattingTagDialog', 'Configure Formatting Tags'))
self.edit_group_box.setTitle(translate('OpenLP.FormattingTagDialog', 'Edit Selection'))
self.save_push_button.setText(translate('OpenLP.FormattingTagDialog', 'Save'))
self.description_label.setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_label.setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.start_tag_label.setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))
self.end_tag_label.setText(translate('OpenLP.FormattingTagDialog', 'End HTML'))
self.delete_push_button.setText(UiStrings().Delete)
self.new_push_button.setText(UiStrings().New)
self.delete_button.setText(UiStrings().Delete)
self.new_button.setText(UiStrings().New)
self.tag_table_widget_read_label.setText(translate('OpenLP.FormattingTagDialog', 'Default Formatting'))
self.tag_table_widget_read.horizontalHeaderItem(0).\
setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_table_widget_read.horizontalHeaderItem(1).setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.tag_table_widget_read.horizontalHeaderItem(2).\
setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))
self.tag_table_widget_read.horizontalHeaderItem(3).setText(translate('OpenLP.FormattingTagDialog', 'End HTML'))
self.tag_table_widget_read.setColumnWidth(0, 120)
self.tag_table_widget_read.setColumnWidth(1, 80)
self.tag_table_widget_read.setColumnWidth(2, 330)
self.tag_table_widget_label.setText(translate('OpenLP.FormattingTagDialog', 'Custom Formatting'))
self.tag_table_widget.horizontalHeaderItem(0).setText(translate('OpenLP.FormattingTagDialog', 'Description'))
self.tag_table_widget.horizontalHeaderItem(1).setText(translate('OpenLP.FormattingTagDialog', 'Tag'))
self.tag_table_widget.horizontalHeaderItem(2).setText(translate('OpenLP.FormattingTagDialog', 'Start HTML'))

View File

@ -31,14 +31,25 @@ The :mod:`formattingtagform` provides an Tag Edit facility. The Base set are pro
Custom tags can be defined and saved. The Custom Tag arrays are saved in a json string so QSettings works on them.
Base Tags cannot be changed.
"""
from PyQt4 import QtGui
from openlp.core.lib import FormattingTags, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.formattingtagdialog import Ui_FormattingTagDialog
from openlp.core.ui.formattingtagcontroller import FormattingTagController
class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
class EditColumn(object):
"""
Hides the magic numbers for the table columns
"""
Description = 0
Tag = 1
StartHtml = 2
EndHtml = 3
class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagController):
"""
The :class:`FormattingTagForm` manages the settings tab .
"""
@ -48,17 +59,17 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
super(FormattingTagForm, self).__init__(parent)
self.setupUi(self)
self.services = FormattingTagController()
self.tag_table_widget.itemSelectionChanged.connect(self.on_row_selected)
self.new_push_button.clicked.connect(self.on_new_clicked)
self.save_push_button.clicked.connect(self.on_saved_clicked)
self.delete_push_button.clicked.connect(self.on_delete_clicked)
self.new_button.clicked.connect(self.on_new_clicked)
#self.save_button.clicked.connect(self.on_saved_clicked)
self.delete_button.clicked.connect(self.on_delete_clicked)
self.tag_table_widget.currentCellChanged.connect(self.on_current_cell_changed)
self.button_box.rejected.connect(self.close)
self.description_line_edit.textEdited.connect(self.on_text_edited)
self.tag_line_edit.textEdited.connect(self.on_text_edited)
self.start_tag_line_edit.textEdited.connect(self.on_text_edited)
self.end_tag_line_edit.textEdited.connect(self.on_text_edited)
# Forces reloading of tags from openlp configuration.
FormattingTags.load_tags()
self.is_deleting = False
self.reloading = False
def exec_(self):
"""
@ -66,138 +77,128 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog):
"""
# Create initial copy from master
self._reloadTable()
self.selected = -1
return QtGui.QDialog.exec_(self)
def on_row_selected(self):
"""
Table Row selected so display items and set field state.
"""
self.save_push_button.setEnabled(False)
self.selected = self.tag_table_widget.currentRow()
html = FormattingTags.get_html_tags()[self.selected]
self.description_line_edit.setText(html['desc'])
self.tag_line_edit.setText(self._strip(html['start tag']))
self.start_tag_line_edit.setText(html['start html'])
self.end_tag_line_edit.setText(html['end html'])
if html['protected']:
self.description_line_edit.setEnabled(False)
self.tag_line_edit.setEnabled(False)
self.start_tag_line_edit.setEnabled(False)
self.end_tag_line_edit.setEnabled(False)
self.delete_push_button.setEnabled(False)
else:
self.description_line_edit.setEnabled(True)
self.tag_line_edit.setEnabled(True)
self.start_tag_line_edit.setEnabled(True)
self.end_tag_line_edit.setEnabled(True)
self.delete_push_button.setEnabled(True)
def on_text_edited(self, text):
"""
Enable the ``save_push_button`` when any of the selected tag's properties
has been changed.
"""
self.save_push_button.setEnabled(True)
self.delete_button.setEnabled(True)
def on_new_clicked(self):
"""
Add a new tag to list only if it is not a duplicate.
Add a new tag to edit list and select it for editing.
"""
for html in FormattingTags.get_html_tags():
if self._strip(html['start tag']) == 'n':
critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'),
translate('OpenLP.FormattingTagForm', 'Tag "n" already defined.'))
return
# Add new tag to list
tag = {
'desc': translate('OpenLP.FormattingTagForm', 'New Tag'),
'start tag': '{n}',
'start html': translate('OpenLP.FormattingTagForm', '<HTML here>'),
'end tag': '{/n}',
'end html': translate('OpenLP.FormattingTagForm', '</and here>'),
'protected': False,
'temporary': False
}
FormattingTags.add_html_tags([tag])
FormattingTags.save_html_tags()
self._reloadTable()
# Highlight new row
self.tag_table_widget.selectRow(self.tag_table_widget.rowCount() - 1)
self.on_row_selected()
new_row = self.tag_table_widget.rowCount()
self.tag_table_widget.insertRow(new_row)
self.tag_table_widget.setItem(new_row, 0,
QtGui.QTableWidgetItem(translate('OpenLP.FormattingTagForm', 'New Tag%s') % str(new_row)))
self.tag_table_widget.setItem(new_row, 1, QtGui.QTableWidgetItem('n%s' % str(new_row)))
self.tag_table_widget.setItem(new_row, 2,
QtGui.QTableWidgetItem(translate('OpenLP.FormattingTagForm', '<HTML here>')))
self.tag_table_widget.setItem(new_row, 3, QtGui.QTableWidgetItem(''))
self.tag_table_widget.resizeRowsToContents()
self.tag_table_widget.scrollToBottom()
self.tag_table_widget.selectRow(new_row)
def on_delete_clicked(self):
"""
Delete selected custom tag.
Delete selected custom row.
"""
if self.selected != -1:
FormattingTags.remove_html_tag(self.selected)
# As the first items are protected we should not have to take care
# of negative indexes causing tracebacks.
self.tag_table_widget.selectRow(self.selected - 1)
self.selected = -1
FormattingTags.save_html_tags()
self._reloadTable()
selected = self.tag_table_widget.currentRow()
if selected != -1:
self.is_deleting = True
self.tag_table_widget.removeRow(selected)
def on_saved_clicked(self):
def accept(self):
"""
Update Custom Tag details if not duplicate and save the data.
"""
html_expands = FormattingTags.get_html_tags()
if self.selected != -1:
html = html_expands[self.selected]
tag = self.tag_line_edit.text()
for linenumber, html1 in enumerate(html_expands):
if self._strip(html1['start tag']) == tag and linenumber != self.selected:
critical_error_message_box(
translate('OpenLP.FormattingTagForm', 'Update Error'),
translate('OpenLP.FormattingTagForm', 'Tag %s already defined.') % tag)
return
html['desc'] = self.description_line_edit.text()
html['start html'] = self.start_tag_line_edit.text()
html['end html'] = self.end_tag_line_edit.text()
html['start tag'] = '{%s}' % tag
html['end tag'] = '{/%s}' % tag
# Keep temporary tags when the user changes one.
html['temporary'] = False
self.selected = -1
FormattingTags.save_html_tags()
self._reloadTable()
count = 0
self.services.pre_save()
while count < self.tag_table_widget.rowCount():
error = self.services.validate_for_save(self.tag_table_widget.item(count, 0).text(),
self.tag_table_widget.item(count, 1).text(), self.tag_table_widget.item(count, 2).text(),
self.tag_table_widget.item(count, 3).text())
if error:
QtGui.QMessageBox.warning(self,
translate('OpenLP.FormattingTagForm', 'Validation Error'), error, QtGui.QMessageBox.Ok)
self.tag_table_widget.selectRow(count)
return
count += 1
self.services.save_tags()
QtGui.QDialog.accept(self)
def _reloadTable(self):
"""
Reset List for loading.
"""
self.reloading = True
self.tag_table_widget_read.clearContents()
self.tag_table_widget_read.setRowCount(0)
self.tag_table_widget.clearContents()
self.tag_table_widget.setRowCount(0)
self.new_push_button.setEnabled(True)
self.save_push_button.setEnabled(False)
self.delete_push_button.setEnabled(False)
self.new_button.setEnabled(True)
self.delete_button.setEnabled(False)
for linenumber, html in enumerate(FormattingTags.get_html_tags()):
self.tag_table_widget.setRowCount(self.tag_table_widget.rowCount() + 1)
self.tag_table_widget.setItem(linenumber, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget.setItem(linenumber, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget.setItem(linenumber, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget.setItem(linenumber, 3, QtGui.QTableWidgetItem(html['end html']))
# Permanent (persistent) tags do not have this key.
if 'temporary' not in html:
if html['protected']:
line = self.tag_table_widget_read.rowCount()
self.tag_table_widget_read.setRowCount(line + 1)
self.tag_table_widget_read.setItem(line, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget_read.setItem(line, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget_read.setItem(line, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget_read.setItem(line, 3, QtGui.QTableWidgetItem(html['end html']))
self.tag_table_widget_read.resizeRowsToContents()
else:
line = self.tag_table_widget.rowCount()
self.tag_table_widget.setRowCount(line + 1)
self.tag_table_widget.setItem(line, 0, QtGui.QTableWidgetItem(html['desc']))
self.tag_table_widget.setItem(line, 1, QtGui.QTableWidgetItem(self._strip(html['start tag'])))
self.tag_table_widget.setItem(line, 2, QtGui.QTableWidgetItem(html['start html']))
self.tag_table_widget.setItem(line, 3, QtGui.QTableWidgetItem(html['end html']))
self.tag_table_widget.resizeRowsToContents()
# Permanent (persistent) tags do not have this key
html['temporary'] = False
self.tag_table_widget.resizeRowsToContents()
self.description_line_edit.setText('')
self.tag_line_edit.setText('')
self.start_tag_line_edit.setText('')
self.end_tag_line_edit.setText('')
self.description_line_edit.setEnabled(False)
self.tag_line_edit.setEnabled(False)
self.start_tag_line_edit.setEnabled(False)
self.end_tag_line_edit.setEnabled(False)
self.reloading = False
def _strip(self, tag):
def on_current_cell_changed(self, cur_row, cur_col, pre_row, pre_col):
"""
Remove tag wrappers for editing.
This function processes all user edits in the table. It is called on each cell change.
"""
tag = tag.replace('{', '')
tag = tag.replace('}', '')
return tag
if self.is_deleting:
self.is_deleting = False
return
if self.reloading:
return
# only process for editable rows
if self.tag_table_widget.item(pre_row, 0):
item = self.tag_table_widget.item(pre_row, pre_col)
text = item.text()
errors = None
if pre_col is EditColumn.Description:
if not text:
errors = translate('OpenLP.FormattingTagForm', 'Description is missing')
elif pre_col is EditColumn.Tag:
if not text:
errors = translate('OpenLP.FormattingTagForm', 'Tag is missing')
elif pre_col is EditColumn.StartHtml:
# HTML edited
item = self.tag_table_widget.item(pre_row, 3)
end_html = item.text()
errors, tag = self.services.start_tag_changed(text, end_html)
if tag:
self.tag_table_widget.setItem(pre_row, 3, QtGui.QTableWidgetItem(tag))
self.tag_table_widget.resizeRowsToContents()
elif pre_col is EditColumn.EndHtml:
# HTML edited
item = self.tag_table_widget.item(pre_row, 2)
start_html = item.text()
errors, tag = self.services.end_tag_changed(start_html, text)
if tag:
self.tag_table_widget.setItem(pre_row, 3, QtGui.QTableWidgetItem(tag))
if errors:
QtGui.QMessageBox.warning(self,
translate('OpenLP.FormattingTagForm', 'Validation Error'), errors, QtGui.QMessageBox.Ok)
#self.tag_table_widget.selectRow(pre_row - 1)
self.tag_table_widget.resizeRowsToContents()

View File

@ -48,6 +48,7 @@ __default_settings__ = {
'bibles/verse layout style': LayoutStyle.VersePerSlide,
'bibles/book name language': LanguageSelection.Bible,
'bibles/display brackets': DisplayStyle.NoBrackets,
'bibles/is verse number visible': True,
'bibles/display new chapter': False,
'bibles/second bibles': True,
'bibles/advanced bible': '',
@ -66,6 +67,9 @@ __default_settings__ = {
class BiblePlugin(Plugin):
"""
The Bible plugin provides a plugin for managing and displaying Bibles.
"""
log.info('Bible Plugin loaded')
def __init__(self):
@ -73,13 +77,14 @@ class BiblePlugin(Plugin):
self.weight = -9
self.icon_path = ':/plugins/plugin_bibles.png'
self.icon = build_icon(self.icon_path)
self.manager = None
self.manager = BibleManager(self)
def initialise(self):
"""
Initialise the Bible plugin.
"""
log.info('bibles Initialising')
if self.manager is None:
self.manager = BibleManager(self)
Plugin.initialise(self)
super(BiblePlugin, self).initialise()
self.import_bible_item.setVisible(True)
action_list = ActionList.get_instance()
action_list.add_action(self.import_bible_item, UiStrings().Import)
@ -106,7 +111,7 @@ class BiblePlugin(Plugin):
"""
Perform tasks on application startup
"""
Plugin.app_startup(self)
super(BiblePlugin, self).app_startup()
if self.manager.old_bible_databases:
if QtGui.QMessageBox.information(self.main_window,
translate('OpenLP', 'Information'),

View File

@ -58,6 +58,9 @@ class BiblesTab(SettingsTab):
self.verse_display_group_box.setObjectName('verse_display_group_box')
self.verse_display_layout = QtGui.QFormLayout(self.verse_display_group_box)
self.verse_display_layout.setObjectName('verse_display_layout')
self.is_verse_number_visible_check_box = QtGui.QCheckBox(self.verse_display_group_box)
self.is_verse_number_visible_check_box.setObjectName('is_verse_number_visible_check_box')
self.verse_display_layout.addRow(self.is_verse_number_visible_check_box)
self.new_chapters_check_box = QtGui.QCheckBox(self.verse_display_group_box)
self.new_chapters_check_box.setObjectName('new_chapters_check_box')
self.verse_display_layout.addRow(self.new_chapters_check_box)
@ -134,6 +137,7 @@ class BiblesTab(SettingsTab):
self.left_layout.addStretch()
self.right_layout.addStretch()
# Signals and slots
self.is_verse_number_visible_check_box.stateChanged.connect(self.on_is_verse_number_visible_check_box_changed)
self.new_chapters_check_box.stateChanged.connect(self.on_new_chapters_check_box_changed)
self.display_style_combo_box.activated.connect(self.on_display_style_combo_box_changed)
self.bible_theme_combo_box.activated.connect(self.on_bible_theme_combo_box_changed)
@ -156,6 +160,7 @@ class BiblesTab(SettingsTab):
def retranslateUi(self):
self.verse_display_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Verse Display'))
self.is_verse_number_visible_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Show verse numbers'))
self.new_chapters_check_box.setText(translate('BiblesPlugin.BiblesTab', 'Only show new chapter numbers'))
self.layout_style_label.setText(UiStrings().LayoutStyle)
self.display_style_label.setText(UiStrings().DisplayStyle)
@ -208,6 +213,13 @@ class BiblesTab(SettingsTab):
def on_language_selection_combo_box_changed(self):
self.language_selection = self.language_selection_combo_box.currentIndex()
def on_is_verse_number_visible_check_box_changed(self, check_state):
"""
Event handler for the 'verse number visible' check box
"""
self.is_verse_number_visible = (check_state == QtCore.Qt.Checked)
self.check_is_verse_number_visible()
def on_new_chapters_check_box_changed(self, check_state):
self.show_new_chapters = False
# We have a set value convert to True/False.
@ -299,11 +311,14 @@ class BiblesTab(SettingsTab):
def load(self):
settings = Settings()
settings.beginGroup(self.settings_section)
self.is_verse_number_visible = settings.value('is verse number visible')
self.show_new_chapters = settings.value('display new chapter')
self.display_style = settings.value('display brackets')
self.layout_style = settings.value('verse layout style')
self.bible_theme = settings.value('bible theme')
self.second_bibles = settings.value('second bibles')
self.is_verse_number_visible_check_box.setChecked(self.is_verse_number_visible)
self.check_is_verse_number_visible()
self.new_chapters_check_box.setChecked(self.show_new_chapters)
self.display_style_combo_box.setCurrentIndex(self.display_style)
self.layout_style_combo_box.setCurrentIndex(self.layout_style)
@ -351,6 +366,7 @@ class BiblesTab(SettingsTab):
def save(self):
settings = Settings()
settings.beginGroup(self.settings_section)
settings.setValue('is verse number visible', self.is_verse_number_visible)
settings.setValue('display new chapter', self.show_new_chapters)
settings.setValue('display brackets', self.display_style)
settings.setValue('verse layout style', self.layout_style)
@ -405,3 +421,12 @@ class BiblesTab(SettingsTab):
color.setAlpha(128)
palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
return palette
def check_is_verse_number_visible(self):
"""
Enables / Disables verse settings dependent on is_verse_number_visible
"""
self.new_chapters_check_box.setEnabled(self.is_verse_number_visible)
self.display_style_label.setEnabled(self.is_verse_number_visible)
self.display_style_combo_box.setEnabled(self.is_verse_number_visible)

View File

@ -64,6 +64,11 @@ class BibleMediaItem(MediaManagerItem):
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
MediaManagerItem.__init__(self, parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
# Place to store the search results for both bibles.
self.settings = self.plugin.settings_tab
self.quick_preview_allowed = True
@ -803,20 +808,20 @@ class BibleMediaItem(MediaManagerItem):
verses.add(book, chapter, verse, version, copyright, permissions)
verse_text = self.formatVerse(old_chapter, chapter, verse)
if second_bible:
bible_text = '%s&nbsp;%s\n\n%s&nbsp;%s' % (verse_text, text, verse_text, second_text)
bible_text = '%s%s\n\n%s&nbsp;%s' % (verse_text, text, verse_text, second_text)
raw_slides.append(bible_text.rstrip())
bible_text = ''
# If we are 'Verse Per Slide' then create a new slide.
elif self.settings.layout_style == LayoutStyle.VersePerSlide:
bible_text = '%s&nbsp;%s' % (verse_text, text)
bible_text = '%s%s' % (verse_text, text)
raw_slides.append(bible_text.rstrip())
bible_text = ''
# If we are 'Verse Per Line' then force a new line.
elif self.settings.layout_style == LayoutStyle.VersePerLine:
bible_text = '%s%s&nbsp;%s\n' % (bible_text, verse_text, text)
bible_text = '%s%s%s\n' % (bible_text, verse_text, text)
# We have to be 'Continuous'.
else:
bible_text = '%s %s&nbsp;%s\n' % (bible_text, verse_text, text)
bible_text = '%s %s%s\n' % (bible_text, verse_text, text)
bible_text = bible_text.strip(' ')
if not old_item:
start_item = bitem
@ -943,17 +948,19 @@ class BibleMediaItem(MediaManagerItem):
The verse number (int).
"""
verse_separator = get_reference_separator('sep_v_display')
if not self.settings.is_verse_number_visible:
return ''
if not self.settings.show_new_chapters or old_chapter != chapter:
verse_text = str(chapter) + verse_separator + str(verse)
else:
verse_text = str(verse)
if self.settings.display_style == DisplayStyle.Round:
return '{su}(%s){/su}' % verse_text
return '{su}(%s){/su}&nbsp;' % verse_text
if self.settings.display_style == DisplayStyle.Curly:
return '{su}{%s}{/su}' % verse_text
return '{su}{%s}{/su}&nbsp;' % verse_text
if self.settings.display_style == DisplayStyle.Square:
return '{su}[%s]{/su}' % verse_text
return '{su}%s{/su}' % verse_text
return '{su}[%s]{/su}&nbsp;' % verse_text
return '{su}%s{/su}&nbsp;' % verse_text
def search(self, string, showError):
"""

View File

@ -58,6 +58,11 @@ class CustomMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'custom/custom'
super(CustomMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.edit_custom_form = EditCustomForm(self, self.main_window, self.plugin.manager)
self.single_service_item = False
self.quick_preview_allowed = True
@ -65,7 +70,7 @@ class CustomMediaItem(MediaManagerItem):
# Holds information about whether the edit is remotely triggered and
# which Custom is required.
self.remote_custom = -1
self.manager = plugin.manager
self.manager = self.plugin.manager
def add_end_header_bar(self):
self.toolbar.addSeparator()

View File

@ -52,10 +52,18 @@ class ImageMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'images/image'
self.manager = None
self.choose_group_form = None
self.add_group_form = None
super(ImageMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.quick_preview_allowed = True
self.has_search = True
self.manager = plugin.manager
self.manager = self.plugin.manager
self.choose_group_form = ChooseGroupForm(self)
self.add_group_form = AddGroupForm(self)
self.fill_groups_combobox(self.choose_group_form.group_combobox)
@ -91,8 +99,8 @@ class ImageMediaItem(MediaManagerItem):
self.list_view.setIconSize(QtCore.QSize(88, 50))
self.list_view.setIndentation(self.list_view.default_indentation)
self.list_view.allow_internal_dnd = True
self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
check_directory_exists(self.servicePath)
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
check_directory_exists(self.service_path)
# Load images from the database
self.load_full_list(
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
@ -193,7 +201,7 @@ class ImageMediaItem(MediaManagerItem):
"""
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
for image in images:
delete_file(os.path.join(self.servicePath, os.path.split(image.filename)[1]))
delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1]))
self.manager.delete_object(ImageFilenames, image.id)
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
for group in image_groups:
@ -215,7 +223,7 @@ class ImageMediaItem(MediaManagerItem):
if row_item:
item_data = row_item.data(0, QtCore.Qt.UserRole)
if isinstance(item_data, ImageFilenames):
delete_file(os.path.join(self.servicePath, row_item.text(0)))
delete_file(os.path.join(self.service_path, row_item.text(0)))
if item_data.group_id == 0:
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
else:
@ -339,7 +347,7 @@ class ImageMediaItem(MediaManagerItem):
for imageFile in images:
log.debug('Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1]
thumb = os.path.join(self.servicePath, filename)
thumb = os.path.join(self.service_path, filename)
if not os.path.exists(imageFile.filename):
icon = build_icon(':/general/general_delete.png')
else:
@ -672,7 +680,16 @@ class ImageMediaItem(MediaManagerItem):
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
'the image file "%s" no longer exists.') % filename)
def search(self, string, showError):
def search(self, string, show_error=True):
"""
Perform a search on the image file names.
``string``
The glob to search for
``show_error``
Unused.
"""
files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
order_by_ref=ImageFilenames.filename)
results = []

View File

@ -61,10 +61,15 @@ class MediaMediaItem(MediaManagerItem):
self.background = False
self.automatic = ''
super(MediaMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False
self.has_search = True
self.media_object = None
self.display_controller = DisplayController(parent)
self.display_controller = DisplayController(self.parent())
self.display_controller.controller_layout = QtGui.QVBoxLayout()
self.media_controller.register_controller(self.display_controller)
self.media_controller.set_controls_visible(self.display_controller, False)

View File

@ -52,14 +52,26 @@ class PresentationMediaItem(MediaManagerItem):
"""
log.info('Presentations Media Item loaded')
def __init__(self, parent, plugin, icon, controllers):
def __init__(self, parent, plugin, controllers):
"""
Constructor. Setup defaults
"""
self.controllers = controllers
self.icon_path = 'presentations/presentation'
self.Automatic = ''
self.controllers = controllers
super(PresentationMediaItem, self).__init__(parent, plugin)
def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)')
self.automatic = translate('PresentationPlugin.MediaItem', 'Automatic')
self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:'))
def setup_item(self):
"""
Do some additional setup.
"""
self.message_listener = MessageListener(self)
self.has_search = True
self.single_service_item = False
@ -68,14 +80,6 @@ class PresentationMediaItem(MediaManagerItem):
# Allow DnD from the desktop
self.list_view.activateDnD()
def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)')
self.Automatic = translate('PresentationPlugin.MediaItem', 'Automatic')
self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:'))
def build_file_mask_string(self):
"""
Build the list of file extensions to be used in the Open file dialog.
@ -137,7 +141,7 @@ class PresentationMediaItem(MediaManagerItem):
if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item)
if self.display_type_combo_box.count() > 1:
self.display_type_combo_box.insertItem(0, self.Automatic)
self.display_type_combo_box.insertItem(0, self.automatic)
self.display_type_combo_box.setCurrentIndex(0)
if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked:
self.presentation_widget.show()
@ -253,7 +257,7 @@ class PresentationMediaItem(MediaManagerItem):
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
if service_item.processor == self.Automatic:
if service_item.processor == self.automatic:
service_item.processor = self.findControllerByType(filename)
if not service_item.processor:
return False

View File

@ -109,8 +109,7 @@ class PresentationPlugin(Plugin):
"""
Create the Media Manager List.
"""
self.media_item = PresentationMediaItem(
self.main_window.media_dock_manager.media_dock, self, self.icon, self.controllers)
self.media_item = PresentationMediaItem(self.main_window.media_dock_manager.media_dock, self, self.controllers)
def register_controllers(self, controller):
"""

View File

@ -40,6 +40,8 @@ window.OpenLP = {
// defeat Safari bug
targ = targ.parentNode;
}
var isSecure = false;
var isAuthorised = false;
return $(targ);
},
getSearchablePlugins: function () {
@ -147,11 +149,13 @@ window.OpenLP = {
},
pollServer: function () {
$.getJSON(
"/stage/poll",
"/api/poll",
function (data, status) {
var prevItem = OpenLP.currentItem;
OpenLP.currentSlide = data.results.slide;
OpenLP.currentItem = data.results.item;
OpenLP.isSecure = data.results.isSecure;
OpenLP.isAuthorised = data.results.isAuthorised;
if ($("#service-manager").is(":visible")) {
if (OpenLP.currentService != data.results.service) {
OpenLP.currentService = data.results.service;

View File

@ -26,7 +26,7 @@
window.OpenLP = {
loadService: function (event) {
$.getJSON(
"/stage/service/list",
"/api/service/list",
function (data, status) {
OpenLP.nextSong = "";
$("#notes").html("");
@ -46,7 +46,7 @@ window.OpenLP = {
},
loadSlides: function (event) {
$.getJSON(
"/stage/controller/live/text",
"/api/controller/live/text",
function (data, status) {
OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0;
@ -137,7 +137,7 @@ window.OpenLP = {
},
pollServer: function () {
$.getJSON(
"/stage/poll",
"/api/poll",
function (data, status) {
OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item ||

View File

@ -28,6 +28,7 @@
###############################################################################
from .remotetab import RemoteTab
from .httpserver import HttpServer
from .httprouter import HttpRouter
from .httpserver import OpenLPServer
__all__ = ['RemoteTab', 'HttpServer']
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']

View File

@ -0,0 +1,638 @@
# -*- 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:`http` module contains the API web server. This is a lightweight web
server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes.
*Routes:*
``/``
Go to the web interface.
``/stage``
Show the stage view.
``/files/{filename}``
Serve a static file.
``/stage/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/display/{hide|show}``
Blank or unblank the screen.
``/api/alert``
Sends an alert message to the alerts plugin. This method expects a
JSON-encoded dict like this::
{"request": {"text": "<your alert text>"}}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``set``
Set a specific slide. Requires an id return in a JSON-encoded dict like
this::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Fetches the text of the current song. The output is a JSON-encoded
dict which looks like this::
{"result": {"slides": ["...", "..."]}}
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``set``
Set a specific item in the service. Requires an id returned in a
JSON-encoded dict like this::
{"request": {"id": 1}}
``list``
Request a list of items in the service. Returns a list of items in the
current service in a JSON-encoded dict like this::
{"results": {"items": [{...}, {...}]}}
"""
import base64
import json
import logging
import os
import re
import urllib.request
import urllib.error
from urllib.parse import urlparse, parse_qs
from mako.template import Template
from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte
from openlp.core.utils import AppLocation, translate
log = logging.getLogger(__name__)
class HttpRouter(object):
"""
This code is called by the HttpServer upon a request and it processes it based on the routing table.
This code is stateless and is created on each request.
Some variables may look incorrect but this extends BaseHTTPRequestHandler.
"""
def initialise(self):
"""
Initialise the router stack and any other variables.
"""
authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password'))
try:
self.auth = base64.b64encode(authcode)
except TypeError:
self.auth = base64.b64encode(authcode.encode()).decode()
self.routes = [
('^/$', {'function': self.serve_file, 'secure': False}),
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
(r'^/api/controller/(live|preview)/text$', {'function': self.controller_text, 'secure': False}),
(r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}),
(r'^/api/service/list$', {'function': self.service_list, 'secure': False}),
(r'^/api/service/(.*)$', {'function': self.service, 'secure': True}),
(r'^/api/display/(hide|show|blank|theme|desktop)$', {'function': self.display, 'secure': True}),
(r'^/api/alert$', {'function': self.alert, 'secure': True}),
(r'^/api/plugin/(search)$', {'function': self.plugin_info, 'secure': False}),
(r'^/api/(.*)/search$', {'function': self.search, 'secure': False}),
(r'^/api/(.*)/live$', {'function': self.go_live, 'secure': True}),
(r'^/api/(.*)/add$', {'function': self.add_to_service, 'secure': True})
]
self.settings_section = 'remotes'
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
def do_post_processor(self):
"""
Handle the POST amd GET requests placed on the server.
"""
if self.path == '/favicon.ico':
return
if not hasattr(self, 'auth'):
self.initialise()
function, args = self.process_http_request(self.path)
if not function:
self.do_http_error()
return
self.authorised = self.headers['Authorization'] is None
if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'):
if self.headers['Authorization'] is None:
self.do_authorisation()
self.wfile.write(bytes('no auth header received', 'UTF-8'))
elif self.headers['Authorization'] == 'Basic %s' % self.auth:
self.do_http_success()
self.call_function(function, *args)
else:
self.do_authorisation()
self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8'))
self.wfile.write(bytes(' not authenticated', 'UTF-8'))
else:
self.call_function(function, *args)
def call_function(self, function, *args):
"""
Invoke the route function passing the relevant values
``function``
The function to be calledL.
``*args``
Any passed data.
"""
response = function['function'](*args)
if response:
self.wfile.write(response)
return
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests
``url_path``
The requested URL.
``*args``
Any passed data.
"""
self.request_data = None
url_path_split = urlparse(url_path)
url_query = parse_qs(url_path_split.query)
if 'data' in url_query.keys():
self.request_data = url_query['data'][0]
for route, func in self.routes:
match = re.match(route, url_path_split.path)
if match:
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
return func, args
return None, None
def do_http_success(self):
"""
Create a success http header.
"""
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_json_header(self):
"""
Create a header for JSON messages
"""
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
def do_http_error(self):
"""
Create a error http header.
"""
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_authorisation(self):
"""
Create a needs authorisation http header.
"""
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"')
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_not_found(self):
"""
Create a not found http header.
"""
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
def _get_service_items(self):
"""
Read the service item in use and return the data as a json object
"""
service_items = []
if self.live_controller.service_item:
current_unique_identifier = self.live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in self.service_manager.service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return service_items
def translate(self):
"""
Translate various strings in the mobile app.
"""
self.template_vars = {
'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'),
'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'),
'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
'search': translate('RemotePlugin.Mobile', 'Search'),
'home': translate('RemotePlugin.Mobile', 'Home'),
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
'blank': translate('RemotePlugin.Mobile', 'Blank'),
'theme': translate('RemotePlugin.Mobile', 'Theme'),
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
'show': translate('RemotePlugin.Mobile', 'Show'),
'prev': translate('RemotePlugin.Mobile', 'Prev'),
'next': translate('RemotePlugin.Mobile', 'Next'),
'text': translate('RemotePlugin.Mobile', 'Text'),
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add &amp; Go to Service'),
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
}
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self.do_not_found()
ext = os.path.splitext(file_name)[1]
html = None
if ext == '.html':
self.send_header('Content-type', 'text/html')
variables = self.template_vars
html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
elif ext == '.css':
self.send_header('Content-type', 'text/css')
elif ext == '.js':
self.send_header('Content-type', 'application/javascript')
elif ext == '.jpg':
self.send_header('Content-type', 'image/jpeg')
elif ext == '.gif':
self.send_header('Content-type', 'image/gif')
elif ext == '.ico':
self.send_header('Content-type', 'image/x-icon')
elif ext == '.png':
self.send_header('Content-type', 'image/png')
else:
self.send_header('Content-type', 'text/plain')
file_handle = None
try:
if html:
content = html
else:
file_handle = open(path, 'rb')
log.debug('Opened %s' % path)
content = file_handle.read()
except IOError:
log.exception('Failed to open %s' % path)
return self.do_not_found()
finally:
if file_handle:
file_handle.close()
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
"""
result = {
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': Settings().value('remotes/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked(),
'version': 2,
'isSecure': Settings().value(self.settings_section + '/authentication enabled'),
'isAuthorised': self.authorised
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def main_poll(self):
"""
Poll OpenLP to determine the current slide count.
"""
result = {
'slide_count': self.live_controller.slide_count
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def main_image(self):
"""
Return the latest display image as a byte stream.
"""
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
}
self.do_json_header()
return json.dumps({'results': result}).encode()
def display(self, action):
"""
Hide or show the display screen.
This is a cross Thread call and UI is updated so Events need to be used.
``action``
This is the action, either ``hide`` or ``show``.
"""
self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action)
self.do_json_header()
return json.dumps({'results': {'success': True}}).encode()
def alert(self):
"""
Send an alert.
"""
plugin = self.plugin_manager.get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self.do_http_error()
text = urllib.parse.unquote(text)
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
success = True
else:
success = False
self.do_json_header()
return json.dumps({'results': {'success': success}}).encode()
def controller_text(self, var):
"""
Perform an action on the slide controller.
"""
current_item = self.live_controller.service_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
json_data['results']['item'] = self.live_controller.service_item.unique_identifier
self.do_json_header()
return json.dumps(json_data).encode()
def controller(self, display_type, action):
"""
Perform an action on the slide controller.
``display_type``
This is the type of slide controller, either ``preview`` or ``live``.
``action``
The action to perform.
"""
event = 'slidecontroller_%s_%s' % (display_type, action)
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
log.info(data)
# This slot expects an int within a list.
self.live_controller.emit(QtCore.SIGNAL(event), [data])
else:
self.live_controller.emit(QtCore.SIGNAL(event))
json_data = {'results': {'success': True}}
self.do_json_header()
return json.dumps(json_data).encode()
def service_list(self):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
self.do_json_header()
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
def service(self, action):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
event = 'servicemanager_%s_item' % action
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError:
return self.do_http_error()
self.service_manager.emit(QtCore.SIGNAL(event), data)
else:
Registry().execute(event)
self.do_json_header()
return json.dumps({'results': {'success': True}}).encode()
def plugin_info(self, action):
"""
Return plugin related information, based on the action.
``action``
The action to perform. If *search* return a list of plugin names
which support search.
"""
if action == 'search':
searches = []
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
self.do_json_header()
return json.dumps({'results': {'items': searches}}).encode()
def search(self, plugin_name):
"""
Return a list of items that match the search text.
``plugin``
The plugin name to search in.
"""
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self.do_http_error()
text = urllib.parse.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
else:
results = []
self.do_json_header()
return json.dumps({'results': {'items': results}}).encode()
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True])
return self.do_http_success()
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self.do_http_error()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
self.do_http_success()
def _get_service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager'):
self._service_manager = Registry().get('service_manager')
return self._service_manager
service_manager = property(_get_service_manager)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller'):
self._live_controller = Registry().get('live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_plugin_manager(self):
"""
Adds the plugin manager to the class dynamically
"""
if not hasattr(self, '_plugin_manager'):
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
plugin_manager = property(_get_plugin_manager)
def _get_alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager'):
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
alerts_manager = property(_get_alerts_manager)

View File

@ -31,661 +31,122 @@
The :mod:`http` module contains the API web server. This is a lightweight web
server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes.
*Routes:*
``/``
Go to the web interface.
``/stage``
Show the stage view.
``/files/{filename}``
Serve a static file.
``/stage/api/poll``
Poll to see if there are any changes. Returns a JSON-encoded dict of
any changes that occurred::
{"results": {"type": "controller"}}
Or, if there were no results, False::
{"results": False}
``/api/display/{hide|show}``
Blank or unblank the screen.
``/api/alert``
Sends an alert message to the alerts plugin. This method expects a
JSON-encoded dict like this::
{"request": {"text": "<your alert text>"}}
``/api/controller/{live|preview}/{action}``
Perform ``{action}`` on the live or preview controller. Valid actions
are:
``next``
Load the next slide.
``previous``
Load the previous slide.
``set``
Set a specific slide. Requires an id return in a JSON-encoded dict like
this::
{"request": {"id": 1}}
``first``
Load the first slide.
``last``
Load the last slide.
``text``
Fetches the text of the current song. The output is a JSON-encoded
dict which looks like this::
{"result": {"slides": ["...", "..."]}}
``/api/service/{action}``
Perform ``{action}`` on the service manager (e.g. go live). Data is
passed as a json-encoded ``data`` parameter. Valid actions are:
``next``
Load the next item in the service.
``previous``
Load the previews item in the service.
``set``
Set a specific item in the service. Requires an id returned in a
JSON-encoded dict like this::
{"request": {"id": 1}}
``list``
Request a list of items in the service. Returns a list of items in the
current service in a JSON-encoded dict like this::
{"results": {"items": [{...}, {...}]}}
"""
import json
import logging
import ssl
import socket
import os
import re
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import cherrypy
import logging
from urllib.parse import urlparse, parse_qs
from mako.template import Template
from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte
from openlp.core.utils import AppLocation, translate
from openlp.core.lib import Settings
from openlp.core.utils import AppLocation
from hashlib import sha1
from openlp.plugins.remotes.lib import HttpRouter
from socketserver import BaseServer, ThreadingMixIn
from http.server import BaseHTTPRequestHandler, HTTPServer
log = logging.getLogger(__name__)
def make_sha_hash(password):
class CustomHandler(BaseHTTPRequestHandler, HttpRouter):
"""
Create an encrypted password for the given password.
Stateless session handler to handle the HTTP request and process it.
This class handles just the overrides to the base methods and the logic to invoke the
methods within the HttpRouter class.
DO not try change the structure as this is as per the documentation.
"""
log.debug("make_sha_hash")
return sha1(password.encode()).hexdigest()
def do_POST(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def do_GET(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def fetch_password(username):
"""
Fetch the password for a provided user.
"""
log.debug("Fetch Password")
if username != Settings().value('remotes/user id'):
return None
return make_sha_hash(Settings().value('remotes/password'))
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
pass
class HttpServer(object):
class HttpThread(QtCore.QThread):
"""
Ability to control OpenLP via a web browser.
This class controls the Cherrypy server and configuration.
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
"""
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
def __init__(self, server):
"""
Constructor for the thread class.
``server``
The http server class.
"""
super(HttpThread, self).__init__(None)
self.http_server = server
def run(self):
"""
Run the thread.
"""
self.http_server.start_server()
class OpenLPServer():
def __init__(self):
"""
Initialise the http server, and start the server.
Initialise the http server, and start the server of the correct type http / https
"""
log.debug('Initialise httpserver')
self.settings_section = 'remotes'
self.router = HttpRouter()
self.http_thread = HttpThread(self)
self.http_thread.start()
def start_server(self):
"""
Start the http server based on configuration.
"""
log.debug('Start CherryPy server')
# Define to security levels and inject the router code
self.root = self.Public()
self.root.files = self.Files()
self.root.stage = self.Stage()
self.root.main = self.Main()
self.root.router = self.router
self.root.files.router = self.router
self.root.stage.router = self.router
self.root.main.router = self.router
cherrypy.tree.mount(self.root, '/', config=self.define_config())
# Turn off the flood of access messages cause by poll
cherrypy.log.access_log.propagate = False
cherrypy.engine.start()
def define_config(self):
"""
Define the configuration of the server.
Start the correct server and save the handler
"""
address = Settings().value(self.settings_section + '/ip address')
if Settings().value(self.settings_section + '/https enabled'):
port = Settings().value(self.settings_section + '/https port')
address = Settings().value(self.settings_section + '/ip address')
local_data = AppLocation.get_directory(AppLocation.DataDir)
cherrypy.config.update({'server.socket_host': str(address),
'server.socket_port': port,
'server.ssl_certificate': os.path.join(local_data, 'remotes', 'openlp.crt'),
'server.ssl_private_key': os.path.join(local_data, 'remotes', 'openlp.key')})
self.httpd = HTTPSServer((address, port), CustomHandler)
log.debug('Started ssl httpd...')
else:
port = Settings().value(self.settings_section + '/port')
address = Settings().value(self.settings_section + '/ip address')
cherrypy.config.update({'server.socket_host': str(address)})
cherrypy.config.update({'server.socket_port': port})
cherrypy.config.update({'environment': 'embedded'})
cherrypy.config.update({'engine.autoreload_on': False})
directory_config = {'/': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': Settings().value('remotes/authentication enabled'),
'tools.basic_auth.realm': 'OpenLP Remote Login',
'tools.basic_auth.users': fetch_password,
'tools.basic_auth.encrypt': make_sha_hash},
'/files': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False},
'/stage': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False},
'/main': {'tools.staticdir.on': True,
'tools.staticdir.dir': self.router.html_dir,
'tools.basic_auth.on': False}}
return directory_config
self.httpd = ThreadingHTTPServer((address, port), CustomHandler)
log.debug('Started non ssl httpd...')
self.httpd.serve_forever()
class Public(object):
def stop_server(self):
"""
Main access class with may have security enabled on it.
Stop the server
"""
@cherrypy.expose
def default(self, *args, **kwargs):
self.router.request_data = None
if isinstance(kwargs, dict):
self.router.request_data = kwargs.get('data', None)
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Files(object):
"""
Provides access to files and has no security available. These are read only accesses
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Stage(object):
"""
Stage view is read only so security is not relevant and would reduce it's usability
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
class Main(object):
"""
Main view is read only so security is not relevant and would reduce it's usability
"""
@cherrypy.expose
def default(self, *args, **kwargs):
url = urllib.parse.urlparse(cherrypy.url())
return self.router.process_http_request(url.path, *args)
def close(self):
"""
Close down the http server.
"""
log.debug('close http server')
cherrypy.engine.exit()
self.http_thread.exit(0)
self.httpd = None
log.debug('Stopped the server.')
class HttpRouter(object):
"""
This code is called by the HttpServer upon a request and it processes it based on the routing table.
"""
def __init__(self):
class HTTPSServer(HTTPServer):
def __init__(self, address, handler):
"""
Initialise the router
Initialise the secure handlers for the SSL server if required.s
"""
self.routes = [
('^/$', self.serve_file),
('^/(stage)$', self.serve_file),
('^/(main)$', self.serve_file),
(r'^/files/(.*)$', self.serve_file),
(r'^/api/poll$', self.poll),
(r'^/stage/poll$', self.poll),
(r'^/main/poll$', self.main_poll),
(r'^/main/image$', self.main_image),
(r'^/api/controller/(live|preview)/(.*)$', self.controller),
(r'^/stage/controller/(live|preview)/(.*)$', self.controller),
(r'^/api/service/(.*)$', self.service),
(r'^/stage/service/(.*)$', self.service),
(r'^/api/display/(hide|show|blank|theme|desktop)$', self.display),
(r'^/api/alert$', self.alert),
(r'^/api/plugin/(search)$', self.plugin_info),
(r'^/api/(.*)/search$', self.search),
(r'^/api/(.*)/live$', self.go_live),
(r'^/api/(.*)/add$', self.add_to_service)
]
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
BaseServer.__init__(self, address, handler)
local_data = AppLocation.get_directory(AppLocation.DataDir)
self.socket = ssl.SSLSocket(
sock=socket.socket(self.address_family, self.socket_type),
ssl_version=ssl.PROTOCOL_TLSv1,
certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
server_side=True)
self.server_bind()
self.server_activate()
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests
``url_path``
The requested URL.
``*args``
Any passed data.
"""
response = None
for route, func in self.routes:
match = re.match(route, url_path)
if match:
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
response = func(*args)
break
if response:
return response
else:
log.debug('Path not found %s', url_path)
return self._http_not_found()
def _get_service_items(self):
"""
Read the service item in use and return the data as a json object
"""
service_items = []
if self.live_controller.service_item:
current_unique_identifier = self.live_controller.service_item.unique_identifier
else:
current_unique_identifier = None
for item in self.service_manager.service_items:
service_item = item['service_item']
service_items.append({
'id': str(service_item.unique_identifier),
'title': str(service_item.get_display_title()),
'plugin': str(service_item.name),
'notes': str(service_item.notes),
'selected': (service_item.unique_identifier == current_unique_identifier)
})
return service_items
def translate(self):
"""
Translate various strings in the mobile app.
"""
self.template_vars = {
'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'),
'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'),
'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
'search': translate('RemotePlugin.Mobile', 'Search'),
'home': translate('RemotePlugin.Mobile', 'Home'),
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
'blank': translate('RemotePlugin.Mobile', 'Blank'),
'theme': translate('RemotePlugin.Mobile', 'Theme'),
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
'show': translate('RemotePlugin.Mobile', 'Show'),
'prev': translate('RemotePlugin.Mobile', 'Prev'),
'next': translate('RemotePlugin.Mobile', 'Next'),
'text': translate('RemotePlugin.Mobile', 'Text'),
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add &amp; Go to Service'),
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
}
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self._http_not_found()
ext = os.path.splitext(file_name)[1]
html = None
if ext == '.html':
mimetype = 'text/html'
variables = self.template_vars
html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
elif ext == '.css':
mimetype = 'text/css'
elif ext == '.js':
mimetype = 'application/x-javascript'
elif ext == '.jpg':
mimetype = 'image/jpeg'
elif ext == '.gif':
mimetype = 'image/gif'
elif ext == '.png':
mimetype = 'image/png'
else:
mimetype = 'text/plain'
file_handle = None
try:
if html:
content = html
else:
file_handle = open(path, 'rb')
log.debug('Opened %s' % path)
content = file_handle.read()
except IOError:
log.exception('Failed to open %s' % path)
return self._http_not_found()
finally:
if file_handle:
file_handle.close()
cherrypy.response.headers['Content-Type'] = mimetype
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
"""
result = {
'service': self.service_manager.service_id,
'slide': self.live_controller.selected_row or 0,
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
'twelve': Settings().value('remotes/twelve hour'),
'blank': self.live_controller.blank_screen.isChecked(),
'theme': self.live_controller.theme_screen.isChecked(),
'display': self.live_controller.desktop_screen.isChecked()
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def main_poll(self):
"""
Poll OpenLP to determine the current slide count.
"""
result = {
'slide_count': self.live_controller.slide_count
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def main_image(self):
"""
Return the latest display image as a byte stream.
"""
result = {
'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': result}).encode()
def display(self, action):
"""
Hide or show the display screen.
This is a cross Thread call and UI is updated so Events need to be used.
``action``
This is the action, either ``hide`` or ``show``.
"""
self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action)
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': True}}).encode()
def alert(self):
"""
Send an alert.
"""
plugin = self.plugin_manager.get_plugin_by_name("alerts")
if plugin.status == PluginStatus.Active:
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self._http_bad_request()
text = urllib.parse.unquote(text)
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
success = True
else:
success = False
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': success}}).encode()
def controller(self, display_type, action):
"""
Perform an action on the slide controller.
``display_type``
This is the type of slide controller, either ``preview`` or ``live``.
``action``
The action to perform.
"""
event = 'slidecontroller_%s_%s' % (display_type, action)
if action == 'text':
current_item = self.live_controller.service_item
data = []
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
json_data['results']['item'] = self.live_controller.service_item.unique_identifier
else:
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
log.info(data)
# This slot expects an int within a list.
self.live_controller.emit(QtCore.SIGNAL(event), [data])
else:
self.live_controller.emit(QtCore.SIGNAL(event))
json_data = {'results': {'success': True}}
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps(json_data).encode()
def service(self, action):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
event = 'servicemanager_%s' % action
if action == 'list':
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
event += '_item'
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError:
return self._http_bad_request()
self.service_manager.emit(QtCore.SIGNAL(event), data)
else:
Registry().execute(event)
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'success': True}}).encode()
def plugin_info(self, action):
"""
Return plugin related information, based on the action.
``action``
The action to perform. If *search* return a list of plugin names
which support search.
"""
if action == 'search':
searches = []
for plugin in self.plugin_manager.plugins:
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': searches}}).encode()
def search(self, plugin_name):
"""
Return a list of items that match the search text.
``plugin``
The plugin name to search in.
"""
try:
text = json.loads(self.request_data)['request']['text']
except KeyError as ValueError:
return self._http_bad_request()
text = urllib.parse.unquote(text)
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
else:
results = []
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps({'results': {'items': results}}).encode()
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True])
return self._http_success()
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
"""
try:
id = json.loads(self.request_data)['request']['id']
except KeyError as ValueError:
return self._http_bad_request()
plugin = self.plugin_manager.get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item:
item_id = plugin.media_item.create_item_from_id(id)
plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True])
self._http_success()
def _http_success(self):
"""
Set the HTTP success return code.
"""
cherrypy.response.status = 200
def _http_bad_request(self):
"""
Set the HTTP bad response return code.
"""
cherrypy.response.status = 400
def _http_not_found(self):
"""
Set the HTTP not found return code.
"""
cherrypy.response.status = 404
cherrypy.response.body = [b'<html><body>Sorry, an error occurred </body></html>']
def _get_service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager'):
self._service_manager = Registry().get('service_manager')
return self._service_manager
service_manager = property(_get_service_manager)
def _get_live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller'):
self._live_controller = Registry().get('live_controller')
return self._live_controller
live_controller = property(_get_live_controller)
def _get_plugin_manager(self):
"""
Adds the plugin manager to the class dynamically
"""
if not hasattr(self, '_plugin_manager'):
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
plugin_manager = property(_get_plugin_manager)
def _get_alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager'):
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
alerts_manager = property(_get_alerts_manager)

View File

@ -207,8 +207,8 @@ class RemoteTab(SettingsTab):
https_url_temp = https_url + 'stage'
self.stage_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.stage_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))
http_url_temp = http_url + 'live'
https_url_temp = https_url + 'live'
http_url_temp = http_url + 'main'
https_url_temp = https_url + 'main'
self.live_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.live_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))

View File

@ -28,11 +28,10 @@
###############################################################################
import logging
from PyQt4 import QtGui
import time
from openlp.core.lib import Plugin, StringContent, translate, build_icon
from openlp.plugins.remotes.lib import RemoteTab, HttpServer
from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer
log = logging.getLogger(__name__)
@ -67,8 +66,7 @@ class RemotesPlugin(Plugin):
"""
log.debug('initialise')
super(RemotesPlugin, self).initialise()
self.server = HttpServer()
self.server.start_server()
self.server = OpenLPServer()
def finalise(self):
"""
@ -77,7 +75,7 @@ class RemotesPlugin(Plugin):
log.debug('finalise')
super(RemotesPlugin, self).finalise()
if self.server:
self.server.close()
self.server.stop_server()
self.server = None
def about(self):
@ -109,5 +107,6 @@ class RemotesPlugin(Plugin):
Called when Config is changed to restart the server on new address or port
"""
log.debug('remote config changed')
self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'),
translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.'))
self.finalise()
time.sleep(0.5)
self.initialise()

View File

@ -692,7 +692,6 @@ 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):
"""
Checks if the verse order is complete or missing. Shows a error message according to the state of the verse

View File

@ -72,6 +72,11 @@ class SongMediaItem(MediaManagerItem):
def __init__(self, parent, plugin):
self.icon_path = 'songs/song'
super(SongMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False
# Holds information about whether the edit is remotely triggered and which Song is required.
self.remote_song = -1

View File

@ -30,13 +30,14 @@
The :mod:`songshowplusimport` module provides the functionality for importing
SongShow Plus songs into the OpenLP database.
"""
import chardet
import os
import logging
import re
import struct
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib.songimport import SongImport
TITLE = 1
@ -132,41 +133,43 @@ class SongShowPlusImport(SongImport):
else:
length_descriptor, = struct.unpack("B", song_data.read(1))
log.debug(length_descriptor_size)
data = song_data.read(length_descriptor).decode()
data = song_data.read(length_descriptor)
if block_key == TITLE:
self.title = data
self.title = self.decode(data)
elif block_key == AUTHOR:
authors = data.split(" / ")
authors = self.decode(data).split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
self.parse_author(author)
elif block_key == COPYRIGHT:
self.addCopyright(data)
self.addCopyright(self.decode(data))
elif block_key == CCLI_NO:
self.ccliNumber = int(data)
elif block_key == VERSE:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Verse], verse_no))
elif block_key == CHORUS:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Chorus], verse_no))
elif block_key == BRIDGE:
self.addVerse(data, "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
self.addVerse(self.decode(data), "%s%s" % (VerseType.tags[VerseType.Bridge], verse_no))
elif block_key == TOPIC:
self.topics.append(data)
self.topics.append(self.decode(data))
elif block_key == COMMENTS:
self.comments = data
self.comments = self.decode(data)
elif block_key == VERSE_ORDER:
verse_tag = self.to_openlp_verse_tag(data, True)
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
if verse_tag:
if not isinstance(verse_tag, str):
verse_tag = self.decode(verse_tag)
self.ssp_verse_order_list.append(verse_tag)
elif block_key == SONG_BOOK:
self.songBookName = data
self.songBookName = self.decode(data)
elif block_key == SONG_NUMBER:
self.songNumber = ord(data)
elif block_key == CUSTOM_VERSE:
verse_tag = self.to_openlp_verse_tag(verse_name)
self.addVerse(data, verse_tag)
self.addVerse(self.decode(data), verse_tag)
else:
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data))
song_data.seek(next_block_starts)
@ -204,3 +207,9 @@ class SongShowPlusImport(SongImport):
verse_tag = VerseType.tags[VerseType.Other]
verse_number = self.other_list[verse_name]
return verse_tag + verse_number
def decode(self, data):
try:
return str(data, chardet.detect(data)['encoding'])
except:
return str(data, retrieve_windows_encoding())

View File

@ -44,10 +44,11 @@ from distutils.version import LooseVersion
try:
import nose
except ImportError:
pass
nose = None
IS_WIN = sys.platform.startswith('win')
VERS = {
'Python': '3.0',
'PyQt4': '4.6',
@ -84,26 +85,39 @@ MODULES = [
'enchant',
'bs4',
'mako',
'cherrypy',
'uno',
]
OPTIONAL_MODULES = [
('MySQLdb', ' (MySQL support)'),
('psycopg2', ' (PostgreSQL support)'),
('nose', ' (testing framework)'),
('mock', ' (testing module)'),
('MySQLdb', '(MySQL support)', True),
('psycopg2', '(PostgreSQL support)', True),
('nose', '(testing framework)', True),
('mock', '(testing module)', sys.version_info[1] < 3),
]
w = sys.stdout.write
def check_vers(version, required, text):
"""
Check the version of a dependency. Returns ``True`` if the version is greater than or equal, or False if less than.
``version``
The actual version of the dependency
``required``
The required version of the dependency
``text``
The dependency's name
"""
space = (27 - len(required) - len(text)) * ' '
if not isinstance(version, str):
version = '.'.join(map(str, version))
if not isinstance(required, str):
required = '.'.join(map(str, required))
w(' %s >= %s ... ' % (text, required))
w(' %s >= %s ... ' % (text, required) + space)
if LooseVersion(version) >= LooseVersion(required):
w(version + os.linesep)
return True
@ -111,13 +125,39 @@ def check_vers(version, required, text):
w('FAIL' + os.linesep)
return False
def check_module(mod, text='', indent=' '):
"""
Check that a module is installed.
``mod``
The module to check for.
``text``
The text to display.
``indent``
How much to indent the text by.
"""
space = (31 - len(mod) - len(text)) * ' '
w(indent + '%s %s... ' % (mod, text) + space)
try:
__import__(mod)
w('OK')
except ImportError:
w('FAIL')
w(os.linesep)
def print_vers_fail(required, text):
print(' %s >= %s ... FAIL' % (text, required))
def verify_python():
if not check_vers(list(sys.version_info), VERS['Python'], text='Python'):
exit(1)
def verify_versions():
print('Verifying version of modules...')
try:
@ -138,17 +178,11 @@ def verify_versions():
except ImportError:
print_vers_fail(VERS['enchant'], 'enchant')
def check_module(mod, text='', indent=' '):
space = (30 - len(mod) - len(text)) * ' '
w(indent + '%s%s... ' % (mod, text) + space)
try:
__import__(mod)
w('OK')
except ImportError:
w('FAIL')
w(os.linesep)
def verify_pyenchant():
def print_enchant_backends_and_languages():
"""
Check if PyEnchant is installed.
"""
w('Enchant (spell checker)... ')
try:
import enchant
@ -160,39 +194,43 @@ def verify_pyenchant():
except ImportError:
w('FAIL' + os.linesep)
def verify_pyqt():
def print_qt_image_formats():
"""
Print out the image formats that Qt4 supports.
"""
w('Qt4 image formats... ')
try:
from PyQt4 import QtGui
read_f = ', '.join([str(format).lower()
for format in QtGui.QImageReader.supportedImageFormats()])
write_f = ', '.join([str(format).lower()
for format in QtGui.QImageWriter.supportedImageFormats()])
read_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()])
write_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageWriter.supportedImageFormats()])
w(os.linesep)
print(' read: %s' % read_f)
print(' write: %s' % write_f)
except ImportError:
w('FAIL' + os.linesep)
def main():
verify_python()
def main():
"""
Run the dependency checker.
"""
print('Checking Python version...')
verify_python()
print('Checking for modules...')
for m in MODULES:
check_module(m)
print('Checking for optional modules...')
for m in OPTIONAL_MODULES:
check_module(m[0], text=m[1])
if m[2]:
check_module(m[0], text=m[1])
if IS_WIN:
print('Checking for Windows specific modules...')
for m in WIN32_MODULES:
check_module(m)
verify_versions()
verify_pyqt()
verify_pyenchant()
print_qt_image_formats()
print_enchant_backends_and_languages()
if __name__ == '__main__':
main()

View File

@ -7,7 +7,13 @@ sip.setapi('QTime', 2)
sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2)
import sys
from PyQt4 import QtGui
if sys.version_info[1] >= 3:
from unittest.mock import patch, MagicMock
else:
from mock import patch, MagicMock
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([])

View File

@ -1,14 +1,42 @@
# -*- 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 #
###############################################################################
"""
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
from tests.functional import patch, MagicMock
class TestDB(TestCase):

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.formattingtags package.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.lib import FormattingTags
from tests.functional import patch
TAG = {

View File

@ -1,15 +1,41 @@
"""
Package to test the openlp.core.ui package.
"""
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.ui package.
"""
import os
from unittest import TestCase
from PyQt4 import QtCore, QtGui
from PyQt4 import QtGui
from openlp.core.lib import Registry, ImageManager, ScreenList
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
"""
@ -6,22 +34,20 @@ import os
from unittest import TestCase
from datetime import datetime, timedelta
from mock import MagicMock, patch
from PyQt4 import QtCore, QtGui
from openlp.core.lib import str_to_bool, create_thumb, translate, check_directory_exists, get_text_file_string, \
build_icon, image_to_byte, check_item_selected, validate_thumb, create_separated_list, clean_tags, expand_tags
from tests.functional import MagicMock, patch
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
class TestLib(TestCase):
def str_to_bool_with_bool_test(self):
def str_to_bool_with_bool_true_test(self):
"""
Test the str_to_bool function with boolean input
Test the str_to_bool function with boolean input of True
"""
# GIVEN: A boolean value set to true
true_boolean = True
@ -30,9 +56,13 @@ class TestLib(TestCase):
true_result = str_to_bool(true_boolean)
# THEN: We should get back a True bool
assert isinstance(true_result, bool), 'The result should be a boolean'
assert true_result is True, 'The result should be True'
self.assertIsInstance(true_result, bool, 'The result should be a boolean')
self.assertTrue(true_result, 'The result should be True')
def str_to_bool_with_bool_false_test(self):
"""
Test the str_to_bool function with boolean input of False
"""
# GIVEN: A boolean value set to false
false_boolean = False
@ -40,12 +70,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_boolean)
# THEN: We should get back a True bool
assert isinstance(false_result, bool), 'The result should be a boolean'
assert false_result is False, 'The result should be True'
self.assertIsInstance(false_result, bool, 'The result should be a boolean')
self.assertFalse(false_result, 'The result should be True')
def str_to_bool_with_invalid_test(self):
def str_to_bool_with_integer_test(self):
"""
Test the str_to_bool function with a set of invalid inputs
Test the str_to_bool function with an integer input
"""
# GIVEN: An integer value
int_string = 1
@ -54,8 +84,12 @@ class TestLib(TestCase):
int_result = str_to_bool(int_string)
# THEN: we should get back a false
assert int_result is False, 'The result should be False'
self.assertFalse(int_result, 'The result should be False')
def str_to_bool_with_invalid_string_test(self):
"""
Test the str_to_bool function with an invalid string
"""
# GIVEN: An string value with completely invalid input
invalid_string = 'my feet are wet'
@ -63,11 +97,11 @@ class TestLib(TestCase):
str_result = str_to_bool(invalid_string)
# THEN: we should get back a false
assert str_result is False, 'The result should be False'
self.assertFalse(str_result, 'The result should be False')
def str_to_bool_with_false_values_test(self):
def str_to_bool_with_string_false_test(self):
"""
Test the str_to_bool function with a set of false inputs
Test the str_to_bool function with a string saying "false"
"""
# GIVEN: A string set to "false"
false_string = 'false'
@ -76,8 +110,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_string)
# THEN: we should get back a false
assert false_result is False, 'The result should be False'
self.assertFalse(false_result, 'The result should be False')
def str_to_bool_with_string_no_test(self):
"""
Test the str_to_bool function with a string saying "NO"
"""
# GIVEN: An string set to "NO"
no_string = 'NO'
@ -85,11 +123,11 @@ class TestLib(TestCase):
str_result = str_to_bool(no_string)
# THEN: we should get back a false
assert str_result is False, 'The result should be False'
self.assertFalse(str_result, 'The result should be False')
def str_to_bool_with_true_values_test(self):
def str_to_bool_with_true_string_value_test(self):
"""
Test the str_to_bool function with a set of true inputs
Test the str_to_bool function with a string set to "True"
"""
# GIVEN: A string set to "True"
true_string = 'True'
@ -98,8 +136,12 @@ class TestLib(TestCase):
true_result = str_to_bool(true_string)
# THEN: we should get back a true
assert true_result is True, 'The result should be True'
self.assertTrue(true_result, 'The result should be True')
def str_to_bool_with_yes_string_value_test(self):
"""
Test the str_to_bool function with a string set to "yes"
"""
# GIVEN: An string set to "yes"
yes_string = 'yes'
@ -107,7 +149,7 @@ class TestLib(TestCase):
str_result = str_to_bool(yes_string)
# THEN: we should get back a true
assert str_result is True, 'The result should be True'
self.assertTrue(str_result, 'The result should be True')
def translate_test(self):
"""
@ -126,7 +168,7 @@ class TestLib(TestCase):
# THEN: the translated string should be returned, and the mocked function should have been called
mocked_translate.assert_called_with(context, text, comment, encoding, n)
assert result == 'Translated string', 'The translated string should have been returned'
self.assertEqual('Translated string', result, 'The translated string should have been returned')
def check_directory_exists_test(self):
"""
@ -143,7 +185,7 @@ class TestLib(TestCase):
# THEN: Only os.path.exists should have been called
mocked_exists.assert_called_with(directory_to_check)
assert not mocked_makedirs.called, 'os.makedirs should not have been called'
self.assertIsNot(mocked_makedirs.called, 'os.makedirs should not have been called')
# WHEN: os.path.exists returns False and we check the directory exists
mocked_exists.return_value = False
@ -181,13 +223,14 @@ class TestLib(TestCase):
# THEN: The result should be False
mocked_isfile.assert_called_with(filename)
assert result is False, 'False should be returned if no file exists'
self.assertFalse(result, 'False should be returned if no file exists')
def get_text_file_string_read_error_test(self):
"""
Test the get_text_file_string() method when a read error happens
"""
with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, patch('openlp.core.lib.open', create=True) as mocked_open:
with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, \
patch('openlp.core.lib.open', create=True) as mocked_open:
# GIVEN: A mocked-out open() which raises an exception and isfile returns True
filename = 'testfile.txt'
mocked_isfile.return_value = True
@ -199,13 +242,13 @@ class TestLib(TestCase):
# THEN: None should be returned
mocked_isfile.assert_called_with(filename)
mocked_open.assert_called_with(filename, 'r')
assert result is None, 'None should be returned if the file cannot be opened'
self.assertIsNone(result, 'None should be returned if the file cannot be opened')
def get_text_file_string_decode_error_test(self):
"""
Test the get_text_file_string() method when the contents cannot be decoded
"""
assert True, 'Impossible to test due to conflicts when mocking out the "open" function'
self.skipTest('Impossible to test due to conflicts when mocking out the "open" function')
def build_icon_with_qicon_test(self):
"""
@ -220,7 +263,7 @@ class TestLib(TestCase):
result = build_icon(mocked_icon)
# THEN: The result should be our mocked QIcon
assert result is mocked_icon, 'The result should be the mocked QIcon'
self.assertIs(mocked_icon, result, 'The result should be the mocked QIcon')
def build_icon_with_resource_test(self):
"""
@ -242,7 +285,7 @@ class TestLib(TestCase):
MockedQPixmap.assert_called_with(resource_uri)
# There really should be more assert statements here but due to type checking and things they all break. The
# best we can do is to assert that we get back a MagicMock object.
assert isinstance(result, MagicMock), 'The result should be a MagicMock, because we mocked it out'
self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out')
def image_to_byte_test(self):
"""
@ -267,7 +310,8 @@ class TestLib(TestCase):
mocked_buffer.open.assert_called_with('writeonly')
mocked_image.save.assert_called_with(mocked_buffer, "PNG")
mocked_byte_array.toBase64.assert_called_with()
assert result == 'base64mock', 'The result should be the return value of the mocked out base64 method'
self.assertEqual('base64mock', result,
'The result should be the return value of the mocked out base64 method')
def create_thumb_with_size_test(self):
"""
@ -286,16 +330,16 @@ class TestLib(TestCase):
pass
# Only continue when the thumb does not exist.
assert not os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.'
self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created.
assert os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.'
assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon.'
assert not icon.isNull(), 'The icon should not be null.'
assert QtGui.QImageReader(thumb_path).size() == thumb_size, 'The thumb should have the given size.'
self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
@ -318,7 +362,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
assert result, 'The result should be True'
self.assertTrue(result, 'The result should be True')
def check_item_selected_false_test(self):
"""
@ -326,7 +370,7 @@ class TestLib(TestCase):
"""
# GIVEN: A mocked out QtGui module and a list widget with selected indexes
with patch('openlp.core.lib.QtGui') as MockedQtGui, \
patch('openlp.core.lib.translate') as mocked_translate:
patch('openlp.core.lib.translate') as mocked_translate:
mocked_translate.return_value = 'mocked translate'
mocked_list_widget = MagicMock()
mocked_list_widget.selectedIndexes.return_value = False
@ -339,7 +383,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with()
MockedQtGui.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message')
assert not result, 'The result should be False'
self.assertFalse(result, 'The result should be False')
def clean_tags_test(self):
"""
@ -361,7 +405,7 @@ class TestLib(TestCase):
result_string = clean_tags(string_to_pass)
# THEN: The strings should be identical.
assert result_string == wanted_string, 'The strings should be identical.'
self.assertEqual(wanted_string, result_string, 'The strings should be identical')
def expand_tags_test(self):
"""
@ -400,7 +444,7 @@ class TestLib(TestCase):
result_string = expand_tags(string_to_pass)
# THEN: The strings should be identical.
assert result_string == wanted_string, 'The strings should be identical.'
self.assertEqual(wanted_string, result_string, 'The strings should be identical.')
def validate_thumb_file_does_not_exist_test(self):
"""

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.pluginmanager package.
"""
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Settings, Registry, PluginStatus
from tests.functional import MagicMock
class TestPluginManager(TestCase):
@ -42,8 +69,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_media_manager()
# THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count,
'The create_media_manager_item() method should not have been called.')
def hook_media_manager_with_active_plugin_test(self):
"""
@ -75,8 +102,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The hook_settings_tabs() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count,
'The create_media_manager_item() method should not have been called.')
def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self):
"""
@ -95,8 +122,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same
assert mocked_plugin.create_settings_tab.call_count == 0, \
'The create_media_manager_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.create_settings_tab.call_count,
'The create_media_manager_item() method should not have been called.')
self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
@ -117,10 +144,10 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs()
# THEN: The create_media_manager_item() method should have been called with the mocked settings form
assert mocked_plugin.create_settings_tab.call_count == 1, \
'The create_media_manager_item() method should have been called once.'
self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
self.assertEqual(1, mocked_plugin.create_settings_tab.call_count,
'The create_media_manager_item() method should have been called once.')
self.assertEqual(plugin_manager.plugins, mocked_settings_form.plugin_manager.plugins,
'The plugins on the settings form should be the same as the plugins in the plugin manager')
def hook_settings_tabs_with_active_plugin_and_no_form_test(self):
"""
@ -152,8 +179,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_import_menu()
# THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.add_import_menu_item.call_count == 0, \
'The add_import_menu_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_import_menu_item.call_count,
'The add_import_menu_item() method should not have been called.')
def hook_import_menu_with_active_plugin_test(self):
"""
@ -185,8 +212,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_export_menu()
# THEN: The add_export_menu_Item() method should not have been called
assert mocked_plugin.add_export_menu_Item.call_count == 0, \
'The add_export_menu_Item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_export_menu_Item.call_count,
'The add_export_menu_Item() method should not have been called.')
def hook_export_menu_with_active_plugin_test(self):
"""
@ -219,8 +246,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_upgrade_plugin_settings(settings)
# THEN: The upgrade_settings() method should not have been called
assert mocked_plugin.upgrade_settings.call_count == 0, \
'The upgrade_settings() method should not have been called.'
self.assertEqual(0, mocked_plugin.upgrade_settings.call_count,
'The upgrade_settings() method should not have been called.')
def hook_upgrade_plugin_settings_with_active_plugin_test(self):
"""
@ -253,8 +280,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_tools_menu()
# THEN: The add_tools_menu_item() method should have been called
assert mocked_plugin.add_tools_menu_item.call_count == 0, \
'The add_tools_menu_item() method should not have been called.'
self.assertEqual(0, mocked_plugin.add_tools_menu_item.call_count,
'The add_tools_menu_item() method should not have been called.')
def hook_tools_menu_with_active_plugin_test(self):
"""
@ -288,7 +315,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.initialise.call_count == 0, 'The initialise() method should not have been called.'
self.assertEqual(0, mocked_plugin.initialise.call_count, 'The initialise() method should not have been called.')
def initialise_plugins_with_active_plugin_test(self):
"""
@ -324,7 +351,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.finalise.call_count == 0, 'The finalise() method should not have been called.'
self.assertEqual(0, mocked_plugin.finalise.call_count, 'The finalise() method should not have been called.')
def finalise_plugins_with_active_plugin_test(self):
"""
@ -392,8 +419,8 @@ class TestPluginManager(TestCase):
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.new_service_created.call_count == 0,\
'The new_service_created() method should not have been called.'
self.assertEqual(0, mocked_plugin.new_service_created.call_count,
'The new_service_created() method should not have been called.')
def new_service_created_with_active_plugin_test(self):
"""

View File

@ -1,12 +1,39 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
"""
import os
from unittest import TestCase
from mock import MagicMock
from openlp.core.lib import Registry
from tests.functional import MagicMock
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -1,14 +1,40 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.screenlist package.
"""
from unittest import TestCase
from mock import MagicMock
from PyQt4 import QtGui, QtCore
from openlp.core.lib import Registry, ScreenList
from tests.functional import MagicMock
SCREEN = {
'primary': False,
@ -55,5 +81,6 @@ class TestScreenList(TestCase):
# THEN: The screen should have been added and the screens should be identical
new_screen_count = len(self.screens.screen_list)
assert old_screen_count + 1 == new_screen_count, 'The new_screens list should be bigger'
assert SCREEN == self.screens.screen_list.pop(), 'The 2nd screen should be identical to the first screen'
self.assertEqual(old_screen_count + 1, new_screen_count, 'The new_screens list should be bigger')
self.assertEqual(SCREEN, self.screens.screen_list.pop(),
'The 2nd screen should be identical to the first screen')

View File

@ -1,16 +1,40 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib package.
Package to test the openlp.core.lib package.
"""
import os
import json
import tempfile
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
from lxml import objectify, etree
from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@ -20,7 +44,6 @@ VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@ -36,7 +59,7 @@ class TestServiceItem(TestCase):
Registry().register('renderer', mocked_renderer)
Registry().register('image_manager', MagicMock())
def serviceitem_basic_test(self):
def service_item_basic_test(self):
"""
Test the Service Item - basic test
"""
@ -46,10 +69,10 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid'
assert service_item.missing_frames() is True, 'There should not be any frames in the service item'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertTrue(service_item.missing_frames(), 'There should not be any frames in the service item')
def serviceitem_load_custom_from_service_test(self):
def service_item_load_custom_from_service_test(self):
"""
Test the Service Item - adding a custom slide from a saved service
"""
@ -57,24 +80,29 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service
line = self.convert_file_service_item('serviceitem_custom_1.osj')
# WHEN: We add a custom from a saved service
line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj')
service_item.set_from_service(line)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid'
assert len(service_item._display_frames) == 0, 'The service item should have no display frames'
assert len(service_item.capabilities) == 5, 'There should be 5 default custom item capabilities'
service_item.render(True)
assert service_item.get_display_title() == 'Test Custom', 'The title should be "Test Custom"'
assert service_item.get_frames()[0]['text'] == VERSE[:-1], \
'The returned text matches the input, except the last line feed'
assert service_item.get_rendered_frame(1) == VERSE.split('\n', 1)[0], 'The first line has been returned'
assert service_item.get_frame_title(0) == 'Slide 1', '"Slide 1" has been returned as the title'
assert service_item.get_frame_title(1) == 'Slide 2', '"Slide 2" has been returned as the title'
assert service_item.get_frame_title(2) == '', 'Blank has been returned as the title of slide 3'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
assert_length(0, service_item._display_frames, 'The service item should have no display frames')
assert_length(5, service_item.capabilities, 'There should be 5 default custom item capabilities')
def serviceitem_load_image_from_service_test(self):
# WHEN: We render the frames of the service item
service_item.render(True)
# THEN: The frames should also be valid
self.assertEqual('Test Custom', service_item.get_display_title(), 'The title should be "Test Custom"')
self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'],
'The returned text matches the input, except the last line feed')
self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1),
'The first line has been returned')
self.assertEqual('Slide 1', service_item.get_frame_title(0), '"Slide 1" has been returned as the title')
self.assertEqual('Slide 2', service_item.get_frame_title(1), '"Slide 2" has been returned as the title')
self.assertEqual('', service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3')
def service_item_load_image_from_service_test(self):
"""
Test the Service Item - adding an image from a saved service
"""
@ -87,29 +115,34 @@ class TestServiceItem(TestCase):
service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item('serviceitem_image_1.osj')
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid'
assert service_item.get_rendered_frame(0) == test_file, 'The first frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array, 'The return should match frame array1'
assert service_item.get_frame_path(0) == test_file, 'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name, 'The frame title should match the image name'
assert service_item.get_display_title() == image_name, 'The display title should match the first image name'
assert service_item.is_image() is True, 'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
'This service item should be able to be run in a can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
'This service item should be able to have new items added to it'
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertEqual(test_file, service_item.get_rendered_frame(0),
'The first frame should match the path to the image')
self.assertEqual(frame_array, service_item.get_frames()[0],
'The return should match frame array1')
self.assertEqual(test_file, service_item.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(image_name, service_item.get_frame_title(0),
'The frame title should match the image name')
self.assertEqual(image_name, service_item.get_display_title(),
'The display title should match the first image name')
self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
'This service item should be able to be Maintained')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview),
'This service item should be able to be be Previewed')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop),
'This service item should be able to be run in a can be made to Loop')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')
def serviceitem_load_image_from_local_service_test(self):
def service_item_load_image_from_local_service_test(self):
"""
Test the Service Item - adding an image from a saved local service
"""
@ -128,50 +161,42 @@ class TestServiceItem(TestCase):
service_item2.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item('serviceitem_image_2.osj')
line2 = self.convert_file_service_item('serviceitem_image_2.osj', 1)
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item2.set_from_service(line2)
service_item.set_from_service(line)
# THEN: We should get back a valid service item
# This test is copied from service_item.py, but is changed since to conform to
# new layout of service item. The layout use in serviceitem_image_2.osd is actually invalid now.
assert service_item.is_valid is True, 'The first service item should be valid'
assert service_item2.is_valid is True, 'The second service item should be valid'
assert service_item.get_rendered_frame(0) == test_file1, 'The first frame should match the path to the image'
assert service_item2.get_rendered_frame(0) == test_file2, 'The Second frame should match the path to the image'
assert service_item.get_frames()[0] == frame_array1, 'The return should match the frame array1'
assert service_item2.get_frames()[0] == frame_array2, 'The return should match the frame array2'
assert service_item.get_frame_path(0) == test_file1, 'The frame path should match the full path to the image'
assert service_item2.get_frame_path(0) == test_file2, 'The frame path should match the full path to the image'
assert service_item.get_frame_title(0) == image_name1, 'The 1st frame title should match the image name'
assert service_item2.get_frame_title(0) == image_name2, 'The 2nd frame title should match the image name'
assert service_item.title.lower() == service_item.name, \
'The plugin name should match the display title, as there are > 1 Images'
assert service_item.is_image() is True, 'This service item should be of an "image" type'
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \
'This service item should be able to be Maintained'
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \
'This service item should be able to be be Previewed'
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \
'This service item should be able to be run in a can be made to Loop'
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \
'This service item should be able to have new items added to it'
def convert_file_service_item(self, name, row=0):
service_file = os.path.join(TEST_PATH, name)
try:
open_file = open(service_file, 'r')
items = json.load(open_file)
first_line = items[row]
except IOError:
first_line = ''
finally:
open_file.close()
return first_line
self.assertTrue(service_item.is_valid, 'The first service item should be valid')
self.assertTrue(service_item2.is_valid, 'The second service item should be valid')
self.assertEqual(test_file1, service_item.get_rendered_frame(0),
'The first frame should match the path to the image')
self.assertEqual(test_file2, service_item2.get_rendered_frame(0),
'The Second frame should match the path to the image')
self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1')
self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2')
self.assertEqual(test_file1, service_item.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(test_file2, service_item2.get_frame_path(0),
'The frame path should match the full path to the image')
self.assertEqual(image_name1, service_item.get_frame_title(0),
'The 1st frame title should match the image name')
self.assertEqual(image_name2, service_item2.get_frame_title(0),
'The 2nd frame title should match the image name')
self.assertEqual(service_item.name, service_item.title.lower(),
'The plugin name should match the display title, as there are > 1 Images')
self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
'This service item should be able to be Maintained')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview),
'This service item should be able to be be Previewed')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop),
'This service item should be able to be run in a can be made to Loop')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')

View File

@ -1,14 +1,42 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.settings package.
Package to test the openlp.core.lib.settings package.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from openlp.core.lib import Settings
from PyQt4 import QtGui
from openlp.core.lib import Settings
class TestSettings(TestCase):
"""
@ -40,13 +68,13 @@ class TestSettings(TestCase):
default_value = Settings().value('core/has run wizard')
# THEN the default value is returned
assert default_value is False, 'The default value should be False'
self.assertFalse(default_value, 'The default value should be False')
# WHEN a new value is saved into config
Settings().setValue('core/has run wizard', True)
# THEN the new value is returned when re-read
assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')
def settings_override_test(self):
"""
@ -62,13 +90,13 @@ class TestSettings(TestCase):
extend = Settings().value('test/extend')
# THEN the default value is returned
assert extend == 'very wide', 'The default value of "very wide" should be returned'
self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')
# WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read
assert Settings().value('test/extend') == 'very short', 'The saved value should be returned'
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def settings_override_with_group_test(self):
"""
@ -86,10 +114,10 @@ class TestSettings(TestCase):
extend = settings.value('extend')
# THEN the default value is returned
assert extend == 'very wide', 'The default value defined should be returned'
self.assertEqual('very wide', extend, 'The default value defined should be returned')
# WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read
assert Settings().value('test/extend') == 'very short', 'The saved value should be returned'
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')

View File

@ -1,7 +1,34 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.lib.uistrings package.
"""
from unittest import TestCase
from openlp.core.lib import UiStrings
@ -18,6 +45,6 @@ class TestUiStrings(TestCase):
second_instance = UiStrings()
# THEN: Check if the instances are the same.
assert first_instance is second_instance, "They should be the same instance!"
self.assertIs(first_instance, second_instance, 'Two UiStrings objects should be the same instance')

View File

@ -0,0 +1,116 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.ui.formattingtagscontroller package.
"""
from unittest import TestCase
from openlp.core.ui import FormattingTagController
class TestFormattingTagController(TestCase):
def setUp(self):
self.services = FormattingTagController()
def test_strip(self):
"""
Test that the _strip strips the correct chars
"""
# GIVEN: An instance of the Formatting Tag Form and a string containing a tag
tag = '{tag}'
# WHEN: Calling _strip
result = self.services._strip(tag)
# THEN: The tag should be returned with the wrappers removed.
self.assertEqual(result, 'tag', 'FormattingTagForm._strip should return u\'tag\' when called with u\'{tag}\'')
def test_end_tag_changed_processes_correctly(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of start , end tags and error messages
tests = []
test = {'start': '<b>', 'end': None, 'gen': '</b>', 'valid': None}
tests.append(test)
test = {'start': '<i>', 'end': '</i>', 'gen': None, 'valid': None}
tests.append(test)
test = {'start': '<b>', 'end': '</i>', 'gen': None,
'valid': 'End tag </b> does not match end tag for start tag <b>'}
tests.append(test)
# WHEN: Testing each one of them in turn
for test in tests:
error, result = self.services.end_tag_changed(test['start'], test['end'])
# THEN: The result should match the predetermined value.
self.assertTrue(result == test['gen'],
'Function should handle end tag correctly : %s and %s for %s ' % (test['gen'], result, test['start']))
self.assertTrue(error == test['valid'],
'Function should not generate unexpected error messages : %s ' % error)
def test_start_tag_changed_processes_correctly(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of start , end tags and error messages
tests = []
test = {'start': '<b>', 'end': '', 'gen': '</b>', 'valid': None}
tests.append(test)
test = {'start': '<i>', 'end': '</i>', 'gen': None, 'valid': None}
tests.append(test)
test = {'start': 'superfly', 'end': '', 'gen': None, 'valid': 'Start tag superfly is not valid HTML'}
tests.append(test)
# WHEN: Testing each one of them in turn
for test in tests:
error, result = self.services.start_tag_changed(test['start'], test['end'])
# THEN: The result should match the predetermined value.
self.assertTrue(result == test['gen'],
'Function should handle end tag correctly : %s and %s ' % (test['gen'], result))
self.assertTrue(error == test['valid'],
'Function should not generate unexpected error messages : %s ' % error)
def test_start_html_to_end_html(self):
"""
Test that the end html tags are generated correctly
"""
# GIVEN: A list of valid and invalid tags
tests = {'<b>': '</b>', '<i>': '</i>', 'superfly': '', '<HTML START>': None,
'<span style="-webkit-text-fill-color:red">': '</span>'}
# WHEN: Testing each one of them
for test1, test2 in tests.items():
result = self.services.start_html_to_end_html(test1)
# THEN: The result should match the predetermined value.
self.assertTrue(result == test2, 'Calculated end tag should be valid: %s and %s = %s'
% (test1, test2, result))

View File

@ -0,0 +1,77 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.ui.formattingtagsform package.
"""
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.core.ui.formattingtagform import FormattingTagForm
# TODO: Tests Still TODO
# __init__
# exec_
# on_new_clicked
# on_delete_clicked
# on_saved_clicked
# _reloadTable
class TestFormattingTagForm(TestCase):
def setUp(self):
self.init_patcher = patch('openlp.core.ui.formattingtagform.FormattingTagForm.__init__')
self.qdialog_patcher = patch('openlp.core.ui.formattingtagform.QtGui.QDialog')
self.ui_formatting_tag_dialog_patcher = patch('openlp.core.ui.formattingtagform.Ui_FormattingTagDialog')
self.mocked_init = self.init_patcher.start()
self.mocked_qdialog = self.qdialog_patcher.start()
self.mocked_ui_formatting_tag_dialog = self.ui_formatting_tag_dialog_patcher.start()
self.mocked_init.return_value = None
def tearDown(self):
self.init_patcher.stop()
self.qdialog_patcher.stop()
self.ui_formatting_tag_dialog_patcher.stop()
def test_on_text_edited(self):
"""
Test that the appropriate actions are preformed when on_text_edited is called
"""
# GIVEN: An instance of the Formatting Tag Form and a mocked save_push_button
form = FormattingTagForm()
form.save_button = MagicMock()
# WHEN: on_text_edited is called with an arbitrary value
#form.on_text_edited('text')
# THEN: setEnabled and setDefault should have been called on save_push_button
#form.save_button.setEnabled.assert_called_with(True)

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.utils.actions package.
"""
@ -12,6 +40,9 @@ from openlp.core.utils import ActionList
class TestActionList(TestCase):
"""
Test the ActionList class
"""
def setUp(self):
"""

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
Functional tests to test the AppLocation class and related methods.
"""
import copy
from unittest import TestCase
from mock import patch
from openlp.core.utils import AppLocation
from tests.functional import patch
FILE_LIST = ['file1', 'file2', 'file3.txt', 'file4.txt', 'file5.mp3', 'file6.mp3']
@ -38,7 +64,7 @@ class TestAppLocation(TestCase):
mocked_settings.contains.assert_called_with('advanced/data path')
mocked_get_directory.assert_called_with(AppLocation.DataDir)
mocked_check_directory_exists.assert_called_with('test/dir')
assert data_path == 'test/dir', 'Result should be "test/dir"'
self.assertEqual('test/dir', data_path, 'Result should be "test/dir"')
def get_data_path_with_custom_location_test(self):
"""
@ -58,7 +84,7 @@ class TestAppLocation(TestCase):
# THEN: the mocked Settings methods were called and the value returned was our set up value
mocked_settings.contains.assert_called_with('advanced/data path')
mocked_settings.value.assert_called_with('advanced/data path')
assert data_path == 'custom/dir', 'Result should be "custom/dir"'
self.assertEqual('custom/dir', data_path, 'Result should be "custom/dir"')
def get_files_no_section_no_extension_test(self):
"""
@ -74,7 +100,7 @@ class TestAppLocation(TestCase):
result = AppLocation.get_files()
# Then: check if the file lists are identical.
assert result == FILE_LIST, 'The file lists should be identical.'
self.assertListEqual(FILE_LIST, result, 'The file lists should be identical.')
def get_files_test(self):
"""
@ -93,7 +119,7 @@ class TestAppLocation(TestCase):
mocked_listdir.assert_called_with('test/dir/section')
# Then: check if the file lists are identical.
assert result == ['file5.mp3', 'file6.mp3'], 'The file lists should be identical.'
self.assertListEqual(['file5.mp3', 'file6.mp3'], result, 'The file lists should be identical.')
def get_section_data_path_test(self):
"""
@ -110,25 +136,27 @@ class TestAppLocation(TestCase):
# THEN: check that all the correct methods were called, and the result is correct
mocked_check_directory_exists.assert_called_with('test/dir/section')
assert data_path == 'test/dir/section', 'Result should be "test/dir/section"'
self.assertEqual('test/dir/section', data_path, 'Result should be "test/dir/section"')
def get_directory_for_app_dir_test(self):
"""
Test the AppLocation.get_directory() method for AppLocation.AppDir
"""
# GIVEN: A mocked out _get_frozen_path function
with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path:
mocked_get_frozen_path.return_value = 'app/dir'
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.AppDir)
# THEN:
assert directory == 'app/dir', 'Directory should be "app/dir"'
# THEN: check that the correct directory is returned
self.assertEqual('app/dir', directory, 'Directory should be "app/dir"')
def get_directory_for_plugins_dir_test(self):
"""
Test the AppLocation.get_directory() method for AppLocation.PluginsDir
"""
# GIVEN: _get_frozen_path, abspath, split and sys are mocked out
with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path, \
patch('openlp.core.utils.applocation.os.path.abspath') as mocked_abspath, \
patch('openlp.core.utils.applocation.os.path.split') as mocked_split, \
@ -142,6 +170,5 @@ class TestAppLocation(TestCase):
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN:
assert directory == 'plugins/dir', 'Directory should be "plugins/dir"'
# THEN: The correct directory should be returned
self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')

View File

@ -1,25 +1,52 @@
# -*- 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 #
###############################################################################
"""
Functional tests to test the AppLocation class and related methods.
"""
from unittest import TestCase
from mock import patch
from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \
get_natural_key, split_filename
from tests.functional import patch
class TestUtils(TestCase):
"""
A test suite to test out various methods around the AppLocation class.
"""
def get_filesystem_encoding_test(self):
def get_filesystem_encoding_sys_function_not_called_test(self):
"""
Test the get_filesystem_encoding() function
Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function
"""
# GIVEN: sys.getfilesystemencoding returns "cp1252"
with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \
patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding:
# GIVEN: sys.getfilesystemencoding returns "cp1252"
mocked_getfilesystemencoding.return_value = 'cp1252'
# WHEN: get_filesystem_encoding() is called
@ -27,10 +54,16 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with()
assert not mocked_getdefaultencoding.called
assert result == 'cp1252', 'The result should be "cp1252"'
self.assertEqual(0, mocked_getdefaultencoding.called, 'getdefaultencoding should not have been called')
self.assertEqual('cp1252', result, 'The result should be "cp1252"')
# GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8"
def get_filesystem_encoding_sys_function_is_called_test(self):
"""
Test the get_filesystem_encoding() function calls the sys.getdefaultencoding() function
"""
# GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8"
with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \
patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding:
mocked_getfilesystemencoding.return_value = None
mocked_getdefaultencoding.return_value = 'utf-8'
@ -40,23 +73,35 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with()
mocked_getdefaultencoding.assert_called_with()
assert result == 'utf-8', 'The result should be "utf-8"'
self.assertEqual('utf-8', result, 'The result should be "utf-8"')
def get_frozen_path_test(self):
def get_frozen_path_in_unfrozen_app_test(self):
"""
Test the _get_frozen_path() function
Test the _get_frozen_path() function when the application is not frozen (compiled by PyInstaller)
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The sys module "without" a "frozen" attribute
mocked_sys.frozen = None
# WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The non-frozen parameter is returned
assert _get_frozen_path('frozen', 'not frozen') == 'not frozen', 'Should return "not frozen"'
self.assertEqual('not frozen', frozen_path, '_get_frozen_path should return "not frozen"')
def get_frozen_path_in_frozen_app_test(self):
"""
Test the _get_frozen_path() function when the application is frozen (compiled by PyInstaller)
"""
with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The sys module *with* a "frozen" attribute
mocked_sys.frozen = 1
# WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The frozen parameter is returned
assert _get_frozen_path('frozen', 'not frozen') == 'frozen', 'Should return "frozen"'
self.assertEqual('frozen', frozen_path, 'Should return "frozen"')
def split_filename_with_file_path_test(self):
"""
@ -72,7 +117,7 @@ class TestUtils(TestCase):
result = split_filename(file_path)
# THEN: A tuple should be returned.
assert result == wanted_result, 'A tuple with the directory and file name should have been returned.'
self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned')
def split_filename_with_dir_path_test(self):
"""
@ -88,8 +133,8 @@ class TestUtils(TestCase):
result = split_filename(file_path)
# THEN: A tuple should be returned.
assert result == wanted_result, \
'A two-entry tuple with the directory and file name (empty) should have been returned.'
self.assertEqual(wanted_result, result,
'A two-entry tuple with the directory and file name (empty) should have been returned.')
def clean_filename_test(self):
"""
@ -103,40 +148,24 @@ class TestUtils(TestCase):
result = clean_filename(invalid_name)
# THEN: The file name should be cleaned.
assert result == wanted_name, 'The file name should not contain any special characters.'
self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.')
def get_locale_key_windows_test(self):
def get_locale_key_test(self):
"""
Test the get_locale_key(string) function
"""
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language, \
patch('openlp.core.utils.os') as mocked_os:
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = 'de'
mocked_os.name = 'nt'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug']
assert test_passes, 'Strings should be sorted properly'
def get_locale_key_linux_test(self):
"""
Test the get_locale_key(string) function
"""
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language, \
patch('openlp.core.utils.os.name') as mocked_os:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = 'de'
mocked_os.name = 'linux'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_locale_key)
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug']
assert test_passes, 'Strings should be sorted properly'
self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
'Strings should be sorted properly')
def get_natural_key_test(self):
"""
@ -146,7 +175,9 @@ class TestUtils(TestCase):
# GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = 'en'
unsorted_list = ['item 10a', 'item 3b', '1st item']
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys
sorted_list = sorted(unsorted_list, key=get_natural_key)
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_natural_key) == ['1st item', 'item 3b', 'item 10a']
assert test_passes, 'Numbers should be sorted naturally'
self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally')

View File

@ -1,3 +1,31 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Bibles plugin.
"""

View File

@ -0,0 +1,145 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the versereferencelist submodule of the Bibles plugin.
"""
from unittest import TestCase
from openlp.plugins.bibles.lib.versereferencelist import VerseReferenceList
class TestVerseReferenceList(TestCase):
"""
Test the VerseReferenceList class
"""
def add_first_verse_test(self):
"""
Test the addition of a verse to the empty list
"""
# GIVEN: an empty list
reference_list = VerseReferenceList()
book = 'testBook'
chapter = 1
verse = 1
version = 'testVersion'
copyright_ = 'testCopyright'
permission = 'testPermision'
# WHEN: We add it to the verse list
reference_list.add(book, chapter, verse, version, copyright_, permission)
# THEN: The entries should be in the first entry of the list
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
self.assertEqual(reference_list.verse_list[0]['book'], book, 'The book in first entry should be %s' % book)
self.assertEqual(reference_list.verse_list[0]['chapter'], chapter, 'The chapter in first entry should be %u' % chapter)
self.assertEqual(reference_list.verse_list[0]['start'], verse, 'The start in first entry should be %u' % verse)
self.assertEqual(reference_list.verse_list[0]['version'], version, 'The version in first entry should be %s' % version)
self.assertEqual(reference_list.verse_list[0]['end'], verse, 'The end in first entry should be %u' % verse)
def add_next_verse_test(self):
"""
Test the addition of the following verse
"""
# GIVEN: 1 line in the list of verses
book = 'testBook'
chapter = 1
verse = 1
next_verse = 2
version = 'testVersion'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list = VerseReferenceList()
reference_list.add(book, chapter, verse, version, copyright_, permission)
# WHEN: We add the following verse to the verse list
reference_list.add(book, chapter, next_verse, version, copyright_, permission)
# THEN: The current index should be 0 and the end pointer of the entry should be '2'
self.assertEqual(reference_list.current_index, 0, 'The current index should be 0')
self.assertEqual(reference_list.verse_list[0]['end'], next_verse,
'The end in first entry should be %u' % next_verse)
def add_another_verse_test(self):
"""
Test the addition of a verse in another book
"""
# GIVEN: 1 line in the list of verses
book = 'testBook'
chapter = 1
verse = 1
another_book = 'testBook2'
another_chapter = 2
another_verse = 5
version = 'testVersion'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list = VerseReferenceList()
reference_list.add(book, chapter, verse, version, copyright_, permission)
# WHEN: We add a verse of another book to the verse list
reference_list.add(another_book, another_chapter, another_verse, version, copyright_, permission)
# THEN: the current index should be 1
self.assertEqual(reference_list.current_index, 1, 'The current index should be 1')
def add_version_test(self):
"""
Test the addition of a version to the list
"""
# GIVEN: version, copyright and permission
reference_list = VerseReferenceList()
version = 'testVersion'
copyright_ = 'testCopyright'
permission = 'testPermision'
# WHEN: a not existing version will be added
reference_list.add_version(version, copyright_, permission)
# THEN: the data will be appended to the list
self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended')
self.assertEqual(reference_list.version_list[0],
{'version': version, 'copyright': copyright_, 'permission': permission},
'The version data should be appended')
def add_existing_version_test(self):
"""
Test the addition of an existing version to the list
"""
# GIVEN: version, copyright and permission, added to the version list
reference_list = VerseReferenceList()
version = 'testVersion'
copyright_ = 'testCopyright'
permission = 'testPermision'
reference_list.add_version(version, copyright_, permission)
# WHEN: an existing version will be added
reference_list.add_version(version, copyright_, permission)
# THEN: the data will not be appended to the list
self.assertEqual(len(reference_list.version_list), 1, 'The version data should not be appended')

View File

@ -1,16 +1,40 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Images plugin.
"""
from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import Registry
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
from tests.functional import MagicMock, patch
class TestImageMediaItem(TestCase):
@ -24,11 +48,10 @@ class TestImageMediaItem(TestCase):
Registry().register('service_list', MagicMock())
Registry().register('main_window', self.mocked_main_window)
Registry().register('live_controller', MagicMock())
mocked_parent = MagicMock()
mocked_plugin = MagicMock()
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.__init__') as mocked_init:
mocked_init.return_value = None
self.media_item = ImageMediaItem(mocked_parent, mocked_plugin)
with patch('openlp.plugins.images.lib.mediaitem.MediaManagerItem._setup'), \
patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'):
self.media_item = ImageMediaItem(None, mocked_plugin)
def save_new_images_list_empty_list_test(self):
"""
@ -36,7 +59,7 @@ class TestImageMediaItem(TestCase):
"""
# GIVEN: An empty image_list
image_list = []
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list'):
self.media_item.manager = MagicMock()
# WHEN: We run save_new_images_list with the empty list
@ -50,8 +73,8 @@ class TestImageMediaItem(TestCase):
"""
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
"""
# GIVEN: A list with 1 image
image_list = [ 'test_image.jpg' ]
# GIVEN: A list with 1 image and a mocked out manager
image_list = ['test_image.jpg']
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
ImageFilenames.filename = ''
self.media_item.manager = MagicMock()
@ -69,8 +92,8 @@ class TestImageMediaItem(TestCase):
"""
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
"""
# GIVEN: A list with 1 image
image_list = [ 'test_image.jpg' ]
# GIVEN: A list with 1 image and a mocked out manager
image_list = ['test_image.jpg']
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list:
self.media_item.manager = MagicMock()
@ -126,9 +149,35 @@ class TestImageMediaItem(TestCase):
self.media_item.reset_action.setVisible.assert_called_with(False)
self.media_item.live_controller.display.reset_image.assert_called_with()
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
ImageGroups.parent_id = 1
self.media_item.manager = MagicMock()
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
self.media_item.service_path = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')
def _recursively_delete_group_side_effect(*args, **kwargs):
"""
Side effect method that creates custom retun values for the recursively_delete_group method
Side effect method that creates custom return values for the recursively_delete_group method
"""
if args[1] == ImageFilenames and args[2]:
# Create some fake objects that should be removed
@ -150,29 +199,3 @@ class TestImageMediaItem(TestCase):
returned_object1.id = 1
return [returned_object1]
return []
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
ImageGroups.parent_id = 1
self.media_item.manager = MagicMock()
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
self.media_item.servicePath = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')

View File

@ -1,17 +1,41 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Presentations plugin.
"""
import os
from tempfile import mkstemp
from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtGui
from openlp.core.lib import Registry
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase):
@ -25,11 +49,9 @@ class TestMediaItem(TestCase):
Registry.create()
Registry().register('service_manager', MagicMock())
Registry().register('main_window', MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.__init__') as mocked_init:
mocked_init.return_value = None
self.media_item = PresentationMediaItem(MagicMock(), MagicMock, MagicMock(), MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup'), \
patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item'):
self.media_item = PresentationMediaItem(None, MagicMock, MagicMock())
self.application = QtGui.QApplication.instance()
def tearDown(self):
@ -65,7 +87,8 @@ class TestMediaItem(TestCase):
mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate
self.media_item.build_file_mask_string()
# THEN: The file mask should be generated.
assert self.media_item.on_new_file_masks == 'Presentations (*.odp *.ppt )', \
'The file mask should contain the odp and ppt extensions'
# THEN: The file mask should be generated correctly
self.assertIn('*.odp', self.media_item.on_new_file_masks,
'The file mask should contain the odp extension')
self.assertIn('*.ppt', self.media_item.on_new_file_masks,
'The file mask should contain the ppt extension')

View File

@ -1,17 +1,44 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
import re
from unittest import TestCase
from tempfile import mkstemp
from mock import patch
from PyQt4 import QtGui
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.remotetab import RemoteTab
from PyQt4 import QtGui
from tests.functional import patch
__default_settings__ = {
'remotes/twelve hour': True,
@ -23,9 +50,7 @@ __default_settings__ = {
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
}
ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
@ -60,7 +85,8 @@ class TestRemoteTab(TestCase):
# WHEN: the default ip address is given
ip_address = self.form.get_ip_address(ZERO_URL)
# THEN: the default ip address will be returned
self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), 'The return value should be a valid ip address')
self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address),
'The return value should be a valid ip address')
def get_ip_address_with_ip_test(self):
"""
@ -80,9 +106,9 @@ class TestRemoteTab(TestCase):
"""
# GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False
@ -96,7 +122,7 @@ class TestRemoteTab(TestCase):
# THEN: the following screen values should be set
self.assertEqual(self.form.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen')
self.assertEqual(self.form.https_settings_group_box.isEnabled(), False,
'The Https box should not be enabled')
'The Https box should not be enabled')
self.assertEqual(self.form.https_settings_group_box.isChecked(), False,
'The Https checked box should note be Checked')
self.assertEqual(self.form.user_login_group_box.isChecked(), False,
@ -108,9 +134,9 @@ class TestRemoteTab(TestCase):
"""
# GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \
patch('openlp.core.utils.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False

View File

@ -1,15 +1,43 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from mock import MagicMock
from PyQt4 import QtGui
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.httpserver import HttpRouter, fetch_password, make_sha_hash
from PyQt4 import QtGui
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock
__default_settings__ = {
'remotes/twelve hour': True,
@ -44,40 +72,22 @@ class TestRouter(TestCase):
del self.application
os.unlink(self.ini_file)
def fetch_password_unknown_test(self):
def password_encrypter_test(self):
"""
Test the fetch password code with an unknown userid
Test hash userid and password function
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
password = fetch_password('itwinkle')
Settings().setValue('remotes/user id', 'openlp')
Settings().setValue('remotes/password', 'password')
# THEN: the function should return None
self.assertEqual(password, None, 'The result for fetch_password should be None')
def fetch_password_known_test(self):
"""
Test the fetch password code with the defined userid
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
password = fetch_password('openlp')
required_password = make_sha_hash('password')
router = HttpRouter()
router.initialise()
test_value = 'b3BlbmxwOnBhc3N3b3Jk'
print(router.auth)
# THEN: the function should return the correct password
self.assertEqual(password, required_password, 'The result for fetch_password should be the defined password')
def sha_password_encrypter_test(self):
"""
Test hash password function
"""
# GIVEN: A default configuration
# WHEN: called with the defined userid
required_password = make_sha_hash('password')
test_value = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'
# THEN: the function should return the correct password
self.assertEqual(required_password, test_value,
self.assertEqual(router.auth, test_value,
'The result for make_sha_hash should return the correct encrypted password')
def process_http_request_test(self):
@ -85,15 +95,18 @@ class TestRouter(TestCase):
Test the router control functionality
"""
# GIVEN: A testing set of Routes
router = HttpRouter()
mocked_function = MagicMock()
test_route = [
(r'^/stage/api/poll$', mocked_function),
(r'^/stage/api/poll$', {'function': mocked_function, 'secure': False}),
]
self.router.routes = test_route
router.routes = test_route
# WHEN: called with a poll route
self.router.process_http_request('/stage/api/poll', None)
function, args = router.process_http_request('/stage/api/poll', None)
# THEN: the function should have been called only once
assert mocked_function.call_count == 1, \
'The mocked function should have been matched and called once.'
assert function['function'] == mocked_function, \
'The mocked function should match defined value.'
assert function['secure'] == False, \
'The mocked function should not require any security.'

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the EasyWorship song importer.
"""
import os
from unittest import TestCase
from mock import patch, MagicMock
from tests.functional import MagicMock, patch
from openlp.plugins.songs.lib.ewimport import EasyWorshipSongImport, FieldDescEntry, FieldType
@ -43,6 +69,7 @@ SONG_TEST_DATA = [
'Just to bow and receive a new blessing,\nIn the beautiful garden of prayer.', 'v3')],
'verse_order_list': []}]
class EasyWorshipSongImportLogger(EasyWorshipSongImport):
"""
This class logs changes in the title instance variable
@ -60,6 +87,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
def title(self, title):
self._title_assignment_list.append(title)
class TestFieldDesc:
def __init__(self, name, field_type, size):
self.name = name

View File

@ -32,9 +32,8 @@ This module contains tests for the SongShow Plus song importer.
import os
from unittest import TestCase
from mock import patch, MagicMock
from tests.functional import patch, MagicMock
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.foilpresenterimport import FoilPresenter
TEST_PATH = os.path.abspath(
@ -192,4 +191,4 @@ class TestFoilPresenter(TestCase):
# THEN: _process_lyrics should return None and the song_import logError method should have been called once
self.assertIsNone(result)
self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String')
self.process_lyrics_patcher.start()
self.process_lyrics_patcher.start()

View File

@ -1,13 +1,39 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the lib submodule of the Songs plugin.
"""
from unittest import TestCase
from mock import patch, MagicMock
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
from tests.functional import patch, MagicMock
class TestLib(TestCase):
@ -68,10 +94,10 @@ class TestLib(TestCase):
# 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, 'The result should be True'
@ -82,10 +108,10 @@ class TestLib(TestCase):
# 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, 'The result should be True'
@ -96,10 +122,10 @@ class TestLib(TestCase):
# 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, 'The result should be True'
@ -110,10 +136,10 @@ class TestLib(TestCase):
# 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, 'The result should be False'

View File

@ -5,13 +5,11 @@ import os
from tempfile import mkstemp
from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, ServiceItem, Settings
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase):
@ -25,9 +23,9 @@ class TestMediaItem(TestCase):
Registry.create()
Registry().register('service_list', MagicMock())
Registry().register('main_window', MagicMock())
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem.__init__'), \
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
self.media_item = SongMediaItem(MagicMock(), MagicMock())
self.media_item = SongMediaItem(None, MagicMock())
fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file)

View File

@ -1,14 +1,42 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the SongShow Plus song importer.
"""
import os
from unittest import TestCase
from mock import patch, MagicMock
from tests.functional.openlp_plugins.songs.songfileimporthelper import SongImportTestHelper
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport
from tests.functional import patch, MagicMock
TEST_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'songshowplussongs'))

View File

@ -1,15 +1,45 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the WorshipCenter Pro song importer.
"""
import os
from unittest import TestCase, SkipTest
if os.name != 'nt':
raise SkipTest('Not Windows, skipping test')
from unittest import TestCase
from mock import patch, MagicMock
import pyodbc
from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport
from tests.functional import patch, MagicMock
class TestRecord(object):
"""
@ -23,6 +53,7 @@ class TestRecord(object):
self.Field = field
self.Value = value
class WorshipCenterProImportLogger(WorshipCenterProImport):
"""
This class logs changes in the title instance variable
@ -189,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase):
for call in verse_calls:
mocked_add_verse.assert_any_call(call)
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
'Incorrect number of calls made to addVerse')
'Incorrect number of calls made to addVerse')

View File

@ -1,138 +0,0 @@
"""
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
from unittest import TestCase
from tempfile import mkstemp
from mock import MagicMock
import urllib.request, urllib.error, urllib.parse
import cherrypy
from bs4 import BeautifulSoup
from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.httpserver import HttpServer
from PyQt4 import QtGui
__default_settings__ = {
'remotes/twelve hour': True,
'remotes/port': 4316,
'remotes/https port': 4317,
'remotes/https enabled': False,
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
}
class TestRouter(TestCase):
"""
Test the functions in the :mod:`lib` module.
"""
def setUp(self):
"""
Create the UI
"""
fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__)
self.server = HttpServer()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.application
os.unlink(self.ini_file)
self.server.close()
def start_server(self):
"""
Common function to start server then mock out the router. CherryPy crashes if you mock before you start
"""
self.server.start_server()
self.server.router = MagicMock()
self.server.router.process_http_request = process_http_request
def start_default_server_test(self):
"""
Test the default server serves the correct initial page
"""
# GIVEN: A default configuration
Settings().setValue('remotes/authentication enabled', False)
self.start_server()
# WHEN: called the route location
code, page = call_remote_server('http://localhost:4316')
# THEN: default title will be returned
self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote',
'The default menu should be returned')
def start_authenticating_server_test(self):
"""
Test the default server serves the correctly with authentication
"""
# GIVEN: A default authorised configuration
Settings().setValue('remotes/authentication enabled', True)
self.start_server()
# WHEN: called the route location with no user details
code, page = call_remote_server('http://localhost:4316')
# THEN: then server will ask for details
self.assertEqual(code, 401, 'The basic authorisation request should be returned')
# WHEN: called the route location with user details
code, page = call_remote_server('http://localhost:4316', 'openlp', 'password')
# THEN: default title will be returned
self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote',
'The default menu should be returned')
# WHEN: called the route location with incorrect user details
code, page = call_remote_server('http://localhost:4316', 'itwinkle', 'password')
# THEN: then server will ask for details
self.assertEqual(code, 401, 'The basic authorisation request should be returned')
def call_remote_server(url, username=None, password=None):
"""
Helper function
``username``
The username.
``password``
The password.
"""
if username:
passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
authhandler = urllib.request.HTTPBasicAuthHandler(passman)
opener = urllib.request.build_opener(authhandler)
urllib.request.install_opener(opener)
try:
page = urllib.request.urlopen(url)
return 0, page.read()
except urllib.error.HTTPError as e:
return e.code, ''
def process_http_request(url_path, *args):
"""
Override function to make the Mock work but does nothing.
``Url_path``
The url_path.
``*args``
Some args.
"""
cherrypy.response.status = 200
return None

View File

@ -0,0 +1,51 @@
# -*- 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 #
###############################################################################
import os
import json
def assert_length(expected, iterable, msg=None):
if len(iterable) != expected:
if not msg:
msg = 'Expected length %s, got %s' % (expected, len(iterable))
raise AssertionError(msg)
def convert_file_service_item(test_path, name, row=0):
service_file = os.path.join(test_path, name)
open_file = open(service_file, 'r')
try:
items = json.load(open_file)
first_line = items[row]
except IOError:
first_line = ''
finally:
open_file.close()
return first_line