forked from openlp/openlp
HEAD
This commit is contained in:
commit
3b9df96b42
@ -27,3 +27,4 @@ openlp.pro
|
||||
tests.kdev4
|
||||
*.nja
|
||||
*.orig
|
||||
__pycache__
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
@ -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']
|
||||
|
@ -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
|
||||
|
176
openlp/core/ui/formattingtagcontroller.py
Normal file
176
openlp/core/ui/formattingtagcontroller.py
Normal 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
|
@ -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'))
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 %s\n\n%s %s' % (verse_text, text, verse_text, second_text)
|
||||
bible_text = '%s%s\n\n%s %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 %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 %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 %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} ' % verse_text
|
||||
if self.settings.display_style == DisplayStyle.Curly:
|
||||
return '{su}{%s}{/su}' % verse_text
|
||||
return '{su}{%s}{/su} ' % 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} ' % verse_text
|
||||
return '{su}%s{/su} ' % verse_text
|
||||
|
||||
def search(self, string, showError):
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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 = []
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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;
|
||||
|
@ -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 ||
|
||||
|
@ -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']
|
||||
|
638
openlp/plugins/remotes/lib/httprouter.py
Normal file
638
openlp/plugins/remotes/lib/httprouter.py
Normal 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 & 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)
|
@ -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 & 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)
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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())
|
@ -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()
|
||||
|
@ -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([])
|
||||
|
@ -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):
|
||||
|
@ -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 = {
|
||||
|
@ -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'))
|
||||
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
@ -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))
|
77
tests/functional/openlp_core_ui/tests_formattingtagsform.py
Normal file
77
tests/functional/openlp_core_ui/tests_formattingtagsform.py
Normal 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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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"')
|
||||
|
@ -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')
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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')
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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.'
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user